@toiroakr/lines-db 0.1.2 → 0.2.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/CHANGELOG.md +23 -0
- package/README.ja.md +105 -18
- package/README.md +105 -18
- package/assets/logo.svg +57 -0
- package/bin/cli.js +39 -42
- package/dist/index.cjs +32 -40
- package/dist/index.d.cts +7 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -40
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/cli.ts +16 -1
- package/src/database.test.ts +1 -1
- package/src/runtime.ts +1 -19
- package/src/schema-loader.ts +16 -0
- package/src/sqlite-adapter.ts +3 -38
- package/src/type-generator.ts +2 -4
- package/src/validator.test.ts +37 -7
- package/src/validator.ts +23 -4
package/src/sqlite-adapter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite adapter
|
|
2
|
+
* SQLite adapter for Node.js
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { RUNTIME } from './runtime.js';
|
|
@@ -20,51 +20,16 @@ export interface SQLiteStatement {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Create a SQLite database instance
|
|
23
|
+
* Create a SQLite database instance for Node.js
|
|
24
24
|
*/
|
|
25
25
|
export function createDatabase(path: string = ':memory:'): SQLiteDatabase {
|
|
26
|
-
if (RUNTIME === '
|
|
27
|
-
return createBunDatabase(path);
|
|
28
|
-
} else if (RUNTIME === 'node' || RUNTIME === 'deno') {
|
|
26
|
+
if (RUNTIME === 'node') {
|
|
29
27
|
return createNodeDatabase(path);
|
|
30
28
|
} else {
|
|
31
29
|
throw new Error(`Unsupported runtime: ${RUNTIME}`);
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
/**
|
|
36
|
-
* Create a Bun SQLite database
|
|
37
|
-
*/
|
|
38
|
-
function createBunDatabase(path: string): SQLiteDatabase {
|
|
39
|
-
// Dynamic import for Bun
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
|
-
const { Database } = require('bun:sqlite');
|
|
42
|
-
const db = new Database(path);
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
prepare(sql: string): SQLiteStatement {
|
|
46
|
-
const stmt = db.prepare(sql);
|
|
47
|
-
return {
|
|
48
|
-
run(...params: any[]) {
|
|
49
|
-
return stmt.run(...params);
|
|
50
|
-
},
|
|
51
|
-
get(...params: any[]) {
|
|
52
|
-
return stmt.get(...params);
|
|
53
|
-
},
|
|
54
|
-
all(...params: any[]) {
|
|
55
|
-
return stmt.all(...params);
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
},
|
|
59
|
-
exec(sql: string): void {
|
|
60
|
-
db.exec(sql);
|
|
61
|
-
},
|
|
62
|
-
close(): void {
|
|
63
|
-
db.close();
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
33
|
/**
|
|
69
34
|
* Create a Node.js SQLite database
|
|
70
35
|
*/
|
package/src/type-generator.ts
CHANGED
|
@@ -71,9 +71,7 @@ export class TypeGenerator {
|
|
|
71
71
|
const schemaFilePath = join(this.dataDirPath, schemaFileName);
|
|
72
72
|
|
|
73
73
|
// Check if schema file exists
|
|
74
|
-
const hasSchema = entries.some(
|
|
75
|
-
e => e.isFile() && e.name === schemaFileName
|
|
76
|
-
);
|
|
74
|
+
const hasSchema = entries.some((e) => e.isFile() && e.name === schemaFileName);
|
|
77
75
|
|
|
78
76
|
tables.push({
|
|
79
77
|
tableName,
|
|
@@ -137,7 +135,7 @@ export class TypeGenerator {
|
|
|
137
135
|
return `// Auto-generated by lines-db
|
|
138
136
|
// Do not edit this file manually
|
|
139
137
|
|
|
140
|
-
${importSection}import type { DatabaseConfig${inferOutputImport} } from 'lines-db';
|
|
138
|
+
${importSection}import type { DatabaseConfig${inferOutputImport} } from '@toiroakr/lines-db';
|
|
141
139
|
import { fileURLToPath } from 'node:url';
|
|
142
140
|
import { dirname } from 'node:path';
|
|
143
141
|
|
package/src/validator.test.ts
CHANGED
|
@@ -46,6 +46,7 @@ describe('Validator', () => {
|
|
|
46
46
|
|
|
47
47
|
expect(result.valid).toBe(true);
|
|
48
48
|
expect(result.errors).toHaveLength(0);
|
|
49
|
+
expect(result.warnings).toHaveLength(0);
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
it('should detect validation errors', async () => {
|
|
@@ -77,7 +78,7 @@ describe('Validator', () => {
|
|
|
77
78
|
|
|
78
79
|
expect(result.valid).toBe(false);
|
|
79
80
|
expect(result.errors).toHaveLength(1);
|
|
80
|
-
expect(result.errors[0].rowIndex).toBe(
|
|
81
|
+
expect(result.errors[0].rowIndex).toBe(1); // 0-indexed: line 2 is index 1
|
|
81
82
|
expect(result.errors[0].tableName).toBe('users');
|
|
82
83
|
expect(result.errors[0].issues).toHaveLength(1);
|
|
83
84
|
});
|
|
@@ -119,8 +120,8 @@ describe('Validator', () => {
|
|
|
119
120
|
|
|
120
121
|
expect(result.valid).toBe(false);
|
|
121
122
|
expect(result.errors).toHaveLength(2);
|
|
122
|
-
expect(result.errors[0].rowIndex).toBe(
|
|
123
|
-
expect(result.errors[1].rowIndex).toBe(
|
|
123
|
+
expect(result.errors[0].rowIndex).toBe(0); // 0-indexed: line 1 is index 0
|
|
124
|
+
expect(result.errors[1].rowIndex).toBe(1); // 0-indexed: line 2 is index 1
|
|
124
125
|
});
|
|
125
126
|
|
|
126
127
|
it('should return 0-based rowIndex (first line = index 0)', async () => {
|
|
@@ -128,10 +129,7 @@ describe('Validator', () => {
|
|
|
128
129
|
const schemaPath = join(testDir, 'users.schema.ts');
|
|
129
130
|
|
|
130
131
|
// Line 1: valid, Line 2: invalid (no name), Line 3: invalid (no name)
|
|
131
|
-
await writeFile(
|
|
132
|
-
jsonlPath,
|
|
133
|
-
'{"id":1,"name":"Alice"}\n{"id":2}\n{"id":3}\n',
|
|
134
|
-
);
|
|
132
|
+
await writeFile(jsonlPath, '{"id":1,"name":"Alice"}\n{"id":2}\n{"id":3}\n');
|
|
135
133
|
await writeFile(
|
|
136
134
|
schemaPath,
|
|
137
135
|
`
|
|
@@ -239,6 +237,7 @@ describe('Validator', () => {
|
|
|
239
237
|
|
|
240
238
|
expect(result.valid).toBe(true);
|
|
241
239
|
expect(result.errors).toHaveLength(0);
|
|
240
|
+
expect(result.warnings).toHaveLength(0);
|
|
242
241
|
});
|
|
243
242
|
|
|
244
243
|
it('should collect errors from multiple files', async () => {
|
|
@@ -295,6 +294,37 @@ describe('Validator', () => {
|
|
|
295
294
|
|
|
296
295
|
await expect(validator.validate()).rejects.toThrow('No JSONL files found');
|
|
297
296
|
});
|
|
297
|
+
|
|
298
|
+
it('should skip validation and warn for JSONL files without schema', async () => {
|
|
299
|
+
const usersPath = join(testDir, 'users.jsonl');
|
|
300
|
+
const productsPath = join(testDir, 'products.jsonl');
|
|
301
|
+
const usersSchemaPath = join(testDir, 'users.schema.ts');
|
|
302
|
+
|
|
303
|
+
// Only users.jsonl has a schema
|
|
304
|
+
await writeFile(usersPath, '{"id":1,"name":"Alice"}\n');
|
|
305
|
+
await writeFile(productsPath, '{"id":1,"name":"Product"}\n');
|
|
306
|
+
await writeFile(
|
|
307
|
+
usersSchemaPath,
|
|
308
|
+
`
|
|
309
|
+
export const schema = {
|
|
310
|
+
'~standard': {
|
|
311
|
+
version: 1,
|
|
312
|
+
vendor: 'test',
|
|
313
|
+
validate: (data) => ({ value: data, issues: [] })
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
`,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const validator = new Validator({ path: testDir });
|
|
320
|
+
const result = await validator.validate();
|
|
321
|
+
|
|
322
|
+
expect(result.valid).toBe(true);
|
|
323
|
+
expect(result.errors).toHaveLength(0);
|
|
324
|
+
expect(result.warnings).toHaveLength(1);
|
|
325
|
+
expect(result.warnings[0]).toContain('products');
|
|
326
|
+
expect(result.warnings[0]).toContain('schema file not found');
|
|
327
|
+
});
|
|
298
328
|
});
|
|
299
329
|
|
|
300
330
|
describe('error handling', () => {
|
package/src/validator.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { BiDirectionalSchema } from './schema.js';
|
|
|
8
8
|
export interface ValidationResult {
|
|
9
9
|
valid: boolean;
|
|
10
10
|
errors: ValidationErrorDetail[];
|
|
11
|
+
warnings: string[];
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface ValidationErrorDetail {
|
|
@@ -69,20 +70,37 @@ export class Validator {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
const allErrors: ValidationErrorDetail[] = [];
|
|
73
|
+
const allWarnings: string[] = [];
|
|
74
|
+
const filesWithSchema: string[] = [];
|
|
72
75
|
|
|
73
|
-
//
|
|
76
|
+
// Filter files with schema and collect warnings for files without schema
|
|
74
77
|
for (const file of jsonlFiles) {
|
|
78
|
+
const hasSchema = await SchemaLoader.hasSchema(file);
|
|
79
|
+
if (hasSchema) {
|
|
80
|
+
filesWithSchema.push(file);
|
|
81
|
+
} else {
|
|
82
|
+
const tableName = basename(file, '.jsonl');
|
|
83
|
+
allWarnings.push(`Skipping validation for '${tableName}': schema file not found`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Validate schema for each file with schema
|
|
88
|
+
for (const file of filesWithSchema) {
|
|
75
89
|
const result = await this.validateFile(file);
|
|
76
90
|
allErrors.push(...result.errors);
|
|
91
|
+
allWarnings.push(...result.warnings);
|
|
77
92
|
}
|
|
78
93
|
|
|
79
|
-
// Then, validate foreign keys across all tables
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
// Then, validate foreign keys across all tables (only for files with schema)
|
|
95
|
+
if (filesWithSchema.length > 0) {
|
|
96
|
+
const fkErrors = await this.validateForeignKeys(dirPath, filesWithSchema);
|
|
97
|
+
allErrors.push(...fkErrors);
|
|
98
|
+
}
|
|
82
99
|
|
|
83
100
|
return {
|
|
84
101
|
valid: allErrors.length === 0,
|
|
85
102
|
errors: allErrors,
|
|
103
|
+
warnings: allWarnings,
|
|
86
104
|
};
|
|
87
105
|
}
|
|
88
106
|
|
|
@@ -202,6 +220,7 @@ export class Validator {
|
|
|
202
220
|
return {
|
|
203
221
|
valid: errors.length === 0,
|
|
204
222
|
errors,
|
|
223
|
+
warnings: [],
|
|
205
224
|
};
|
|
206
225
|
}
|
|
207
226
|
}
|