fix(web): 18 multi-language CSV tables, RFC 4180 escaping, schema sync

- Generate CSVs for Struct, Enum, Macro, Trait, Impl, TypeAlias, Const,
  Static, Property, Record, Delegate, Annotation, Constructor, Template,
  Module, Namespace, Union, Typedef (were defined in schema but silently
  dropped during CSV generation — non-JS/TS repos lost all nodes)
- Escape community keywords array in CSV to prevent comma breakage
- Add Section to NodeLabel type, NODE_COLORS, NODE_SIZES
- Add 7 missing REL_TYPES: HAS_METHOD, HAS_PROPERTY, OVERRIDES, ACCESSES,
  INHERITS, USES, DECORATES
- RFC 4180 doubled-quote regex for relation CSV parsing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jreakin 2026-03-23 02:53:17 -05:00
parent 519dc3eccc
commit 433f403fe2
5 changed files with 48 additions and 7 deletions

View file

@ -15,7 +15,8 @@ export type NodeLabel =
| 'Type'
| 'CodeElement'
| 'Community'
| 'Process';
| 'Process'
| 'Section';
export type NodeProperties = {

View file

@ -202,6 +202,35 @@ const generateCodeElementCSV = (
return rows.join('\n');
};
/**
* Generate CSV for multi-language code element nodes (Struct, Enum, Macro, etc.)
* These do NOT have isExported column.
* Headers: id,name,filePath,startLine,endLine,content
*/
const generateMultiLangCSV = (
nodes: GraphNode[],
label: NodeLabel,
fileContents: Map<string, string>
): string => {
const headers = ['id', 'name', 'filePath', 'startLine', 'endLine', 'content'];
const rows: string[] = [headers.join(',')];
for (const node of nodes) {
if (node.label !== label) continue;
const content = extractContent(node, fileContents);
rows.push([
escapeCSVField(node.id),
escapeCSVField(node.properties.name || ''),
escapeCSVField(node.properties.filePath || ''),
escapeCSVNumber(node.properties.startLine, -1),
escapeCSVNumber(node.properties.endLine, -1),
escapeCSVField(content),
].join(','));
}
return rows.join('\n');
};
/**
* Generate CSV for Community nodes (from Leiden algorithm)
* Headers: id,label,heuristicLabel,keywords,description,enrichedBy,cohesion,symbolCount
@ -221,7 +250,7 @@ const generateCommunityCSV = (nodes: GraphNode[]): string => {
escapeCSVField(node.id),
escapeCSVField(node.properties.name || ''), // label is stored in name
escapeCSVField(node.properties.heuristicLabel || ''),
keywordsStr, // Array format for LadybugDB
escapeCSVField(keywordsStr), // Array format for LadybugDB, needs CSV escaping for commas
escapeCSVField((node.properties as any).description || ''),
escapeCSVField((node.properties as any).enrichedBy || 'heuristic'),
escapeCSVNumber(node.properties.cohesion, 0),
@ -312,7 +341,17 @@ export const generateAllCSVs = (
nodeCSVs.set('CodeElement', generateCodeElementCSV(nodes, 'CodeElement', fileContents));
nodeCSVs.set('Community', generateCommunityCSV(nodes));
nodeCSVs.set('Process', generateProcessCSV(nodes));
// Generate CSVs for remaining multi-language tables (no isExported column)
const handledTables = new Set<NodeTableName>([
'File', 'Folder', 'Function', 'Class', 'Interface', 'Method', 'CodeElement', 'Community', 'Process',
]);
for (const table of NODE_TABLES) {
if (!handledTables.has(table)) {
nodeCSVs.set(table, generateMultiLangCSV(nodes, table as NodeLabel, fileContents));
}
}
// Generate single relation CSV
const relCSV = generateRelationCSV(graph);

View file

@ -26,7 +26,7 @@ export type NodeTableName = typeof NODE_TABLES[number];
export const REL_TABLE_NAME = 'CodeRelation';
// Valid relation types
export const REL_TYPES = ['CONTAINS', 'DEFINES', 'IMPORTS', 'CALLS', 'EXTENDS', 'IMPLEMENTS', 'MEMBER_OF', 'STEP_IN_PROCESS'] as const;
export const REL_TYPES = ['CONTAINS', 'DEFINES', 'IMPORTS', 'CALLS', 'EXTENDS', 'IMPLEMENTS', 'MEMBER_OF', 'STEP_IN_PROCESS', 'HAS_METHOD', 'HAS_PROPERTY', 'OVERRIDES', 'ACCESSES', 'INHERITS', 'USES', 'DECORATES'] as const;
export type RelType = typeof REL_TYPES[number];
// ============================================================================

View file

@ -19,6 +19,7 @@ export const NODE_COLORS: Record<NodeLabel, string> = {
CodeElement: '#64748b', // Slate - muted
Community: '#818cf8', // Indigo light - cluster indicator
Process: '#f43f5e', // Rose - execution flow indicator
Section: '#60a5fa', // Blue light - structural section
};
// Node sizes by type - clear visual hierarchy with dramatic size differences
@ -41,6 +42,7 @@ export const NODE_SIZES: Record<NodeLabel, number> = {
CodeElement: 2, // Generic small
Community: 0, // Hidden by default - metadata node
Process: 0, // Hidden by default - metadata node
Section: 8, // Structural section - similar to Folder
};
// Community color palette for cluster-based coloring

View file

@ -12,9 +12,8 @@ declare module '@ladybugdb/wasm-core' {
close(): Promise<void>;
}
export interface QueryResult {
getAll?(): Promise<any[]>;
getAllRows?(): Promise<any[]>;
getAllObjects?(): Promise<any[]>;
getAll(): Promise<any[]>;
getAllRows(): Promise<any[]>;
hasNext(): Promise<boolean>;
getNext(): Promise<any>;
}