@toiroakr/lines-db 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -159,6 +159,71 @@ var JsonlWriter = class {
159
159
  }
160
160
  };
161
161
 
162
+ //#endregion
163
+ //#region src/schema-extensions.ts
164
+ /**
165
+ * Supported schema file extensions, in priority order.
166
+ * The first match wins when discovering schema files.
167
+ */
168
+ const SCHEMA_EXTENSIONS = [
169
+ ".schema.ts",
170
+ ".schema.mts",
171
+ ".schema.cts"
172
+ ];
173
+ /**
174
+ * Map from schema extensions to their JavaScript import counterparts.
175
+ */
176
+ const SCHEMA_TO_JS_IMPORT_MAP = {
177
+ ".schema.ts": ".schema.js",
178
+ ".schema.mts": ".schema.mjs",
179
+ ".schema.cts": ".schema.cjs"
180
+ };
181
+ /**
182
+ * Try each supported schema extension and return the full path of the first
183
+ * one that exists on disk. Returns undefined if none is found.
184
+ */
185
+ async function findSchemaFile(dir, tableName) {
186
+ for (const ext of SCHEMA_EXTENSIONS) {
187
+ const candidate = join(dir, `${tableName}${ext}`);
188
+ try {
189
+ await access(candidate);
190
+ return candidate;
191
+ } catch {}
192
+ }
193
+ }
194
+ /**
195
+ * Synchronously find a schema file among directory entries.
196
+ * Returns the full path of the first match, or undefined.
197
+ */
198
+ function findSchemaFileInEntries(dataDirPath, tableName, entries) {
199
+ for (const ext of SCHEMA_EXTENSIONS) {
200
+ const candidateName = `${tableName}${ext}`;
201
+ if (entries.some((e) => e.isFile() && e.name === candidateName)) return join(dataDirPath, candidateName);
202
+ }
203
+ }
204
+ /**
205
+ * Check if a filename matches any supported schema file pattern.
206
+ */
207
+ function isSchemaFile(fileName) {
208
+ return SCHEMA_EXTENSIONS.some((ext) => fileName.endsWith(ext));
209
+ }
210
+ /**
211
+ * Extract table name from a schema filename.
212
+ * e.g., "users.schema.ts" -> "users", "users.schema.mts" -> "users"
213
+ */
214
+ function extractTableNameFromSchemaFile(fileName) {
215
+ for (const ext of SCHEMA_EXTENSIONS) if (fileName.endsWith(ext)) return fileName.slice(0, -ext.length);
216
+ return null;
217
+ }
218
+ /**
219
+ * Rewrite a TypeScript path to its JavaScript counterpart for ESM imports.
220
+ * ".schema.ts" -> ".schema.js", ".schema.mts" -> ".schema.mjs", ".schema.cts" -> ".schema.cjs"
221
+ */
222
+ function rewriteExtensionForImport(filePath) {
223
+ for (const [tsExt, jsExt] of Object.entries(SCHEMA_TO_JS_IMPORT_MAP)) if (filePath.endsWith(tsExt)) return filePath.slice(0, -tsExt.length) + jsExt;
224
+ return filePath;
225
+ }
226
+
162
227
  //#endregion
163
228
  //#region src/schema-loader.ts
164
229
  var SchemaLoader = class {
@@ -166,27 +231,17 @@ var SchemaLoader = class {
166
231
  * Check if a schema file exists for a table
167
232
  */
168
233
  static async hasSchema(jsonlPath) {
169
- const schemaPath = join(dirname(jsonlPath), `${basename(jsonlPath, ".jsonl")}.schema.ts`);
170
- try {
171
- await access(schemaPath);
172
- return true;
173
- } catch {
174
- return false;
175
- }
234
+ return await findSchemaFile(dirname(jsonlPath), basename(jsonlPath, ".jsonl")) !== void 0;
176
235
  }
177
236
  /**
178
237
  * Load a validation schema file for a table
179
- * Requires ${tableName}.schema.ts to exist alongside the JSONL file
238
+ * Requires ${tableName}.schema.{ts,mts,cts} to exist alongside the JSONL file
180
239
  */
181
240
  static async loadSchema(jsonlPath) {
182
241
  const dir = dirname(jsonlPath);
183
242
  const tableName = basename(jsonlPath, ".jsonl");
184
- const schemaPath = join(dir, `${tableName}.schema.ts`);
185
- try {
186
- await access(schemaPath);
187
- } catch (error) {
188
- throw new Error(`Schema file not found for table '${tableName}'. Expected: ${schemaPath}`, { cause: error instanceof Error ? error : void 0 });
189
- }
243
+ const schemaPath = await findSchemaFile(dir, tableName);
244
+ if (!schemaPath) throw new Error(`Schema file not found for table '${tableName}'. Expected one of: ${SCHEMA_EXTENSIONS.map((ext) => `${tableName}${ext}`).join(", ")}`);
190
245
  try {
191
246
  const module = await import(`${pathToFileURL(schemaPath).href}?t=${Date.now()}`);
192
247
  const schema = module.default || module.schema;
@@ -320,6 +375,7 @@ var LinesDB = class LinesDB {
320
375
  async initialize(options) {
321
376
  const allErrors = [];
322
377
  const allWarnings = [];
378
+ const allRowCounts = /* @__PURE__ */ new Map();
323
379
  const tableName = options?.tableName;
324
380
  const detailedValidate = options?.detailedValidate ?? false;
325
381
  const transform = options?.transform;
@@ -331,14 +387,27 @@ var LinesDB = class LinesDB {
331
387
  const attemptedTables = /* @__PURE__ */ new Set();
332
388
  for (const tableNameToLoad of tablesToLoad) if (!attemptedTables.has(tableNameToLoad)) {
333
389
  const tableTransform = tableNameToLoad === tableName ? transform : void 0;
334
- const { errors, warnings } = await this.loadTableWithDependencies(tableNameToLoad, loadedTables, loadingTables, attemptedTables, detailedValidate, tableTransform);
390
+ const { errors, warnings, rowCounts: tableRowCounts } = await this.loadTableWithDependencies(tableNameToLoad, loadedTables, loadingTables, attemptedTables, detailedValidate, tableTransform);
335
391
  allErrors.push(...errors);
336
392
  allWarnings.push(...warnings);
393
+ for (const [k, v] of tableRowCounts) allRowCounts.set(k, v);
337
394
  }
395
+ const tableResults = tablesToLoad.map((name) => {
396
+ const tableErrors = allErrors.filter((e) => e.tableName === name);
397
+ const tableWarnings = allWarnings.filter((w) => w.includes(`'${name}'`));
398
+ return {
399
+ tableName: name,
400
+ valid: tableErrors.length === 0,
401
+ rowCount: allRowCounts.get(name) ?? 0,
402
+ errors: tableErrors,
403
+ warnings: tableWarnings
404
+ };
405
+ });
338
406
  return {
339
407
  valid: allErrors.length === 0,
340
408
  errors: allErrors,
341
- warnings: allWarnings
409
+ warnings: allWarnings,
410
+ tableResults
342
411
  };
343
412
  }
344
413
  /**
@@ -347,9 +416,11 @@ var LinesDB = class LinesDB {
347
416
  async loadTableWithDependencies(tableName, loadedTables, loadingTables, attemptedTables, detailedValidate, transform) {
348
417
  const errors = [];
349
418
  const warnings = [];
419
+ const rowCounts = /* @__PURE__ */ new Map();
350
420
  if (attemptedTables.has(tableName)) return {
351
421
  errors,
352
- warnings
422
+ warnings,
423
+ rowCounts
353
424
  };
354
425
  attemptedTables.add(tableName);
355
426
  if (loadingTables.has(tableName)) throw new Error(`Circular dependency detected for table '${tableName}'`);
@@ -360,8 +431,11 @@ var LinesDB = class LinesDB {
360
431
  let foreignKeys;
361
432
  try {
362
433
  const { pathToFileURL: pathToFileURL$1 } = await import("node:url");
363
- const schemaModule = await import(`${pathToFileURL$1(tableConfig.jsonlPath.replace(".jsonl", ".schema.ts")).href}?t=${Date.now()}`);
364
- foreignKeys = (schemaModule.schema || schemaModule.default)?.foreignKeys || schemaModule.foreignKeys;
434
+ const schemaPath = await findSchemaFile(dirname(tableConfig.jsonlPath), basename(tableConfig.jsonlPath, ".jsonl"));
435
+ if (schemaPath) {
436
+ const schemaModule = await import(`${pathToFileURL$1(schemaPath).href}?t=${Date.now()}`);
437
+ foreignKeys = (schemaModule.schema || schemaModule.default)?.foreignKeys || schemaModule.foreignKeys;
438
+ }
365
439
  } catch {}
366
440
  if (foreignKeys && foreignKeys.length > 0) for (const fk of foreignKeys) {
367
441
  const referencedTable = fk.references.table;
@@ -370,10 +444,21 @@ var LinesDB = class LinesDB {
370
444
  const depResult = await this.loadTableWithDependencies(referencedTable, loadedTables, loadingTables, attemptedTables, detailedValidate, void 0);
371
445
  errors.push(...depResult.errors);
372
446
  warnings.push(...depResult.warnings);
447
+ for (const [k, v] of depResult.rowCounts) rowCounts.set(k, v);
373
448
  } else throw new Error(`Foreign key reference to non-existent table '${referencedTable}' in table '${tableName}'`);
374
449
  }
375
- const { loaded, errors: loadErrors } = await this.loadTable(tableName, tableConfig, detailedValidate, transform);
450
+ const failedDependencies = /* @__PURE__ */ new Set();
451
+ if (foreignKeys && foreignKeys.length > 0) {
452
+ for (const fk of foreignKeys) {
453
+ const referencedTable = fk.references.table;
454
+ if (referencedTable === tableName) continue;
455
+ if (attemptedTables.has(referencedTable) && !loadedTables.has(referencedTable)) failedDependencies.add(referencedTable);
456
+ }
457
+ if (failedDependencies.size > 0) for (const dep of failedDependencies) warnings.push(`Skipping foreign key validation for table '${tableName}': referenced table '${dep}' has validation errors`);
458
+ }
459
+ const { loaded, rowCount, errors: loadErrors } = await this.loadTable(tableName, tableConfig, detailedValidate, transform, failedDependencies);
376
460
  errors.push(...loadErrors);
461
+ rowCounts.set(tableName, rowCount);
377
462
  if (loaded) loadedTables.add(tableName);
378
463
  else {
379
464
  warnings.push(`Table '${tableName}' was not loaded (no data or skipped)`);
@@ -384,14 +469,15 @@ var LinesDB = class LinesDB {
384
469
  }
385
470
  return {
386
471
  errors,
387
- warnings
472
+ warnings,
473
+ rowCounts
388
474
  };
389
475
  }
390
476
  /**
391
477
  * Load a single table from JSONL file
392
478
  * @returns Object with loaded status and validation errors
393
479
  */
394
- async loadTable(tableName, config, detailedValidate, transform) {
480
+ async loadTable(tableName, config, detailedValidate, transform, failedDependencies) {
395
481
  let data = await JsonlReader.read(config.jsonlPath);
396
482
  if (transform) data = data.map((row) => transform(row));
397
483
  let validationSchema = config.validationSchema;
@@ -401,7 +487,9 @@ var LinesDB = class LinesDB {
401
487
  } catch (_error) {}
402
488
  if (!config.validationSchema) try {
403
489
  const { pathToFileURL: pathToFileURL$1 } = await import("node:url");
404
- const schemaModule = await import(`${pathToFileURL$1(config.jsonlPath.replace(".jsonl", ".schema.ts")).href}?t=${Date.now()}`);
490
+ const schemaPath = await findSchemaFile(dirname(config.jsonlPath), basename(config.jsonlPath, ".jsonl"));
491
+ if (!schemaPath) throw new Error("Schema file not found");
492
+ const schemaModule = await import(`${pathToFileURL$1(schemaPath).href}?t=${Date.now()}`);
405
493
  const schemaExport = schemaModule.schema || schemaModule.default;
406
494
  if (schemaExport?.primaryKey) schemaMetadata.primaryKey = schemaExport.primaryKey;
407
495
  else if (schemaModule.primaryKey) schemaMetadata.primaryKey = schemaModule.primaryKey;
@@ -444,6 +532,7 @@ var LinesDB = class LinesDB {
444
532
  }));
445
533
  if (validationErrors.length > 0) return {
446
534
  loaded: false,
535
+ rowCount: data.length,
447
536
  errors: validationErrorDetails
448
537
  };
449
538
  let schema;
@@ -458,6 +547,7 @@ var LinesDB = class LinesDB {
458
547
  } else if (config.autoInferSchema !== false) {
459
548
  if (validatedData.length === 0) return {
460
549
  loaded: false,
550
+ rowCount: 0,
461
551
  errors: []
462
552
  };
463
553
  schema = inferredSchema;
@@ -473,7 +563,7 @@ var LinesDB = class LinesDB {
473
563
  const idColumn = schema.columns.find((c) => c.name === "id");
474
564
  if (idColumn) idColumn.primaryKey = true;
475
565
  }
476
- if (foreignKeys) schema.foreignKeys = foreignKeys;
566
+ if (foreignKeys) schema.foreignKeys = failedDependencies && failedDependencies.size > 0 ? foreignKeys.filter((fk) => !failedDependencies.has(fk.references.table)) : foreignKeys;
477
567
  if (indexes) {
478
568
  schema.indexes = indexes;
479
569
  for (const index of indexes) if (index.unique && index.columns.length === 1) {
@@ -487,11 +577,13 @@ var LinesDB = class LinesDB {
487
577
  const insertErrors = this.insertDataWithDetailedValidation(tableName, schema, validatedData, config.jsonlPath);
488
578
  if (insertErrors.length > 0) return {
489
579
  loaded: false,
580
+ rowCount: data.length,
490
581
  errors: insertErrors
491
582
  };
492
583
  } else this.insertData(tableName, schema, validatedData);
493
584
  return {
494
585
  loaded: true,
586
+ rowCount: data.length,
495
587
  errors: []
496
588
  };
497
589
  }
@@ -1089,7 +1181,7 @@ var TypeGenerator = class {
1089
1181
  this.projectRoot = envProjectRoot !== void 0 ? envProjectRoot : options.projectRoot || process.cwd();
1090
1182
  this.dataDir = options.dataDir;
1091
1183
  this.dataDirPath = isAbsolute(this.dataDir) ? this.dataDir : join(this.projectRoot, this.dataDir);
1092
- this.outputFile = join(this.dataDirPath, "db.ts");
1184
+ this.outputFile = options.output ? isAbsolute(options.output) ? options.output : join(this.projectRoot, options.output) : join(this.dataDirPath, "db.ts");
1093
1185
  }
1094
1186
  /**
1095
1187
  * Generate types file from JSONL files and their optional schema files.
@@ -1112,12 +1204,10 @@ var TypeGenerator = class {
1112
1204
  const tables = [];
1113
1205
  for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".jsonl")) {
1114
1206
  const tableName = basename(entry.name, ".jsonl");
1115
- const schemaFileName = `${tableName}.schema.ts`;
1116
- const schemaFilePath = join(this.dataDirPath, schemaFileName);
1117
- const hasSchema = entries.some((e) => e.isFile() && e.name === schemaFileName);
1207
+ const schemaFilePath = findSchemaFileInEntries(this.dataDirPath, tableName, entries);
1118
1208
  tables.push({
1119
1209
  tableName,
1120
- schemaFile: hasSchema ? schemaFilePath : void 0
1210
+ schemaFile: schemaFilePath
1121
1211
  });
1122
1212
  }
1123
1213
  return tables;
@@ -1138,7 +1228,7 @@ var TypeGenerator = class {
1138
1228
  if (table.schemaFile) {
1139
1229
  const schemaIdentifier = this.createSchemaIdentifier(table.tableName, usedAliases);
1140
1230
  usedAliases.add(schemaIdentifier);
1141
- let relativePath = relative(join(this.outputFile, ".."), table.schemaFile).replace(/\\/g, "/").replace(".ts", ".js");
1231
+ let relativePath = rewriteExtensionForImport(relative(join(this.outputFile, ".."), table.schemaFile).replace(/\\/g, "/"));
1142
1232
  if (!relativePath.startsWith(".")) relativePath = "./" + relativePath;
1143
1233
  imports.push(`import { schema as ${schemaIdentifier} } from '${relativePath}';`);
1144
1234
  tableEntries.push(` ${tableKey}: InferOutput<typeof ${schemaIdentifier}>;`);
@@ -1214,5 +1304,5 @@ async function ensureTableRowsValid(options) {
1214
1304
  }
1215
1305
 
1216
1306
  //#endregion
1217
- export { DirectoryScanner, JsonlReader, JsonlWriter, LinesDB, RUNTIME, SchemaLoader, TypeGenerator, defineSchema, detectRuntime, ensureTableRowsValid, hasBackward };
1307
+ export { DirectoryScanner, JsonlReader, JsonlWriter, LinesDB, RUNTIME, SCHEMA_EXTENSIONS, SchemaLoader, TypeGenerator, defineSchema, detectRuntime, ensureTableRowsValid, extractTableNameFromSchemaFile, findSchemaFile, hasBackward, isSchemaFile, rewriteExtensionForImport };
1218
1308
  //# sourceMappingURL=index.js.map