@toiroakr/lines-db 0.5.0 → 0.6.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/src/cli.ts CHANGED
@@ -5,13 +5,13 @@ import { register } from 'node:module';
5
5
  register('tsx', import.meta.url, { data: {} });
6
6
 
7
7
  import { TypeGenerator } from './type-generator.js';
8
- import { Validator } from './validator.js';
9
8
  import { LinesDB } from './database.js';
10
9
  import { ErrorFormatter } from './error-formatter.js';
11
- import type { ValidationError } from './types.js';
10
+ import type { ValidationError, JsonObject } from './types.js';
12
11
  import { Command } from 'commander';
13
12
  import { styleText } from 'node:util';
14
13
  import { writeFile, stat, readdir } from 'node:fs/promises';
14
+ import { basename, dirname } from 'node:path';
15
15
  import { runInNewContext } from 'node:vm';
16
16
 
17
17
  const originalEmitWarning = process.emitWarning;
@@ -81,8 +81,29 @@ program
81
81
  .option('-v, --verbose', 'Show verbose error output', false)
82
82
  .action(async (path: string, options: { verbose: boolean }) => {
83
83
  try {
84
- const validator = new Validator({ path });
85
- const result = await validator.validate();
84
+ // Determine if path is a file or directory
85
+ const stats = await stat(path);
86
+ let dataDir: string;
87
+ let tableName: string | undefined;
88
+
89
+ if (stats.isDirectory()) {
90
+ dataDir = path;
91
+ // Validate all tables in directory
92
+ } else if (stats.isFile() && path.endsWith('.jsonl')) {
93
+ dataDir = dirname(path);
94
+ tableName = basename(path, '.jsonl');
95
+ } else {
96
+ throw new Error(`Invalid path: ${path}. Must be a directory or .jsonl file.`);
97
+ }
98
+
99
+ // Use LinesDB.initialize() with detailed validation
100
+ const db = LinesDB.create({ dataDir });
101
+ let result;
102
+ try {
103
+ result = await db.initialize({ tableName, detailedValidate: true });
104
+ } finally {
105
+ await db.close();
106
+ }
86
107
 
87
108
  // Display warnings if any
88
109
  if (result.warnings.length > 0) {
@@ -223,7 +244,33 @@ async function migrateDirectory(
223
244
 
224
245
  // Initialize database
225
246
  const db = LinesDB.create({ dataDir: dirPath });
226
- await db.initialize();
247
+ const initResult = await db.initialize({ detailedValidate: true });
248
+
249
+ // Display warnings if any
250
+ if (initResult.warnings.length > 0) {
251
+ for (const warning of initResult.warnings) {
252
+ console.warn(styleText('yellow', `⚠ ${warning}`));
253
+ }
254
+ }
255
+
256
+ // Check for initialization errors
257
+ if (!initResult.valid) {
258
+ console.error(`Error: Failed to initialize database due to validation errors:`);
259
+ const formatter = new ErrorFormatter({ verbose: options.verbose });
260
+ for (const error of initResult.errors) {
261
+ console.error(
262
+ formatter.formatValidationErrors([
263
+ {
264
+ file: error.file,
265
+ rowIndex: error.rowIndex,
266
+ issues: error.issues,
267
+ },
268
+ ]),
269
+ );
270
+ }
271
+ await db.close();
272
+ process.exit(1);
273
+ }
227
274
 
228
275
  const tableNames = db.getTableNames();
229
276
  if (tableNames.length === 0) {
@@ -275,7 +322,7 @@ async function migrateDirectory(
275
322
  console.log(` Found ${rowsToMigrate.length} row(s) to migrate`);
276
323
 
277
324
  // Apply transformation
278
- const transformedRows = rowsToMigrate.map((row) => transform(row));
325
+ const transformedRows = rowsToMigrate.map((row) => transform(row as JsonObject));
279
326
 
280
327
  // Perform the migration in a transaction
281
328
  await db.transaction(async () => {
@@ -370,19 +417,52 @@ async function migrateFile(
370
417
  const lastSlashIndex = filePath.lastIndexOf('/');
371
418
  const dataDir = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : '.';
372
419
 
373
- // Initialize database
374
- const db = LinesDB.create({ dataDir });
375
- await db.initialize();
376
-
420
+ // Parse transform function first (before initialization)
421
+ let transform: (row: JsonObject) => JsonObject;
377
422
  try {
378
- // Parse transform function
379
- const transform = runInSandbox<unknown>(`(${transformStr})`);
380
-
381
- if (typeof transform !== 'function') {
423
+ const parsedTransform = runInSandbox<unknown>(`(${transformStr})`);
424
+ if (typeof parsedTransform !== 'function') {
382
425
  console.error('Error: Transform must be a function');
383
426
  process.exit(1);
384
427
  }
428
+ transform = parsedTransform as (row: JsonObject) => JsonObject;
429
+ } catch (error) {
430
+ console.error('Error: Failed to parse transform function');
431
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
432
+ process.exit(1);
433
+ }
434
+
435
+ // Initialize database with transform applied
436
+ const db = LinesDB.create({ dataDir });
437
+ const initResult = await db.initialize({ tableName, transform, detailedValidate: true });
438
+
439
+ // Display warnings if any
440
+ if (initResult.warnings.length > 0) {
441
+ for (const warning of initResult.warnings) {
442
+ console.warn(styleText('yellow', `⚠ ${warning}`));
443
+ }
444
+ }
445
+
446
+ // Check for initialization errors
447
+ if (!initResult.valid) {
448
+ console.error(`Error: Failed to initialize database due to validation errors:`);
449
+ const formatter = new ErrorFormatter({ verbose: options.verbose });
450
+ for (const error of initResult.errors) {
451
+ console.error(
452
+ formatter.formatValidationErrors([
453
+ {
454
+ file: error.file,
455
+ rowIndex: error.rowIndex,
456
+ issues: error.issues,
457
+ },
458
+ ]),
459
+ );
460
+ }
461
+ await db.close();
462
+ process.exit(1);
463
+ }
385
464
 
465
+ try {
386
466
  // Parse filter if provided
387
467
  let filter: unknown = undefined;
388
468
  if (options.filter) {
@@ -395,136 +475,156 @@ async function migrateFile(
395
475
  }
396
476
  }
397
477
 
398
- // Get rows to migrate
399
- let rowsToMigrate;
400
- try {
401
- rowsToMigrate = filter
402
- ? db.find(tableName, filter as Parameters<typeof db.find>[1])
403
- : db.find(tableName);
404
- } catch (error) {
405
- console.error(`Error: Failed to access table '${tableName}'`);
406
- console.error(` ${error instanceof Error ? error.message : String(error)}`);
407
- console.error(`\nThe table may have failed to load during initialization.`);
408
- console.error(`Check the table's data and schema for any constraint violations.`);
409
- await db.close();
410
- process.exit(1);
411
- }
478
+ // If filter is provided, we need to apply transform only to matching rows
479
+ if (filter) {
480
+ // Get rows to migrate
481
+ let rowsToMigrate;
482
+ try {
483
+ rowsToMigrate = db.find(tableName, filter as Parameters<typeof db.find>[1]);
484
+ } catch (error) {
485
+ console.error(`Error: Failed to access table '${tableName}'`);
486
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
487
+ console.error(`\nThe table may have failed to load during initialization.`);
488
+ console.error(`Check the table's data and schema for any constraint violations.`);
489
+ await db.close();
490
+ process.exit(1);
491
+ }
412
492
 
413
- console.log(`Found ${rowsToMigrate.length} row(s) to migrate in table '${tableName}'`);
493
+ console.log(`Found ${rowsToMigrate.length} row(s) to migrate in table '${tableName}'`);
414
494
 
415
- if (rowsToMigrate.length === 0) {
416
- console.log('No rows to migrate. Exiting.');
417
- await db.close();
418
- process.exit(0);
419
- }
495
+ if (rowsToMigrate.length === 0) {
496
+ console.log('No rows to migrate. Exiting.');
497
+ await db.close();
498
+ process.exit(0);
499
+ }
420
500
 
421
- // Apply transformation
422
- const transformedRows = rowsToMigrate.map((row) => transform(row));
501
+ // Apply transformation
502
+ const transformedRows = rowsToMigrate.map((row) => transform(row as JsonObject));
423
503
 
424
- // Perform the migration in a transaction
425
- try {
426
- await db.transaction(async () => {
427
- db.batchUpdate(tableName, transformedRows as Parameters<typeof db.batchUpdate>[1], {
428
- validate: true,
504
+ // Perform the migration in a transaction
505
+ try {
506
+ await db.transaction(async () => {
507
+ db.batchUpdate(tableName, transformedRows as Parameters<typeof db.batchUpdate>[1], {
508
+ validate: true,
509
+ });
429
510
  });
430
- });
431
511
 
432
- await db.close();
512
+ await db.close();
433
513
 
434
- console.log(`\nMigration completed successfully:`);
435
- console.log(` ✓ ${rowsToMigrate.length} row(s) updated`);
436
- process.exit(0);
437
- } catch (error) {
438
- await db.close();
514
+ console.log(`\nMigration completed successfully:`);
515
+ console.log(` ✓ ${rowsToMigrate.length} row(s) updated`);
516
+ process.exit(0);
517
+ } catch (error) {
518
+ await db.close();
439
519
 
440
- // Write transformed data to error output file if --errorOutput is specified
441
- if (options.errorOutput) {
442
- try {
443
- const jsonlContent = transformedRows.map((row) => JSON.stringify(row)).join('\n');
444
- await writeFile(options.errorOutput, jsonlContent, 'utf-8');
445
- console.error(
446
- styleText(
447
- 'yellow',
448
- `\n⚠ Transformed data (${transformedRows.length} rows) written to: ${options.errorOutput}`,
449
- ),
450
- );
451
- } catch (writeError) {
452
- console.error(
453
- styleText(
454
- 'red',
455
- `\n✗ Failed to write error output file: ${writeError instanceof Error ? writeError.message : String(writeError)}`,
456
- ),
457
- );
520
+ // Write transformed data to error output file if --errorOutput is specified
521
+ if (options.errorOutput) {
522
+ try {
523
+ const jsonlContent = transformedRows.map((row) => JSON.stringify(row)).join('\n');
524
+ await writeFile(options.errorOutput, jsonlContent, 'utf-8');
525
+ console.error(
526
+ styleText(
527
+ 'yellow',
528
+ `\n⚠ Transformed data (${transformedRows.length} rows) written to: ${options.errorOutput}`,
529
+ ),
530
+ );
531
+ } catch (writeError) {
532
+ console.error(
533
+ styleText(
534
+ 'red',
535
+ `\n✗ Failed to write error output file: ${writeError instanceof Error ? writeError.message : String(writeError)}`,
536
+ ),
537
+ );
538
+ }
458
539
  }
459
- }
460
540
 
461
- const formatter = new ErrorFormatter({ verbose: options.verbose });
462
- console.error(formatter.formatMigrationFailureHeader());
463
-
464
- // Display detailed error information
465
- if (error instanceof Error && error.name === 'ValidationError') {
466
- const validationError = error as ValidationError & {
467
- validationErrors?: Array<{
468
- rowIndex: number;
469
- rowData: unknown;
470
- pkValue: unknown;
471
- error: ValidationError;
472
- }>;
473
- };
474
-
475
- // Display all validation errors
476
- if (validationError.validationErrors) {
477
- console.error(
478
- `\nFound ${validationError.validationErrors.length} validation error(s) in transformed data:\n`,
479
- );
541
+ const formatter = new ErrorFormatter({ verbose: options.verbose });
542
+ console.error(formatter.formatMigrationFailureHeader());
543
+
544
+ // Display detailed error information
545
+ if (error instanceof Error && error.name === 'ValidationError') {
546
+ const validationError = error as ValidationError & {
547
+ validationErrors?: Array<{
548
+ rowIndex: number;
549
+ rowData: unknown;
550
+ pkValue: unknown;
551
+ error: ValidationError;
552
+ }>;
553
+ };
480
554
 
481
- const errorInfos = validationError.validationErrors.map(
482
- ({ rowIndex, rowData, error: rowError }) => ({
555
+ // Display all validation errors
556
+ if (validationError.validationErrors) {
557
+ console.error(
558
+ `\nFound ${validationError.validationErrors.length} validation error(s) in transformed data:\n`,
559
+ );
560
+
561
+ const errorInfos = validationError.validationErrors.map(
562
+ ({ rowIndex, rowData, error: rowError }) => ({
563
+ file: filePath,
564
+ rowIndex,
565
+ issues: rowError.issues,
566
+ data: rowData,
567
+ originalData: rowsToMigrate[rowIndex],
568
+ }),
569
+ );
570
+
571
+ const formatted = formatter.formatValidationErrors(errorInfos);
572
+ console.error(formatted);
573
+ } else {
574
+ // Fallback for single validation error (backward compatibility)
575
+ console.error('\nValidation error:\n');
576
+ const errorInfo = {
483
577
  file: filePath,
484
- rowIndex,
485
- issues: rowError.issues,
486
- data: rowData,
487
- originalData: rowsToMigrate[rowIndex],
488
- }),
489
- );
578
+ rowIndex: 0,
579
+ issues: validationError.issues,
580
+ };
581
+ const formatted = formatter.formatValidationErrors([errorInfo]);
582
+ console.error(formatted);
583
+ }
584
+ } else if (error instanceof Error) {
585
+ console.error(`\n ${error.message}`);
490
586
 
491
- const formatted = formatter.formatValidationErrors(errorInfos);
492
- console.error(formatted);
493
- } else {
494
- // Fallback for single validation error (backward compatibility)
495
- console.error('\nValidation error:\n');
496
- const errorInfo = {
497
- file: filePath,
498
- rowIndex: 0,
499
- issues: validationError.issues,
500
- };
501
- const formatted = formatter.formatValidationErrors([errorInfo]);
502
- console.error(formatted);
503
- }
504
- } else if (error instanceof Error) {
505
- console.error(`\n ${error.message}`);
587
+ // Output stack trace for debugging
588
+ if (options.verbose && error.stack) {
589
+ console.error(`\nStack trace:\n${error.stack}`);
590
+ }
506
591
 
507
- // Output stack trace for debugging
508
- if (options.verbose && error.stack) {
509
- console.error(`\nStack trace:\n${error.stack}`);
592
+ // Check if it's a SQLite constraint error
593
+ if (
594
+ error.message.includes('UNIQUE constraint failed') ||
595
+ error.message.includes('FOREIGN KEY constraint failed') ||
596
+ error.message.includes('NOT NULL constraint failed') ||
597
+ error.message.includes('CHECK constraint failed')
598
+ ) {
599
+ console.error('\n This is a SQLite constraint violation.');
600
+ console.error(' Please check your data and schema requirements.');
601
+ }
602
+ } else {
603
+ console.error(`\n ${String(error)}`);
510
604
  }
511
605
 
512
- // Check if it's a SQLite constraint error
513
- if (
514
- error.message.includes('UNIQUE constraint failed') ||
515
- error.message.includes('FOREIGN KEY constraint failed') ||
516
- error.message.includes('NOT NULL constraint failed') ||
517
- error.message.includes('CHECK constraint failed')
518
- ) {
519
- console.error('\n This is a SQLite constraint violation.');
520
- console.error(' Please check your data and schema requirements.');
521
- }
522
- } else {
523
- console.error(`\n ${String(error)}`);
606
+ console.error('');
607
+ process.exit(1);
524
608
  }
609
+ } else {
610
+ // No filter - all rows have been transformed during initialization
611
+ // Just sync to write back to JSONL file
612
+ try {
613
+ const allRows = db.find(tableName);
614
+ console.log(`Migrated ${allRows.length} row(s) in table '${tableName}'`);
525
615
 
526
- console.error('');
527
- process.exit(1);
616
+ await db.sync(tableName);
617
+ await db.close();
618
+
619
+ console.log(`\nMigration completed successfully:`);
620
+ console.log(` ✓ ${allRows.length} row(s) updated`);
621
+ process.exit(0);
622
+ } catch (error) {
623
+ await db.close();
624
+ console.error('Error: Failed to sync changes to file');
625
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
626
+ process.exit(1);
627
+ }
528
628
  }
529
629
  } catch (error) {
530
630
  await db.close();