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