@toiroakr/lines-db 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1212 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let node_fs_promises = require("node:fs/promises");
25
+ node_fs_promises = __toESM(node_fs_promises);
26
+ let node_path = require("node:path");
27
+ node_path = __toESM(node_path);
28
+ let node_url = require("node:url");
29
+ node_url = __toESM(node_url);
30
+
31
+ //#region src/runtime.ts
32
+ function detectRuntime() {
33
+ if (typeof globalThis !== "undefined" && "Bun" in globalThis && typeof globalThis.Bun !== "undefined") return "bun";
34
+ if (typeof globalThis !== "undefined" && "Deno" in globalThis && typeof globalThis.Deno !== "undefined") return "deno";
35
+ if (typeof process !== "undefined" && process.versions && process.versions.node) return "node";
36
+ return "unknown";
37
+ }
38
+ const RUNTIME = detectRuntime();
39
+
40
+ //#endregion
41
+ //#region src/sqlite-adapter.ts
42
+ /**
43
+ * Create a SQLite database instance based on the runtime environment
44
+ */
45
+ function createDatabase(path = ":memory:") {
46
+ if (RUNTIME === "bun") return createBunDatabase(path);
47
+ else if (RUNTIME === "node" || RUNTIME === "deno") return createNodeDatabase(path);
48
+ else throw new Error(`Unsupported runtime: ${RUNTIME}`);
49
+ }
50
+ /**
51
+ * Create a Bun SQLite database
52
+ */
53
+ function createBunDatabase(path) {
54
+ const { Database } = require("bun:sqlite");
55
+ const db = new Database(path);
56
+ return {
57
+ prepare(sql) {
58
+ const stmt = db.prepare(sql);
59
+ return {
60
+ run(...params) {
61
+ return stmt.run(...params);
62
+ },
63
+ get(...params) {
64
+ return stmt.get(...params);
65
+ },
66
+ all(...params) {
67
+ return stmt.all(...params);
68
+ }
69
+ };
70
+ },
71
+ exec(sql) {
72
+ db.exec(sql);
73
+ },
74
+ close() {
75
+ db.close();
76
+ }
77
+ };
78
+ }
79
+ /**
80
+ * Create a Node.js SQLite database
81
+ */
82
+ function createNodeDatabase(path) {
83
+ const { DatabaseSync } = require("node:sqlite");
84
+ const db = new DatabaseSync(path);
85
+ return {
86
+ prepare(sql) {
87
+ const stmt = db.prepare(sql);
88
+ return {
89
+ run(...params) {
90
+ return stmt.run(...params);
91
+ },
92
+ get(...params) {
93
+ return stmt.get(...params);
94
+ },
95
+ all(...params) {
96
+ return stmt.all(...params);
97
+ }
98
+ };
99
+ },
100
+ exec(sql) {
101
+ db.exec(sql);
102
+ },
103
+ close() {
104
+ db.close();
105
+ }
106
+ };
107
+ }
108
+
109
+ //#endregion
110
+ //#region src/jsonl-reader.ts
111
+ var JsonlReader = class {
112
+ static overrides = null;
113
+ /**
114
+ * Temporarily override the data returned for specific JSONL files.
115
+ * Useful for scenarios like migration validation where in-memory data should be used.
116
+ */
117
+ static async withOverrides(overrides, fn) {
118
+ const normalized = /* @__PURE__ */ new Map();
119
+ for (const [filePath, rows] of overrides) normalized.set((0, node_path.normalize)(filePath), rows);
120
+ const previousOverrides = this.overrides;
121
+ this.overrides = normalized;
122
+ try {
123
+ return await fn();
124
+ } finally {
125
+ this.overrides = previousOverrides;
126
+ }
127
+ }
128
+ /**
129
+ * Read JSONL file and parse each line as JSON
130
+ */
131
+ static async read(filePath) {
132
+ const overrideRows = this.overrides?.get((0, node_path.normalize)(filePath));
133
+ if (overrideRows) return overrideRows.map((row) => JSON.parse(JSON.stringify(row)));
134
+ return (await (0, node_fs_promises.readFile)(filePath, "utf-8")).trim().split("\n").filter((line) => line.trim().length > 0).map((line) => {
135
+ try {
136
+ return JSON.parse(line);
137
+ } catch (error) {
138
+ throw new Error(`Failed to parse JSON line: ${line}`, { cause: error });
139
+ }
140
+ });
141
+ }
142
+ /**
143
+ * Infer schema from JSONL data
144
+ */
145
+ static inferSchema(tableName, data) {
146
+ if (data.length === 0) throw new Error("Cannot infer schema from empty data");
147
+ const columnTypes = /* @__PURE__ */ new Map();
148
+ const booleanColumns = /* @__PURE__ */ new Set();
149
+ const nonBooleanColumns = /* @__PURE__ */ new Set();
150
+ for (const row of data) for (const [key, value] of Object.entries(row)) {
151
+ if (!columnTypes.has(key)) columnTypes.set(key, /* @__PURE__ */ new Set());
152
+ columnTypes.get(key).add(this.inferType(value));
153
+ if (typeof value === "boolean") booleanColumns.add(key);
154
+ else if (value !== null) nonBooleanColumns.add(key);
155
+ }
156
+ const columns = [];
157
+ for (const [columnName, types] of columnTypes.entries()) {
158
+ const typeArray = Array.from(types);
159
+ let sqlType = "TEXT";
160
+ if (typeArray.length === 1) sqlType = typeArray[0];
161
+ else if (typeArray.every((t) => t === "INTEGER" || t === "REAL")) sqlType = "REAL";
162
+ else if (!typeArray.includes("NULL")) sqlType = "TEXT";
163
+ else if (typeArray.length === 2 && typeArray.includes("NULL")) sqlType = typeArray.find((t) => t !== "NULL");
164
+ const isBooleanColumn = booleanColumns.has(columnName) && !nonBooleanColumns.has(columnName);
165
+ columns.push({
166
+ name: columnName,
167
+ type: sqlType,
168
+ notNull: !typeArray.includes("NULL"),
169
+ valueType: isBooleanColumn ? "boolean" : void 0
170
+ });
171
+ }
172
+ const idColumn = columns.find((col) => col.name === "id");
173
+ if (idColumn) idColumn.primaryKey = true;
174
+ return {
175
+ name: tableName,
176
+ columns
177
+ };
178
+ }
179
+ static inferType(value) {
180
+ if (value === null) return "NULL";
181
+ if (typeof value === "number") return Number.isInteger(value) ? "INTEGER" : "REAL";
182
+ if (typeof value === "string") return "TEXT";
183
+ if (typeof value === "boolean") return "INTEGER";
184
+ if (typeof value === "object") return "JSON";
185
+ return "TEXT";
186
+ }
187
+ };
188
+
189
+ //#endregion
190
+ //#region src/jsonl-writer.ts
191
+ var JsonlWriter = class {
192
+ /**
193
+ * Write data to JSONL file
194
+ */
195
+ static async write(filePath, data) {
196
+ await (0, node_fs_promises.writeFile)(filePath, data.map((obj) => JSON.stringify(obj)).join("\n") + "\n", "utf-8");
197
+ }
198
+ /**
199
+ * Append data to JSONL file
200
+ */
201
+ static async append(filePath, data) {
202
+ const { readFile: readFile$1, writeFile: writeFile$2 } = await import("node:fs/promises");
203
+ try {
204
+ const existing = await readFile$1(filePath, "utf-8");
205
+ const lines = data.map((obj) => JSON.stringify(obj)).join("\n");
206
+ await writeFile$2(filePath, existing.trim() + "\n" + lines + "\n", "utf-8");
207
+ } catch (error) {
208
+ if (error.code === "ENOENT") await this.write(filePath, data);
209
+ else throw error;
210
+ }
211
+ }
212
+ };
213
+
214
+ //#endregion
215
+ //#region src/schema-loader.ts
216
+ var SchemaLoader = class {
217
+ /**
218
+ * Load a validation schema file for a table
219
+ * Requires ${tableName}.schema.ts to exist alongside the JSONL file
220
+ */
221
+ static async loadSchema(jsonlPath) {
222
+ const dir = (0, node_path.dirname)(jsonlPath);
223
+ const tableName = (0, node_path.basename)(jsonlPath, ".jsonl");
224
+ const schemaPath = (0, node_path.join)(dir, `${tableName}.schema.ts`);
225
+ try {
226
+ await (0, node_fs_promises.access)(schemaPath);
227
+ } catch (error) {
228
+ throw new Error(`Schema file not found for table '${tableName}'. Expected: ${schemaPath}`, { cause: error instanceof Error ? error : void 0 });
229
+ }
230
+ try {
231
+ const module$1 = await import(`${(0, node_url.pathToFileURL)(schemaPath).href}?t=${Date.now()}`);
232
+ const schema = module$1.default || module$1.schema;
233
+ if (schema && this.isStandardSchema(schema)) return schema;
234
+ throw new Error(`Schema file ${schemaPath} does not export a valid StandardSchema`);
235
+ } catch (error) {
236
+ throw new Error(`Failed to load schema for table '${tableName}' from ${schemaPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : void 0 });
237
+ }
238
+ }
239
+ /**
240
+ * Check if an object implements the StandardSchema interface
241
+ */
242
+ static isStandardSchema(obj) {
243
+ if (!obj || typeof obj !== "object") return false;
244
+ const standard = obj["~standard"];
245
+ if (!standard || typeof standard !== "object") return false;
246
+ const standardObj = standard;
247
+ return standardObj.version === 1 && typeof standardObj.vendor === "string" && typeof standardObj.validate === "function";
248
+ }
249
+ };
250
+
251
+ //#endregion
252
+ //#region src/directory-scanner.ts
253
+ var DirectoryScanner = class {
254
+ /**
255
+ * Scan directory for JSONL files and create table configurations
256
+ */
257
+ static async scanDirectory(dataDir) {
258
+ const tables = /* @__PURE__ */ new Map();
259
+ try {
260
+ const files = await (0, node_fs_promises.readdir)(dataDir);
261
+ for (const file of files) if ((0, node_path.extname)(file) === ".jsonl") {
262
+ const tableName = (0, node_path.basename)(file, ".jsonl");
263
+ const jsonlPath = (0, node_path.join)(dataDir, file);
264
+ tables.set(tableName, {
265
+ jsonlPath,
266
+ autoInferSchema: true
267
+ });
268
+ }
269
+ if (tables.size === 0) console.warn(`Warning: No JSONL files found in directory: ${dataDir}`);
270
+ return tables;
271
+ } catch (error) {
272
+ throw new Error(`Failed to scan directory ${dataDir}: ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ }
275
+ };
276
+
277
+ //#endregion
278
+ //#region src/schema.ts
279
+ /**
280
+ * Define a bidirectional schema with optional backward transformation
281
+ *
282
+ * @param schema - Standard Schema for validation
283
+ * @param optionsOrBackward - Optional SchemaOptions object or backward transformation function (Output → Input)
284
+ * Required when schema performs transformations
285
+ *
286
+ * @example
287
+ * // No transformation - backward not needed
288
+ * const schema = defineSchema(
289
+ * v.object({ id: v.number(), name: v.string() })
290
+ * );
291
+ *
292
+ * @example
293
+ * // With transformation - backward recommended (legacy)
294
+ * const schema = defineSchema(
295
+ * v.pipe(v.string(), v.transform(Number)),
296
+ * (num) => String(num) // backward: number → string
297
+ * );
298
+ *
299
+ * @example
300
+ * // With primary key and foreign key
301
+ * const schema = defineSchema(
302
+ * v.object({ id: v.number(), customerId: v.number() }),
303
+ * {
304
+ * primaryKey: ['id'],
305
+ * foreignKeys: [
306
+ * { columns: ['customerId'], references: { table: 'users', columns: ['id'] } }
307
+ * ]
308
+ * }
309
+ * );
310
+ */
311
+ function defineSchema(schema, optionsOrBackward) {
312
+ const bidirectionalSchema = Object.create(schema);
313
+ if (optionsOrBackward) if (typeof optionsOrBackward === "function") bidirectionalSchema.backward = optionsOrBackward;
314
+ else {
315
+ if (optionsOrBackward.backward) bidirectionalSchema.backward = optionsOrBackward.backward;
316
+ if (optionsOrBackward.primaryKey) bidirectionalSchema.primaryKey = optionsOrBackward.primaryKey;
317
+ if (optionsOrBackward.foreignKeys) bidirectionalSchema.foreignKeys = optionsOrBackward.foreignKeys;
318
+ if (optionsOrBackward.indexes) bidirectionalSchema.indexes = optionsOrBackward.indexes;
319
+ }
320
+ Object.defineProperty(bidirectionalSchema, "~standard", {
321
+ value: schema["~standard"],
322
+ enumerable: true,
323
+ configurable: true
324
+ });
325
+ return bidirectionalSchema;
326
+ }
327
+ /**
328
+ * Check if a schema has backward transformation
329
+ */
330
+ function hasBackward(schema) {
331
+ return "backward" in schema && typeof schema.backward === "function";
332
+ }
333
+
334
+ //#endregion
335
+ //#region src/database.ts
336
+ var LinesDB = class LinesDB {
337
+ db;
338
+ config;
339
+ schemas = /* @__PURE__ */ new Map();
340
+ validationSchemas = /* @__PURE__ */ new Map();
341
+ tables = /* @__PURE__ */ new Map();
342
+ inTransaction = false;
343
+ constructor(config, dbPath) {
344
+ this.config = config;
345
+ this.db = createDatabase(dbPath ?? ":memory:");
346
+ }
347
+ static create(config, dbPath) {
348
+ return new LinesDB(config, dbPath);
349
+ }
350
+ /**
351
+ * Initialize database by loading all JSONL files
352
+ */
353
+ async initialize() {
354
+ this.tables = await DirectoryScanner.scanDirectory(this.config.dataDir);
355
+ for (const [tableName, tableConfig] of this.tables) try {
356
+ await this.loadTable(tableName, tableConfig);
357
+ } catch (error) {
358
+ console.warn(`Warning: Failed to load table '${tableName}':`, error instanceof Error ? error.message : String(error));
359
+ this.tables.delete(tableName);
360
+ }
361
+ }
362
+ /**
363
+ * Load a single table from JSONL file
364
+ */
365
+ async loadTable(tableName, config) {
366
+ const data = await JsonlReader.read(config.jsonlPath);
367
+ if (data.length === 0) {
368
+ console.warn(`Warning: Table ${tableName} has no data`);
369
+ return;
370
+ }
371
+ let validationSchema = config.validationSchema;
372
+ if (!validationSchema) try {
373
+ validationSchema = await SchemaLoader.loadSchema(config.jsonlPath);
374
+ } catch (error) {
375
+ console.log(`[LinesDB] No validation schema for table '${tableName}':`, error instanceof Error ? error.message : String(error));
376
+ }
377
+ console.log(`[LinesDB] Loaded validation schema for table '${tableName}':`, validationSchema ? "FOUND" : "NOT FOUND");
378
+ if (validationSchema) {
379
+ console.log(`[LinesDB] Schema type:`, typeof validationSchema);
380
+ console.log(`[LinesDB] Schema has '~standard':`, "~standard" in validationSchema);
381
+ }
382
+ this.validationSchemas.set(tableName, validationSchema);
383
+ let schema;
384
+ if (config.schema) schema = config.schema;
385
+ else if (config.autoInferSchema !== false) schema = JsonlReader.inferSchema(tableName, data);
386
+ else throw new Error(`No schema provided for table ${tableName} and autoInferSchema is disabled`);
387
+ if (validationSchema) {
388
+ const biSchema = validationSchema;
389
+ if (biSchema.primaryKey && !schema.columns.some((col) => col.primaryKey)) for (const pkColumn of biSchema.primaryKey) {
390
+ const col = schema.columns.find((c) => c.name === pkColumn);
391
+ if (col) col.primaryKey = true;
392
+ }
393
+ if (biSchema.foreignKeys) schema.foreignKeys = biSchema.foreignKeys;
394
+ if (biSchema.indexes) schema.indexes = biSchema.indexes;
395
+ }
396
+ this.schemas.set(tableName, schema);
397
+ this.createTable(schema);
398
+ const validationErrors = [];
399
+ for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
400
+ const row = data[rowIndex];
401
+ try {
402
+ this.validateData(tableName, row);
403
+ } catch (error) {
404
+ if (error instanceof Error && error.name === "ValidationError") validationErrors.push({
405
+ rowIndex,
406
+ rowData: row,
407
+ error
408
+ });
409
+ else throw error;
410
+ }
411
+ }
412
+ if (validationErrors.length > 0) {
413
+ const enhancedError = /* @__PURE__ */ new Error(`Validation failed for ${validationErrors.length} row(s) in table ${tableName}`);
414
+ enhancedError.name = "ValidationError";
415
+ enhancedError.validationErrors = validationErrors;
416
+ enhancedError.issues = validationErrors[0].error.issues;
417
+ throw enhancedError;
418
+ }
419
+ this.insertData(tableName, schema, data);
420
+ }
421
+ /**
422
+ * Create table in SQLite with constraints and indexes
423
+ */
424
+ createTable(schema) {
425
+ this.db.exec("PRAGMA foreign_keys = ON");
426
+ const quotedTableName = this.quoteTableName(schema.name);
427
+ const columnDefs = schema.columns.map((col) => {
428
+ const sqlType = col.type === "JSON" ? "TEXT" : col.type;
429
+ const parts = [this.quoteIdentifier(col.name), sqlType];
430
+ if (col.primaryKey) parts.push("PRIMARY KEY");
431
+ if (col.notNull) parts.push("NOT NULL");
432
+ if (col.unique) parts.push("UNIQUE");
433
+ return parts.join(" ");
434
+ });
435
+ const foreignKeyDefs = [];
436
+ if (schema.foreignKeys && schema.foreignKeys.length > 0) for (const fk of schema.foreignKeys) {
437
+ const fkParts = [`FOREIGN KEY (${fk.columns.map((col) => this.quoteIdentifier(col)).join(", ")})`, `REFERENCES ${this.quoteTableName(fk.references.table)}(${fk.references.columns.map((col) => this.quoteIdentifier(col)).join(", ")})`];
438
+ if (fk.onDelete) fkParts.push(`ON DELETE ${fk.onDelete}`);
439
+ if (fk.onUpdate) fkParts.push(`ON UPDATE ${fk.onUpdate}`);
440
+ foreignKeyDefs.push(fkParts.join(" "));
441
+ }
442
+ const sql = `CREATE TABLE IF NOT EXISTS ${quotedTableName} (${[...columnDefs, ...foreignKeyDefs].join(", ")})`;
443
+ this.db.exec(sql);
444
+ if (schema.indexes && schema.indexes.length > 0) for (let i = 0; i < schema.indexes.length; i++) {
445
+ const index = schema.indexes[i];
446
+ const safeTableName = schema.name.replace(/[^a-zA-Z0-9]/g, "_");
447
+ const resolvedIndexName = index.name || `idx_${safeTableName}_${index.columns.join("_")}_${i}`;
448
+ const indexSql = `CREATE ${index.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${this.quoteIdentifier(resolvedIndexName)} ON ${quotedTableName} (${index.columns.map((col) => this.quoteIdentifier(col)).join(", ")})`;
449
+ this.db.exec(indexSql);
450
+ }
451
+ }
452
+ /**
453
+ * Quote table name to handle special characters in SQL
454
+ */
455
+ quoteTableName(tableName) {
456
+ return this.quoteIdentifier(tableName);
457
+ }
458
+ /**
459
+ * Quote identifier for SQL statements, escaping embedded quotes
460
+ */
461
+ quoteIdentifier(identifier) {
462
+ return `"${identifier.replace(/"/g, "\"\"")}"`;
463
+ }
464
+ /**
465
+ * Insert data into table
466
+ */
467
+ insertData(tableName, schema, data) {
468
+ const columnNames = schema.columns.map((col) => col.name);
469
+ const quotedColumns = columnNames.map((name) => this.quoteIdentifier(name));
470
+ const placeholders = columnNames.map(() => "?").join(", ");
471
+ const sql = `INSERT INTO ${this.quoteTableName(tableName)} (${quotedColumns.join(", ")}) VALUES (${placeholders})`;
472
+ const stmt = this.db.prepare(sql);
473
+ for (const row of data) {
474
+ const values = columnNames.map((col) => this.normalizeValue(row[col]));
475
+ stmt.run(...values);
476
+ }
477
+ }
478
+ /**
479
+ * Execute a raw SQL query
480
+ */
481
+ query(sql, params = []) {
482
+ return this.db.prepare(sql).all(...params);
483
+ }
484
+ /**
485
+ * Execute a SQL query that returns a single row
486
+ */
487
+ queryOne(sql, params = []) {
488
+ const result = this.db.prepare(sql).get(...params);
489
+ return result === void 0 ? null : result;
490
+ }
491
+ /**
492
+ * Execute a SQL statement (INSERT, UPDATE, DELETE)
493
+ */
494
+ execute(sql, params = []) {
495
+ return this.db.prepare(sql).run(...params);
496
+ }
497
+ /**
498
+ * Find rows by condition (supports OR/AND with arrays and function filters)
499
+ * If where is not provided, returns all rows
500
+ */
501
+ find(tableName, where) {
502
+ if (where === void 0) return this.query(`SELECT * FROM ${this.quoteTableName(tableName)}`).map((row) => this.deserializeRow(tableName, row));
503
+ if (Array.isArray(where) && where.length === 0) return [];
504
+ const { sql, values, functionFilters, hasOrWithFunctionFilters } = this.buildWhereClause(where);
505
+ let rows;
506
+ if (hasOrWithFunctionFilters) {
507
+ rows = this.query(`SELECT * FROM ${this.quoteTableName(tableName)}`).map((row) => this.deserializeRow(tableName, row));
508
+ return this.applyOrConditionWithFilters(rows, where);
509
+ }
510
+ if (sql) rows = this.query(`SELECT * FROM ${this.quoteTableName(tableName)} WHERE ${sql}`, values).map((row) => this.deserializeRow(tableName, row));
511
+ else rows = this.query(`SELECT * FROM ${this.quoteTableName(tableName)}`).map((row) => this.deserializeRow(tableName, row));
512
+ return this.applyFunctionFilters(rows, functionFilters);
513
+ }
514
+ /**
515
+ * Find a single row by condition (supports OR/AND with arrays and function filters)
516
+ */
517
+ findOne(tableName, where) {
518
+ const { sql, values, functionFilters } = this.buildWhereClause(where);
519
+ let rows;
520
+ if (sql) rows = this.query(`SELECT * FROM ${this.quoteTableName(tableName)} WHERE ${sql}`, values).map((row) => this.deserializeRow(tableName, row));
521
+ else rows = this.query(`SELECT * FROM ${this.quoteTableName(tableName)}`).map((row) => this.deserializeRow(tableName, row));
522
+ const filtered = this.applyFunctionFilters(rows, functionFilters);
523
+ return filtered.length > 0 ? filtered[0] : null;
524
+ }
525
+ /**
526
+ * Deserialize JSON columns in a row
527
+ */
528
+ deserializeRow(tableName, row) {
529
+ const schema = this.schemas.get(tableName);
530
+ if (!schema) return row;
531
+ const deserializedRow = { ...row };
532
+ for (const column of schema.columns) {
533
+ const colName = column.name;
534
+ if (!(colName in deserializedRow)) continue;
535
+ const value = deserializedRow[colName];
536
+ if (column.type === "JSON" && typeof value === "string") {
537
+ try {
538
+ deserializedRow[colName] = JSON.parse(value);
539
+ } catch (error) {
540
+ console.warn(`Failed to parse JSON column ${colName}:`, error);
541
+ }
542
+ continue;
543
+ }
544
+ if (column.valueType === "boolean") {
545
+ if (typeof value === "number") deserializedRow[colName] = value === 0 ? false : true;
546
+ else if (typeof value === "bigint") deserializedRow[colName] = value === 0n ? false : true;
547
+ }
548
+ }
549
+ return deserializedRow;
550
+ }
551
+ /**
552
+ * Validate data using StandardSchema
553
+ * Note: Only synchronous validation is supported
554
+ */
555
+ validateData(tableName, data) {
556
+ const schema = this.validationSchemas.get(tableName);
557
+ console.log(`[LinesDB] validateData called for table '${tableName}', schema exists:`, !!schema);
558
+ if (!schema) {
559
+ console.log(`[LinesDB] No validation schema found for table '${tableName}', skipping validation`);
560
+ return;
561
+ }
562
+ console.log(`[LinesDB] Validating data:`, JSON.stringify(data));
563
+ const result = schema["~standard"].validate(data);
564
+ if (result instanceof Promise) throw new Error("Asynchronous validation is not supported. Please use synchronous validation schemas.");
565
+ if (result.issues && result.issues.length > 0) {
566
+ const errorMessage = `Validation failed for table '${tableName}':\n${result.issues.map((issue) => {
567
+ let pathStr = "root";
568
+ if (issue.path && issue.path.length > 0) pathStr = issue.path.map((segment) => {
569
+ if (typeof segment === "object" && segment !== null && "key" in segment) return String(segment.key);
570
+ return String(segment);
571
+ }).join(".");
572
+ return ` - ${pathStr}: ${issue.message}`;
573
+ }).join("\n")}`;
574
+ const error = new Error(errorMessage);
575
+ error.name = "ValidationError";
576
+ error.issues = result.issues;
577
+ throw error;
578
+ }
579
+ }
580
+ /**
581
+ * Insert a row into a table with validation
582
+ */
583
+ insert(tableName, data) {
584
+ this.validateData(tableName, data);
585
+ if (!this.schemas.get(tableName)) throw new Error(`Table ${tableName} does not exist`);
586
+ const columnNames = Object.keys(data);
587
+ const quotedColumns = columnNames.map((col) => this.quoteIdentifier(col));
588
+ const placeholders = columnNames.map(() => "?").join(", ");
589
+ const sql = `INSERT INTO ${this.quoteTableName(tableName)} (${quotedColumns.join(", ")}) VALUES (${placeholders})`;
590
+ const values = Object.values(data).map((v) => this.normalizeValue(v));
591
+ const result = this.execute(sql, values);
592
+ if (!this.inTransaction) this.syncTable(tableName).catch((err) => {
593
+ console.error(`Failed to sync table ${tableName}:`, err);
594
+ });
595
+ return result;
596
+ }
597
+ /**
598
+ * Batch insert rows with validation per record.
599
+ */
600
+ batchInsert(tableName, records) {
601
+ if (!this.schemas.get(tableName)) throw new Error(`Table ${tableName} does not exist`);
602
+ if (records.length === 0) return {
603
+ changes: 0,
604
+ lastInsertRowid: 0
605
+ };
606
+ let totalChanges = 0n;
607
+ let lastRowid = 0n;
608
+ for (const record of records) {
609
+ this.validateData(tableName, record);
610
+ const columnNames = Object.keys(record);
611
+ const quotedColumns = columnNames.map((col) => this.quoteIdentifier(col));
612
+ const placeholders = columnNames.map(() => "?").join(", ");
613
+ const sql = `INSERT INTO ${this.quoteTableName(tableName)} (${quotedColumns.join(", ")}) VALUES (${placeholders})`;
614
+ const values = columnNames.map((col) => this.normalizeValue(record[col]));
615
+ const result = this.execute(sql, values);
616
+ totalChanges += BigInt(result.changes);
617
+ lastRowid = BigInt(result.lastInsertRowid);
618
+ }
619
+ if (!this.inTransaction) this.syncTable(tableName).catch((err) => {
620
+ console.error(`Failed to sync table ${tableName}:`, err);
621
+ });
622
+ return {
623
+ changes: totalChanges,
624
+ lastInsertRowid: lastRowid
625
+ };
626
+ }
627
+ /**
628
+ * Update rows in a table with validation (supports OR/AND with arrays)
629
+ * Note: Function filters are not supported for update operations
630
+ * Note: By default, validation is enabled. For partial updates, existing data is fetched
631
+ * and merged before validation. Set options.validate = false to disable validation.
632
+ */
633
+ update(tableName, data, where, options) {
634
+ if (!this.schemas.get(tableName)) throw new Error(`Table ${tableName} does not exist`);
635
+ const shouldValidate = options?.validate !== false;
636
+ const hasValidationSchema = this.validationSchemas.has(tableName);
637
+ if (shouldValidate && hasValidationSchema) {
638
+ const existingRows = this.find(tableName, where);
639
+ for (const existingRow of existingRows) {
640
+ const mergedData = {
641
+ ...existingRow,
642
+ ...data
643
+ };
644
+ this.validateData(tableName, mergedData);
645
+ }
646
+ }
647
+ const { sql: whereSql, values: whereValues, functionFilters } = this.buildWhereClause(where);
648
+ if (functionFilters.length > 0) throw new Error("Function filters are not supported in update operations");
649
+ const setClauses = Object.keys(data).map((key) => `${this.quoteIdentifier(key)} = ?`).join(", ");
650
+ const sql = `UPDATE ${this.quoteTableName(tableName)} SET ${setClauses} WHERE ${whereSql}`;
651
+ const values = [...Object.values(data).map((v) => this.normalizeValue(v)), ...whereValues];
652
+ const result = this.execute(sql, values);
653
+ if (!this.inTransaction) this.syncTable(tableName).catch((err) => {
654
+ console.error(`Failed to sync table ${tableName}:`, err);
655
+ });
656
+ return result;
657
+ }
658
+ /**
659
+ * Batch update rows with record-specific values and validation.
660
+ * Each record must include the primary key to identify the target row.
661
+ * Validation runs once per merged record unless explicitly disabled.
662
+ */
663
+ batchUpdate(tableName, records, options) {
664
+ const schema = this.schemas.get(tableName);
665
+ if (!schema) throw new Error(`Table ${tableName} does not exist`);
666
+ if (records.length === 0) return {
667
+ changes: 0,
668
+ lastInsertRowid: 0
669
+ };
670
+ const pkColumn = schema.columns.find((col) => col.primaryKey);
671
+ if (!pkColumn) throw new Error(`Table ${tableName} does not have a primary key`);
672
+ const pkName = pkColumn.name;
673
+ const pkValues = [];
674
+ for (const record of records) {
675
+ const pkValue = record[pkName];
676
+ if (pkValue === void 0) throw new Error(`Record is missing primary key '${String(pkName)}': ${JSON.stringify(record)}`);
677
+ pkValues.push(pkValue);
678
+ }
679
+ const shouldValidate = options?.validate !== false;
680
+ const hasValidationSchema = this.validationSchemas.has(tableName);
681
+ if (shouldValidate && hasValidationSchema) {
682
+ const orCondition = pkValues.map((pkValue) => ({ [pkName]: pkValue }));
683
+ const existingRows = this.find(tableName, orCondition);
684
+ const existingRowsMap = /* @__PURE__ */ new Map();
685
+ for (const row of existingRows) {
686
+ const pkValue = row[pkName];
687
+ existingRowsMap.set(pkValue, row);
688
+ }
689
+ const validationErrors = [];
690
+ for (let i = 0; i < records.length; i++) {
691
+ const record = records[i];
692
+ const pkValue = record[pkName];
693
+ const existingRow = existingRowsMap.get(pkValue);
694
+ if (!existingRow) throw new Error(`No existing row found with ${String(pkName)}=${JSON.stringify(pkValue)}`);
695
+ const mergedData = {
696
+ ...existingRow,
697
+ ...record
698
+ };
699
+ try {
700
+ this.validateData(tableName, mergedData);
701
+ } catch (error) {
702
+ if (error instanceof Error && error.name === "ValidationError") validationErrors.push({
703
+ rowIndex: i,
704
+ rowData: mergedData,
705
+ pkValue,
706
+ error
707
+ });
708
+ else throw error;
709
+ }
710
+ }
711
+ if (validationErrors.length > 0) {
712
+ const enhancedError = /* @__PURE__ */ new Error(`Validation failed for ${validationErrors.length} row(s)`);
713
+ enhancedError.name = "ValidationError";
714
+ enhancedError.validationErrors = validationErrors;
715
+ enhancedError.issues = validationErrors[0].error.issues;
716
+ throw enhancedError;
717
+ }
718
+ }
719
+ let totalChanges = 0n;
720
+ let lastRowid = 0n;
721
+ for (const record of records) {
722
+ const pkValue = record[pkName];
723
+ const where = { [pkName]: pkValue };
724
+ const result = this.update(tableName, record, where, { validate: false });
725
+ totalChanges += BigInt(result.changes);
726
+ lastRowid = BigInt(result.lastInsertRowid);
727
+ }
728
+ return {
729
+ changes: totalChanges,
730
+ lastInsertRowid: lastRowid
731
+ };
732
+ }
733
+ /**
734
+ * Delete rows from a table (supports OR/AND with arrays)
735
+ * Note: Function filters are not supported for delete operations
736
+ */
737
+ delete(tableName, where) {
738
+ if (!this.schemas.get(tableName)) throw new Error(`Table ${tableName} does not exist`);
739
+ const { sql: whereSql, values, functionFilters } = this.buildWhereClause(where);
740
+ if (functionFilters.length > 0) throw new Error("Function filters are not supported in delete operations");
741
+ const sql = `DELETE FROM ${this.quoteTableName(tableName)} WHERE ${whereSql}`;
742
+ const result = this.execute(sql, values);
743
+ if (!this.inTransaction) this.syncTable(tableName).catch((err) => {
744
+ console.error(`Failed to sync table ${tableName}:`, err);
745
+ });
746
+ return result;
747
+ }
748
+ /**
749
+ * Batch delete rows by primary key.
750
+ */
751
+ batchDelete(tableName, records) {
752
+ const schema = this.schemas.get(tableName);
753
+ if (!schema) throw new Error(`Table ${tableName} does not exist`);
754
+ if (records.length === 0) return {
755
+ changes: 0,
756
+ lastInsertRowid: 0
757
+ };
758
+ const pkColumn = schema.columns.find((col) => col.primaryKey);
759
+ if (!pkColumn) throw new Error(`Table ${tableName} does not have a primary key`);
760
+ const pkName = pkColumn.name;
761
+ const pkValues = records.map((record, index) => {
762
+ const pkValue = record[pkName];
763
+ if (pkValue === void 0) throw new Error(`Record at index ${index} is missing primary key '${String(pkName)}'`);
764
+ return pkValue;
765
+ });
766
+ const placeholders = pkValues.map(() => "?").join(", ");
767
+ const sql = `DELETE FROM ${this.quoteTableName(tableName)} WHERE ${this.quoteIdentifier(pkName)} IN (${placeholders})`;
768
+ const values = pkValues.map((value) => this.normalizeValue(value));
769
+ const result = this.execute(sql, values);
770
+ if (!this.inTransaction) this.syncTable(tableName).catch((err) => {
771
+ console.error(`Failed to sync table ${tableName}:`, err);
772
+ });
773
+ return {
774
+ changes: BigInt(result.changes),
775
+ lastInsertRowid: BigInt(result.lastInsertRowid)
776
+ };
777
+ }
778
+ /**
779
+ * Normalize value for SQLite
780
+ */
781
+ normalizeValue(value) {
782
+ if (value === null || value === void 0) return null;
783
+ if (typeof value === "boolean") return value ? 1 : 0;
784
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") return value;
785
+ if (value instanceof Uint8Array) return value;
786
+ return JSON.stringify(value);
787
+ }
788
+ /**
789
+ * Build WHERE clause from condition (supports OR/AND with arrays and functions)
790
+ */
791
+ buildWhereClause(condition) {
792
+ const values = [];
793
+ const functionFilters = [];
794
+ let hasOrWithFunctionFilters = false;
795
+ const buildCondition = (cond, isInOr = false) => {
796
+ if (Array.isArray(cond)) return cond.map((item) => {
797
+ const clause = Array.isArray(item) ? buildCondition(item, true) : buildCondition(item, true);
798
+ return clause ? `(${clause})` : "";
799
+ }).filter((clause) => clause !== "").join(" OR ");
800
+ const conditions = [];
801
+ let hasFunctionFilter = false;
802
+ for (const [key, value] of Object.entries(cond)) if (typeof value === "function") {
803
+ functionFilters.push({
804
+ key,
805
+ fn: value
806
+ });
807
+ hasFunctionFilter = true;
808
+ } else {
809
+ conditions.push(`${this.quoteIdentifier(key)} = ?`);
810
+ values.push(this.normalizeValue(value));
811
+ }
812
+ if (isInOr && hasFunctionFilter) hasOrWithFunctionFilters = true;
813
+ return conditions.join(" AND ");
814
+ };
815
+ return {
816
+ sql: buildCondition(condition),
817
+ values,
818
+ functionFilters,
819
+ hasOrWithFunctionFilters
820
+ };
821
+ }
822
+ /**
823
+ * Apply OR condition with function filters by evaluating each row against the condition
824
+ */
825
+ applyOrConditionWithFilters(rows, condition) {
826
+ return rows.filter((row) => this.matchesOrCondition(row, condition));
827
+ }
828
+ /**
829
+ * Check if a row matches an OR/AND condition (recursively)
830
+ */
831
+ matchesOrCondition(row, condition) {
832
+ if (Array.isArray(condition)) return condition.some((item) => this.matchesOrCondition(row, item));
833
+ return Object.entries(condition).every(([key, value]) => {
834
+ const rowValue = row[key];
835
+ if (typeof value === "function") return value(rowValue);
836
+ return rowValue === value;
837
+ });
838
+ }
839
+ /**
840
+ * Apply function filters to rows
841
+ */
842
+ applyFunctionFilters(rows, functionFilters) {
843
+ if (functionFilters.length === 0) return rows;
844
+ return rows.filter((row) => {
845
+ return functionFilters.every(({ key, fn }) => {
846
+ const value = row[key];
847
+ return fn(value);
848
+ });
849
+ });
850
+ }
851
+ /**
852
+ * Get table schema
853
+ */
854
+ getSchema(tableName) {
855
+ return this.schemas.get(tableName);
856
+ }
857
+ /**
858
+ * Get all table names
859
+ */
860
+ getTableNames() {
861
+ return Array.from(this.schemas.keys());
862
+ }
863
+ /**
864
+ * Sync a specific table back to its JSONL file
865
+ * Uses backward transformation when available
866
+ */
867
+ async syncTable(tableName) {
868
+ const tableConfig = this.tables.get(tableName);
869
+ if (!tableConfig) throw new Error(`Table ${tableName} not found`);
870
+ const deserializedRows = this.query(`SELECT * FROM ${this.quoteTableName(tableName)}`).map((row) => this.deserializeRow(tableName, row));
871
+ const validationSchema = this.validationSchemas.get(tableName);
872
+ let finalRows = deserializedRows;
873
+ if (validationSchema && hasBackward(validationSchema)) {
874
+ const biSchema = validationSchema;
875
+ finalRows = deserializedRows.map((row) => biSchema.backward(row));
876
+ }
877
+ await JsonlWriter.write(tableConfig.jsonlPath, finalRows);
878
+ }
879
+ /**
880
+ * Sync database changes back to JSONL files
881
+ * Uses backward transformation when available
882
+ */
883
+ async sync() {
884
+ for (const [tableName] of this.tables) await this.syncTable(tableName);
885
+ }
886
+ /**
887
+ * Execute a function within a transaction
888
+ * Automatically commits on success or rolls back on error
889
+ */
890
+ async transaction(fn) {
891
+ if (this.inTransaction) throw new Error("Nested transactions are not supported");
892
+ this.db.exec("BEGIN TRANSACTION");
893
+ this.inTransaction = true;
894
+ try {
895
+ const result = await fn(this);
896
+ this.db.exec("COMMIT");
897
+ this.inTransaction = false;
898
+ await this.sync();
899
+ return result;
900
+ } catch (error) {
901
+ this.db.exec("ROLLBACK");
902
+ this.inTransaction = false;
903
+ throw error;
904
+ }
905
+ }
906
+ /**
907
+ * Close the database connection
908
+ */
909
+ async close() {
910
+ try {
911
+ this.db.close();
912
+ } catch (_error) {}
913
+ }
914
+ /**
915
+ * Get the underlying SQLite database instance
916
+ */
917
+ getDb() {
918
+ return this.db;
919
+ }
920
+ };
921
+
922
+ //#endregion
923
+ //#region src/type-generator.ts
924
+ var TypeGenerator = class {
925
+ dataDir;
926
+ projectRoot;
927
+ outputFile;
928
+ dataDirPath;
929
+ constructor(options) {
930
+ const envProjectRoot = process.env.LINES_DB_TEST_PROJECT_ROOT;
931
+ this.projectRoot = envProjectRoot !== void 0 ? envProjectRoot : options.projectRoot || process.cwd();
932
+ this.dataDir = options.dataDir;
933
+ this.dataDirPath = (0, node_path.isAbsolute)(this.dataDir) ? this.dataDir : (0, node_path.join)(this.projectRoot, this.dataDir);
934
+ this.outputFile = (0, node_path.join)(this.dataDirPath, "db.ts");
935
+ }
936
+ /**
937
+ * Generate types file from JSONL files and their optional schema files.
938
+ */
939
+ async generate() {
940
+ const tables = await this.findTables();
941
+ if (tables.length === 0) throw new Error(`No JSONL files found in ${this.dataDirPath}. Place one or more *.jsonl files in the directory.`);
942
+ const content = this.generateTypeDeclarations(tables);
943
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(this.outputFile), { recursive: true });
944
+ await (0, node_fs_promises.writeFile)(this.outputFile, content, "utf-8");
945
+ console.log(`Generated types at ${this.outputFile}`);
946
+ return this.outputFile;
947
+ }
948
+ /**
949
+ * Find all *.jsonl files and check if they have corresponding *.schema.ts files
950
+ */
951
+ async findTables() {
952
+ try {
953
+ const entries = await (0, node_fs_promises.readdir)(this.dataDirPath, { withFileTypes: true });
954
+ const tables = [];
955
+ for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".jsonl")) {
956
+ const tableName = (0, node_path.basename)(entry.name, ".jsonl");
957
+ const schemaFileName = `${tableName}.schema.ts`;
958
+ const schemaFilePath = (0, node_path.join)(this.dataDirPath, schemaFileName);
959
+ const hasSchema = entries.some((e) => e.isFile() && e.name === schemaFileName);
960
+ tables.push({
961
+ tableName,
962
+ schemaFile: hasSchema ? schemaFilePath : void 0
963
+ });
964
+ }
965
+ return tables;
966
+ } catch (error) {
967
+ if (error.code === "ENOENT") throw new Error(`Data directory not found: ${this.dataDirPath}. Set lines-db.dataDir to the correct location.`);
968
+ throw error;
969
+ }
970
+ }
971
+ /**
972
+ * Generate type declaration content
973
+ */
974
+ generateTypeDeclarations(tables) {
975
+ const imports = [];
976
+ const tableEntries = [];
977
+ const usedAliases = /* @__PURE__ */ new Set();
978
+ for (const table of tables) {
979
+ const tableKey = this.formatTableKey(table.tableName);
980
+ if (table.schemaFile) {
981
+ const schemaIdentifier = this.createSchemaIdentifier(table.tableName, usedAliases);
982
+ usedAliases.add(schemaIdentifier);
983
+ let relativePath = (0, node_path.relative)((0, node_path.join)(this.outputFile, ".."), table.schemaFile).replace(/\\/g, "/").replace(".ts", ".js");
984
+ if (!relativePath.startsWith(".")) relativePath = "./" + relativePath;
985
+ imports.push(`import { schema as ${schemaIdentifier} } from '${relativePath}';`);
986
+ tableEntries.push(` ${tableKey}: InferOutput<typeof ${schemaIdentifier}>;`);
987
+ } else tableEntries.push(` ${tableKey}: Record<string, unknown>;`);
988
+ }
989
+ return `// Auto-generated by lines-db
990
+ // Do not edit this file manually
991
+
992
+ ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}import type { DatabaseConfig${imports.length > 0 ? ", InferOutput" : ""} } from 'lines-db';
993
+ import { fileURLToPath } from 'node:url';
994
+ import { dirname } from 'node:path';
995
+
996
+ const __filename = fileURLToPath(import.meta.url);
997
+ const __dirname = dirname(__filename);
998
+
999
+ export type Tables = {
1000
+ ${tableEntries.join("\n")}
1001
+ };
1002
+
1003
+ export const config: DatabaseConfig<Tables> = {
1004
+ dataDir: __dirname,
1005
+ };
1006
+ `;
1007
+ }
1008
+ createSchemaIdentifier(tableName, usedAliases) {
1009
+ let base = sanitizeIdentifier(toCamelCase(tableName)) || "table";
1010
+ if (!/^[A-Za-z_$]/.test(base)) base = `_${base}`;
1011
+ let candidate = `${base}Schema`;
1012
+ let suffix = 1;
1013
+ while (usedAliases.has(candidate)) candidate = `${base}${++suffix}Schema`;
1014
+ return candidate;
1015
+ }
1016
+ formatTableKey(tableName) {
1017
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(tableName)) return tableName;
1018
+ return `'${tableName.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1019
+ }
1020
+ };
1021
+ function toCamelCase(value) {
1022
+ const parts = value.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.toLowerCase());
1023
+ if (parts.length === 0) return value;
1024
+ const [first, ...rest] = parts;
1025
+ return first + rest.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1026
+ }
1027
+ function sanitizeIdentifier(value) {
1028
+ return value.replace(/[^A-Za-z0-9_$]/g, "");
1029
+ }
1030
+
1031
+ //#endregion
1032
+ //#region src/validator.ts
1033
+ var Validator = class {
1034
+ path;
1035
+ projectRoot;
1036
+ constructor(options) {
1037
+ this.path = options.path;
1038
+ this.projectRoot = options.projectRoot || process.cwd();
1039
+ }
1040
+ /**
1041
+ * Validate JSONL file(s)
1042
+ */
1043
+ async validate() {
1044
+ const fullPath = this.path.startsWith("/") ? this.path : (0, node_path.join)(this.projectRoot, this.path);
1045
+ const stats = await (0, node_fs_promises.stat)(fullPath);
1046
+ if (stats.isDirectory()) return this.validateDirectory(fullPath);
1047
+ else if (stats.isFile() && fullPath.endsWith(".jsonl")) return this.validateFile(fullPath);
1048
+ else throw new Error(`Invalid path: ${this.path}. Must be a directory or .jsonl file.`);
1049
+ }
1050
+ /**
1051
+ * Validate all JSONL files in a directory
1052
+ */
1053
+ async validateDirectory(dirPath) {
1054
+ const jsonlFiles = (await (0, node_fs_promises.readdir)(dirPath, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => (0, node_path.join)(dirPath, entry.name));
1055
+ if (jsonlFiles.length === 0) throw new Error(`No JSONL files found in directory: ${dirPath}`);
1056
+ const allErrors = [];
1057
+ for (const file of jsonlFiles) {
1058
+ const result = await this.validateFile(file);
1059
+ allErrors.push(...result.errors);
1060
+ }
1061
+ const fkErrors = await this.validateForeignKeys(dirPath, jsonlFiles);
1062
+ allErrors.push(...fkErrors);
1063
+ return {
1064
+ valid: allErrors.length === 0,
1065
+ errors: allErrors
1066
+ };
1067
+ }
1068
+ /**
1069
+ * Validate foreign key constraints across all tables
1070
+ */
1071
+ async validateForeignKeys(dirPath, jsonlFiles) {
1072
+ const errors = [];
1073
+ const tableData = /* @__PURE__ */ new Map();
1074
+ const tableSchemas = /* @__PURE__ */ new Map();
1075
+ for (const file of jsonlFiles) {
1076
+ const tableName = (0, node_path.basename)(file, ".jsonl");
1077
+ const data = await JsonlReader.read(file);
1078
+ const schema = await SchemaLoader.loadSchema(file);
1079
+ tableData.set(tableName, data);
1080
+ tableSchemas.set(tableName, schema);
1081
+ }
1082
+ for (const file of jsonlFiles) {
1083
+ const tableName = (0, node_path.basename)(file, ".jsonl");
1084
+ const schema = tableSchemas.get(tableName);
1085
+ const data = tableData.get(tableName);
1086
+ if (!schema || !data || !schema.foreignKeys) continue;
1087
+ for (const fk of schema.foreignKeys) {
1088
+ const referencedTable = fk.references.table;
1089
+ const referencedData = tableData.get(referencedTable);
1090
+ if (!referencedData) continue;
1091
+ const referencedValues = /* @__PURE__ */ new Set();
1092
+ for (const refRow of referencedData) {
1093
+ const keyValues = fk.references.columns.map((col) => refRow[col]);
1094
+ const compositeKey = JSON.stringify(keyValues);
1095
+ referencedValues.add(compositeKey);
1096
+ }
1097
+ for (let i = 0; i < data.length; i++) {
1098
+ const row = data[i];
1099
+ const foreignKeyValues = fk.columns.map((col) => row[col]);
1100
+ const compositeKey = JSON.stringify(foreignKeyValues);
1101
+ if (!referencedValues.has(compositeKey)) errors.push({
1102
+ file,
1103
+ tableName,
1104
+ rowIndex: i,
1105
+ issues: [],
1106
+ type: "foreignKey",
1107
+ foreignKeyError: {
1108
+ column: fk.columns.join(", "),
1109
+ value: foreignKeyValues.length === 1 ? foreignKeyValues[0] : foreignKeyValues,
1110
+ referencedTable,
1111
+ referencedColumn: fk.references.columns.join(", ")
1112
+ }
1113
+ });
1114
+ }
1115
+ }
1116
+ }
1117
+ return errors;
1118
+ }
1119
+ /**
1120
+ * Validate a single JSONL file
1121
+ */
1122
+ async validateFile(filePath) {
1123
+ const tableName = (0, node_path.basename)(filePath, ".jsonl");
1124
+ const data = await JsonlReader.read(filePath);
1125
+ const schema = await SchemaLoader.loadSchema(filePath);
1126
+ const errors = [];
1127
+ for (let i = 0; i < data.length; i++) {
1128
+ const row = data[i];
1129
+ const result = schema["~standard"].validate(row);
1130
+ if (result instanceof Promise) throw new Error("Asynchronous validation is not supported.");
1131
+ if (result.issues && result.issues.length > 0) errors.push({
1132
+ file: filePath,
1133
+ tableName,
1134
+ rowIndex: i,
1135
+ issues: result.issues,
1136
+ type: "schema"
1137
+ });
1138
+ }
1139
+ return {
1140
+ valid: errors.length === 0,
1141
+ errors
1142
+ };
1143
+ }
1144
+ };
1145
+
1146
+ //#endregion
1147
+ //#region src/jsonl-migration.ts
1148
+ /**
1149
+ * Validate a table by temporarily supplying in-memory rows while reusing the existing LinesDB validation pipeline.
1150
+ * If validation fails, the underlying LinesDB error is rethrown so callers can inspect validation details.
1151
+ */
1152
+ async function ensureTableRowsValid(options) {
1153
+ console.log("[ensureTableRowsValid] START");
1154
+ console.log("[ensureTableRowsValid] dataDir:", options.dataDir);
1155
+ console.log("[ensureTableRowsValid] tableName:", options.tableName);
1156
+ console.log("[ensureTableRowsValid] rows count:", options.rows.length);
1157
+ const tablePath = (0, node_path.join)(options.dataDir, `${options.tableName}.jsonl`);
1158
+ const overrides = new Map([[tablePath, options.rows]]);
1159
+ console.log("[ensureTableRowsValid] tablePath:", tablePath);
1160
+ let capturedError = null;
1161
+ const originalWarn = console.warn;
1162
+ const warnMessages = [];
1163
+ console.warn = (...args) => {
1164
+ const message = args.join(" ");
1165
+ console.log("[ensureTableRowsValid] Captured warn:", message);
1166
+ warnMessages.push(message);
1167
+ if (message.includes(`Failed to load table '${options.tableName}'`) && message.includes("Validation failed")) {
1168
+ capturedError = new Error(message);
1169
+ console.log("[ensureTableRowsValid] Captured validation error!");
1170
+ }
1171
+ };
1172
+ try {
1173
+ console.log("[ensureTableRowsValid] Calling JsonlReader.withOverrides");
1174
+ await JsonlReader.withOverrides(overrides, async () => {
1175
+ console.log("[ensureTableRowsValid] Inside withOverrides callback");
1176
+ const db = LinesDB.create({ dataDir: options.dataDir });
1177
+ console.log("[ensureTableRowsValid] LinesDB created");
1178
+ try {
1179
+ console.log("[ensureTableRowsValid] Calling db.initialize()");
1180
+ await db.initialize();
1181
+ console.log("[ensureTableRowsValid] db.initialize() completed");
1182
+ } finally {
1183
+ console.log("[ensureTableRowsValid] Calling db.close()");
1184
+ await db.close();
1185
+ }
1186
+ });
1187
+ console.log("[ensureTableRowsValid] withOverrides completed");
1188
+ } finally {
1189
+ console.warn = originalWarn;
1190
+ }
1191
+ console.log("[ensureTableRowsValid] Warnings captured:", warnMessages.length);
1192
+ console.log("[ensureTableRowsValid] capturedError:", capturedError ? "YES" : "NO");
1193
+ if (capturedError) {
1194
+ console.log("[ensureTableRowsValid] Throwing captured error");
1195
+ throw capturedError;
1196
+ }
1197
+ console.log("[ensureTableRowsValid] END (success)");
1198
+ }
1199
+
1200
+ //#endregion
1201
+ exports.DirectoryScanner = DirectoryScanner;
1202
+ exports.JsonlReader = JsonlReader;
1203
+ exports.JsonlWriter = JsonlWriter;
1204
+ exports.LinesDB = LinesDB;
1205
+ exports.RUNTIME = RUNTIME;
1206
+ exports.SchemaLoader = SchemaLoader;
1207
+ exports.TypeGenerator = TypeGenerator;
1208
+ exports.Validator = Validator;
1209
+ exports.defineSchema = defineSchema;
1210
+ exports.detectRuntime = detectRuntime;
1211
+ exports.ensureTableRowsValid = ensureTableRowsValid;
1212
+ exports.hasBackward = hasBackward;