@toiroakr/lines-db 0.5.0 → 0.6.1

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/src/database.ts CHANGED
@@ -10,11 +10,13 @@ import type {
10
10
  JsonObject,
11
11
  TableConfig,
12
12
  StandardSchema,
13
- StandardSchemaIssue,
14
13
  ValidationError,
15
14
  Table,
16
15
  TableDefs,
17
16
  WhereCondition,
17
+ ValidationResult,
18
+ ValidationErrorDetail,
19
+ ForeignKeyDefinition,
18
20
  } from './types.js';
19
21
  import type { BiDirectionalSchema } from './schema.js';
20
22
 
@@ -39,35 +41,68 @@ export class LinesDB<Tables extends TableDefs> {
39
41
  }
40
42
 
41
43
  /**
42
- * Initialize database by loading all JSONL files
44
+ * Initialize database by loading all JSONL files or a specific table
43
45
  * Uses dependency resolution to ensure foreign key references are loaded in correct order
46
+ * @param options Optional configuration for initialization
47
+ * @param options.tableName Optional table name to initialize. If not provided, initializes all tables
48
+ * @param options.detailedValidate If true, performs detailed validation by inserting rows one by one to catch constraint violations
49
+ * @param options.transform Optional transform function to apply to rows before validation (only applied to the specified tableName)
50
+ * @returns ValidationResult containing validation status, errors, and warnings
44
51
  */
45
- async initialize(): Promise<void> {
52
+ async initialize(options?: {
53
+ tableName?: string;
54
+ detailedValidate?: boolean;
55
+ transform?: (row: JsonObject) => JsonObject;
56
+ }): Promise<ValidationResult> {
57
+ const allErrors: ValidationErrorDetail[] = [];
58
+ const allWarnings: string[] = [];
59
+ const tableName = options?.tableName;
60
+ const detailedValidate = options?.detailedValidate ?? false;
61
+ const transform = options?.transform;
62
+
46
63
  // Scan directory for JSONL files
47
64
  this.tables = await DirectoryScanner.scanDirectory(this.config.dataDir);
48
65
 
66
+ // Determine which tables to load
67
+ const tablesToLoad = tableName ? [tableName] : Array.from(this.tables.keys());
68
+
69
+ // Validate that all requested tables exist BEFORE starting to load
70
+ for (const tableNameToLoad of tablesToLoad) {
71
+ if (!this.tables.has(tableNameToLoad)) {
72
+ throw new Error(
73
+ `Table '${tableNameToLoad}' not found in directory '${this.config.dataDir}'`,
74
+ );
75
+ }
76
+ }
77
+
49
78
  // Track loaded tables and tables currently being loaded (for circular dependency detection)
50
79
  const loadedTables = new Set<string>();
51
80
  const loadingTables = new Set<string>();
52
-
53
- // Load all tables with dependency resolution
54
- for (const [tableName] of this.tables) {
55
- if (!loadedTables.has(tableName)) {
56
- try {
57
- await this.loadTableWithDependencies(tableName, loadedTables, loadingTables);
58
- } catch (error) {
59
- // Log error but continue loading other tables
60
- console.warn(
61
- `Warning: Failed to load table '${tableName}':`,
62
- error instanceof Error ? error.message : String(error),
63
- );
64
- // Remove the failed table from the tables map
65
- this.tables.delete(tableName);
66
- this.schemas.delete(tableName);
67
- this.validationSchemas.delete(tableName);
68
- }
81
+ const attemptedTables = new Set<string>(); // Track all attempted tables (loaded or not)
82
+
83
+ // Load tables with dependency resolution
84
+ for (const tableNameToLoad of tablesToLoad) {
85
+ if (!attemptedTables.has(tableNameToLoad)) {
86
+ // Only apply transform to the specified table
87
+ const tableTransform = tableNameToLoad === tableName ? transform : undefined;
88
+ const { errors, warnings } = await this.loadTableWithDependencies(
89
+ tableNameToLoad,
90
+ loadedTables,
91
+ loadingTables,
92
+ attemptedTables,
93
+ detailedValidate,
94
+ tableTransform,
95
+ );
96
+ allErrors.push(...errors);
97
+ allWarnings.push(...warnings);
69
98
  }
70
99
  }
100
+
101
+ return {
102
+ valid: allErrors.length === 0,
103
+ errors: allErrors,
104
+ warnings: allWarnings,
105
+ };
71
106
  }
72
107
 
73
108
  /**
@@ -77,12 +112,21 @@ export class LinesDB<Tables extends TableDefs> {
77
112
  tableName: string,
78
113
  loadedTables: Set<string>,
79
114
  loadingTables: Set<string>,
80
- ): Promise<void> {
81
- // Skip if already loaded
82
- if (loadedTables.has(tableName)) {
83
- return;
115
+ attemptedTables: Set<string>,
116
+ detailedValidate: boolean,
117
+ transform?: (row: JsonObject) => JsonObject,
118
+ ): Promise<{ errors: ValidationErrorDetail[]; warnings: string[] }> {
119
+ const errors: ValidationErrorDetail[] = [];
120
+ const warnings: string[] = [];
121
+
122
+ // Skip if already attempted (loaded or not)
123
+ if (attemptedTables.has(tableName)) {
124
+ return { errors, warnings };
84
125
  }
85
126
 
127
+ // Mark as attempted
128
+ attemptedTables.add(tableName);
129
+
86
130
  // Check for circular dependencies
87
131
  if (loadingTables.has(tableName)) {
88
132
  throw new Error(`Circular dependency detected for table '${tableName}'`);
@@ -119,10 +163,26 @@ export class LinesDB<Tables extends TableDefs> {
119
163
  if (foreignKeys && foreignKeys.length > 0) {
120
164
  for (const fk of foreignKeys) {
121
165
  const referencedTable = fk.references.table;
122
- if (!loadedTables.has(referencedTable)) {
166
+
167
+ // Skip self-referencing foreign keys (e.g., nullable parent_id columns)
168
+ if (referencedTable === tableName) {
169
+ continue;
170
+ }
171
+
172
+ if (!attemptedTables.has(referencedTable)) {
123
173
  // Check if referenced table exists in our tables map
124
174
  if (this.tables.has(referencedTable)) {
125
- await this.loadTableWithDependencies(referencedTable, loadedTables, loadingTables);
175
+ // Dependencies should not have transform applied
176
+ const depResult = await this.loadTableWithDependencies(
177
+ referencedTable,
178
+ loadedTables,
179
+ loadingTables,
180
+ attemptedTables,
181
+ detailedValidate,
182
+ undefined,
183
+ );
184
+ errors.push(...depResult.errors);
185
+ warnings.push(...depResult.warnings);
126
186
  } else {
127
187
  throw new Error(
128
188
  `Foreign key reference to non-existent table '${referencedTable}' in table '${tableName}'`,
@@ -133,26 +193,46 @@ export class LinesDB<Tables extends TableDefs> {
133
193
  }
134
194
 
135
195
  // Now load this table
136
- const wasLoaded = await this.loadTable(tableName, tableConfig);
137
- if (wasLoaded) {
196
+ const { loaded, errors: loadErrors } = await this.loadTable(
197
+ tableName,
198
+ tableConfig,
199
+ detailedValidate,
200
+ transform,
201
+ );
202
+ errors.push(...loadErrors);
203
+
204
+ if (loaded) {
138
205
  loadedTables.add(tableName);
139
206
  } else {
140
207
  // Table was not loaded (e.g., empty data)
208
+ warnings.push(`Table '${tableName}' was not loaded (no data or skipped)`);
141
209
  this.tables.delete(tableName);
142
210
  }
143
211
  } finally {
144
212
  // Remove from loading set
145
213
  loadingTables.delete(tableName);
146
214
  }
215
+
216
+ return { errors, warnings };
147
217
  }
148
218
 
149
219
  /**
150
220
  * Load a single table from JSONL file
151
- * @returns true if table was loaded, false if skipped
221
+ * @returns Object with loaded status and validation errors
152
222
  */
153
- private async loadTable(tableName: string, config: TableConfig): Promise<boolean> {
223
+ private async loadTable(
224
+ tableName: string,
225
+ config: TableConfig,
226
+ detailedValidate: boolean,
227
+ transform?: (row: JsonObject) => JsonObject,
228
+ ): Promise<{ loaded: boolean; errors: ValidationErrorDetail[] }> {
154
229
  // Read JSONL file
155
- const data = await JsonlReader.read(config.jsonlPath);
230
+ let data = await JsonlReader.read(config.jsonlPath);
231
+
232
+ // Apply transform if provided (before validation)
233
+ if (transform) {
234
+ data = data.map((row) => transform(row));
235
+ }
156
236
 
157
237
  // Load validation schema if provided or try to auto-load
158
238
  let validationSchema = config.validationSchema;
@@ -200,8 +280,23 @@ export class LinesDB<Tables extends TableDefs> {
200
280
  } else if (schemaModule.indexes) {
201
281
  schemaMetadata.indexes = schemaModule.indexes;
202
282
  }
283
+
284
+ // Debug: log loaded metadata
285
+ if (process.env.DEBUG_LINES_DB) {
286
+ console.log(`[lines-db] Schema metadata for ${tableName}:`);
287
+ console.log(` primaryKey: ${schemaMetadata.primaryKey}`);
288
+ console.log(` foreignKeys: ${JSON.stringify(schemaMetadata.foreignKeys)}`);
289
+ console.log(` indexes: ${JSON.stringify(schemaMetadata.indexes)}`);
290
+ }
203
291
  } catch (_error) {
204
292
  // Schema file not found - this is OK
293
+ // Debug: log error for investigation
294
+ if (process.env.DEBUG_LINES_DB) {
295
+ console.warn(
296
+ `[lines-db] Failed to load schema metadata for ${tableName}:`,
297
+ _error instanceof Error ? _error.message : String(_error),
298
+ );
299
+ }
205
300
  }
206
301
  }
207
302
 
@@ -233,29 +328,48 @@ export class LinesDB<Tables extends TableDefs> {
233
328
  }
234
329
  }
235
330
 
331
+ // Convert validation errors to ValidationErrorDetail format
332
+ const validationErrorDetails: ValidationErrorDetail[] = validationErrors.map((ve) => ({
333
+ file: config.jsonlPath,
334
+ tableName,
335
+ rowIndex: ve.rowIndex,
336
+ issues: ve.error.issues,
337
+ type: 'schema' as const,
338
+ }));
339
+
236
340
  if (validationErrors.length > 0) {
237
- const enhancedError = new Error(
238
- `Validation failed for ${validationErrors.length} row(s) in table ${tableName}`,
239
- );
240
- enhancedError.name = 'ValidationError';
241
- (enhancedError as unknown as { validationErrors: typeof validationErrors }).validationErrors =
242
- validationErrors;
243
- (enhancedError as unknown as { issues: ReadonlyArray<StandardSchemaIssue> }).issues =
244
- validationErrors[0].error.issues;
245
- throw enhancedError;
341
+ // Return errors instead of throwing
342
+ return { loaded: false, errors: validationErrorDetails };
246
343
  }
247
344
 
248
345
  // Determine schema - infer from validated data if auto-inference is enabled
249
346
  let schema: TableSchema;
347
+ let inferredSchema: TableSchema | undefined;
348
+
349
+ // Always infer schema from validated data to capture valueType information (e.g., boolean)
350
+ if (validatedData.length > 0) {
351
+ inferredSchema = JsonlReader.inferSchema(tableName, validatedData);
352
+ }
353
+
250
354
  if (config.schema) {
251
355
  schema = config.schema;
356
+ // Merge valueType information from inferred schema
357
+ if (inferredSchema) {
358
+ for (const inferredCol of inferredSchema.columns) {
359
+ const schemaCol = schema.columns.find((c) => c.name === inferredCol.name);
360
+ if (schemaCol && inferredCol.valueType && !schemaCol.valueType) {
361
+ schemaCol.valueType = inferredCol.valueType;
362
+ }
363
+ }
364
+ }
252
365
  } else if (config.autoInferSchema !== false) {
253
366
  if (validatedData.length === 0) {
254
- return false;
367
+ return { loaded: false, errors: [] };
255
368
  }
256
- // Infer schema from validated data (which may have additional fields added by validation)
257
- schema = JsonlReader.inferSchema(tableName, validatedData);
369
+ // Use inferred schema
370
+ schema = inferredSchema!;
258
371
  } else {
372
+ // Critical error - throw exception
259
373
  throw new Error(`No schema provided for table ${tableName} and autoInferSchema is disabled`);
260
374
  }
261
375
 
@@ -285,6 +399,18 @@ export class LinesDB<Tables extends TableDefs> {
285
399
  }
286
400
  if (indexes) {
287
401
  schema.indexes = indexes;
402
+
403
+ // Apply unique constraint from single-column unique indexes to column definitions
404
+ // This is required for foreign key references, as SQLite requires the referenced column
405
+ // to have a UNIQUE constraint in the table definition (not just an index)
406
+ for (const index of indexes) {
407
+ if (index.unique && index.columns.length === 1) {
408
+ const col = schema.columns.find((c) => c.name === index.columns[0]);
409
+ if (col && !col.unique && !col.primaryKey) {
410
+ col.unique = true;
411
+ }
412
+ }
413
+ }
288
414
  }
289
415
 
290
416
  this.schemas.set(tableName, schema);
@@ -292,10 +418,22 @@ export class LinesDB<Tables extends TableDefs> {
292
418
  // Create table
293
419
  this.createTable(schema);
294
420
 
295
- // Insert validated data
296
- this.insertData(tableName, schema, validatedData);
421
+ // Insert validated data (with detailed validation if requested)
422
+ if (detailedValidate) {
423
+ const insertErrors = this.insertDataWithDetailedValidation(
424
+ tableName,
425
+ schema,
426
+ validatedData,
427
+ config.jsonlPath,
428
+ );
429
+ if (insertErrors.length > 0) {
430
+ return { loaded: false, errors: insertErrors };
431
+ }
432
+ } else {
433
+ this.insertData(tableName, schema, validatedData);
434
+ }
297
435
 
298
- return true;
436
+ return { loaded: true, errors: [] };
299
437
  }
300
438
 
301
439
  /**
@@ -308,13 +446,31 @@ export class LinesDB<Tables extends TableDefs> {
308
446
  // Quote table name to handle special characters
309
447
  const quotedTableName = this.quoteTableName(schema.name);
310
448
 
449
+ // Build a set of columns that should have UNIQUE constraint
450
+ // This includes columns marked as unique in schema AND single-column unique indexes
451
+ // The latter is required for foreign key references, as SQLite requires the referenced column
452
+ // to have a UNIQUE constraint in the table definition (not just a separately created index)
453
+ const uniqueColumns = new Set<string>();
454
+ for (const col of schema.columns) {
455
+ if (col.unique) {
456
+ uniqueColumns.add(col.name);
457
+ }
458
+ }
459
+ if (schema.indexes) {
460
+ for (const index of schema.indexes) {
461
+ if (index.unique && index.columns.length === 1) {
462
+ uniqueColumns.add(index.columns[0]);
463
+ }
464
+ }
465
+ }
466
+
311
467
  const columnDefs = schema.columns.map((col) => {
312
468
  // JSON type is stored as TEXT in SQLite
313
469
  const sqlType = col.type === 'JSON' ? 'TEXT' : col.type;
314
470
  const parts = [this.quoteIdentifier(col.name), sqlType];
315
471
  if (col.primaryKey) parts.push('PRIMARY KEY');
316
472
  if (col.notNull) parts.push('NOT NULL');
317
- if (col.unique) parts.push('UNIQUE');
473
+ if (uniqueColumns.has(col.name) && !col.primaryKey) parts.push('UNIQUE');
318
474
  return parts.join(' ');
319
475
  });
320
476
 
@@ -372,9 +528,52 @@ export class LinesDB<Tables extends TableDefs> {
372
528
  }
373
529
 
374
530
  /**
375
- * Insert data into table
531
+ * Insert data into table using batch insert (multiple rows per SQL)
532
+ * SQLite has a parameter limit (default 999), so we batch rows accordingly
533
+ * Throws exception if any constraint violation occurs
376
534
  */
377
535
  private insertData(tableName: string, schema: TableSchema, data: JsonObject[]): void {
536
+ if (data.length === 0) return;
537
+
538
+ const columnNames = schema.columns.map((col) => col.name);
539
+ const quotedColumns = columnNames.map((name) => this.quoteIdentifier(name));
540
+ const columnCount = columnNames.length;
541
+
542
+ // Calculate batch size to stay under SQLite's parameter limit (999)
543
+ // Leave some margin for safety
544
+ const maxBatchSize = Math.floor(900 / columnCount);
545
+ const batchSize = Math.max(1, Math.min(maxBatchSize, 100));
546
+
547
+ // Process data in batches
548
+ for (let i = 0; i < data.length; i += batchSize) {
549
+ const batch = data.slice(i, i + batchSize);
550
+ const rowPlaceholders = columnNames.map(() => '?').join(', ');
551
+ const valuesPlaceholders = batch.map(() => `(${rowPlaceholders})`).join(', ');
552
+ const sql = `INSERT INTO ${this.quoteTableName(tableName)} (${quotedColumns.join(', ')}) VALUES ${valuesPlaceholders}`;
553
+
554
+ const values: (string | number | bigint | null | Uint8Array)[] = [];
555
+ for (const row of batch) {
556
+ for (const col of columnNames) {
557
+ values.push(this.normalizeValue(row[col]));
558
+ }
559
+ }
560
+
561
+ const stmt = this.db.prepare(sql);
562
+ stmt.run(...values);
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Insert data into table one row at a time with detailed error reporting
568
+ * This is used for validation to catch constraint violations
569
+ */
570
+ private insertDataWithDetailedValidation(
571
+ tableName: string,
572
+ schema: TableSchema,
573
+ data: JsonObject[],
574
+ filePath: string,
575
+ ): ValidationErrorDetail[] {
576
+ const errors: ValidationErrorDetail[] = [];
378
577
  const columnNames = schema.columns.map((col) => col.name);
379
578
  const quotedColumns = columnNames.map((name) => this.quoteIdentifier(name));
380
579
  const placeholders = columnNames.map(() => '?').join(', ');
@@ -382,10 +581,90 @@ export class LinesDB<Tables extends TableDefs> {
382
581
 
383
582
  const stmt = this.db.prepare(sql);
384
583
 
385
- for (const row of data) {
386
- const values = columnNames.map((col) => this.normalizeValue(row[col]));
387
- stmt.run(...values);
584
+ for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
585
+ const row = data[rowIndex];
586
+ try {
587
+ const values = columnNames.map((col) => this.normalizeValue(row[col]));
588
+ stmt.run(...values);
589
+ } catch (error) {
590
+ // Constraint violation occurred - analyze and record details
591
+ const constraintError = this.analyzeConstraintError(
592
+ error,
593
+ filePath,
594
+ tableName,
595
+ rowIndex,
596
+ row,
597
+ schema.foreignKeys || [],
598
+ );
599
+ if (constraintError) {
600
+ errors.push(constraintError);
601
+ }
602
+ }
388
603
  }
604
+
605
+ return errors;
606
+ }
607
+
608
+ /**
609
+ * Analyze constraint error and extract detailed information
610
+ */
611
+ private analyzeConstraintError(
612
+ error: unknown,
613
+ file: string,
614
+ tableName: string,
615
+ rowIndex: number,
616
+ row: JsonObject,
617
+ foreignKeys: ForeignKeyDefinition[],
618
+ ): ValidationErrorDetail | null {
619
+ const errorMessage = error instanceof Error ? error.message : String(error);
620
+
621
+ // Foreign key constraint
622
+ if (errorMessage.includes('FOREIGN KEY constraint failed')) {
623
+ // Find which foreign key failed
624
+ for (const fk of foreignKeys) {
625
+ const fkValue = row[fk.column];
626
+ if (fkValue === null || fkValue === undefined) continue;
627
+
628
+ // Check if referenced value exists
629
+ try {
630
+ const result = this.query(
631
+ `SELECT COUNT(*) as count FROM ${this.quoteIdentifier(fk.references.table)} WHERE ${this.quoteIdentifier(fk.references.column)} = ?`,
632
+ [this.normalizeValue(fkValue)],
633
+ );
634
+ if (result.length > 0 && (result[0] as { count: number }).count === 0) {
635
+ return {
636
+ file,
637
+ tableName,
638
+ rowIndex,
639
+ issues: [],
640
+ type: 'foreignKey',
641
+ foreignKeyError: {
642
+ column: fk.column,
643
+ value: fkValue,
644
+ referencedTable: fk.references.table,
645
+ referencedColumn: fk.references.column,
646
+ },
647
+ };
648
+ }
649
+ } catch (_) {
650
+ // Referenced table doesn't exist yet
651
+ }
652
+ }
653
+ }
654
+
655
+ // Other constraint errors (primary key, unique, etc.)
656
+ return {
657
+ file,
658
+ tableName,
659
+ rowIndex,
660
+ issues: [
661
+ {
662
+ message: errorMessage,
663
+ path: [],
664
+ },
665
+ ],
666
+ type: 'schema',
667
+ };
389
668
  }
390
669
 
391
670
  /**
@@ -1112,10 +1391,20 @@ export class LinesDB<Tables extends TableDefs> {
1112
1391
  /**
1113
1392
  * Sync database changes back to JSONL files
1114
1393
  * Uses backward transformation when available
1394
+ * @param tableName Optional table name to sync. If not provided, syncs all loaded tables
1115
1395
  */
1116
- async sync(): Promise<void> {
1117
- for (const [tableName] of this.tables) {
1396
+ async sync(tableName?: string): Promise<void> {
1397
+ if (tableName) {
1398
+ // Sync only the specified table
1399
+ if (!this.schemas.has(tableName)) {
1400
+ throw new Error(`Table '${tableName}' is not loaded`);
1401
+ }
1118
1402
  await this.syncTable(tableName);
1403
+ } else {
1404
+ // Sync all tables that are loaded (present in schemas map)
1405
+ for (const [name] of this.schemas) {
1406
+ await this.syncTable(name);
1407
+ }
1119
1408
  }
1120
1409
  }
1121
1410
 
package/src/index.ts CHANGED
@@ -5,14 +5,12 @@ export { SchemaLoader } from './schema-loader.js';
5
5
  export { DirectoryScanner } from './directory-scanner.js';
6
6
  export { defineSchema, hasBackward } from './schema.js';
7
7
  export { TypeGenerator } from './type-generator.js';
8
- export { Validator } from './validator.js';
9
8
  export { ensureTableRowsValid } from './jsonl-migration.js';
10
9
  export type { TableValidationOptions } from './jsonl-migration.js';
11
10
  export { detectRuntime, RUNTIME } from './runtime.js';
12
11
  export type { RuntimeEnvironment } from './runtime.js';
13
12
  export type { SQLiteDatabase, SQLiteStatement } from './sqlite-adapter.js';
14
13
  export type { TypeGeneratorOptions } from './type-generator.js';
15
- export type { ValidatorOptions, ValidationResult, ValidationErrorDetail } from './validator.js';
16
14
  export type {
17
15
  TableSchema,
18
16
  ColumnDefinition,
@@ -25,6 +23,8 @@ export type {
25
23
  StandardSchemaResult,
26
24
  StandardSchemaIssue,
27
25
  ValidationError,
26
+ ValidationResult,
27
+ ValidationErrorDetail,
28
28
  InferInput,
29
29
  InferOutput,
30
30
  Table,
@@ -11,66 +11,34 @@ export interface TableValidationOptions {
11
11
 
12
12
  /**
13
13
  * Validate a table by temporarily supplying in-memory rows while reusing the existing LinesDB validation pipeline.
14
- * If validation fails, the underlying LinesDB error is rethrown so callers can inspect validation details.
14
+ * If validation fails, throws an error with validation details.
15
15
  */
16
16
  export async function ensureTableRowsValid(options: TableValidationOptions): Promise<void> {
17
- console.log('[ensureTableRowsValid] START');
18
- console.log('[ensureTableRowsValid] dataDir:', options.dataDir);
19
- console.log('[ensureTableRowsValid] tableName:', options.tableName);
20
- console.log('[ensureTableRowsValid] rows count:', options.rows.length);
21
-
22
17
  const tablePath = join(options.dataDir, `${options.tableName}.jsonl`);
23
18
  const overrides = new Map<string, JsonObject[]>([[tablePath, options.rows]]);
24
- console.log('[ensureTableRowsValid] tablePath:', tablePath);
25
-
26
- let capturedError: Error | null = null;
27
-
28
- // Intercept console.warn to capture validation errors
29
- const originalWarn = console.warn;
30
- const warnMessages: string[] = [];
31
- console.warn = (...args: any[]) => {
32
- const message = args.join(' ');
33
- console.log('[ensureTableRowsValid] Captured warn:', message);
34
- warnMessages.push(message);
35
- // Check if this is a validation error for our table
36
- if (
37
- message.includes(`Failed to load table '${options.tableName}'`) &&
38
- message.includes('Validation failed')
39
- ) {
40
- // Extract the original error from the warn message
41
- capturedError = new Error(message);
42
- console.log('[ensureTableRowsValid] Captured validation error!');
43
- }
44
- };
45
19
 
46
- try {
47
- console.log('[ensureTableRowsValid] Calling JsonlReader.withOverrides');
48
- await JsonlReader.withOverrides(overrides, async () => {
49
- console.log('[ensureTableRowsValid] Inside withOverrides callback');
50
- const db = LinesDB.create({ dataDir: options.dataDir });
51
- console.log('[ensureTableRowsValid] LinesDB created');
52
- try {
53
- console.log('[ensureTableRowsValid] Calling db.initialize()');
54
- await db.initialize();
55
- console.log('[ensureTableRowsValid] db.initialize() completed');
56
- } finally {
57
- console.log('[ensureTableRowsValid] Calling db.close()');
58
- await db.close();
20
+ await JsonlReader.withOverrides(overrides, async () => {
21
+ const db = LinesDB.create({ dataDir: options.dataDir });
22
+ try {
23
+ // Initialize only the target table
24
+ const result = await db.initialize({ tableName: options.tableName });
25
+
26
+ // If validation failed, throw an error with details
27
+ if (!result.valid) {
28
+ const errorCount = result.errors.length;
29
+ const errorDetails = result.errors
30
+ .map((e) => {
31
+ const issueMessages = e.issues.map((issue) => issue.message).join(', ');
32
+ return ` Row ${e.rowIndex}: ${issueMessages}`;
33
+ })
34
+ .join('\n');
35
+
36
+ throw new Error(
37
+ `Validation failed for table '${options.tableName}' (${errorCount} error(s)):\n${errorDetails}`,
38
+ );
59
39
  }
60
- });
61
- console.log('[ensureTableRowsValid] withOverrides completed');
62
- } finally {
63
- // Restore original console.warn
64
- console.warn = originalWarn;
65
- }
66
-
67
- console.log('[ensureTableRowsValid] Warnings captured:', warnMessages.length);
68
- console.log('[ensureTableRowsValid] capturedError:', capturedError ? 'YES' : 'NO');
69
-
70
- if (capturedError) {
71
- console.log('[ensureTableRowsValid] Throwing captured error');
72
- throw capturedError;
73
- }
74
-
75
- console.log('[ensureTableRowsValid] END (success)');
40
+ } finally {
41
+ await db.close();
42
+ }
43
+ });
76
44
  }