@toiroakr/lines-db 0.1.0 → 0.2.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/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # lines-db
2
+
3
+ A data management library that treats JSONL (JSON Lines) files as tables. Perfect for managing application seed data and testing.
4
+
5
+ ## Features
6
+
7
+ - 📝 Load JSONL files as database tables
8
+ - ✅ **CLI tools for validation and data migration**
9
+ - 🔄 Automatic schema inference
10
+ - 📦 **JSON column support** with automatic serialization/deserialization
11
+ - ✅ Built-in validation using StandardSchema (Valibot, Zod, etc.)
12
+ - 🎯 **Automatic type inference from table names**
13
+ - 🔄 **Bidirectional schema transformations**
14
+ - 💾 **Auto-sync to JSONL files**
15
+ - 🛡️ Type-safe with TypeScript
16
+ - Node.js 22.5+ support
17
+
18
+ ## VS Code Extension
19
+
20
+ A VS Code extension is available that provides syntax highlighting and validation for JSONL files with schema support.
21
+
22
+ [![VS Code Marketplace](https://img.shields.io/visual-studio-marketplace/v/toiroakr.lines-db-vscode?label=VS%20Code%20Marketplace&logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=toiroakr.lines-db-vscode)
23
+
24
+ [Install from VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=toiroakr.lines-db-vscode)
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @toiroakr/lines-db
30
+ # or
31
+ pnpm add @toiroakr/lines-db
32
+ ```
33
+
34
+ ## CLI Usage
35
+
36
+ ### Setting Up Schemas
37
+
38
+ Create schema files alongside your JSONL files:
39
+
40
+ **Directory structure:**
41
+
42
+ ```
43
+ data/
44
+ ├── users.jsonl
45
+ ├── users.schema.ts
46
+ ├── products.jsonl
47
+ └── products.schema.ts
48
+ ```
49
+
50
+ **Example schema (users.schema.ts):**
51
+
52
+ ```typescript
53
+ import * as v from 'valibot';
54
+ import { defineSchema } from '@toiroakr/lines-db';
55
+
56
+ export const schema = defineSchema(
57
+ v.object({
58
+ id: v.pipe(v.number(), v.integer(), v.minValue(1)),
59
+ name: v.pipe(v.string(), v.minLength(1)),
60
+ age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
61
+ email: v.pipe(v.string(), v.email()),
62
+ }),
63
+ );
64
+ export default schema;
65
+ ```
66
+
67
+ **Supported validation libraries:**
68
+
69
+ - Any library implementing [StandardSchema](https://standardschema.dev/)
70
+
71
+ ### Validate JSONL Files
72
+
73
+ Validate your JSONL files against their schemas:
74
+
75
+ ```bash
76
+ npx lines-db validate <path>
77
+ ```
78
+
79
+ **Example:**
80
+
81
+ ```bash
82
+ # Validate all JSONL files in ./data directory
83
+ npx lines-db validate ./data
84
+
85
+ # Validate a specific file
86
+ npx lines-db validate ./data/users.jsonl
87
+
88
+ # Verbose output
89
+ npx lines-db validate ./data --verbose
90
+ ```
91
+
92
+ This command will:
93
+
94
+ - For directories: Find all `.jsonl` files in the directory
95
+ - For files: Validate the specified `.jsonl` file
96
+ - Load corresponding `.schema.ts` files
97
+ - Validate each record against the schema
98
+ - Report validation errors with detailed messages
99
+
100
+ ### Migrate Data
101
+
102
+ Transform data in JSONL files with validation:
103
+
104
+ ```bash
105
+ npx lines-db migrate <file> <transform> [options]
106
+ ```
107
+
108
+ **Example:**
109
+
110
+ ```bash
111
+ # Update all ages by adding 1
112
+ npx lines-db migrate ./data/users.jsonl "(row) => ({ ...row, age: row.age + 1 })"
113
+
114
+ # Migrate with filter
115
+ npx lines-db migrate ./data/users.jsonl "(row) => ({ ...row, active: true })" --filter "{ age: (age) => age > 18 }"
116
+
117
+ # Save transformed data on error
118
+ npx lines-db migrate ./data/users.jsonl "(row) => ({ ...row, age: row.age + 1 })" --errorOutput ./migrated.jsonl
119
+ ```
120
+
121
+ **Options:**
122
+
123
+ - `--filter, -f <expr>` - Filter expression to select rows
124
+ - `--errorOutput, -e <path>` - Save transformed data to file if migration fails
125
+ - `--verbose, -v` - Show detailed error messages
126
+
127
+ The migration runs in a transaction and validates all transformed rows before committing.
128
+
129
+ ## TypeScript Usage
130
+
131
+ ### Generate Types
132
+
133
+ Generate TypeScript types from your schemas for type-safe database access:
134
+
135
+ ```bash
136
+ npx lines-db generate <dataDir>
137
+ ```
138
+
139
+ **Example:**
140
+
141
+ ```bash
142
+ # Generate types (creates ./data/db.ts by default)
143
+ npx lines-db generate ./data
144
+ ```
145
+
146
+ **Add to package.json:**
147
+
148
+ ```json
149
+ "scripts": {
150
+ "db:validate": "lines-db validate ./data",
151
+ "db:generate": "lines-db generate ./data"
152
+ }
153
+ ```
154
+
155
+ ### Quick Start
156
+
157
+ **1. Create a JSONL file (./data/users.jsonl):**
158
+
159
+ ```jsonl
160
+ {"id":1,"name":"Alice","age":30,"email":"alice@example.com"}
161
+ {"id":2,"name":"Bob","age":25,"email":"bob@example.com"}
162
+ {"id":3,"name":"Charlie","age":35,"email":"charlie@example.com"}
163
+ ```
164
+
165
+ **2. Use in TypeScript:**
166
+
167
+ ```typescript
168
+ import { LinesDB } from '@toiroakr/lines-db';
169
+
170
+ const db = LinesDB.create({ dataDir: './data' });
171
+ await db.initialize();
172
+
173
+ // Find all users
174
+ const users = db.find('users');
175
+ console.log(users); // [{ id: 1, name: "Alice", ... }, ...]
176
+
177
+ // Find a specific user
178
+ const user = db.findOne('users', { id: 1 });
179
+ console.log(user); // { id: 1, name: "Alice", age: 30, ... }
180
+
181
+ // Find with conditions
182
+ const adults = db.find('users', { age: (age) => age >= 30 });
183
+
184
+ await db.close();
185
+ ```
186
+
187
+ ### Using Generated Types
188
+
189
+ After running `npx lines-db generate ./data`:
190
+
191
+ ```typescript
192
+ import { LinesDB } from '@toiroakr/lines-db';
193
+ import { config } from './data/db.js';
194
+
195
+ const db = LinesDB.create(config);
196
+ await db.initialize();
197
+
198
+ // ✨ Type is automatically inferred!
199
+ const users = db.find('users');
200
+
201
+ // ✨ Type-safe operations
202
+ db.insert('users', {
203
+ id: 10,
204
+ name: 'Alice',
205
+ age: 30,
206
+ email: 'alice@example.com',
207
+ });
208
+
209
+ await db.close();
210
+ ```
211
+
212
+ ### Core API
213
+
214
+ **Query Operations:**
215
+
216
+ - `find(table, where?)` - Find all matching records
217
+ - `findOne(table, where?)` - Find a single record
218
+ - `query(sql, params?)` - Execute raw SQL query
219
+
220
+ **Modify Operations:**
221
+
222
+ - `insert(table, data)` - Insert a single record
223
+ - `update(table, data, where)` - Update matching records
224
+ - `delete(table, where)` - Delete matching records
225
+
226
+ **Batch Operations:**
227
+
228
+ - `batchInsert(table, data[])` - Insert multiple records
229
+ - `batchUpdate(table, updates[])` - Update multiple records
230
+ - `batchDelete(table, where)` - Delete multiple records
231
+
232
+ **Transaction & Schema:**
233
+
234
+ - `transaction(fn)` - Execute operations in a transaction
235
+ - `getSchema(table)` - Get table schema
236
+ - `getTableNames()` - Get all table names
237
+
238
+ **Where Conditions:**
239
+
240
+ ```typescript
241
+ // Simple equality
242
+ db.find('users', { age: 30 });
243
+
244
+ // Multiple conditions (AND)
245
+ db.find('users', { age: 30, name: 'Alice' });
246
+
247
+ // Advanced conditions
248
+ db.find('users', {
249
+ age: (age) => age > 25,
250
+ name: (name) => name.startsWith('A'),
251
+ });
252
+ ```
253
+
254
+ ### JSON Columns
255
+
256
+ Objects and arrays are automatically handled as JSON columns:
257
+
258
+ ```typescript
259
+ db.insert('orders', {
260
+ id: 1,
261
+ items: [{ name: 'Laptop', quantity: 1 }],
262
+ metadata: { source: 'web' },
263
+ });
264
+
265
+ const order = db.findOne('orders', { id: 1 });
266
+ console.log(order.items[0].name); // "Laptop"
267
+ ```
268
+
269
+ ### Schema Transformations
270
+
271
+ When your schema transforms data types (e.g., parsing date strings into Date objects), you need to provide a backward transformation to save data back to JSONL files.
272
+
273
+ **Why?** JSONL files store strings like `"2024-01-01"`, but your app works with `Date` objects. You need to convert both ways.
274
+
275
+ **Example:**
276
+
277
+ ```typescript
278
+ import * as v from 'valibot';
279
+ import { defineSchema } from '@toiroakr/lines-db';
280
+
281
+ const eventSchema = v.pipe(
282
+ v.object({
283
+ id: v.number(),
284
+ // Transform: string → Date (when reading)
285
+ date: v.pipe(
286
+ v.string(),
287
+ v.isoDate(),
288
+ v.transform((str) => new Date(str)),
289
+ ),
290
+ }),
291
+ );
292
+
293
+ // Provide backward transformation: Date → string (when writing)
294
+ export const schema = defineSchema(eventSchema, (output) => ({
295
+ ...output,
296
+ date: output.date.toISOString(), // Convert Date back to string
297
+ }));
298
+ ```
299
+
300
+ **In your JSONL file (events.jsonl):**
301
+
302
+ ```jsonl
303
+ {
304
+ "id": 1,
305
+ "date": "2024-01-01T00:00:00.000Z"
306
+ }
307
+ ```
308
+
309
+ **In your TypeScript code:**
310
+
311
+ ```typescript
312
+ const event = db.findOne('events', { id: 1 });
313
+ console.log(event.date instanceof Date); // true
314
+ console.log(event.date.getFullYear()); // 2024
315
+ ```
316
+
317
+ ### Transactions
318
+
319
+ Operations outside transactions are auto-synced:
320
+
321
+ ```typescript
322
+ db.insert('users', { id: 10, name: 'Alice', age: 30 });
323
+ // ↑ Automatically synced to users.jsonl
324
+ ```
325
+
326
+ Batch operations with transactions:
327
+
328
+ ```typescript
329
+ await db.transaction(async (tx) => {
330
+ tx.insert('users', { id: 10, name: 'Alice', age: 30 });
331
+ tx.update('users', { age: 31 }, { id: 1 });
332
+ // All changes synced atomically on commit
333
+ });
334
+ ```
335
+
336
+ ## Configuration
337
+
338
+ ```typescript
339
+ interface DatabaseConfig {
340
+ dataDir: string; // Directory containing JSONL files
341
+ }
342
+
343
+ const db = LinesDB.create({ dataDir: './data' });
344
+ ```
345
+
346
+ ## Type Mapping
347
+
348
+ | JSON Type | Column Type | SQLite Storage |
349
+ | ---------------- | ----------- | -------------- |
350
+ | number (integer) | INTEGER | INTEGER |
351
+ | number (float) | REAL | REAL |
352
+ | string | TEXT | TEXT |
353
+ | boolean | INTEGER | INTEGER |
354
+ | object | JSON | TEXT |
355
+ | array | JSON | TEXT |
356
+
357
+ ## License
358
+
359
+ MIT
@@ -0,0 +1,57 @@
1
+ <svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Database icon with background centered -->
3
+ <g transform="translate(200, 200) scale(0.24)">
4
+ <g transform="translate(-512, -512)">
5
+ <!-- Database background from original SVG -->
6
+ <path transform="translate(-184.32, -184.32), scale(87.04)" fill="#7ed0ec" d="M9.166.33a2.25 2.25 0 00-2.332 0l-5.25 3.182A2.25 2.25 0 00.5 5.436v5.128a2.25 2.25 0 001.084 1.924l5.25 3.182a2.25 2.25 0 002.332 0l5.25-3.182a2.25 2.25 0 001.084-1.924V5.436a2.25 2.25 0 00-1.084-1.924L9.166.33z" strokewidth="0"/>
7
+
8
+ <!-- Database layers -->
9
+ <path d="M117 608.4v178.5c1.5 93.7 155.7 169.5 395 169.5s393.4-75.8 395-169.5V608.4H117z" fill="#1565c0"/>
10
+ <path d="M907 607.7c0 99.4-154.8 180-395 180s-395-80.6-395-180 154.8-180 395-180 395 80.5 395 180z" fill="#FFFFFF"/>
11
+ <path d="M117 428.4v158.5c1.5 93.7 155.7 179.5 395 179.5s393.4-85.8 395-179.5V428.4H117z" fill="#1565c0"/>
12
+ <path d="M907 427.7c0 99.4-154.8 180-395 180s-395-80.6-395-180 154.8-180 395-180 395 80.5 395 180z" fill="#FFFFFF"/>
13
+ <path d="M117 248.4v158.5c1.5 93.7 155.7 179.5 395 179.5s393.4-85.8 395-179.5V248.4H117z" fill="#1565c0"/>
14
+ <path d="M907 247.7c0 99.4-154.8 180-395 180s-395-80.6-395-180 154.8-180 395-180 395 80.5 395 180z" fill="#1976d2"/>
15
+ </g>
16
+ </g>
17
+
18
+ <!-- LinesDB text overlapping database bottom -->
19
+ <g transform="translate(200, 315)">
20
+ <!-- White outline for text -->
21
+ <text x="-140" y="12" text-anchor="middle"
22
+ font-family="'Fira Code', 'JetBrains Mono', Consolas, monospace"
23
+ font-size="70" font-weight="500"
24
+ fill="none" stroke="#ffffff" stroke-width="5" stroke-linejoin="round">
25
+ {
26
+ </text>
27
+ <text x="0" y="8" text-anchor="middle"
28
+ font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
29
+ font-size="56" font-weight="bold"
30
+ fill="none" stroke="#ffffff" stroke-width="5" stroke-linejoin="round">
31
+ LinesDB
32
+ </text>
33
+ <text x="140" y="12" text-anchor="middle"
34
+ font-family="'Fira Code', 'JetBrains Mono', Consolas, monospace"
35
+ font-size="70" font-weight="500"
36
+ fill="none" stroke="#ffffff" stroke-width="5" stroke-linejoin="round">
37
+ }
38
+ </text>
39
+
40
+ <!-- Actual text -->
41
+ <text x="-140" y="12" text-anchor="middle"
42
+ font-family="'Fira Code', 'JetBrains Mono', Consolas, monospace"
43
+ font-size="70" font-weight="500" fill="#999999">
44
+ {
45
+ </text>
46
+ <text x="0" y="8" text-anchor="middle"
47
+ font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
48
+ font-size="56" font-weight="bold" fill="#444444">
49
+ LinesDB
50
+ </text>
51
+ <text x="140" y="12" text-anchor="middle"
52
+ font-family="'Fira Code', 'JetBrains Mono', Consolas, monospace"
53
+ font-size="70" font-weight="500" fill="#999999">
54
+ }
55
+ </text>
56
+ </g>
57
+ </svg>
package/bin/cli.js CHANGED
@@ -80,7 +80,7 @@ var TypeGenerator = class {
80
80
  return `// Auto-generated by lines-db
81
81
  // Do not edit this file manually
82
82
 
83
- ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}import type { DatabaseConfig${imports.length > 0 ? ", InferOutput" : ""} } from 'lines-db';
83
+ ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}import type { DatabaseConfig${imports.length > 0 ? ", InferOutput" : ""} } from '@toiroakr/lines-db';
84
84
  import { fileURLToPath } from 'node:url';
85
85
  import { dirname } from 'node:path';
86
86
 
@@ -202,6 +202,18 @@ var JsonlReader = class {
202
202
  //#endregion
203
203
  //#region src/schema-loader.ts
204
204
  var SchemaLoader = class {
205
+ /**
206
+ * Check if a schema file exists for a table
207
+ */
208
+ static async hasSchema(jsonlPath) {
209
+ const schemaPath = join(dirname(jsonlPath), `${basename(jsonlPath, ".jsonl")}.schema.ts`);
210
+ try {
211
+ await access(schemaPath);
212
+ return true;
213
+ } catch {
214
+ return false;
215
+ }
216
+ }
205
217
  /**
206
218
  * Load a validation schema file for a table
207
219
  * Requires ${tableName}.schema.ts to exist alongside the JSONL file
@@ -262,15 +274,26 @@ var Validator = class {
262
274
  const jsonlFiles = (await readdir(dirPath, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => join(dirPath, entry.name));
263
275
  if (jsonlFiles.length === 0) throw new Error(`No JSONL files found in directory: ${dirPath}`);
264
276
  const allErrors = [];
265
- for (const file of jsonlFiles) {
277
+ const allWarnings = [];
278
+ const filesWithSchema = [];
279
+ for (const file of jsonlFiles) if (await SchemaLoader.hasSchema(file)) filesWithSchema.push(file);
280
+ else {
281
+ const tableName = basename(file, ".jsonl");
282
+ allWarnings.push(`Skipping validation for '${tableName}': schema file not found`);
283
+ }
284
+ for (const file of filesWithSchema) {
266
285
  const result = await this.validateFile(file);
267
286
  allErrors.push(...result.errors);
287
+ allWarnings.push(...result.warnings);
288
+ }
289
+ if (filesWithSchema.length > 0) {
290
+ const fkErrors = await this.validateForeignKeys(dirPath, filesWithSchema);
291
+ allErrors.push(...fkErrors);
268
292
  }
269
- const fkErrors = await this.validateForeignKeys(dirPath, jsonlFiles);
270
- allErrors.push(...fkErrors);
271
293
  return {
272
294
  valid: allErrors.length === 0,
273
- errors: allErrors
295
+ errors: allErrors,
296
+ warnings: allWarnings
274
297
  };
275
298
  }
276
299
  /**
@@ -346,7 +369,8 @@ var Validator = class {
346
369
  }
347
370
  return {
348
371
  valid: errors.length === 0,
349
- errors
372
+ errors,
373
+ warnings: []
350
374
  };
351
375
  }
352
376
  };
@@ -354,8 +378,6 @@ var Validator = class {
354
378
  //#endregion
355
379
  //#region src/runtime.ts
356
380
  function detectRuntime() {
357
- if (typeof globalThis !== "undefined" && "Bun" in globalThis && typeof globalThis.Bun !== "undefined") return "bun";
358
- if (typeof globalThis !== "undefined" && "Deno" in globalThis && typeof globalThis.Deno !== "undefined") return "deno";
359
381
  if (typeof process !== "undefined" && process.versions && process.versions.node) return "node";
360
382
  return "unknown";
361
383
  }
@@ -364,43 +386,13 @@ const RUNTIME = detectRuntime();
364
386
  //#endregion
365
387
  //#region src/sqlite-adapter.ts
366
388
  /**
367
- * Create a SQLite database instance based on the runtime environment
389
+ * Create a SQLite database instance for Node.js
368
390
  */
369
391
  function createDatabase(path = ":memory:") {
370
- if (RUNTIME === "bun") return createBunDatabase(path);
371
- else if (RUNTIME === "node" || RUNTIME === "deno") return createNodeDatabase(path);
392
+ if (RUNTIME === "node") return createNodeDatabase(path);
372
393
  else throw new Error(`Unsupported runtime: ${RUNTIME}`);
373
394
  }
374
395
  /**
375
- * Create a Bun SQLite database
376
- */
377
- function createBunDatabase(path) {
378
- const { Database } = __require("bun:sqlite");
379
- const db = new Database(path);
380
- return {
381
- prepare(sql) {
382
- const stmt = db.prepare(sql);
383
- return {
384
- run(...params) {
385
- return stmt.run(...params);
386
- },
387
- get(...params) {
388
- return stmt.get(...params);
389
- },
390
- all(...params) {
391
- return stmt.all(...params);
392
- }
393
- };
394
- },
395
- exec(sql) {
396
- db.exec(sql);
397
- },
398
- close() {
399
- db.close();
400
- }
401
- };
402
- }
403
- /**
404
396
  * Create a Node.js SQLite database
405
397
  */
406
398
  function createNodeDatabase(path) {
@@ -1212,7 +1204,7 @@ function runInSandbox(expression, context = {}) {
1212
1204
  return runInNewContext(expression, sandbox, { timeout: 1e3 });
1213
1205
  }
1214
1206
  const program = new Command();
1215
- program.name("lines-db").description("Database utilities for JSONL files").version("1.0.0");
1207
+ program.name("@toiroakr/lines-db").description("Database utilities for JSONL files").version("1.0.0");
1216
1208
  program.command("generate").description("Generate TypeScript type definitions from schema files").argument("<dataDir>", "Directory containing JSONL and schema files").action(async (dataDir) => {
1217
1209
  try {
1218
1210
  await new TypeGenerator({ dataDir }).generate();
@@ -1225,6 +1217,10 @@ program.command("generate").description("Generate TypeScript type definitions fr
1225
1217
  program.command("validate").description("Validate JSONL file(s) against schema").argument("<path>", "File or directory path to validate").option("-v, --verbose", "Show verbose error output", false).action(async (path, options) => {
1226
1218
  try {
1227
1219
  const result = await new Validator({ path }).validate();
1220
+ if (result.warnings.length > 0) {
1221
+ for (const warning of result.warnings) console.warn(styleText("yellow", `⚠ ${warning}`));
1222
+ console.log("");
1223
+ }
1228
1224
  if (result.valid) {
1229
1225
  console.log("✓ All records are valid");
1230
1226
  process.exit(0);
package/dist/index.cjs CHANGED
@@ -30,8 +30,6 @@ node_url = __toESM(node_url);
30
30
 
31
31
  //#region src/runtime.ts
32
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
33
  if (typeof process !== "undefined" && process.versions && process.versions.node) return "node";
36
34
  return "unknown";
37
35
  }
@@ -40,43 +38,13 @@ const RUNTIME = detectRuntime();
40
38
  //#endregion
41
39
  //#region src/sqlite-adapter.ts
42
40
  /**
43
- * Create a SQLite database instance based on the runtime environment
41
+ * Create a SQLite database instance for Node.js
44
42
  */
45
43
  function createDatabase(path = ":memory:") {
46
- if (RUNTIME === "bun") return createBunDatabase(path);
47
- else if (RUNTIME === "node" || RUNTIME === "deno") return createNodeDatabase(path);
44
+ if (RUNTIME === "node") return createNodeDatabase(path);
48
45
  else throw new Error(`Unsupported runtime: ${RUNTIME}`);
49
46
  }
50
47
  /**
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
48
  * Create a Node.js SQLite database
81
49
  */
82
50
  function createNodeDatabase(path) {
@@ -214,6 +182,18 @@ var JsonlWriter = class {
214
182
  //#endregion
215
183
  //#region src/schema-loader.ts
216
184
  var SchemaLoader = class {
185
+ /**
186
+ * Check if a schema file exists for a table
187
+ */
188
+ static async hasSchema(jsonlPath) {
189
+ const schemaPath = (0, node_path.join)((0, node_path.dirname)(jsonlPath), `${(0, node_path.basename)(jsonlPath, ".jsonl")}.schema.ts`);
190
+ try {
191
+ await (0, node_fs_promises.access)(schemaPath);
192
+ return true;
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
217
197
  /**
218
198
  * Load a validation schema file for a table
219
199
  * Requires ${tableName}.schema.ts to exist alongside the JSONL file
@@ -989,7 +969,7 @@ var TypeGenerator = class {
989
969
  return `// Auto-generated by lines-db
990
970
  // Do not edit this file manually
991
971
 
992
- ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}import type { DatabaseConfig${imports.length > 0 ? ", InferOutput" : ""} } from 'lines-db';
972
+ ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}import type { DatabaseConfig${imports.length > 0 ? ", InferOutput" : ""} } from '@toiroakr/lines-db';
993
973
  import { fileURLToPath } from 'node:url';
994
974
  import { dirname } from 'node:path';
995
975
 
@@ -1054,15 +1034,26 @@ var Validator = class {
1054
1034
  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
1035
  if (jsonlFiles.length === 0) throw new Error(`No JSONL files found in directory: ${dirPath}`);
1056
1036
  const allErrors = [];
1057
- for (const file of jsonlFiles) {
1037
+ const allWarnings = [];
1038
+ const filesWithSchema = [];
1039
+ for (const file of jsonlFiles) if (await SchemaLoader.hasSchema(file)) filesWithSchema.push(file);
1040
+ else {
1041
+ const tableName = (0, node_path.basename)(file, ".jsonl");
1042
+ allWarnings.push(`Skipping validation for '${tableName}': schema file not found`);
1043
+ }
1044
+ for (const file of filesWithSchema) {
1058
1045
  const result = await this.validateFile(file);
1059
1046
  allErrors.push(...result.errors);
1047
+ allWarnings.push(...result.warnings);
1048
+ }
1049
+ if (filesWithSchema.length > 0) {
1050
+ const fkErrors = await this.validateForeignKeys(dirPath, filesWithSchema);
1051
+ allErrors.push(...fkErrors);
1060
1052
  }
1061
- const fkErrors = await this.validateForeignKeys(dirPath, jsonlFiles);
1062
- allErrors.push(...fkErrors);
1063
1053
  return {
1064
1054
  valid: allErrors.length === 0,
1065
- errors: allErrors
1055
+ errors: allErrors,
1056
+ warnings: allWarnings
1066
1057
  };
1067
1058
  }
1068
1059
  /**
@@ -1138,7 +1129,8 @@ var Validator = class {
1138
1129
  }
1139
1130
  return {
1140
1131
  valid: errors.length === 0,
1141
- errors
1132
+ errors,
1133
+ warnings: []
1142
1134
  };
1143
1135
  }
1144
1136
  };