@toiroakr/lines-db 0.1.2 → 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 +17 -0
- package/README.ja.md +105 -18
- package/README.md +105 -18
- 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 +1 -1
- package/src/cli.ts +12 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @toiroakr/lines-db
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b8e0afe: feat!: remove bun/deno
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 49089e1: fix: skip validation with warning instead of error when schema file is not found
|
|
12
|
+
|
|
13
|
+
When validating a directory containing JSONL files, if a schema file is missing for some tables, the validator will now:
|
|
14
|
+
- Skip validation for those files with a warning message instead of throwing an error
|
|
15
|
+
- Display warnings in yellow in the CLI output
|
|
16
|
+
- Continue validation for other files that have schema files
|
|
17
|
+
|
|
18
|
+
This allows for more flexible validation workflows where not all tables require validation schemas.
|
|
19
|
+
|
|
3
20
|
## 0.1.2
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.ja.md
CHANGED
|
@@ -13,7 +13,15 @@ JSONLファイルをテーブルとして扱うデータ管理ライブラリで
|
|
|
13
13
|
- 🔄 **双方向スキーマ変換**
|
|
14
14
|
- 💾 **JSONLファイルへの自動同期**
|
|
15
15
|
- 🛡️ TypeScriptによる型安全性
|
|
16
|
-
-
|
|
16
|
+
- Node.js 22.5+サポート
|
|
17
|
+
|
|
18
|
+
## VS Code拡張機能
|
|
19
|
+
|
|
20
|
+
JSONLファイルのシンタックスハイライトとバリデーションをサポートするVS Code拡張機能が利用可能です。
|
|
21
|
+
|
|
22
|
+
[](https://marketplace.visualstudio.com/items?itemName=toiroakr.lines-db-vscode)
|
|
23
|
+
|
|
24
|
+
[VS Code Marketplaceからインストール](https://marketplace.visualstudio.com/items?itemName=toiroakr.lines-db-vscode)
|
|
17
25
|
|
|
18
26
|
## インストール
|
|
19
27
|
|
|
@@ -44,25 +52,20 @@ data/
|
|
|
44
52
|
```typescript
|
|
45
53
|
import * as v from 'valibot';
|
|
46
54
|
import { defineSchema } from '@toiroakr/lines-db';
|
|
47
|
-
import type { InferOutput } from '@toiroakr/lines-db';
|
|
48
|
-
|
|
49
|
-
const userSchema = v.object({
|
|
50
|
-
id: v.pipe(v.number(), v.integer(), v.minValue(1)),
|
|
51
|
-
name: v.pipe(v.string(), v.minLength(1)),
|
|
52
|
-
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
|
|
53
|
-
email: v.pipe(v.string(), v.email()),
|
|
54
|
-
});
|
|
55
55
|
|
|
56
|
-
export const schema = defineSchema(
|
|
57
|
-
|
|
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
|
+
);
|
|
58
64
|
export default schema;
|
|
59
65
|
```
|
|
60
66
|
|
|
61
67
|
**サポートされているバリデーションライブラリ:**
|
|
62
68
|
|
|
63
|
-
- Valibot
|
|
64
|
-
- Zod(StandardSchemaサポート付き)
|
|
65
|
-
- Yup(StandardSchemaサポート付き)
|
|
66
69
|
- [StandardSchema](https://standardschema.dev/)を実装する任意のライブラリ
|
|
67
70
|
|
|
68
71
|
### JSONL ファイルのバリデーション
|
|
@@ -70,7 +73,7 @@ export default schema;
|
|
|
70
73
|
JSONLファイルをスキーマに対してバリデーションします:
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
|
-
npx lines-db validate <
|
|
76
|
+
npx lines-db validate <path>
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
**例:**
|
|
@@ -79,13 +82,17 @@ npx lines-db validate <dataDir>
|
|
|
79
82
|
# ./dataディレクトリ内の全JSONLファイルをバリデーション
|
|
80
83
|
npx lines-db validate ./data
|
|
81
84
|
|
|
85
|
+
# 特定のファイルをバリデーション
|
|
86
|
+
npx lines-db validate ./data/users.jsonl
|
|
87
|
+
|
|
82
88
|
# 詳細出力
|
|
83
89
|
npx lines-db validate ./data --verbose
|
|
84
90
|
```
|
|
85
91
|
|
|
86
92
|
このコマンドは以下を実行します:
|
|
87
93
|
|
|
88
|
-
-
|
|
94
|
+
- ディレクトリの場合:ディレクトリ内の全ての `.jsonl` ファイルを検索
|
|
95
|
+
- ファイルの場合:指定された `.jsonl` ファイルをバリデーション
|
|
89
96
|
- 対応する `.schema.ts` ファイルを読み込み
|
|
90
97
|
- 各レコードをスキーマに対してバリデーション
|
|
91
98
|
- 詳細なメッセージとともにバリデーションエラーを報告
|
|
@@ -147,14 +154,32 @@ npx lines-db generate ./data
|
|
|
147
154
|
|
|
148
155
|
### クイックスタート
|
|
149
156
|
|
|
157
|
+
**1. JSONLファイルを作成(./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. TypeScriptで使用:**
|
|
166
|
+
|
|
150
167
|
```typescript
|
|
151
168
|
import { LinesDB } from '@toiroakr/lines-db';
|
|
152
169
|
|
|
153
170
|
const db = LinesDB.create({ dataDir: './data' });
|
|
154
171
|
await db.initialize();
|
|
155
172
|
|
|
173
|
+
// 全てのユーザーを検索
|
|
156
174
|
const users = db.find('users');
|
|
175
|
+
console.log(users); // [{ id: 1, name: "Alice", ... }, ...]
|
|
176
|
+
|
|
177
|
+
// 特定のユーザーを検索
|
|
157
178
|
const user = db.findOne('users', { id: 1 });
|
|
179
|
+
console.log(user); // { id: 1, name: "Alice", age: 30, ... }
|
|
180
|
+
|
|
181
|
+
// 条件付きで検索
|
|
182
|
+
const adults = db.find('users', { age: (age) => age >= 30 });
|
|
158
183
|
|
|
159
184
|
await db.close();
|
|
160
185
|
```
|
|
@@ -186,7 +211,45 @@ await db.close();
|
|
|
186
211
|
|
|
187
212
|
### コア API
|
|
188
213
|
|
|
189
|
-
|
|
214
|
+
**クエリ操作:**
|
|
215
|
+
|
|
216
|
+
- `find(table, where?)` - 一致する全てのレコードを検索
|
|
217
|
+
- `findOne(table, where?)` - 単一のレコードを検索
|
|
218
|
+
- `query(sql, params?)` - 生のSQLクエリを実行
|
|
219
|
+
|
|
220
|
+
**変更操作:**
|
|
221
|
+
|
|
222
|
+
- `insert(table, data)` - 単一のレコードを挿入
|
|
223
|
+
- `update(table, data, where)` - 一致するレコードを更新
|
|
224
|
+
- `delete(table, where)` - 一致するレコードを削除
|
|
225
|
+
|
|
226
|
+
**バッチ操作:**
|
|
227
|
+
|
|
228
|
+
- `batchInsert(table, data[])` - 複数のレコードを挿入
|
|
229
|
+
- `batchUpdate(table, updates[])` - 複数のレコードを更新
|
|
230
|
+
- `batchDelete(table, where)` - 複数のレコードを削除
|
|
231
|
+
|
|
232
|
+
**トランザクションとスキーマ:**
|
|
233
|
+
|
|
234
|
+
- `transaction(fn)` - トランザクション内で操作を実行
|
|
235
|
+
- `getSchema(table)` - テーブルスキーマを取得
|
|
236
|
+
- `getTableNames()` - 全てのテーブル名を取得
|
|
237
|
+
|
|
238
|
+
**WHERE条件:**
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// シンプルな等価条件
|
|
242
|
+
db.find('users', { age: 30 });
|
|
243
|
+
|
|
244
|
+
// 複数条件(AND)
|
|
245
|
+
db.find('users', { age: 30, name: 'Alice' });
|
|
246
|
+
|
|
247
|
+
// 高度な条件
|
|
248
|
+
db.find('users', {
|
|
249
|
+
age: (age) => age > 25,
|
|
250
|
+
name: (name) => name.startsWith('A'),
|
|
251
|
+
});
|
|
252
|
+
```
|
|
190
253
|
|
|
191
254
|
### JSON型カラム
|
|
192
255
|
|
|
@@ -205,7 +268,11 @@ console.log(order.items[0].name); // "Laptop"
|
|
|
205
268
|
|
|
206
269
|
### スキーマ変換
|
|
207
270
|
|
|
208
|
-
|
|
271
|
+
スキーマがデータ型を変換する場合(例:日付文字列をDateオブジェクトに変換)、データをJSONLファイルに保存し直すためのバックワード変換を提供する必要があります。
|
|
272
|
+
|
|
273
|
+
**なぜ必要?** JSONLファイルは`"2024-01-01"`のような文字列を保存しますが、アプリケーションは`Date`オブジェクトで動作します。双方向の変換が必要です。
|
|
274
|
+
|
|
275
|
+
**例:**
|
|
209
276
|
|
|
210
277
|
```typescript
|
|
211
278
|
import * as v from 'valibot';
|
|
@@ -213,6 +280,8 @@ import { defineSchema } from '@toiroakr/lines-db';
|
|
|
213
280
|
|
|
214
281
|
const eventSchema = v.pipe(
|
|
215
282
|
v.object({
|
|
283
|
+
id: v.number(),
|
|
284
|
+
// 変換:string → Date(読み込み時)
|
|
216
285
|
date: v.pipe(
|
|
217
286
|
v.string(),
|
|
218
287
|
v.isoDate(),
|
|
@@ -221,12 +290,30 @@ const eventSchema = v.pipe(
|
|
|
221
290
|
}),
|
|
222
291
|
);
|
|
223
292
|
|
|
293
|
+
// バックワード変換を提供:Date → string(書き込み時)
|
|
224
294
|
export const schema = defineSchema(eventSchema, (output) => ({
|
|
225
295
|
...output,
|
|
226
296
|
date: output.date.toISOString(), // DateをStringに変換
|
|
227
297
|
}));
|
|
228
298
|
```
|
|
229
299
|
|
|
300
|
+
**JSONLファイル内(events.jsonl):**
|
|
301
|
+
|
|
302
|
+
```jsonl
|
|
303
|
+
{
|
|
304
|
+
"id": 1,
|
|
305
|
+
"date": "2024-01-01T00:00:00.000Z"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**TypeScriptコード内:**
|
|
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
|
+
|
|
230
317
|
### トランザクション
|
|
231
318
|
|
|
232
319
|
トランザクション外の操作は自動的に同期されます:
|
package/README.md
CHANGED
|
@@ -13,7 +13,15 @@ A data management library that treats JSONL (JSON Lines) files as tables. Perfec
|
|
|
13
13
|
- 🔄 **Bidirectional schema transformations**
|
|
14
14
|
- 💾 **Auto-sync to JSONL files**
|
|
15
15
|
- 🛡️ Type-safe with TypeScript
|
|
16
|
-
-
|
|
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)
|
|
17
25
|
|
|
18
26
|
## Installation
|
|
19
27
|
|
|
@@ -44,25 +52,20 @@ data/
|
|
|
44
52
|
```typescript
|
|
45
53
|
import * as v from 'valibot';
|
|
46
54
|
import { defineSchema } from '@toiroakr/lines-db';
|
|
47
|
-
import type { InferOutput } from '@toiroakr/lines-db';
|
|
48
|
-
|
|
49
|
-
const userSchema = v.object({
|
|
50
|
-
id: v.pipe(v.number(), v.integer(), v.minValue(1)),
|
|
51
|
-
name: v.pipe(v.string(), v.minLength(1)),
|
|
52
|
-
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
|
|
53
|
-
email: v.pipe(v.string(), v.email()),
|
|
54
|
-
});
|
|
55
55
|
|
|
56
|
-
export const schema = defineSchema(
|
|
57
|
-
|
|
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
|
+
);
|
|
58
64
|
export default schema;
|
|
59
65
|
```
|
|
60
66
|
|
|
61
67
|
**Supported validation libraries:**
|
|
62
68
|
|
|
63
|
-
- Valibot
|
|
64
|
-
- Zod (with StandardSchema support)
|
|
65
|
-
- Yup (with StandardSchema support)
|
|
66
69
|
- Any library implementing [StandardSchema](https://standardschema.dev/)
|
|
67
70
|
|
|
68
71
|
### Validate JSONL Files
|
|
@@ -70,7 +73,7 @@ export default schema;
|
|
|
70
73
|
Validate your JSONL files against their schemas:
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
|
-
npx lines-db validate <
|
|
76
|
+
npx lines-db validate <path>
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
**Example:**
|
|
@@ -79,13 +82,17 @@ npx lines-db validate <dataDir>
|
|
|
79
82
|
# Validate all JSONL files in ./data directory
|
|
80
83
|
npx lines-db validate ./data
|
|
81
84
|
|
|
85
|
+
# Validate a specific file
|
|
86
|
+
npx lines-db validate ./data/users.jsonl
|
|
87
|
+
|
|
82
88
|
# Verbose output
|
|
83
89
|
npx lines-db validate ./data --verbose
|
|
84
90
|
```
|
|
85
91
|
|
|
86
92
|
This command will:
|
|
87
93
|
|
|
88
|
-
- Find all `.jsonl` files in the directory
|
|
94
|
+
- For directories: Find all `.jsonl` files in the directory
|
|
95
|
+
- For files: Validate the specified `.jsonl` file
|
|
89
96
|
- Load corresponding `.schema.ts` files
|
|
90
97
|
- Validate each record against the schema
|
|
91
98
|
- Report validation errors with detailed messages
|
|
@@ -147,14 +154,32 @@ npx lines-db generate ./data
|
|
|
147
154
|
|
|
148
155
|
### Quick Start
|
|
149
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
|
+
|
|
150
167
|
```typescript
|
|
151
168
|
import { LinesDB } from '@toiroakr/lines-db';
|
|
152
169
|
|
|
153
170
|
const db = LinesDB.create({ dataDir: './data' });
|
|
154
171
|
await db.initialize();
|
|
155
172
|
|
|
173
|
+
// Find all users
|
|
156
174
|
const users = db.find('users');
|
|
175
|
+
console.log(users); // [{ id: 1, name: "Alice", ... }, ...]
|
|
176
|
+
|
|
177
|
+
// Find a specific user
|
|
157
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 });
|
|
158
183
|
|
|
159
184
|
await db.close();
|
|
160
185
|
```
|
|
@@ -186,7 +211,45 @@ await db.close();
|
|
|
186
211
|
|
|
187
212
|
### Core API
|
|
188
213
|
|
|
189
|
-
Query
|
|
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
|
+
```
|
|
190
253
|
|
|
191
254
|
### JSON Columns
|
|
192
255
|
|
|
@@ -205,7 +268,11 @@ console.log(order.items[0].name); // "Laptop"
|
|
|
205
268
|
|
|
206
269
|
### Schema Transformations
|
|
207
270
|
|
|
208
|
-
|
|
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:**
|
|
209
276
|
|
|
210
277
|
```typescript
|
|
211
278
|
import * as v from 'valibot';
|
|
@@ -213,6 +280,8 @@ import { defineSchema } from '@toiroakr/lines-db';
|
|
|
213
280
|
|
|
214
281
|
const eventSchema = v.pipe(
|
|
215
282
|
v.object({
|
|
283
|
+
id: v.number(),
|
|
284
|
+
// Transform: string → Date (when reading)
|
|
216
285
|
date: v.pipe(
|
|
217
286
|
v.string(),
|
|
218
287
|
v.isoDate(),
|
|
@@ -221,12 +290,30 @@ const eventSchema = v.pipe(
|
|
|
221
290
|
}),
|
|
222
291
|
);
|
|
223
292
|
|
|
293
|
+
// Provide backward transformation: Date → string (when writing)
|
|
224
294
|
export const schema = defineSchema(eventSchema, (output) => ({
|
|
225
295
|
...output,
|
|
226
296
|
date: output.date.toISOString(), // Convert Date back to string
|
|
227
297
|
}));
|
|
228
298
|
```
|
|
229
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
|
+
|
|
230
317
|
### Transactions
|
|
231
318
|
|
|
232
319
|
Operations outside transactions are auto-synced:
|
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);
|