@toiroakr/lines-db 0.8.0 → 0.9.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 +14 -0
- package/bin/cli.js +57 -7
- package/dist/index.cjs +163 -7
- package/dist/index.d.cts +67 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +162 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/database.test.ts +160 -0
- package/src/database.ts +100 -5
- package/src/index.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, extname, isAbsolute, join, normalize, relative } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { styleText } from "node:util";
|
|
5
6
|
|
|
6
7
|
//#region rolldown:runtime
|
|
7
8
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
@@ -385,13 +386,20 @@ var LinesDB = class LinesDB {
|
|
|
385
386
|
const loadedTables = /* @__PURE__ */ new Set();
|
|
386
387
|
const loadingTables = /* @__PURE__ */ new Set();
|
|
387
388
|
const attemptedTables = /* @__PURE__ */ new Set();
|
|
389
|
+
const allDeferredForeignKeys = [];
|
|
388
390
|
for (const tableNameToLoad of tablesToLoad) if (!attemptedTables.has(tableNameToLoad)) {
|
|
389
391
|
const tableTransform = tableNameToLoad === tableName ? transform : void 0;
|
|
390
|
-
const { errors, warnings, rowCounts: tableRowCounts } = await this.loadTableWithDependencies(tableNameToLoad, loadedTables, loadingTables, attemptedTables, detailedValidate, tableTransform);
|
|
392
|
+
const { errors, warnings, rowCounts: tableRowCounts, deferredForeignKeys } = await this.loadTableWithDependencies(tableNameToLoad, loadedTables, loadingTables, attemptedTables, detailedValidate, tableTransform);
|
|
391
393
|
allErrors.push(...errors);
|
|
392
394
|
allWarnings.push(...warnings);
|
|
395
|
+
allDeferredForeignKeys.push(...deferredForeignKeys);
|
|
393
396
|
for (const [k, v] of tableRowCounts) allRowCounts.set(k, v);
|
|
394
397
|
}
|
|
398
|
+
if (detailedValidate && allDeferredForeignKeys.length > 0) for (const { tableName: tName, foreignKey: fk, filePath } of allDeferredForeignKeys) {
|
|
399
|
+
if (!loadedTables.has(fk.references.table)) continue;
|
|
400
|
+
const deferredErrors = this.validateDeferredForeignKey(tName, fk, filePath);
|
|
401
|
+
allErrors.push(...deferredErrors);
|
|
402
|
+
}
|
|
395
403
|
const tableResults = tablesToLoad.map((name) => {
|
|
396
404
|
const tableErrors = allErrors.filter((e) => e.tableName === name);
|
|
397
405
|
const tableWarnings = allWarnings.filter((w) => w.includes(`'${name}'`));
|
|
@@ -417,10 +425,12 @@ var LinesDB = class LinesDB {
|
|
|
417
425
|
const errors = [];
|
|
418
426
|
const warnings = [];
|
|
419
427
|
const rowCounts = /* @__PURE__ */ new Map();
|
|
428
|
+
const deferredForeignKeys = [];
|
|
420
429
|
if (attemptedTables.has(tableName)) return {
|
|
421
430
|
errors,
|
|
422
431
|
warnings,
|
|
423
|
-
rowCounts
|
|
432
|
+
rowCounts,
|
|
433
|
+
deferredForeignKeys
|
|
424
434
|
};
|
|
425
435
|
attemptedTables.add(tableName);
|
|
426
436
|
if (loadingTables.has(tableName)) throw new Error(`Circular dependency detected for table '${tableName}'`);
|
|
@@ -444,23 +454,35 @@ var LinesDB = class LinesDB {
|
|
|
444
454
|
const depResult = await this.loadTableWithDependencies(referencedTable, loadedTables, loadingTables, attemptedTables, detailedValidate, void 0);
|
|
445
455
|
errors.push(...depResult.errors);
|
|
446
456
|
warnings.push(...depResult.warnings);
|
|
457
|
+
deferredForeignKeys.push(...depResult.deferredForeignKeys);
|
|
447
458
|
for (const [k, v] of depResult.rowCounts) rowCounts.set(k, v);
|
|
448
459
|
} else throw new Error(`Foreign key reference to non-existent table '${referencedTable}' in table '${tableName}'`);
|
|
449
460
|
}
|
|
450
461
|
const failedDependencies = /* @__PURE__ */ new Set();
|
|
462
|
+
const circularDependencies = /* @__PURE__ */ new Set();
|
|
451
463
|
if (foreignKeys && foreignKeys.length > 0) {
|
|
452
464
|
for (const fk of foreignKeys) {
|
|
453
465
|
const referencedTable = fk.references.table;
|
|
454
466
|
if (referencedTable === tableName) continue;
|
|
455
|
-
if (attemptedTables.has(referencedTable) && !loadedTables.has(referencedTable))
|
|
467
|
+
if (attemptedTables.has(referencedTable) && !loadedTables.has(referencedTable)) if (loadingTables.has(referencedTable)) circularDependencies.add(referencedTable);
|
|
468
|
+
else failedDependencies.add(referencedTable);
|
|
456
469
|
}
|
|
457
470
|
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
471
|
}
|
|
459
|
-
const
|
|
472
|
+
const allSkippedDependencies = new Set([...failedDependencies, ...circularDependencies]);
|
|
473
|
+
const { loaded, rowCount, errors: loadErrors } = await this.loadTable(tableName, tableConfig, detailedValidate, transform, allSkippedDependencies);
|
|
460
474
|
errors.push(...loadErrors);
|
|
461
475
|
rowCounts.set(tableName, rowCount);
|
|
462
|
-
if (loaded)
|
|
463
|
-
|
|
476
|
+
if (loaded) {
|
|
477
|
+
loadedTables.add(tableName);
|
|
478
|
+
if (foreignKeys && circularDependencies.size > 0) {
|
|
479
|
+
for (const fk of foreignKeys) if (circularDependencies.has(fk.references.table)) deferredForeignKeys.push({
|
|
480
|
+
tableName,
|
|
481
|
+
foreignKey: fk,
|
|
482
|
+
filePath: tableConfig.jsonlPath
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
464
486
|
warnings.push(`Table '${tableName}' was not loaded (no data or skipped)`);
|
|
465
487
|
this.tables.delete(tableName);
|
|
466
488
|
}
|
|
@@ -470,7 +492,8 @@ var LinesDB = class LinesDB {
|
|
|
470
492
|
return {
|
|
471
493
|
errors,
|
|
472
494
|
warnings,
|
|
473
|
-
rowCounts
|
|
495
|
+
rowCounts,
|
|
496
|
+
deferredForeignKeys
|
|
474
497
|
};
|
|
475
498
|
}
|
|
476
499
|
/**
|
|
@@ -716,6 +739,34 @@ var LinesDB = class LinesDB {
|
|
|
716
739
|
};
|
|
717
740
|
}
|
|
718
741
|
/**
|
|
742
|
+
* Validate a deferred foreign key constraint after all tables have been loaded.
|
|
743
|
+
* Used for circular dependency FK validation.
|
|
744
|
+
*/
|
|
745
|
+
validateDeferredForeignKey(tableName, fk, filePath) {
|
|
746
|
+
const errors = [];
|
|
747
|
+
const quotedTable = this.quoteTableName(tableName);
|
|
748
|
+
const quotedColumn = this.quoteIdentifier(fk.column);
|
|
749
|
+
const quotedRefTable = this.quoteTableName(fk.references.table);
|
|
750
|
+
const sql = `SELECT rowid - 1 as idx, ${quotedColumn} as val FROM ${quotedTable} WHERE ${quotedColumn} IS NOT NULL AND ${quotedColumn} NOT IN (SELECT ${this.quoteIdentifier(fk.references.column)} FROM ${quotedRefTable})`;
|
|
751
|
+
try {
|
|
752
|
+
const rows = this.query(sql);
|
|
753
|
+
for (const row of rows) errors.push({
|
|
754
|
+
file: filePath,
|
|
755
|
+
tableName,
|
|
756
|
+
rowIndex: row.idx,
|
|
757
|
+
issues: [],
|
|
758
|
+
type: "foreignKey",
|
|
759
|
+
foreignKeyError: {
|
|
760
|
+
column: fk.column,
|
|
761
|
+
value: row.val,
|
|
762
|
+
referencedTable: fk.references.table,
|
|
763
|
+
referencedColumn: fk.references.column
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
} catch (_) {}
|
|
767
|
+
return errors;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
719
770
|
* Execute a raw SQL query
|
|
720
771
|
*/
|
|
721
772
|
query(sql, params = []) {
|
|
@@ -1304,5 +1355,108 @@ async function ensureTableRowsValid(options) {
|
|
|
1304
1355
|
}
|
|
1305
1356
|
|
|
1306
1357
|
//#endregion
|
|
1307
|
-
|
|
1358
|
+
//#region src/error-formatter.ts
|
|
1359
|
+
var ErrorFormatter = class {
|
|
1360
|
+
verbose;
|
|
1361
|
+
constructor(options = {}) {
|
|
1362
|
+
this.verbose = options.verbose ?? false;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Format validation errors
|
|
1366
|
+
*/
|
|
1367
|
+
formatValidationErrors(errors) {
|
|
1368
|
+
if (this.verbose) return this.formatValidationErrorsVerbose(errors);
|
|
1369
|
+
return this.formatValidationErrorsCompact(errors);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Format foreign key error
|
|
1373
|
+
*/
|
|
1374
|
+
formatForeignKeyError(error) {
|
|
1375
|
+
if (this.verbose) return this.formatForeignKeyErrorVerbose(error);
|
|
1376
|
+
return this.formatForeignKeyErrorCompact(error);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Format compact (default) validation errors
|
|
1380
|
+
*/
|
|
1381
|
+
formatValidationErrorsCompact(errors) {
|
|
1382
|
+
const lines = [];
|
|
1383
|
+
for (const error of errors) for (const issue of error.issues) {
|
|
1384
|
+
const fieldPath = this.getFieldPath(issue);
|
|
1385
|
+
const line = `${error.file}:${error.rowIndex + 1} • ${fieldPath}: ${issue.message}`;
|
|
1386
|
+
lines.push(line);
|
|
1387
|
+
}
|
|
1388
|
+
return lines.join("\n");
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Format verbose validation errors
|
|
1392
|
+
*/
|
|
1393
|
+
formatValidationErrorsVerbose(errors) {
|
|
1394
|
+
const blocks = [];
|
|
1395
|
+
for (let i = 0; i < errors.length; i++) {
|
|
1396
|
+
const error = errors[i];
|
|
1397
|
+
const isLast = i === errors.length - 1;
|
|
1398
|
+
const prefix = isLast ? "└─" : "├─";
|
|
1399
|
+
const linePrefix = isLast ? " " : "│ ";
|
|
1400
|
+
const lines = [`${prefix} ${error.file}:${error.rowIndex + 1}`];
|
|
1401
|
+
for (const issue of error.issues) {
|
|
1402
|
+
const fieldPath = this.getFieldPath(issue);
|
|
1403
|
+
lines.push(`${linePrefix}Field: ${fieldPath}`);
|
|
1404
|
+
lines.push(`${linePrefix}Error: ${issue.message}`);
|
|
1405
|
+
}
|
|
1406
|
+
if (error.originalData !== void 0) lines.push(`${linePrefix}Original data: ${JSON.stringify(error.originalData)}`);
|
|
1407
|
+
if (error.data !== void 0) {
|
|
1408
|
+
const label = error.originalData !== void 0 ? "Transformed data" : "Data";
|
|
1409
|
+
lines.push(`${linePrefix}${label}: ${JSON.stringify(error.data)}`);
|
|
1410
|
+
}
|
|
1411
|
+
blocks.push(lines.join("\n"));
|
|
1412
|
+
}
|
|
1413
|
+
return blocks.join("\n│\n");
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Format compact foreign key error
|
|
1417
|
+
*/
|
|
1418
|
+
formatForeignKeyErrorCompact(error) {
|
|
1419
|
+
return `${error.file}:${error.rowIndex + 1} • ${error.column}: Foreign key constraint failed - Referenced value ${JSON.stringify(error.value)} does not exist in ${error.referencedTable}(${error.referencedColumn})`;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Format verbose foreign key error
|
|
1423
|
+
*/
|
|
1424
|
+
formatForeignKeyErrorVerbose(error) {
|
|
1425
|
+
const lines = [
|
|
1426
|
+
`└─ ${error.file}:${error.rowIndex + 1}`,
|
|
1427
|
+
` Type: Foreign Key Violation`,
|
|
1428
|
+
` Field: ${error.column}`,
|
|
1429
|
+
` Value: ${JSON.stringify(error.value)}`,
|
|
1430
|
+
` References: ${error.referencedTable}(${error.referencedColumn})`,
|
|
1431
|
+
` Error: Referenced value does not exist in target table`
|
|
1432
|
+
];
|
|
1433
|
+
if (error.data !== void 0) lines.push(` Data: ${JSON.stringify(error.data)}`);
|
|
1434
|
+
return lines.join("\n");
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Get field path from issue
|
|
1438
|
+
*/
|
|
1439
|
+
getFieldPath(issue) {
|
|
1440
|
+
if (!issue.path || issue.path.length === 0) return "root";
|
|
1441
|
+
return issue.path.map((segment) => {
|
|
1442
|
+
if (typeof segment === "object" && segment !== null && "key" in segment) return String(segment.key);
|
|
1443
|
+
return String(segment);
|
|
1444
|
+
}).join(".");
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Format error header with count
|
|
1448
|
+
*/
|
|
1449
|
+
formatErrorHeader(count, file) {
|
|
1450
|
+
return styleText("red", `✗ Found ${count} error(s)${file ? ` in ${file}` : ""}`);
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Format migration failure header
|
|
1454
|
+
*/
|
|
1455
|
+
formatMigrationFailureHeader() {
|
|
1456
|
+
return styleText("red", "\n✗ Migration failed and was rolled back");
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
//#endregion
|
|
1461
|
+
export { DirectoryScanner, ErrorFormatter, JsonlReader, JsonlWriter, LinesDB, RUNTIME, SCHEMA_EXTENSIONS, SchemaLoader, TypeGenerator, defineSchema, detectRuntime, ensureTableRowsValid, extractTableNameFromSchemaFile, findSchemaFile, hasBackward, isSchemaFile, rewriteExtensionForImport };
|
|
1308
1462
|
//# sourceMappingURL=index.js.map
|