@toiroakr/lines-db 0.9.1 → 0.10.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/package.json CHANGED
@@ -1,28 +1,24 @@
1
1
  {
2
2
  "name": "@toiroakr/lines-db",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "A database implementation that treats JSONL files as tables using SQLite",
5
5
  "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
6
+ "types": "./dist/index.d.mts",
9
7
  "bin": {
10
- "lines-db": "./bin/cli.js"
8
+ "lines-db": "./bin/cli.mjs"
11
9
  },
12
10
  "exports": {
13
11
  ".": {
14
- "import": {
15
- "types": "./dist/index.d.ts",
16
- "default": "./dist/index.js"
17
- },
18
- "require": {
19
- "types": "./dist/index.d.cts",
20
- "default": "./dist/index.cjs"
21
- }
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
22
14
  }
23
15
  },
16
+ "engines": {
17
+ "node": ">=22.12.0"
18
+ },
24
19
  "scripts": {
25
- "build": "tsdown && cp dist/index.d.ts dist/index.d.cts",
20
+ "build": "tsdown",
21
+ "publint": "publint",
26
22
  "typecheck": "tsc --noEmit",
27
23
  "test": "vitest run"
28
24
  },
@@ -36,19 +32,22 @@
36
32
  "license": "MIT",
37
33
  "repository": {
38
34
  "type": "git",
39
- "url": "https://github.com/toiroakr/lines-db.git"
35
+ "url": "git+https://github.com/toiroakr/lines-db.git"
40
36
  },
41
37
  "bugs": {
42
38
  "url": "https://github.com/toiroakr/lines-db/issues"
43
39
  },
44
40
  "homepage": "https://github.com/toiroakr/lines-db#readme",
45
41
  "devDependencies": {
46
- "@types/node": "^22.0.0",
47
- "tsdown": "^0.15.12",
48
- "type-fest": "^5.2.0",
49
- "typescript": "^5.6.0",
50
- "valibot": "^1.1.0",
51
- "vitest": "^4.0.6"
42
+ "@types/node": "24.13.2",
43
+ "politty": "0.9.2",
44
+ "publint": "0.3.21",
45
+ "tsdown": "0.22.3",
46
+ "type-fest": "5.7.0",
47
+ "typescript": "6.0.3",
48
+ "valibot": "1.4.1",
49
+ "vitest": "4.1.9",
50
+ "zod": "4.4.3"
52
51
  },
53
52
  "peerDependencies": {
54
53
  "valibot": ">=1.0.0"
@@ -60,7 +59,6 @@
60
59
  },
61
60
  "dependencies": {
62
61
  "@standard-schema/spec": "^1.0.0",
63
- "commander": "^14.0.2",
64
- "tsx": "^4.19.2"
62
+ "amaro": "^1.1.10"
65
63
  }
66
64
  }
package/src/cli.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Register tsx for TypeScript schema file support
3
+ // Register amaro for TypeScript schema file support
4
4
  import { register } from 'node:module';
5
- register('tsx', import.meta.url, { data: {} });
5
+ register('amaro/transform', import.meta.url);
6
6
 
7
7
  import { TypeGenerator } from './type-generator.js';
8
8
  import { LinesDB } from './database.js';
9
9
  import { ErrorFormatter } from './error-formatter.js';
10
10
  import type { ValidationError, JsonObject } from './types.js';
11
- import { Command } from 'commander';
11
+ import { z } from 'zod';
12
+ import { arg, defineCommand, runMain } from 'politty';
12
13
  import { styleText } from 'node:util';
13
14
  import { writeFile, stat, readdir } from 'node:fs/promises';
14
15
  import { basename, dirname } from 'node:path';
@@ -16,12 +17,7 @@ import { runInNewContext } from 'node:vm';
16
17
 
17
18
  const originalEmitWarning = process.emitWarning;
18
19
  process.emitWarning = (warning, ...args) => {
19
- if (
20
- typeof warning === 'string' &&
21
- warning.startsWith('SQLite') &&
22
- args[0] === 'ExperimentalWarning'
23
- )
24
- return;
20
+ if (typeof warning === 'string' && warning.startsWith('SQLite') && args[0] === 'ExperimentalWarning') return;
25
21
  originalEmitWarning(warning, ...(args as any[]));
26
22
  };
27
23
 
@@ -50,54 +46,59 @@ function runInSandbox<T>(expression: string, context: Record<string, unknown> =
50
46
  return runInNewContext(expression, sandbox, { timeout: 1000 }) as T;
51
47
  }
52
48
 
53
- const program = new Command();
54
-
55
- program
56
- .name('@toiroakr/lines-db')
57
- .description('Database utilities for JSONL files')
58
- .version('1.0.0');
59
-
60
- // Generate command
61
- program
62
- .command('generate')
63
- .description('Generate TypeScript type definitions from schema files')
64
- .argument('<dataDir>', 'Directory containing JSONL and schema files')
65
- .option('-o, --output <path>', 'Output file path (default: db.ts in dataDir)')
66
- .action(async (dataDir: string, options: { output?: string }) => {
49
+ const generateCommand = defineCommand({
50
+ name: 'generate',
51
+ description: 'Generate TypeScript type definitions from schema files',
52
+ args: z.object({
53
+ dataDir: arg(z.string(), {
54
+ positional: true,
55
+ description: 'Directory containing JSONL and schema files',
56
+ }),
57
+ output: arg(z.string().optional(), {
58
+ alias: 'o',
59
+ description: 'Output file path (default: db.ts in dataDir)',
60
+ }),
61
+ }),
62
+ run: async (args) => {
67
63
  try {
68
- const generator = new TypeGenerator({ dataDir, output: options.output });
64
+ const generator = new TypeGenerator({ dataDir: args.dataDir, output: args.output });
69
65
  await generator.generate();
70
66
  console.log('Type generation completed successfully!');
71
67
  } catch (error) {
72
68
  console.error('Error:', error instanceof Error ? error.message : String(error));
73
69
  process.exit(1);
74
70
  }
75
- });
76
-
77
- // Validate command
78
- program
79
- .command('validate')
80
- .description('Validate JSONL file(s) against schema')
81
- .argument('<path>', 'File or directory path to validate')
82
- .option('-v, --verbose', 'Show verbose error output', false)
83
- .action(async (path: string, options: { verbose: boolean }) => {
71
+ },
72
+ });
73
+
74
+ const validateCommand = defineCommand({
75
+ name: 'validate',
76
+ description: 'Validate JSONL file(s) against schema',
77
+ args: z.object({
78
+ path: arg(z.string(), {
79
+ positional: true,
80
+ description: 'File or directory path to validate',
81
+ }),
82
+ verbose: arg(z.boolean().default(false), {
83
+ alias: 'v',
84
+ description: 'Show verbose error output',
85
+ }),
86
+ }),
87
+ run: async (args) => {
84
88
  try {
85
- // Determine if path is a file or directory
86
- const stats = await stat(path);
89
+ const stats = await stat(args.path);
87
90
  let dataDir: string;
88
91
  let tableName: string | undefined;
89
92
 
90
93
  if (stats.isDirectory()) {
91
- dataDir = path;
92
- // Validate all tables in directory
93
- } else if (stats.isFile() && path.endsWith('.jsonl')) {
94
- dataDir = dirname(path);
95
- tableName = basename(path, '.jsonl');
94
+ dataDir = args.path;
95
+ } else if (stats.isFile() && args.path.endsWith('.jsonl')) {
96
+ dataDir = dirname(args.path);
97
+ tableName = basename(args.path, '.jsonl');
96
98
  } else {
97
- throw new Error(`Invalid path: ${path}. Must be a directory or .jsonl file.`);
99
+ throw new Error(`Invalid path: ${args.path}. Must be a directory or .jsonl file.`);
98
100
  }
99
101
 
100
- // Use LinesDB.initialize() with detailed validation
101
102
  const db = LinesDB.create({ dataDir });
102
103
  let result;
103
104
  try {
@@ -106,33 +107,23 @@ program
106
107
  await db.close();
107
108
  }
108
109
 
109
- // Directory validation: display per-table results
110
110
  if (!tableName) {
111
- const formatter = new ErrorFormatter({ verbose: options.verbose });
111
+ const formatter = new ErrorFormatter({ verbose: args.verbose });
112
112
 
113
113
  for (const tableResult of result.tableResults) {
114
114
  if (tableResult.valid && tableResult.warnings.length === 0) {
115
- // Success
116
- console.log(
117
- styleText('green', `✓ ${tableResult.tableName} (${tableResult.rowCount} records)`),
118
- );
115
+ console.log(styleText('green', `✓ ${tableResult.tableName} (${tableResult.rowCount} records)`));
119
116
  } else if (tableResult.valid && tableResult.warnings.length > 0) {
120
- // Warnings
121
117
  for (const warning of tableResult.warnings) {
122
118
  console.warn(styleText('yellow', `⚠ ${warning}`));
123
119
  }
124
120
  } else {
125
- // Errors
126
121
  const fileErrors = tableResult.errors;
127
122
  console.error(formatter.formatErrorHeader(fileErrors.length, fileErrors[0]?.file));
128
123
  console.error('');
129
124
 
130
- const validationErrors = fileErrors.filter(
131
- (e) => e.type !== 'foreignKey' || !e.foreignKeyError,
132
- );
133
- const foreignKeyErrors = fileErrors.filter(
134
- (e) => e.type === 'foreignKey' && e.foreignKeyError,
135
- );
125
+ const validationErrors = fileErrors.filter((e) => e.type !== 'foreignKey' || !e.foreignKeyError);
126
+ const foreignKeyErrors = fileErrors.filter((e) => e.type === 'foreignKey' && e.foreignKeyError);
136
127
 
137
128
  if (validationErrors.length > 0) {
138
129
  console.error(
@@ -173,7 +164,6 @@ program
173
164
  process.exit(1);
174
165
  }
175
166
  } else {
176
- // Single file validation: existing behavior
177
167
  if (result.warnings.length > 0) {
178
168
  for (const warning of result.warnings) {
179
169
  console.warn(styleText('yellow', `⚠ ${warning}`));
@@ -185,7 +175,7 @@ program
185
175
  console.log(styleText('green', '✓ All records are valid'));
186
176
  process.exit(0);
187
177
  } else {
188
- const formatter = new ErrorFormatter({ verbose: options.verbose });
178
+ const formatter = new ErrorFormatter({ verbose: args.verbose });
189
179
 
190
180
  for (const [, fileErrors] of result.errors.reduce((map, error) => {
191
181
  const errors = map.get(error.file) || [];
@@ -196,12 +186,8 @@ program
196
186
  console.error(formatter.formatErrorHeader(fileErrors.length, fileErrors[0]?.file));
197
187
  console.error('');
198
188
 
199
- const validationErrors = fileErrors.filter(
200
- (e) => e.type !== 'foreignKey' || !e.foreignKeyError,
201
- );
202
- const foreignKeyErrors = fileErrors.filter(
203
- (e) => e.type === 'foreignKey' && e.foreignKeyError,
204
- );
189
+ const validationErrors = fileErrors.filter((e) => e.type !== 'foreignKey' || !e.foreignKeyError);
190
+ const foreignKeyErrors = fileErrors.filter((e) => e.type === 'foreignKey' && e.foreignKeyError);
205
191
 
206
192
  if (validationErrors.length > 0) {
207
193
  console.error(
@@ -240,50 +226,76 @@ program
240
226
  console.error('Error:', error instanceof Error ? error.message : String(error));
241
227
  process.exit(1);
242
228
  }
243
- });
244
-
245
- // Migrate command
246
- program
247
- .command('migrate')
248
- .description('Migrate data with transformation function')
249
- .argument('<path>', 'File or directory path to migrate')
250
- .argument('<transform>', 'Transform function (e.g., "(row) => ({ ...row, age: row.age + 1 })")')
251
- .option('-f, --filter <expr>', 'Filter expression')
252
- .option('-e, --errorOutput <path>', 'Output file path for transformed data when migration fails')
253
- .option('-v, --verbose', 'Show verbose error output', false)
254
- .action(
255
- async (
256
- path: string,
257
- transformStr: string,
258
- options: { filter?: string; errorOutput?: string; verbose: boolean },
259
- ) => {
260
- try {
261
- const stats = await stat(path);
262
-
263
- if (stats.isDirectory()) {
264
- // Migrate all JSONL files in directory
265
- await migrateDirectory(path, transformStr, options);
266
- } else if (stats.isFile() && path.endsWith('.jsonl')) {
267
- // Migrate single file
268
- await migrateFile(path, transformStr, options);
269
- } else {
270
- console.error(`Error: Invalid path: ${path}. Must be a directory or .jsonl file.`);
271
- process.exit(1);
272
- }
273
- } catch (error) {
274
- if (
275
- error instanceof Error &&
276
- 'code' in error &&
277
- (error as NodeJS.ErrnoException).code === 'ENOENT'
278
- ) {
279
- console.error(`Error: Path not found: ${path}`);
280
- } else {
281
- console.error(`Error: ${String(error)}`);
282
- }
229
+ },
230
+ });
231
+
232
+ const migrateCommand = defineCommand({
233
+ name: 'migrate',
234
+ description: 'Migrate data with transformation function',
235
+ args: z.object({
236
+ path: arg(z.string(), {
237
+ positional: true,
238
+ description: 'File or directory path to migrate',
239
+ }),
240
+ transform: arg(z.string(), {
241
+ positional: true,
242
+ description: 'Transform function (e.g., "(row) => ({ ...row, age: row.age + 1 })")',
243
+ }),
244
+ filter: arg(z.string().optional(), {
245
+ alias: 'f',
246
+ description: 'Filter expression',
247
+ }),
248
+ errorOutput: arg(z.string().optional(), {
249
+ alias: 'e',
250
+ description: 'Output file path for transformed data when migration fails',
251
+ }),
252
+ verbose: arg(z.boolean().default(false), {
253
+ alias: 'v',
254
+ description: 'Show verbose error output',
255
+ }),
256
+ }),
257
+ run: async (args) => {
258
+ try {
259
+ const stats = await stat(args.path);
260
+
261
+ if (stats.isDirectory()) {
262
+ await migrateDirectory(args.path, args.transform, {
263
+ filter: args.filter,
264
+ errorOutput: args.errorOutput,
265
+ verbose: args.verbose,
266
+ });
267
+ } else if (stats.isFile() && args.path.endsWith('.jsonl')) {
268
+ await migrateFile(args.path, args.transform, {
269
+ filter: args.filter,
270
+ errorOutput: args.errorOutput,
271
+ verbose: args.verbose,
272
+ });
273
+ } else {
274
+ console.error(`Error: Invalid path: ${args.path}. Must be a directory or .jsonl file.`);
283
275
  process.exit(1);
284
276
  }
285
- },
286
- );
277
+ } catch (error) {
278
+ if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
279
+ console.error(`Error: Path not found: ${args.path}`);
280
+ } else {
281
+ console.error(`Error: ${String(error)}`);
282
+ }
283
+ process.exit(1);
284
+ }
285
+ },
286
+ });
287
+
288
+ const program = defineCommand({
289
+ name: '@toiroakr/lines-db',
290
+ description: 'Database utilities for JSONL files',
291
+ subCommands: {
292
+ generate: generateCommand,
293
+ validate: validateCommand,
294
+ migrate: migrateCommand,
295
+ },
296
+ });
297
+
298
+ runMain(program, { version: '1.0.0' });
287
299
 
288
300
  /**
289
301
  * Migrate all JSONL files in a directory
@@ -293,7 +305,6 @@ async function migrateDirectory(
293
305
  transformStr: string,
294
306
  options: { filter?: string; errorOutput?: string; verbose: boolean },
295
307
  ) {
296
- // Find all JSONL files in directory
297
308
  const entries = await readdir(dirPath, { withFileTypes: true });
298
309
  const jsonlFiles = entries
299
310
  .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
@@ -306,18 +317,15 @@ async function migrateDirectory(
306
317
 
307
318
  console.log(`Found ${jsonlFiles.length} JSONL file(s) in directory`);
308
319
 
309
- // Initialize database
310
320
  const db = LinesDB.create({ dataDir: dirPath });
311
321
  const initResult = await db.initialize({ detailedValidate: true });
312
322
 
313
- // Display warnings if any
314
323
  if (initResult.warnings.length > 0) {
315
324
  for (const warning of initResult.warnings) {
316
325
  console.warn(styleText('yellow', `⚠ ${warning}`));
317
326
  }
318
327
  }
319
328
 
320
- // Check for initialization errors
321
329
  if (!initResult.valid) {
322
330
  console.error(`Error: Failed to initialize database due to validation errors:`);
323
331
  const formatter = new ErrorFormatter({ verbose: options.verbose });
@@ -346,7 +354,6 @@ async function migrateDirectory(
346
354
  console.log(`Loaded ${tableNames.length} table(s): ${tableNames.join(', ')}\n`);
347
355
 
348
356
  try {
349
- // Parse transform function
350
357
  const transform = runInSandbox<unknown>(`(${transformStr})`);
351
358
 
352
359
  if (typeof transform !== 'function') {
@@ -355,7 +362,6 @@ async function migrateDirectory(
355
362
  process.exit(1);
356
363
  }
357
364
 
358
- // Parse filter if provided
359
365
  let filter: unknown = undefined;
360
366
  if (options.filter) {
361
367
  try {
@@ -368,15 +374,11 @@ async function migrateDirectory(
368
374
  let totalRowsMigrated = 0;
369
375
  let hasErrors = false;
370
376
 
371
- // Process each table
372
377
  for (const tableName of tableNames) {
373
378
  try {
374
379
  console.log(`Processing table '${tableName}'...`);
375
380
 
376
- // Get rows to migrate
377
- const rowsToMigrate = filter
378
- ? db.find(tableName, filter as Parameters<typeof db.find>[1])
379
- : db.find(tableName);
381
+ const rowsToMigrate = filter ? db.find(tableName, filter as Parameters<typeof db.find>[1]) : db.find(tableName);
380
382
 
381
383
  if (rowsToMigrate.length === 0) {
382
384
  console.log(` No rows to migrate`);
@@ -385,10 +387,8 @@ async function migrateDirectory(
385
387
 
386
388
  console.log(` Found ${rowsToMigrate.length} row(s) to migrate`);
387
389
 
388
- // Apply transformation
389
390
  const transformedRows = rowsToMigrate.map((row) => transform(row as JsonObject));
390
391
 
391
- // Perform the migration in a transaction
392
392
  await db.transaction(async () => {
393
393
  db.batchUpdate(tableName, transformedRows as Parameters<typeof db.batchUpdate>[1], {
394
394
  validate: true,
@@ -414,23 +414,19 @@ async function migrateDirectory(
414
414
  };
415
415
 
416
416
  if (validationError.validationErrors) {
417
- console.error(
418
- ` Found ${validationError.validationErrors.length} validation error(s):\n`,
419
- );
417
+ console.error(` Found ${validationError.validationErrors.length} validation error(s):\n`);
420
418
 
421
419
  const rowsToMigrate = filter
422
420
  ? db.find(tableName, filter as Parameters<typeof db.find>[1])
423
421
  : db.find(tableName);
424
422
 
425
- const errorInfos = validationError.validationErrors.map(
426
- ({ rowIndex, rowData, error: rowError }) => ({
427
- file: `${dirPath}/${tableName}.jsonl`,
428
- rowIndex,
429
- issues: rowError.issues,
430
- data: rowData,
431
- originalData: rowsToMigrate[rowIndex],
432
- }),
433
- );
423
+ const errorInfos = validationError.validationErrors.map(({ rowIndex, rowData, error: rowError }) => ({
424
+ file: `${dirPath}/${tableName}.jsonl`,
425
+ rowIndex,
426
+ issues: rowError.issues,
427
+ data: rowData,
428
+ originalData: rowsToMigrate[rowIndex],
429
+ }));
434
430
 
435
431
  const formatted = formatter.formatValidationErrors(errorInfos);
436
432
  console.error(formatted);
@@ -468,7 +464,6 @@ async function migrateFile(
468
464
  transformStr: string,
469
465
  options: { filter?: string; errorOutput?: string; verbose: boolean },
470
466
  ) {
471
- // Extract table name from file path
472
467
  const fileName = filePath.split('/').pop() || '';
473
468
  const tableName = fileName.replace('.jsonl', '');
474
469
 
@@ -477,11 +472,9 @@ async function migrateFile(
477
472
  process.exit(1);
478
473
  }
479
474
 
480
- // Get directory from file path
481
475
  const lastSlashIndex = filePath.lastIndexOf('/');
482
476
  const dataDir = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : '.';
483
477
 
484
- // Parse transform function first (before initialization)
485
478
  let transform: (row: JsonObject) => JsonObject;
486
479
  try {
487
480
  const parsedTransform = runInSandbox<unknown>(`(${transformStr})`);
@@ -496,18 +489,15 @@ async function migrateFile(
496
489
  process.exit(1);
497
490
  }
498
491
 
499
- // Initialize database with transform applied
500
492
  const db = LinesDB.create({ dataDir });
501
493
  const initResult = await db.initialize({ tableName, transform, detailedValidate: true });
502
494
 
503
- // Display warnings if any
504
495
  if (initResult.warnings.length > 0) {
505
496
  for (const warning of initResult.warnings) {
506
497
  console.warn(styleText('yellow', `⚠ ${warning}`));
507
498
  }
508
499
  }
509
500
 
510
- // Check for initialization errors
511
501
  if (!initResult.valid) {
512
502
  console.error(`Error: Failed to initialize database due to validation errors:`);
513
503
  const formatter = new ErrorFormatter({ verbose: options.verbose });
@@ -527,21 +517,16 @@ async function migrateFile(
527
517
  }
528
518
 
529
519
  try {
530
- // Parse filter if provided
531
520
  let filter: unknown = undefined;
532
521
  if (options.filter) {
533
522
  try {
534
- // Try JSON parse first
535
523
  filter = JSON.parse(options.filter);
536
524
  } catch {
537
- // Fall back to eval for JavaScript expressions
538
525
  filter = runInSandbox(`(${options.filter})`);
539
526
  }
540
527
  }
541
528
 
542
- // If filter is provided, we need to apply transform only to matching rows
543
529
  if (filter) {
544
- // Get rows to migrate
545
530
  let rowsToMigrate;
546
531
  try {
547
532
  rowsToMigrate = db.find(tableName, filter as Parameters<typeof db.find>[1]);
@@ -562,10 +547,8 @@ async function migrateFile(
562
547
  process.exit(0);
563
548
  }
564
549
 
565
- // Apply transformation
566
550
  const transformedRows = rowsToMigrate.map((row) => transform(row as JsonObject));
567
551
 
568
- // Perform the migration in a transaction
569
552
  try {
570
553
  await db.transaction(async () => {
571
554
  db.batchUpdate(tableName, transformedRows as Parameters<typeof db.batchUpdate>[1], {
@@ -581,7 +564,6 @@ async function migrateFile(
581
564
  } catch (error) {
582
565
  await db.close();
583
566
 
584
- // Write transformed data to error output file if --errorOutput is specified
585
567
  if (options.errorOutput) {
586
568
  try {
587
569
  const jsonlContent = transformedRows.map((row) => JSON.stringify(row)).join('\n');
@@ -605,7 +587,6 @@ async function migrateFile(
605
587
  const formatter = new ErrorFormatter({ verbose: options.verbose });
606
588
  console.error(formatter.formatMigrationFailureHeader());
607
589
 
608
- // Display detailed error information
609
590
  if (error instanceof Error && error.name === 'ValidationError') {
610
591
  const validationError = error as ValidationError & {
611
592
  validationErrors?: Array<{
@@ -616,26 +597,22 @@ async function migrateFile(
616
597
  }>;
617
598
  };
618
599
 
619
- // Display all validation errors
620
600
  if (validationError.validationErrors) {
621
601
  console.error(
622
602
  `\nFound ${validationError.validationErrors.length} validation error(s) in transformed data:\n`,
623
603
  );
624
604
 
625
- const errorInfos = validationError.validationErrors.map(
626
- ({ rowIndex, rowData, error: rowError }) => ({
627
- file: filePath,
628
- rowIndex,
629
- issues: rowError.issues,
630
- data: rowData,
631
- originalData: rowsToMigrate[rowIndex],
632
- }),
633
- );
605
+ const errorInfos = validationError.validationErrors.map(({ rowIndex, rowData, error: rowError }) => ({
606
+ file: filePath,
607
+ rowIndex,
608
+ issues: rowError.issues,
609
+ data: rowData,
610
+ originalData: rowsToMigrate[rowIndex],
611
+ }));
634
612
 
635
613
  const formatted = formatter.formatValidationErrors(errorInfos);
636
614
  console.error(formatted);
637
615
  } else {
638
- // Fallback for single validation error (backward compatibility)
639
616
  console.error('\nValidation error:\n');
640
617
  const errorInfo = {
641
618
  file: filePath,
@@ -648,12 +625,10 @@ async function migrateFile(
648
625
  } else if (error instanceof Error) {
649
626
  console.error(`\n ${error.message}`);
650
627
 
651
- // Output stack trace for debugging
652
628
  if (options.verbose && error.stack) {
653
629
  console.error(`\nStack trace:\n${error.stack}`);
654
630
  }
655
631
 
656
- // Check if it's a SQLite constraint error
657
632
  if (
658
633
  error.message.includes('UNIQUE constraint failed') ||
659
634
  error.message.includes('FOREIGN KEY constraint failed') ||
@@ -671,8 +646,6 @@ async function migrateFile(
671
646
  process.exit(1);
672
647
  }
673
648
  } else {
674
- // No filter - all rows have been transformed during initialization
675
- // Just sync to write back to JSONL file
676
649
  try {
677
650
  const allRows = db.find(tableName);
678
651
  console.log(`Migrated ${allRows.length} row(s) in table '${tableName}'`);
@@ -695,5 +668,3 @@ async function migrateFile(
695
668
  throw error;
696
669
  }
697
670
  }
698
-
699
- program.parse();