@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/CHANGELOG.md +30 -0
- package/README.ja.md +359 -0
- package/README.md +359 -0
- package/assets/logo.svg +57 -0
- package/bin/cli.js +37 -41
- package/dist/index.cjs +32 -40
- package/dist/index.d.cts +7 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -40
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/cli.ts +13 -2
- package/src/database.test.ts +1 -1
- package/src/runtime.ts +1 -19
- package/src/schema-loader.ts +16 -0
- package/src/sqlite-adapter.ts +3 -38
- package/src/type-generator.ts +2 -4
- package/src/validator.test.ts +37 -7
- package/src/validator.ts +23 -4
- package/LICENSE +0 -21
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
|
+
[](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
|
package/assets/logo.svg
ADDED
|
@@ -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
|
-
|
|
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
|
|
389
|
+
* Create a SQLite database instance for Node.js
|
|
368
390
|
*/
|
|
369
391
|
function createDatabase(path = ":memory:") {
|
|
370
|
-
if (RUNTIME === "
|
|
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
|
|
41
|
+
* Create a SQLite database instance for Node.js
|
|
44
42
|
*/
|
|
45
43
|
function createDatabase(path = ":memory:") {
|
|
46
|
-
if (RUNTIME === "
|
|
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
|
-
|
|
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
|
};
|