@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/LICENSE +21 -0
- package/bin/cli.js +1373 -0
- package/dist/index.cjs +1212 -0
- package/dist/index.d.cts +486 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +486 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1181 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/src/cli.ts +333 -0
- package/src/database.test.ts +493 -0
- package/src/database.ts +1025 -0
- package/src/directory-scanner.test.ts +91 -0
- package/src/directory-scanner.ts +38 -0
- package/src/error-formatter.ts +166 -0
- package/src/index.ts +35 -0
- package/src/jsonl-migration.ts +76 -0
- package/src/jsonl-reader.test.ts +168 -0
- package/src/jsonl-reader.ts +135 -0
- package/src/jsonl-writer.test.ts +101 -0
- package/src/jsonl-writer.ts +33 -0
- package/src/runtime.ts +34 -0
- package/src/schema-loader.test.ts +136 -0
- package/src/schema-loader.ts +64 -0
- package/src/schema.ts +135 -0
- package/src/sqlite-adapter.ts +99 -0
- package/src/type-generator.ts +201 -0
- package/src/types.ts +99 -0
- package/src/validator.test.ts +337 -0
- package/src/validator.ts +207 -0
- package/tsconfig.json +20 -0
- package/tsconfig.test.json +8 -0
- package/tsdown.config.ts +26 -0
- package/vitest.config.ts +9 -0
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;
|