diff --git a/src/csv-loader/loader.test.ts b/src/csv-loader/loader.test.ts index 013680c..5cd256c 100644 --- a/src/csv-loader/loader.test.ts +++ b/src/csv-loader/loader.test.ts @@ -1166,26 +1166,49 @@ describe("parseCsv - reverse reference resolution", () => { }); it("should handle multiple comment lines including plain comments and reverse references", () => { - const csv = [ - "# id: id of user", - "# orders: list of related orders", - "# orders := ~order(user)", - "id,name", - "string,string", - "u01,Alice", - "u02,Bob", + const orderCsvPath = path.join(fixturesDir, "order.csv"); + const orderContent = [ + "id,user,total", + "string,string,number", + "o01,u01,100", + "o02,u01,50", + "o03,u02,75", ].join("\n"); + fs.writeFileSync(orderCsvPath, orderContent); - const result = parseCsv(csv, { - emitTypes: false, - currentFilePath: path.join(fixturesDir, "test.csv"), - }); + try { + const csv = [ + "# id: id of user", + "# orders: list of related orders", + "# orders := ~order(user)", + "id,name", + "string,string", + "u01,Alice", + "u02,Bob", + ].join("\n"); - expect(result.reverseReferences).toHaveLength(1); - expect(result.reverseReferences[0].fieldName).toBe("orders"); - expect(result.data).toHaveLength(2); - expect(result.data[0].id).toBe("u01"); - expect(result.data[1].id).toBe("u02"); + const result = parseCsv(csv, { + emitTypes: false, + currentFilePath: path.join(fixturesDir, "test.csv"), + }); + + expect(result.reverseReferences).toHaveLength(1); + expect(result.reverseReferences[0].fieldName).toBe("orders"); + expect(result.data).toHaveLength(2); + expect(result.data[0].id).toBe("u01"); + expect(result.data[1].id).toBe("u02"); + // User u01 (Alice) should have 2 orders + const aliceOrders = result.data[0].orders as Record[]; + expect(aliceOrders).toHaveLength(2); + expect(aliceOrders[0]).toEqual({ id: "o01", user: "u01", total: 100 }); + expect(aliceOrders[1]).toEqual({ id: "o02", user: "u01", total: 50 }); + // User u02 (Bob) should have 1 order + const bobOrders = result.data[1].orders as Record[]; + expect(bobOrders).toHaveLength(1); + expect(bobOrders[0].id).toBe("o03"); + } finally { + fs.unlinkSync(orderCsvPath); + } }); }); diff --git a/src/csv-loader/loader.ts b/src/csv-loader/loader.ts index 1d4012b..3eb6018 100644 --- a/src/csv-loader/loader.ts +++ b/src/csv-loader/loader.ts @@ -802,25 +802,10 @@ export function parseCsv( relax_column_count: true, }); - if (records.length < 2) { - throw new Error("CSV must have at least 2 rows: headers and schemas"); - } - - const headers = records[0]; - const schemas = records[1]; - - if (headers.length !== schemas.length) { - throw new Error( - `Header count (${headers.length}) does not match schema count (${schemas.length})`, - ); - } - - // Parse reverse reference declarations from comment lines between schema row and data rows + // Filter out comment lines from all records, collecting reverse reference declarations const reverseReferences: ReverseReferenceDeclaration[] = []; - const dataRows: string[][] = []; - for (let i = 2; i < records.length; i++) { - const row = records[i]; - // Check if this is a comment line (starting with the configured comment character) + const filteredRecords: string[][] = []; + for (const row of records) { const firstCell = (row[0] ?? "").trim(); if (comment && firstCell.startsWith(comment)) { const decl = parseReverseReferenceDeclaration(firstCell, comment); @@ -830,9 +815,24 @@ export function parseCsv( // Skip comment lines (whether or not they're reverse ref declarations) continue; } - dataRows.push(row); + filteredRecords.push(row); } + if (filteredRecords.length < 2) { + throw new Error("CSV must have at least 2 rows: headers and schemas"); + } + + const headers = filteredRecords[0]; + const schemas = filteredRecords[1]; + + if (headers.length !== schemas.length) { + throw new Error( + `Header count (${headers.length}) does not match schema count (${schemas.length})`, + ); + } + + const dataRows = filteredRecords.slice(2); + // Also check schema row cells for comment-prefixed reverse reference declarations // (in case they appear as schema cells rather than separate rows) for (let col = 0; col < schemas.length; col++) {