@toiroakr/lines-db 0.6.1 → 0.8.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 +27 -0
- package/bin/cli.js +163 -59
- package/dist/index.cjs +126 -31
- package/dist/index.d.cts +38 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +38 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +121 -57
- package/src/database.test.ts +28 -0
- package/src/database.ts +89 -18
- package/src/index.ts +9 -0
- package/src/schema-extensions.test.ts +155 -0
- package/src/schema-extensions.ts +86 -0
- package/src/schema-loader.test.ts +90 -0
- package/src/schema-loader.ts +10 -18
- package/src/type-generator.ts +12 -10
- package/src/types.ts +9 -0
package/dist/index.js
CHANGED
|
@@ -159,6 +159,71 @@ var JsonlWriter = class {
|
|
|
159
159
|
}
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/schema-extensions.ts
|
|
164
|
+
/**
|
|
165
|
+
* Supported schema file extensions, in priority order.
|
|
166
|
+
* The first match wins when discovering schema files.
|
|
167
|
+
*/
|
|
168
|
+
const SCHEMA_EXTENSIONS = [
|
|
169
|
+
".schema.ts",
|
|
170
|
+
".schema.mts",
|
|
171
|
+
".schema.cts"
|
|
172
|
+
];
|
|
173
|
+
/**
|
|
174
|
+
* Map from schema extensions to their JavaScript import counterparts.
|
|
175
|
+
*/
|
|
176
|
+
const SCHEMA_TO_JS_IMPORT_MAP = {
|
|
177
|
+
".schema.ts": ".schema.js",
|
|
178
|
+
".schema.mts": ".schema.mjs",
|
|
179
|
+
".schema.cts": ".schema.cjs"
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Try each supported schema extension and return the full path of the first
|
|
183
|
+
* one that exists on disk. Returns undefined if none is found.
|
|
184
|
+
*/
|
|
185
|
+
async function findSchemaFile(dir, tableName) {
|
|
186
|
+
for (const ext of SCHEMA_EXTENSIONS) {
|
|
187
|
+
const candidate = join(dir, `${tableName}${ext}`);
|
|
188
|
+
try {
|
|
189
|
+
await access(candidate);
|
|
190
|
+
return candidate;
|
|
191
|
+
} catch {}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Synchronously find a schema file among directory entries.
|
|
196
|
+
* Returns the full path of the first match, or undefined.
|
|
197
|
+
*/
|
|
198
|
+
function findSchemaFileInEntries(dataDirPath, tableName, entries) {
|
|
199
|
+
for (const ext of SCHEMA_EXTENSIONS) {
|
|
200
|
+
const candidateName = `${tableName}${ext}`;
|
|
201
|
+
if (entries.some((e) => e.isFile() && e.name === candidateName)) return join(dataDirPath, candidateName);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if a filename matches any supported schema file pattern.
|
|
206
|
+
*/
|
|
207
|
+
function isSchemaFile(fileName) {
|
|
208
|
+
return SCHEMA_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extract table name from a schema filename.
|
|
212
|
+
* e.g., "users.schema.ts" -> "users", "users.schema.mts" -> "users"
|
|
213
|
+
*/
|
|
214
|
+
function extractTableNameFromSchemaFile(fileName) {
|
|
215
|
+
for (const ext of SCHEMA_EXTENSIONS) if (fileName.endsWith(ext)) return fileName.slice(0, -ext.length);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Rewrite a TypeScript path to its JavaScript counterpart for ESM imports.
|
|
220
|
+
* ".schema.ts" -> ".schema.js", ".schema.mts" -> ".schema.mjs", ".schema.cts" -> ".schema.cjs"
|
|
221
|
+
*/
|
|
222
|
+
function rewriteExtensionForImport(filePath) {
|
|
223
|
+
for (const [tsExt, jsExt] of Object.entries(SCHEMA_TO_JS_IMPORT_MAP)) if (filePath.endsWith(tsExt)) return filePath.slice(0, -tsExt.length) + jsExt;
|
|
224
|
+
return filePath;
|
|
225
|
+
}
|
|
226
|
+
|
|
162
227
|
//#endregion
|
|
163
228
|
//#region src/schema-loader.ts
|
|
164
229
|
var SchemaLoader = class {
|
|
@@ -166,27 +231,17 @@ var SchemaLoader = class {
|
|
|
166
231
|
* Check if a schema file exists for a table
|
|
167
232
|
*/
|
|
168
233
|
static async hasSchema(jsonlPath) {
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
await access(schemaPath);
|
|
172
|
-
return true;
|
|
173
|
-
} catch {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
234
|
+
return await findSchemaFile(dirname(jsonlPath), basename(jsonlPath, ".jsonl")) !== void 0;
|
|
176
235
|
}
|
|
177
236
|
/**
|
|
178
237
|
* Load a validation schema file for a table
|
|
179
|
-
* Requires ${tableName}.schema.ts to exist alongside the JSONL file
|
|
238
|
+
* Requires ${tableName}.schema.{ts,mts,cts} to exist alongside the JSONL file
|
|
180
239
|
*/
|
|
181
240
|
static async loadSchema(jsonlPath) {
|
|
182
241
|
const dir = dirname(jsonlPath);
|
|
183
242
|
const tableName = basename(jsonlPath, ".jsonl");
|
|
184
|
-
const schemaPath =
|
|
185
|
-
|
|
186
|
-
await access(schemaPath);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
throw new Error(`Schema file not found for table '${tableName}'. Expected: ${schemaPath}`, { cause: error instanceof Error ? error : void 0 });
|
|
189
|
-
}
|
|
243
|
+
const schemaPath = await findSchemaFile(dir, tableName);
|
|
244
|
+
if (!schemaPath) throw new Error(`Schema file not found for table '${tableName}'. Expected one of: ${SCHEMA_EXTENSIONS.map((ext) => `${tableName}${ext}`).join(", ")}`);
|
|
190
245
|
try {
|
|
191
246
|
const module = await import(`${pathToFileURL(schemaPath).href}?t=${Date.now()}`);
|
|
192
247
|
const schema = module.default || module.schema;
|
|
@@ -320,6 +375,7 @@ var LinesDB = class LinesDB {
|
|
|
320
375
|
async initialize(options) {
|
|
321
376
|
const allErrors = [];
|
|
322
377
|
const allWarnings = [];
|
|
378
|
+
const allRowCounts = /* @__PURE__ */ new Map();
|
|
323
379
|
const tableName = options?.tableName;
|
|
324
380
|
const detailedValidate = options?.detailedValidate ?? false;
|
|
325
381
|
const transform = options?.transform;
|
|
@@ -331,14 +387,27 @@ var LinesDB = class LinesDB {
|
|
|
331
387
|
const attemptedTables = /* @__PURE__ */ new Set();
|
|
332
388
|
for (const tableNameToLoad of tablesToLoad) if (!attemptedTables.has(tableNameToLoad)) {
|
|
333
389
|
const tableTransform = tableNameToLoad === tableName ? transform : void 0;
|
|
334
|
-
const { errors, warnings } = await this.loadTableWithDependencies(tableNameToLoad, loadedTables, loadingTables, attemptedTables, detailedValidate, tableTransform);
|
|
390
|
+
const { errors, warnings, rowCounts: tableRowCounts } = await this.loadTableWithDependencies(tableNameToLoad, loadedTables, loadingTables, attemptedTables, detailedValidate, tableTransform);
|
|
335
391
|
allErrors.push(...errors);
|
|
336
392
|
allWarnings.push(...warnings);
|
|
393
|
+
for (const [k, v] of tableRowCounts) allRowCounts.set(k, v);
|
|
337
394
|
}
|
|
395
|
+
const tableResults = tablesToLoad.map((name) => {
|
|
396
|
+
const tableErrors = allErrors.filter((e) => e.tableName === name);
|
|
397
|
+
const tableWarnings = allWarnings.filter((w) => w.includes(`'${name}'`));
|
|
398
|
+
return {
|
|
399
|
+
tableName: name,
|
|
400
|
+
valid: tableErrors.length === 0,
|
|
401
|
+
rowCount: allRowCounts.get(name) ?? 0,
|
|
402
|
+
errors: tableErrors,
|
|
403
|
+
warnings: tableWarnings
|
|
404
|
+
};
|
|
405
|
+
});
|
|
338
406
|
return {
|
|
339
407
|
valid: allErrors.length === 0,
|
|
340
408
|
errors: allErrors,
|
|
341
|
-
warnings: allWarnings
|
|
409
|
+
warnings: allWarnings,
|
|
410
|
+
tableResults
|
|
342
411
|
};
|
|
343
412
|
}
|
|
344
413
|
/**
|
|
@@ -347,9 +416,11 @@ var LinesDB = class LinesDB {
|
|
|
347
416
|
async loadTableWithDependencies(tableName, loadedTables, loadingTables, attemptedTables, detailedValidate, transform) {
|
|
348
417
|
const errors = [];
|
|
349
418
|
const warnings = [];
|
|
419
|
+
const rowCounts = /* @__PURE__ */ new Map();
|
|
350
420
|
if (attemptedTables.has(tableName)) return {
|
|
351
421
|
errors,
|
|
352
|
-
warnings
|
|
422
|
+
warnings,
|
|
423
|
+
rowCounts
|
|
353
424
|
};
|
|
354
425
|
attemptedTables.add(tableName);
|
|
355
426
|
if (loadingTables.has(tableName)) throw new Error(`Circular dependency detected for table '${tableName}'`);
|
|
@@ -360,8 +431,11 @@ var LinesDB = class LinesDB {
|
|
|
360
431
|
let foreignKeys;
|
|
361
432
|
try {
|
|
362
433
|
const { pathToFileURL: pathToFileURL$1 } = await import("node:url");
|
|
363
|
-
const
|
|
364
|
-
|
|
434
|
+
const schemaPath = await findSchemaFile(dirname(tableConfig.jsonlPath), basename(tableConfig.jsonlPath, ".jsonl"));
|
|
435
|
+
if (schemaPath) {
|
|
436
|
+
const schemaModule = await import(`${pathToFileURL$1(schemaPath).href}?t=${Date.now()}`);
|
|
437
|
+
foreignKeys = (schemaModule.schema || schemaModule.default)?.foreignKeys || schemaModule.foreignKeys;
|
|
438
|
+
}
|
|
365
439
|
} catch {}
|
|
366
440
|
if (foreignKeys && foreignKeys.length > 0) for (const fk of foreignKeys) {
|
|
367
441
|
const referencedTable = fk.references.table;
|
|
@@ -370,10 +444,21 @@ var LinesDB = class LinesDB {
|
|
|
370
444
|
const depResult = await this.loadTableWithDependencies(referencedTable, loadedTables, loadingTables, attemptedTables, detailedValidate, void 0);
|
|
371
445
|
errors.push(...depResult.errors);
|
|
372
446
|
warnings.push(...depResult.warnings);
|
|
447
|
+
for (const [k, v] of depResult.rowCounts) rowCounts.set(k, v);
|
|
373
448
|
} else throw new Error(`Foreign key reference to non-existent table '${referencedTable}' in table '${tableName}'`);
|
|
374
449
|
}
|
|
375
|
-
const
|
|
450
|
+
const failedDependencies = /* @__PURE__ */ new Set();
|
|
451
|
+
if (foreignKeys && foreignKeys.length > 0) {
|
|
452
|
+
for (const fk of foreignKeys) {
|
|
453
|
+
const referencedTable = fk.references.table;
|
|
454
|
+
if (referencedTable === tableName) continue;
|
|
455
|
+
if (attemptedTables.has(referencedTable) && !loadedTables.has(referencedTable)) failedDependencies.add(referencedTable);
|
|
456
|
+
}
|
|
457
|
+
if (failedDependencies.size > 0) for (const dep of failedDependencies) warnings.push(`Skipping foreign key validation for table '${tableName}': referenced table '${dep}' has validation errors`);
|
|
458
|
+
}
|
|
459
|
+
const { loaded, rowCount, errors: loadErrors } = await this.loadTable(tableName, tableConfig, detailedValidate, transform, failedDependencies);
|
|
376
460
|
errors.push(...loadErrors);
|
|
461
|
+
rowCounts.set(tableName, rowCount);
|
|
377
462
|
if (loaded) loadedTables.add(tableName);
|
|
378
463
|
else {
|
|
379
464
|
warnings.push(`Table '${tableName}' was not loaded (no data or skipped)`);
|
|
@@ -384,14 +469,15 @@ var LinesDB = class LinesDB {
|
|
|
384
469
|
}
|
|
385
470
|
return {
|
|
386
471
|
errors,
|
|
387
|
-
warnings
|
|
472
|
+
warnings,
|
|
473
|
+
rowCounts
|
|
388
474
|
};
|
|
389
475
|
}
|
|
390
476
|
/**
|
|
391
477
|
* Load a single table from JSONL file
|
|
392
478
|
* @returns Object with loaded status and validation errors
|
|
393
479
|
*/
|
|
394
|
-
async loadTable(tableName, config, detailedValidate, transform) {
|
|
480
|
+
async loadTable(tableName, config, detailedValidate, transform, failedDependencies) {
|
|
395
481
|
let data = await JsonlReader.read(config.jsonlPath);
|
|
396
482
|
if (transform) data = data.map((row) => transform(row));
|
|
397
483
|
let validationSchema = config.validationSchema;
|
|
@@ -401,7 +487,9 @@ var LinesDB = class LinesDB {
|
|
|
401
487
|
} catch (_error) {}
|
|
402
488
|
if (!config.validationSchema) try {
|
|
403
489
|
const { pathToFileURL: pathToFileURL$1 } = await import("node:url");
|
|
404
|
-
const
|
|
490
|
+
const schemaPath = await findSchemaFile(dirname(config.jsonlPath), basename(config.jsonlPath, ".jsonl"));
|
|
491
|
+
if (!schemaPath) throw new Error("Schema file not found");
|
|
492
|
+
const schemaModule = await import(`${pathToFileURL$1(schemaPath).href}?t=${Date.now()}`);
|
|
405
493
|
const schemaExport = schemaModule.schema || schemaModule.default;
|
|
406
494
|
if (schemaExport?.primaryKey) schemaMetadata.primaryKey = schemaExport.primaryKey;
|
|
407
495
|
else if (schemaModule.primaryKey) schemaMetadata.primaryKey = schemaModule.primaryKey;
|
|
@@ -444,6 +532,7 @@ var LinesDB = class LinesDB {
|
|
|
444
532
|
}));
|
|
445
533
|
if (validationErrors.length > 0) return {
|
|
446
534
|
loaded: false,
|
|
535
|
+
rowCount: data.length,
|
|
447
536
|
errors: validationErrorDetails
|
|
448
537
|
};
|
|
449
538
|
let schema;
|
|
@@ -458,6 +547,7 @@ var LinesDB = class LinesDB {
|
|
|
458
547
|
} else if (config.autoInferSchema !== false) {
|
|
459
548
|
if (validatedData.length === 0) return {
|
|
460
549
|
loaded: false,
|
|
550
|
+
rowCount: 0,
|
|
461
551
|
errors: []
|
|
462
552
|
};
|
|
463
553
|
schema = inferredSchema;
|
|
@@ -473,7 +563,7 @@ var LinesDB = class LinesDB {
|
|
|
473
563
|
const idColumn = schema.columns.find((c) => c.name === "id");
|
|
474
564
|
if (idColumn) idColumn.primaryKey = true;
|
|
475
565
|
}
|
|
476
|
-
if (foreignKeys) schema.foreignKeys = foreignKeys;
|
|
566
|
+
if (foreignKeys) schema.foreignKeys = failedDependencies && failedDependencies.size > 0 ? foreignKeys.filter((fk) => !failedDependencies.has(fk.references.table)) : foreignKeys;
|
|
477
567
|
if (indexes) {
|
|
478
568
|
schema.indexes = indexes;
|
|
479
569
|
for (const index of indexes) if (index.unique && index.columns.length === 1) {
|
|
@@ -487,11 +577,13 @@ var LinesDB = class LinesDB {
|
|
|
487
577
|
const insertErrors = this.insertDataWithDetailedValidation(tableName, schema, validatedData, config.jsonlPath);
|
|
488
578
|
if (insertErrors.length > 0) return {
|
|
489
579
|
loaded: false,
|
|
580
|
+
rowCount: data.length,
|
|
490
581
|
errors: insertErrors
|
|
491
582
|
};
|
|
492
583
|
} else this.insertData(tableName, schema, validatedData);
|
|
493
584
|
return {
|
|
494
585
|
loaded: true,
|
|
586
|
+
rowCount: data.length,
|
|
495
587
|
errors: []
|
|
496
588
|
};
|
|
497
589
|
}
|
|
@@ -1089,7 +1181,7 @@ var TypeGenerator = class {
|
|
|
1089
1181
|
this.projectRoot = envProjectRoot !== void 0 ? envProjectRoot : options.projectRoot || process.cwd();
|
|
1090
1182
|
this.dataDir = options.dataDir;
|
|
1091
1183
|
this.dataDirPath = isAbsolute(this.dataDir) ? this.dataDir : join(this.projectRoot, this.dataDir);
|
|
1092
|
-
this.outputFile = join(this.dataDirPath, "db.ts");
|
|
1184
|
+
this.outputFile = options.output ? isAbsolute(options.output) ? options.output : join(this.projectRoot, options.output) : join(this.dataDirPath, "db.ts");
|
|
1093
1185
|
}
|
|
1094
1186
|
/**
|
|
1095
1187
|
* Generate types file from JSONL files and their optional schema files.
|
|
@@ -1112,12 +1204,10 @@ var TypeGenerator = class {
|
|
|
1112
1204
|
const tables = [];
|
|
1113
1205
|
for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
1114
1206
|
const tableName = basename(entry.name, ".jsonl");
|
|
1115
|
-
const
|
|
1116
|
-
const schemaFilePath = join(this.dataDirPath, schemaFileName);
|
|
1117
|
-
const hasSchema = entries.some((e) => e.isFile() && e.name === schemaFileName);
|
|
1207
|
+
const schemaFilePath = findSchemaFileInEntries(this.dataDirPath, tableName, entries);
|
|
1118
1208
|
tables.push({
|
|
1119
1209
|
tableName,
|
|
1120
|
-
schemaFile:
|
|
1210
|
+
schemaFile: schemaFilePath
|
|
1121
1211
|
});
|
|
1122
1212
|
}
|
|
1123
1213
|
return tables;
|
|
@@ -1138,7 +1228,7 @@ var TypeGenerator = class {
|
|
|
1138
1228
|
if (table.schemaFile) {
|
|
1139
1229
|
const schemaIdentifier = this.createSchemaIdentifier(table.tableName, usedAliases);
|
|
1140
1230
|
usedAliases.add(schemaIdentifier);
|
|
1141
|
-
let relativePath = relative(join(this.outputFile, ".."), table.schemaFile).replace(/\\/g, "/")
|
|
1231
|
+
let relativePath = rewriteExtensionForImport(relative(join(this.outputFile, ".."), table.schemaFile).replace(/\\/g, "/"));
|
|
1142
1232
|
if (!relativePath.startsWith(".")) relativePath = "./" + relativePath;
|
|
1143
1233
|
imports.push(`import { schema as ${schemaIdentifier} } from '${relativePath}';`);
|
|
1144
1234
|
tableEntries.push(` ${tableKey}: InferOutput<typeof ${schemaIdentifier}>;`);
|
|
@@ -1214,5 +1304,5 @@ async function ensureTableRowsValid(options) {
|
|
|
1214
1304
|
}
|
|
1215
1305
|
|
|
1216
1306
|
//#endregion
|
|
1217
|
-
export { DirectoryScanner, JsonlReader, JsonlWriter, LinesDB, RUNTIME, SchemaLoader, TypeGenerator, defineSchema, detectRuntime, ensureTableRowsValid, hasBackward };
|
|
1307
|
+
export { DirectoryScanner, JsonlReader, JsonlWriter, LinesDB, RUNTIME, SCHEMA_EXTENSIONS, SchemaLoader, TypeGenerator, defineSchema, detectRuntime, ensureTableRowsValid, extractTableNameFromSchemaFile, findSchemaFile, hasBackward, isSchemaFile, rewriteExtensionForImport };
|
|
1218
1308
|
//# sourceMappingURL=index.js.map
|