@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/CHANGELOG.md +23 -0
- package/bin/cli.mjs +8694 -0
- package/dist/{index.d.ts → index.d.mts} +19 -20
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +16 -26
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -23
- package/src/cli.ts +134 -163
- package/src/database.test.ts +10 -39
- package/src/database.ts +17 -64
- package/src/directory-scanner.test.ts +1 -3
- package/src/directory-scanner.ts +1 -3
- package/src/index.ts +1 -5
- package/src/jsonl-reader.test.ts +1 -3
- package/src/jsonl-reader.ts +1 -4
- package/src/schema-loader.test.ts +4 -12
- package/src/schema-loader.ts +1 -3
- package/src/schema.ts +5 -5
- package/src/type-generator.ts +4 -11
- package/src/types.ts +1 -4
- package/tsconfig.json +1 -0
- package/tsdown.config.ts +9 -5
- package/bin/cli.js +0 -1766
- package/dist/index.cjs +0 -1499
- package/dist/index.d.cts +0 -605
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toiroakr/lines-db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "A database implementation that treats JSONL files as tables using SQLite",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
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.
|
|
8
|
+
"lines-db": "./bin/cli.mjs"
|
|
11
9
|
},
|
|
12
10
|
"exports": {
|
|
13
11
|
".": {
|
|
14
|
-
"
|
|
15
|
-
|
|
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
|
|
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": "
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
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
|
-
"
|
|
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
|
|
3
|
+
// Register amaro for TypeScript schema file support
|
|
4
4
|
import { register } from 'node:module';
|
|
5
|
-
register('
|
|
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 {
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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:
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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();
|