@toiroakr/lines-db 0.4.1 → 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/CHANGELOG.md +24 -0
- package/bin/cli.js +472 -406
- package/dist/index.cjs +195 -327
- package/dist/index.d.cts +64 -84
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +64 -84
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -328
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/cli.ts +436 -152
- package/src/database.ts +296 -52
- package/src/index.ts +2 -2
- package/src/jsonl-migration.ts +24 -56
- package/src/schema.ts +37 -32
- package/src/types.ts +21 -0
- package/src/validator.test.ts +0 -507
- package/src/validator.ts +0 -441
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
|
-
import { writeFile } from 'node:fs/promises';
|
|
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
|
-
|
|
85
|
-
const
|
|
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) {
|
|
@@ -161,191 +182,454 @@ program
|
|
|
161
182
|
program
|
|
162
183
|
.command('migrate')
|
|
163
184
|
.description('Migrate data with transformation function')
|
|
164
|
-
.argument('<
|
|
185
|
+
.argument('<path>', 'File or directory path to migrate')
|
|
165
186
|
.argument('<transform>', 'Transform function (e.g., "(row) => ({ ...row, age: row.age + 1 })")')
|
|
166
187
|
.option('-f, --filter <expr>', 'Filter expression')
|
|
167
188
|
.option('-e, --errorOutput <path>', 'Output file path for transformed data when migration fails')
|
|
168
189
|
.option('-v, --verbose', 'Show verbose error output', false)
|
|
169
190
|
.action(
|
|
170
191
|
async (
|
|
171
|
-
|
|
192
|
+
path: string,
|
|
172
193
|
transformStr: string,
|
|
173
194
|
options: { filter?: string; errorOutput?: string; verbose: boolean },
|
|
174
195
|
) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
196
|
+
try {
|
|
197
|
+
const stats = await stat(path);
|
|
198
|
+
|
|
199
|
+
if (stats.isDirectory()) {
|
|
200
|
+
// Migrate all JSONL files in directory
|
|
201
|
+
await migrateDirectory(path, transformStr, options);
|
|
202
|
+
} else if (stats.isFile() && path.endsWith('.jsonl')) {
|
|
203
|
+
// Migrate single file
|
|
204
|
+
await migrateFile(path, transformStr, options);
|
|
205
|
+
} else {
|
|
206
|
+
console.error(`Error: Invalid path: ${path}. Must be a directory or .jsonl file.`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (
|
|
211
|
+
error instanceof Error &&
|
|
212
|
+
'code' in error &&
|
|
213
|
+
(error as NodeJS.ErrnoException).code === 'ENOENT'
|
|
214
|
+
) {
|
|
215
|
+
console.error(`Error: Path not found: ${path}`);
|
|
216
|
+
} else {
|
|
217
|
+
console.error(`Error: ${String(error)}`);
|
|
218
|
+
}
|
|
181
219
|
process.exit(1);
|
|
182
220
|
}
|
|
221
|
+
},
|
|
222
|
+
);
|
|
183
223
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Migrate all JSONL files in a directory
|
|
226
|
+
*/
|
|
227
|
+
async function migrateDirectory(
|
|
228
|
+
dirPath: string,
|
|
229
|
+
transformStr: string,
|
|
230
|
+
options: { filter?: string; errorOutput?: string; verbose: boolean },
|
|
231
|
+
) {
|
|
232
|
+
// Find all JSONL files in directory
|
|
233
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
234
|
+
const jsonlFiles = entries
|
|
235
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
|
|
236
|
+
.map((entry) => entry.name);
|
|
237
|
+
|
|
238
|
+
if (jsonlFiles.length === 0) {
|
|
239
|
+
console.error(`Error: No JSONL files found in directory: ${dirPath}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log(`Found ${jsonlFiles.length} JSONL file(s) in directory`);
|
|
244
|
+
|
|
245
|
+
// Initialize database
|
|
246
|
+
const db = LinesDB.create({ dataDir: dirPath });
|
|
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
|
+
}
|
|
274
|
+
|
|
275
|
+
const tableNames = db.getTableNames();
|
|
276
|
+
if (tableNames.length === 0) {
|
|
277
|
+
console.error(`Error: No tables could be loaded from directory: ${dirPath}`);
|
|
278
|
+
await db.close();
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log(`Loaded ${tableNames.length} table(s): ${tableNames.join(', ')}\n`);
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
// Parse transform function
|
|
286
|
+
const transform = runInSandbox<unknown>(`(${transformStr})`);
|
|
287
|
+
|
|
288
|
+
if (typeof transform !== 'function') {
|
|
289
|
+
console.error('Error: Transform must be a function');
|
|
290
|
+
await db.close();
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
191
293
|
|
|
294
|
+
// Parse filter if provided
|
|
295
|
+
let filter: unknown = undefined;
|
|
296
|
+
if (options.filter) {
|
|
192
297
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
298
|
+
filter = JSON.parse(options.filter);
|
|
299
|
+
} catch {
|
|
300
|
+
filter = runInSandbox(`(${options.filter})`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
195
303
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
304
|
+
let totalRowsMigrated = 0;
|
|
305
|
+
let hasErrors = false;
|
|
200
306
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// Try JSON parse first
|
|
206
|
-
filter = JSON.parse(options.filter);
|
|
207
|
-
} catch {
|
|
208
|
-
// Fall back to eval for JavaScript expressions
|
|
209
|
-
filter = runInSandbox(`(${options.filter})`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
307
|
+
// Process each table
|
|
308
|
+
for (const tableName of tableNames) {
|
|
309
|
+
try {
|
|
310
|
+
console.log(`Processing table '${tableName}'...`);
|
|
212
311
|
|
|
213
312
|
// Get rows to migrate
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
? db.find(tableName, filter as Parameters<typeof db.find>[1])
|
|
218
|
-
: db.find(tableName);
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.error(`Error: Failed to access table '${tableName}'`);
|
|
221
|
-
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
222
|
-
console.error(`\nThe table may have failed to load during initialization.`);
|
|
223
|
-
console.error(`Check the table's data and schema for any constraint violations.`);
|
|
224
|
-
await db.close();
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
console.log(`Found ${rowsToMigrate.length} row(s) to migrate in table '${tableName}'`);
|
|
313
|
+
const rowsToMigrate = filter
|
|
314
|
+
? db.find(tableName, filter as Parameters<typeof db.find>[1])
|
|
315
|
+
: db.find(tableName);
|
|
229
316
|
|
|
230
317
|
if (rowsToMigrate.length === 0) {
|
|
231
|
-
console.log(
|
|
232
|
-
|
|
233
|
-
process.exit(0);
|
|
318
|
+
console.log(` No rows to migrate`);
|
|
319
|
+
continue;
|
|
234
320
|
}
|
|
235
321
|
|
|
322
|
+
console.log(` Found ${rowsToMigrate.length} row(s) to migrate`);
|
|
323
|
+
|
|
236
324
|
// Apply transformation
|
|
237
|
-
const transformedRows = rowsToMigrate.map((row) => transform(row));
|
|
325
|
+
const transformedRows = rowsToMigrate.map((row) => transform(row as JsonObject));
|
|
238
326
|
|
|
239
327
|
// Perform the migration in a transaction
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
validate: true,
|
|
244
|
-
});
|
|
328
|
+
await db.transaction(async () => {
|
|
329
|
+
db.batchUpdate(tableName, transformedRows as Parameters<typeof db.batchUpdate>[1], {
|
|
330
|
+
validate: true,
|
|
245
331
|
});
|
|
332
|
+
});
|
|
246
333
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
);
|
|
273
|
-
|
|
334
|
+
console.log(` ✓ ${rowsToMigrate.length} row(s) updated\n`);
|
|
335
|
+
totalRowsMigrated += rowsToMigrate.length;
|
|
336
|
+
} catch (error) {
|
|
337
|
+
hasErrors = true;
|
|
338
|
+
console.error(styleText('red', ` ✗ Failed to migrate table '${tableName}'`));
|
|
339
|
+
|
|
340
|
+
const formatter = new ErrorFormatter({ verbose: options.verbose });
|
|
341
|
+
|
|
342
|
+
if (error instanceof Error && error.name === 'ValidationError') {
|
|
343
|
+
const validationError = error as ValidationError & {
|
|
344
|
+
validationErrors?: Array<{
|
|
345
|
+
rowIndex: number;
|
|
346
|
+
rowData: unknown;
|
|
347
|
+
pkValue: unknown;
|
|
348
|
+
error: ValidationError;
|
|
349
|
+
}>;
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
if (validationError.validationErrors) {
|
|
353
|
+
console.error(
|
|
354
|
+
` Found ${validationError.validationErrors.length} validation error(s):\n`,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const rowsToMigrate = filter
|
|
358
|
+
? db.find(tableName, filter as Parameters<typeof db.find>[1])
|
|
359
|
+
: db.find(tableName);
|
|
360
|
+
|
|
361
|
+
const errorInfos = validationError.validationErrors.map(
|
|
362
|
+
({ rowIndex, rowData, error: rowError }) => ({
|
|
363
|
+
file: `${dirPath}/${tableName}.jsonl`,
|
|
364
|
+
rowIndex,
|
|
365
|
+
issues: rowError.issues,
|
|
366
|
+
data: rowData,
|
|
367
|
+
originalData: rowsToMigrate[rowIndex],
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
const formatted = formatter.formatValidationErrors(errorInfos);
|
|
372
|
+
console.error(formatted);
|
|
274
373
|
}
|
|
374
|
+
} else if (error instanceof Error) {
|
|
375
|
+
console.error(` ${error.message}`);
|
|
376
|
+
}
|
|
275
377
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// Display detailed error information
|
|
280
|
-
if (error instanceof Error && error.name === 'ValidationError') {
|
|
281
|
-
const validationError = error as ValidationError & {
|
|
282
|
-
validationErrors?: Array<{
|
|
283
|
-
rowIndex: number;
|
|
284
|
-
rowData: unknown;
|
|
285
|
-
pkValue: unknown;
|
|
286
|
-
error: ValidationError;
|
|
287
|
-
}>;
|
|
288
|
-
};
|
|
378
|
+
console.error('');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
289
381
|
|
|
290
|
-
|
|
291
|
-
if (validationError.validationErrors) {
|
|
292
|
-
console.error(
|
|
293
|
-
`\nFound ${validationError.validationErrors.length} validation error(s) in transformed data:\n`,
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
const errorInfos = validationError.validationErrors.map(
|
|
297
|
-
({ rowIndex, rowData, error: rowError }) => ({
|
|
298
|
-
file: filePath,
|
|
299
|
-
rowIndex,
|
|
300
|
-
issues: rowError.issues,
|
|
301
|
-
data: rowData,
|
|
302
|
-
originalData: rowsToMigrate[rowIndex],
|
|
303
|
-
}),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const formatted = formatter.formatValidationErrors(errorInfos);
|
|
307
|
-
console.error(formatted);
|
|
308
|
-
} else {
|
|
309
|
-
// Fallback for single validation error (backward compatibility)
|
|
310
|
-
console.error('\nValidation error:\n');
|
|
311
|
-
const errorInfo = {
|
|
312
|
-
file: filePath,
|
|
313
|
-
rowIndex: 0,
|
|
314
|
-
issues: validationError.issues,
|
|
315
|
-
};
|
|
316
|
-
const formatted = formatter.formatValidationErrors([errorInfo]);
|
|
317
|
-
console.error(formatted);
|
|
318
|
-
}
|
|
319
|
-
} else if (error instanceof Error) {
|
|
320
|
-
console.error(`\n ${error.message}`);
|
|
382
|
+
await db.close();
|
|
321
383
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
384
|
+
if (hasErrors) {
|
|
385
|
+
console.error(styleText('red', `\n✗ Migration completed with errors for some tables`));
|
|
386
|
+
console.log(`Total rows migrated: ${totalRowsMigrated}`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
} else {
|
|
389
|
+
console.log(styleText('green', `\n✓ Migration completed successfully for all tables`));
|
|
390
|
+
console.log(`Total rows migrated: ${totalRowsMigrated}`);
|
|
391
|
+
process.exit(0);
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
await db.close();
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
326
398
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
399
|
+
/**
|
|
400
|
+
* Migrate a single JSONL file
|
|
401
|
+
*/
|
|
402
|
+
async function migrateFile(
|
|
403
|
+
filePath: string,
|
|
404
|
+
transformStr: string,
|
|
405
|
+
options: { filter?: string; errorOutput?: string; verbose: boolean },
|
|
406
|
+
) {
|
|
407
|
+
// Extract table name from file path
|
|
408
|
+
const fileName = filePath.split('/').pop() || '';
|
|
409
|
+
const tableName = fileName.replace('.jsonl', '');
|
|
410
|
+
|
|
411
|
+
if (!tableName) {
|
|
412
|
+
console.error('Error: Invalid file path. Must be a .jsonl file');
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Get directory from file path
|
|
417
|
+
const lastSlashIndex = filePath.lastIndexOf('/');
|
|
418
|
+
const dataDir = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : '.';
|
|
419
|
+
|
|
420
|
+
// Parse transform function first (before initialization)
|
|
421
|
+
let transform: (row: JsonObject) => JsonObject;
|
|
422
|
+
try {
|
|
423
|
+
const parsedTransform = runInSandbox<unknown>(`(${transformStr})`);
|
|
424
|
+
if (typeof parsedTransform !== 'function') {
|
|
425
|
+
console.error('Error: Transform must be a function');
|
|
426
|
+
process.exit(1);
|
|
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
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
// Parse filter if provided
|
|
467
|
+
let filter: unknown = undefined;
|
|
468
|
+
if (options.filter) {
|
|
469
|
+
try {
|
|
470
|
+
// Try JSON parse first
|
|
471
|
+
filter = JSON.parse(options.filter);
|
|
472
|
+
} catch {
|
|
473
|
+
// Fall back to eval for JavaScript expressions
|
|
474
|
+
filter = runInSandbox(`(${options.filter})`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
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
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log(`Found ${rowsToMigrate.length} row(s) to migrate in table '${tableName}'`);
|
|
494
|
+
|
|
495
|
+
if (rowsToMigrate.length === 0) {
|
|
496
|
+
console.log('No rows to migrate. Exiting.');
|
|
497
|
+
await db.close();
|
|
498
|
+
process.exit(0);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Apply transformation
|
|
502
|
+
const transformedRows = rowsToMigrate.map((row) => transform(row as JsonObject));
|
|
503
|
+
|
|
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
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
await db.close();
|
|
513
|
+
|
|
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();
|
|
519
|
+
|
|
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
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
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
|
+
};
|
|
554
|
+
|
|
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);
|
|
337
573
|
} else {
|
|
338
|
-
|
|
574
|
+
// Fallback for single validation error (backward compatibility)
|
|
575
|
+
console.error('\nValidation error:\n');
|
|
576
|
+
const errorInfo = {
|
|
577
|
+
file: filePath,
|
|
578
|
+
rowIndex: 0,
|
|
579
|
+
issues: validationError.issues,
|
|
580
|
+
};
|
|
581
|
+
const formatted = formatter.formatValidationErrors([errorInfo]);
|
|
582
|
+
console.error(formatted);
|
|
339
583
|
}
|
|
584
|
+
} else if (error instanceof Error) {
|
|
585
|
+
console.error(`\n ${error.message}`);
|
|
340
586
|
|
|
341
|
-
|
|
342
|
-
|
|
587
|
+
// Output stack trace for debugging
|
|
588
|
+
if (options.verbose && error.stack) {
|
|
589
|
+
console.error(`\nStack trace:\n${error.stack}`);
|
|
590
|
+
}
|
|
591
|
+
|
|
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)}`);
|
|
343
604
|
}
|
|
605
|
+
|
|
606
|
+
console.error('');
|
|
607
|
+
process.exit(1);
|
|
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}'`);
|
|
615
|
+
|
|
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);
|
|
344
622
|
} catch (error) {
|
|
345
623
|
await db.close();
|
|
346
|
-
|
|
624
|
+
console.error('Error: Failed to sync changes to file');
|
|
625
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
626
|
+
process.exit(1);
|
|
347
627
|
}
|
|
348
|
-
}
|
|
349
|
-
)
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
await db.close();
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
350
634
|
|
|
351
635
|
program.parse();
|