@toiroakr/lines-db 0.2.1 → 0.3.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
@@ -29,6 +29,7 @@ function createDatabase(path = ":memory:") {
29
29
  function createNodeDatabase(path) {
30
30
  const { DatabaseSync } = __require("node:sqlite");
31
31
  const db = new DatabaseSync(path);
32
+ db.exec("PRAGMA foreign_keys = ON");
32
33
  return {
33
34
  prepare(sql) {
34
35
  const stmt = db.prepare(sql);
@@ -124,7 +125,7 @@ var JsonlReader = class {
124
125
  };
125
126
  }
126
127
  static inferType(value) {
127
- if (value === null) return "NULL";
128
+ if (value === null || value === void 0) return "NULL";
128
129
  if (typeof value === "number") return Number.isInteger(value) ? "INTEGER" : "REAL";
129
130
  if (typeof value === "string") return "TEXT";
130
131
  if (typeof value === "boolean") return "INTEGER";
@@ -308,57 +309,73 @@ var LinesDB = class LinesDB {
308
309
  }
309
310
  /**
310
311
  * Initialize database by loading all JSONL files
312
+ * Uses dependency resolution to ensure foreign key references are loaded in correct order
311
313
  */
312
314
  async initialize() {
313
315
  this.tables = await DirectoryScanner.scanDirectory(this.config.dataDir);
314
- for (const [tableName, tableConfig] of this.tables) try {
315
- await this.loadTable(tableName, tableConfig);
316
+ const loadedTables = /* @__PURE__ */ new Set();
317
+ const loadingTables = /* @__PURE__ */ new Set();
318
+ for (const [tableName] of this.tables) if (!loadedTables.has(tableName)) try {
319
+ await this.loadTableWithDependencies(tableName, loadedTables, loadingTables);
316
320
  } catch (error) {
317
321
  console.warn(`Warning: Failed to load table '${tableName}':`, error instanceof Error ? error.message : String(error));
318
322
  this.tables.delete(tableName);
323
+ this.schemas.delete(tableName);
324
+ this.validationSchemas.delete(tableName);
325
+ }
326
+ }
327
+ /**
328
+ * Load a table and its dependencies recursively
329
+ */
330
+ async loadTableWithDependencies(tableName, loadedTables, loadingTables) {
331
+ if (loadedTables.has(tableName)) return;
332
+ if (loadingTables.has(tableName)) throw new Error(`Circular dependency detected for table '${tableName}'`);
333
+ const tableConfig = this.tables.get(tableName);
334
+ if (!tableConfig) throw new Error(`Table configuration not found for '${tableName}'`);
335
+ loadingTables.add(tableName);
336
+ try {
337
+ let foreignKeys;
338
+ try {
339
+ const { pathToFileURL: pathToFileURL$1 } = await import("node:url");
340
+ foreignKeys = (await import(`${pathToFileURL$1(tableConfig.jsonlPath.replace(".jsonl", ".schema.ts")).href}?t=${Date.now()}`)).foreignKeys;
341
+ } catch {}
342
+ if (foreignKeys && foreignKeys.length > 0) for (const fk of foreignKeys) {
343
+ const referencedTable = fk.references.table;
344
+ if (!loadedTables.has(referencedTable)) if (this.tables.has(referencedTable)) await this.loadTableWithDependencies(referencedTable, loadedTables, loadingTables);
345
+ else throw new Error(`Foreign key reference to non-existent table '${referencedTable}' in table '${tableName}'`);
346
+ }
347
+ if (await this.loadTable(tableName, tableConfig)) loadedTables.add(tableName);
348
+ else this.tables.delete(tableName);
349
+ } finally {
350
+ loadingTables.delete(tableName);
319
351
  }
320
352
  }
321
353
  /**
322
354
  * Load a single table from JSONL file
355
+ * @returns true if table was loaded, false if skipped
323
356
  */
324
357
  async loadTable(tableName, config) {
325
358
  const data = await JsonlReader.read(config.jsonlPath);
326
- if (data.length === 0) {
327
- console.warn(`Warning: Table ${tableName} has no data`);
328
- return;
329
- }
330
359
  let validationSchema = config.validationSchema;
360
+ const schemaMetadata = {};
331
361
  if (!validationSchema) try {
332
362
  validationSchema = await SchemaLoader.loadSchema(config.jsonlPath);
333
- } catch (error) {
334
- console.log(`[LinesDB] No validation schema for table '${tableName}':`, error instanceof Error ? error.message : String(error));
335
- }
336
- console.log(`[LinesDB] Loaded validation schema for table '${tableName}':`, validationSchema ? "FOUND" : "NOT FOUND");
337
- if (validationSchema) {
338
- console.log(`[LinesDB] Schema type:`, typeof validationSchema);
339
- console.log(`[LinesDB] Schema has '~standard':`, "~standard" in validationSchema);
340
- }
363
+ } catch (_error) {}
364
+ if (!config.validationSchema) try {
365
+ const { pathToFileURL: pathToFileURL$1 } = await import("node:url");
366
+ const schemaModule = await import(`${pathToFileURL$1(config.jsonlPath.replace(".jsonl", ".schema.ts")).href}?t=${Date.now()}`);
367
+ if (schemaModule.primaryKey) schemaMetadata.primaryKey = schemaModule.primaryKey;
368
+ if (schemaModule.foreignKeys) schemaMetadata.foreignKeys = schemaModule.foreignKeys;
369
+ if (schemaModule.indexes) schemaMetadata.indexes = schemaModule.indexes;
370
+ } catch (_error) {}
341
371
  this.validationSchemas.set(tableName, validationSchema);
342
- let schema;
343
- if (config.schema) schema = config.schema;
344
- else if (config.autoInferSchema !== false) schema = JsonlReader.inferSchema(tableName, data);
345
- else throw new Error(`No schema provided for table ${tableName} and autoInferSchema is disabled`);
346
- if (validationSchema) {
347
- const biSchema = validationSchema;
348
- if (biSchema.primaryKey && !schema.columns.some((col) => col.primaryKey)) for (const pkColumn of biSchema.primaryKey) {
349
- const col = schema.columns.find((c) => c.name === pkColumn);
350
- if (col) col.primaryKey = true;
351
- }
352
- if (biSchema.foreignKeys) schema.foreignKeys = biSchema.foreignKeys;
353
- if (biSchema.indexes) schema.indexes = biSchema.indexes;
354
- }
355
- this.schemas.set(tableName, schema);
356
- this.createTable(schema);
357
372
  const validationErrors = [];
373
+ const validatedData = [];
358
374
  for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
359
375
  const row = data[rowIndex];
360
376
  try {
361
- this.validateData(tableName, row);
377
+ const validatedRow = this.validateAndTransform(tableName, row);
378
+ validatedData.push(validatedRow);
362
379
  } catch (error) {
363
380
  if (error instanceof Error && error.name === "ValidationError") validationErrors.push({
364
381
  rowIndex,
@@ -375,13 +392,31 @@ var LinesDB = class LinesDB {
375
392
  enhancedError.issues = validationErrors[0].error.issues;
376
393
  throw enhancedError;
377
394
  }
378
- this.insertData(tableName, schema, data);
395
+ let schema;
396
+ if (config.schema) schema = config.schema;
397
+ else if (config.autoInferSchema !== false) {
398
+ if (validatedData.length === 0) return false;
399
+ schema = JsonlReader.inferSchema(tableName, validatedData);
400
+ } else throw new Error(`No schema provided for table ${tableName} and autoInferSchema is disabled`);
401
+ const biSchema = validationSchema;
402
+ const primaryKey = biSchema?.primaryKey || schemaMetadata.primaryKey;
403
+ const foreignKeys = biSchema?.foreignKeys || schemaMetadata.foreignKeys;
404
+ const indexes = biSchema?.indexes || schemaMetadata.indexes;
405
+ if (primaryKey && !schema.columns.some((col) => col.primaryKey)) for (const pkColumn of primaryKey) {
406
+ const col = schema.columns.find((c) => c.name === pkColumn);
407
+ if (col) col.primaryKey = true;
408
+ }
409
+ if (foreignKeys) schema.foreignKeys = foreignKeys;
410
+ if (indexes) schema.indexes = indexes;
411
+ this.schemas.set(tableName, schema);
412
+ this.createTable(schema);
413
+ this.insertData(tableName, schema, validatedData);
414
+ return true;
379
415
  }
380
416
  /**
381
417
  * Create table in SQLite with constraints and indexes
382
418
  */
383
419
  createTable(schema) {
384
- this.db.exec("PRAGMA foreign_keys = ON");
385
420
  const quotedTableName = this.quoteTableName(schema.name);
386
421
  const columnDefs = schema.columns.map((col) => {
387
422
  const sqlType = col.type === "JSON" ? "TEXT" : col.type;
@@ -508,17 +543,12 @@ var LinesDB = class LinesDB {
508
543
  return deserializedRow;
509
544
  }
510
545
  /**
511
- * Validate data using StandardSchema
546
+ * Validate data using StandardSchema and return the transformed value
512
547
  * Note: Only synchronous validation is supported
513
548
  */
514
- validateData(tableName, data) {
549
+ validateAndTransform(tableName, data) {
515
550
  const schema = this.validationSchemas.get(tableName);
516
- console.log(`[LinesDB] validateData called for table '${tableName}', schema exists:`, !!schema);
517
- if (!schema) {
518
- console.log(`[LinesDB] No validation schema found for table '${tableName}', skipping validation`);
519
- return;
520
- }
521
- console.log(`[LinesDB] Validating data:`, JSON.stringify(data));
551
+ if (!schema) return data;
522
552
  const result = schema["~standard"].validate(data);
523
553
  if (result instanceof Promise) throw new Error("Asynchronous validation is not supported. Please use synchronous validation schemas.");
524
554
  if (result.issues && result.issues.length > 0) {
@@ -535,6 +565,17 @@ var LinesDB = class LinesDB {
535
565
  error.issues = result.issues;
536
566
  throw error;
537
567
  }
568
+ const transformedValue = "value" in result ? result.value : data;
569
+ const normalizedValue = {};
570
+ for (const [key, value] of Object.entries(transformedValue)) normalizedValue[key] = value === void 0 ? null : value;
571
+ return normalizedValue;
572
+ }
573
+ /**
574
+ * Validate data using StandardSchema (without returning transformed value)
575
+ * Note: Only synchronous validation is supported
576
+ */
577
+ validateData(tableName, data) {
578
+ this.validateAndTransform(tableName, data);
538
579
  }
539
580
  /**
540
581
  * Insert a row into a table with validation
@@ -857,7 +898,7 @@ var LinesDB = class LinesDB {
857
898
  await this.sync();
858
899
  return result;
859
900
  } catch (error) {
860
- this.db.exec("ROLLBACK");
901
+ if (this.inTransaction) this.db.exec("ROLLBACK");
861
902
  this.inTransaction = false;
862
903
  throw error;
863
904
  }