@h-ai/reldb 0.1.0-alpha.66 → 0.1.0-alpha.67
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.en.md +37 -31
- package/README.ja.md +38 -32
- package/README.md +38 -32
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -4,21 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
[Framework home](../../README.en.md) · Related modules: [@h-ai/core](../core/README.en.md)
|
|
6
6
|
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
7
9
|
A relational database module with a unified `reldb` object for SQLite, PostgreSQL, and MySQL.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Core features
|
|
12
|
+
|
|
13
|
+
- Connect to SQLite, PostgreSQL and MySQL through one API.
|
|
14
|
+
- Run parameterized SQL, manage tables/indexes and read paginated results.
|
|
15
|
+
- Organize application reads and writes with transactions, CRUD and repositories.
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
- PostgreSQL
|
|
13
|
-
- MySQL
|
|
17
|
+
## Getting started
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
### Quick start
|
|
16
20
|
|
|
17
21
|
```ts
|
|
18
22
|
import { HaiReldbError, reldb } from '@h-ai/reldb'
|
|
19
23
|
|
|
20
24
|
// 初始化
|
|
21
|
-
await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
25
|
+
const initialized = await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
26
|
+
if (!initialized.success)
|
|
27
|
+
throw new Error(initialized.error.message)
|
|
22
28
|
|
|
23
29
|
// DDL
|
|
24
30
|
await reldb.ddl.createTable('users', {
|
|
@@ -55,7 +61,7 @@ await reldb.close()
|
|
|
55
61
|
|
|
56
62
|
`reldb.config` returns a **redacted configuration snapshot**. Database `password` and credentials embedded in URLs are hidden automatically. Use it to inspect state, not to replay original connection details.
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
### Configuration
|
|
59
65
|
|
|
60
66
|
Use either a connection string (`url`) or separate fields (`host/port/database/user/password`).
|
|
61
67
|
|
|
@@ -78,7 +84,7 @@ await reldb.init({
|
|
|
78
84
|
})
|
|
79
85
|
```
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
#### SQLite execution (asynchronous worker)
|
|
82
88
|
|
|
83
89
|
better-sqlite3 is synchronous, so queries block Node's main event loop. WAL improves database lock concurrency but cannot remove this blocking. To offload it, opt in to a dedicated SQLite worker thread (single connection; defaults to `sync`):
|
|
84
90
|
|
|
@@ -96,7 +102,7 @@ await reldb.init({
|
|
|
96
102
|
|
|
97
103
|
In worker mode, queries, writes, and transactions run serially on the worker thread. Public APIs and return values remain identical to synchronous mode.
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
#### Operation logging
|
|
100
106
|
|
|
101
107
|
Enable SQL operation logs with `operationLog` in the database configuration. Logs are emitted at the DML layer of reldb provider-base, without duplicate wrappers in `reldb-main.ts` or raw providers.
|
|
102
108
|
|
|
@@ -127,11 +133,11 @@ await reldb.init({
|
|
|
127
133
|
})
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
### Transactions
|
|
131
137
|
|
|
132
138
|
Three approaches:
|
|
133
139
|
|
|
134
|
-
|
|
140
|
+
#### wrap (automatic commit/rollback)
|
|
135
141
|
|
|
136
142
|
```ts
|
|
137
143
|
const result = await reldb.tx.wrap(async (tx) => {
|
|
@@ -142,7 +148,7 @@ const result = await reldb.tx.wrap(async (tx) => {
|
|
|
142
148
|
// 回调正常返回 → 自动提交;抛异常 → 自动回滚
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
#### begin + commit (explicit commit)
|
|
146
152
|
|
|
147
153
|
```ts
|
|
148
154
|
const txResult = await reldb.tx.begin()
|
|
@@ -154,7 +160,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户2'])
|
|
|
154
160
|
await tx.commit()
|
|
155
161
|
```
|
|
156
162
|
|
|
157
|
-
|
|
163
|
+
#### begin + rollback (explicit rollback)
|
|
158
164
|
|
|
159
165
|
```ts
|
|
160
166
|
const txResult = await reldb.tx.begin()
|
|
@@ -166,7 +172,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
|
|
|
166
172
|
await tx.rollback()
|
|
167
173
|
```
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
#### batch / queryPage inside transactions
|
|
170
176
|
|
|
171
177
|
```ts
|
|
172
178
|
await reldb.tx.wrap(async (tx) => {
|
|
@@ -182,7 +188,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
182
188
|
})
|
|
183
189
|
```
|
|
184
190
|
|
|
185
|
-
|
|
191
|
+
### Crud
|
|
186
192
|
|
|
187
193
|
Create a single-table CRUD repository with `reldb.crud.table()`, without writing SQL:
|
|
188
194
|
|
|
@@ -197,7 +203,7 @@ const userCrud = reldb.crud.table<{ id: number, name: string, email: string }>({
|
|
|
197
203
|
})
|
|
198
204
|
```
|
|
199
205
|
|
|
200
|
-
|
|
206
|
+
#### create / createMany
|
|
201
207
|
|
|
202
208
|
```ts
|
|
203
209
|
// 单条插入
|
|
@@ -211,7 +217,7 @@ await userCrud.createMany([
|
|
|
211
217
|
])
|
|
212
218
|
```
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
#### createOrUpdate
|
|
215
221
|
|
|
216
222
|
Update when the primary key exists; otherwise create (upsert):
|
|
217
223
|
|
|
@@ -230,7 +236,7 @@ await userCrud.createOrUpdate({ id: 1, name: '张三', email: 'test@example.com'
|
|
|
230
236
|
|
|
231
237
|
`BaseReldbCrudRepository` obtains `dbType` automatically; no manual argument is needed.
|
|
232
238
|
|
|
233
|
-
|
|
239
|
+
#### findById / getById / findAll / findPage
|
|
234
240
|
|
|
235
241
|
```ts
|
|
236
242
|
// 主键查找(不存在时返回 null)
|
|
@@ -260,14 +266,14 @@ const page = await userCrud.findPage({
|
|
|
260
266
|
// page.data → { items: [...], total: 100, page: 1, pageSize: 20 }
|
|
261
267
|
```
|
|
262
268
|
|
|
263
|
-
|
|
269
|
+
#### updateById / deleteById
|
|
264
270
|
|
|
265
271
|
```ts
|
|
266
272
|
await userCrud.updateById(1, { name: '李四' })
|
|
267
273
|
await userCrud.deleteById(1)
|
|
268
274
|
```
|
|
269
275
|
|
|
270
|
-
|
|
276
|
+
#### count / exists / existsById
|
|
271
277
|
|
|
272
278
|
```ts
|
|
273
279
|
const total = await userCrud.count({ where: 'name LIKE ?', params: ['%张%'] })
|
|
@@ -280,7 +286,7 @@ const found = await userCrud.existsById(1)
|
|
|
280
286
|
// found.data → true
|
|
281
287
|
```
|
|
282
288
|
|
|
283
|
-
|
|
289
|
+
#### Crud inside transactions
|
|
284
290
|
|
|
285
291
|
All methods accept `tx` and automatically route operations to that transaction context:
|
|
286
292
|
|
|
@@ -292,7 +298,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
292
298
|
})
|
|
293
299
|
```
|
|
294
300
|
|
|
295
|
-
|
|
301
|
+
### BaseReldbCrudRepository
|
|
296
302
|
|
|
297
303
|
Business repositories extend `BaseReldbCrudRepository` and define field mappings with `fields` for automatic table creation and type conversion:
|
|
298
304
|
|
|
@@ -359,7 +365,7 @@ if (txResult.success) {
|
|
|
359
365
|
}
|
|
360
366
|
```
|
|
361
367
|
|
|
362
|
-
|
|
368
|
+
### JSON path operations
|
|
363
369
|
|
|
364
370
|
Use `reldb.json` to build SQL expressions for consistent JSON path operations across databases.
|
|
365
371
|
|
|
@@ -375,7 +381,7 @@ Paths follow SQL/JSON Path (`$.key` / `$.key.subkey` / `$[0]`); each backend map
|
|
|
375
381
|
|
|
376
382
|
All methods return `{ sql, params }` fragments that can be embedded in `reldb.sql.query` / `reldb.sql.execute`.
|
|
377
383
|
|
|
378
|
-
|
|
384
|
+
#### extract — read a JSON field value
|
|
379
385
|
|
|
380
386
|
```ts
|
|
381
387
|
// 用于 WHERE 条件
|
|
@@ -393,7 +399,7 @@ const result = await reldb.sql.query(
|
|
|
393
399
|
)
|
|
394
400
|
```
|
|
395
401
|
|
|
396
|
-
|
|
402
|
+
#### set — set a JSON path (create or replace)
|
|
397
403
|
|
|
398
404
|
```ts
|
|
399
405
|
const { sql, params } = reldb.json.set('settings', '$.theme', 'dark')
|
|
@@ -403,7 +409,7 @@ await reldb.sql.execute(
|
|
|
403
409
|
)
|
|
404
410
|
```
|
|
405
411
|
|
|
406
|
-
|
|
412
|
+
#### insert — insert at a JSON path (only when absent)
|
|
407
413
|
|
|
408
414
|
```ts
|
|
409
415
|
const { sql, params } = reldb.json.insert('data', '$.firstSeen', new Date().toISOString())
|
|
@@ -413,7 +419,7 @@ await reldb.sql.execute(
|
|
|
413
419
|
)
|
|
414
420
|
```
|
|
415
421
|
|
|
416
|
-
|
|
422
|
+
#### remove — remove a JSON path
|
|
417
423
|
|
|
418
424
|
```ts
|
|
419
425
|
const { sql, params } = reldb.json.remove('settings', '$.deprecated')
|
|
@@ -423,7 +429,7 @@ await reldb.sql.execute(
|
|
|
423
429
|
)
|
|
424
430
|
```
|
|
425
431
|
|
|
426
|
-
|
|
432
|
+
#### merge — merge JSON objects (RFC 7396 Merge Patch)
|
|
427
433
|
|
|
428
434
|
```ts
|
|
429
435
|
// 覆盖已有键、保留未提及键、将 null 键删除
|
|
@@ -437,7 +443,7 @@ await reldb.sql.execute(
|
|
|
437
443
|
)
|
|
438
444
|
```
|
|
439
445
|
|
|
440
|
-
|
|
446
|
+
#### Complete example
|
|
441
447
|
|
|
442
448
|
```ts
|
|
443
449
|
// 建表(含 JSON 列)
|
|
@@ -472,7 +478,7 @@ const { sql: rmSql, params: rmParams } = reldb.json.remove('meta', '$.reviewer')
|
|
|
472
478
|
await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...rmParams, 1])
|
|
473
479
|
```
|
|
474
480
|
|
|
475
|
-
|
|
481
|
+
### Error handling
|
|
476
482
|
|
|
477
483
|
All operations return `HaiResult<T>`; check `result.success` for success or failure.
|
|
478
484
|
|
|
@@ -507,7 +513,7 @@ Common error codes:
|
|
|
507
513
|
| `HaiReldbError.DDL_FAILED` | `hai:reldb:020` | DDL operation failed |
|
|
508
514
|
| `HaiReldbError.CONFIG_ERROR` | `hai:reldb:013` | Invalid configuration |
|
|
509
515
|
|
|
510
|
-
##
|
|
516
|
+
## Development and testing
|
|
511
517
|
|
|
512
518
|
```bash
|
|
513
519
|
pnpm --filter @h-ai/reldb test
|
package/README.ja.md
CHANGED
|
@@ -4,21 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/core](../core/README.ja.md)
|
|
6
6
|
|
|
7
|
+
## 概要
|
|
8
|
+
|
|
7
9
|
共通の `reldb` オブジェクトで SQLite、PostgreSQL、MySQL を操作するリレーショナルデータベースモジュールです。
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## 主な機能
|
|
12
|
+
|
|
13
|
+
- 共通 API で SQLite・PostgreSQL・MySQL に接続。
|
|
14
|
+
- パラメーター付き SQL、テーブル・索引管理、ページ取得。
|
|
15
|
+
- トランザクション、CRUD、Repository による業務の読み書き。
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
- PostgreSQL
|
|
13
|
-
- MySQL
|
|
17
|
+
## 使い始める
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
### クイックスタート
|
|
16
20
|
|
|
17
21
|
```ts
|
|
18
22
|
import { HaiReldbError, reldb } from '@h-ai/reldb'
|
|
19
23
|
|
|
20
24
|
// 初始化
|
|
21
|
-
await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
25
|
+
const initialized = await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
26
|
+
if (!initialized.success)
|
|
27
|
+
throw new Error(initialized.error.message)
|
|
22
28
|
|
|
23
29
|
// DDL
|
|
24
30
|
await reldb.ddl.createTable('users', {
|
|
@@ -55,7 +61,7 @@ await reldb.close()
|
|
|
55
61
|
|
|
56
62
|
`reldb.config` は**機密情報をマスクした設定スナップショット**を返します。DB の `password` と URL 内の認証情報は自動的に非表示になります。状態確認用であり、元の接続情報の再利用には使わないでください。
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
### 設定
|
|
59
65
|
|
|
60
66
|
接続文字列(`url`)または個別フィールド(`host/port/database/user/password`)を指定できます。
|
|
61
67
|
|
|
@@ -78,7 +84,7 @@ await reldb.init({
|
|
|
78
84
|
})
|
|
79
85
|
```
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
#### SQLite の実行方式(非同期 worker)
|
|
82
88
|
|
|
83
89
|
better-sqlite3 は同期ドライバーなので、クエリは Node のメインイベントループをブロックします。WAL は DB ロックの並行性を改善しますが、同期呼び出しによるブロックは解消しません。専用 worker スレッドで SQLite を実行して処理を移せます(単一接続、任意設定、既定は `sync`):
|
|
84
90
|
|
|
@@ -96,7 +102,7 @@ await reldb.init({
|
|
|
96
102
|
|
|
97
103
|
worker モードではクエリ、書き込み、トランザクションを worker スレッドで順番に実行します。公開 API と戻り値は同期モードと同じです。
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
#### 操作ログ
|
|
100
106
|
|
|
101
107
|
DB 設定の `operationLog` で SQL 操作ログを有効にできます。ログは reldb provider-base の DML 層で出力され、`reldb-main.ts` や各 raw provider で重複してラップしません。
|
|
102
108
|
|
|
@@ -127,11 +133,11 @@ await reldb.init({
|
|
|
127
133
|
})
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
### トランザクション
|
|
131
137
|
|
|
132
138
|
3 種類の使用方法:
|
|
133
139
|
|
|
134
|
-
|
|
140
|
+
#### wrap(自動コミット/ロールバック)
|
|
135
141
|
|
|
136
142
|
```ts
|
|
137
143
|
const result = await reldb.tx.wrap(async (tx) => {
|
|
@@ -142,7 +148,7 @@ const result = await reldb.tx.wrap(async (tx) => {
|
|
|
142
148
|
// 回调正常返回 → 自动提交;抛异常 → 自动回滚
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
#### begin + commit(手動コミット)
|
|
146
152
|
|
|
147
153
|
```ts
|
|
148
154
|
const txResult = await reldb.tx.begin()
|
|
@@ -154,7 +160,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户2'])
|
|
|
154
160
|
await tx.commit()
|
|
155
161
|
```
|
|
156
162
|
|
|
157
|
-
|
|
163
|
+
#### begin + rollback(手動ロールバック)
|
|
158
164
|
|
|
159
165
|
```ts
|
|
160
166
|
const txResult = await reldb.tx.begin()
|
|
@@ -166,7 +172,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
|
|
|
166
172
|
await tx.rollback()
|
|
167
173
|
```
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
#### トランザクション内での batch / queryPage
|
|
170
176
|
|
|
171
177
|
```ts
|
|
172
178
|
await reldb.tx.wrap(async (tx) => {
|
|
@@ -182,7 +188,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
182
188
|
})
|
|
183
189
|
```
|
|
184
190
|
|
|
185
|
-
|
|
191
|
+
### Crud
|
|
186
192
|
|
|
187
193
|
`reldb.crud.table()` で単一テーブルの CRUD リポジトリーを作成できます。SQL の記述は不要です:
|
|
188
194
|
|
|
@@ -197,7 +203,7 @@ const userCrud = reldb.crud.table<{ id: number, name: string, email: string }>({
|
|
|
197
203
|
})
|
|
198
204
|
```
|
|
199
205
|
|
|
200
|
-
|
|
206
|
+
#### create / createMany
|
|
201
207
|
|
|
202
208
|
```ts
|
|
203
209
|
// 单条插入
|
|
@@ -211,7 +217,7 @@ await userCrud.createMany([
|
|
|
211
217
|
])
|
|
212
218
|
```
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
#### createOrUpdate
|
|
215
221
|
|
|
216
222
|
主キーが存在すれば更新し、存在しなければ作成します(upsert):
|
|
217
223
|
|
|
@@ -230,7 +236,7 @@ await userCrud.createOrUpdate({ id: 1, name: '张三', email: 'test@example.com'
|
|
|
230
236
|
|
|
231
237
|
`BaseReldbCrudRepository` は `dbType` を自動取得するため、手動指定は不要です。
|
|
232
238
|
|
|
233
|
-
|
|
239
|
+
#### findById / getById / findAll / findPage
|
|
234
240
|
|
|
235
241
|
```ts
|
|
236
242
|
// 主键查找(不存在时返回 null)
|
|
@@ -260,14 +266,14 @@ const page = await userCrud.findPage({
|
|
|
260
266
|
// page.data → { items: [...], total: 100, page: 1, pageSize: 20 }
|
|
261
267
|
```
|
|
262
268
|
|
|
263
|
-
|
|
269
|
+
#### updateById / deleteById
|
|
264
270
|
|
|
265
271
|
```ts
|
|
266
272
|
await userCrud.updateById(1, { name: '李四' })
|
|
267
273
|
await userCrud.deleteById(1)
|
|
268
274
|
```
|
|
269
275
|
|
|
270
|
-
|
|
276
|
+
#### count / exists / existsById
|
|
271
277
|
|
|
272
278
|
```ts
|
|
273
279
|
const total = await userCrud.count({ where: 'name LIKE ?', params: ['%张%'] })
|
|
@@ -280,7 +286,7 @@ const found = await userCrud.existsById(1)
|
|
|
280
286
|
// found.data → true
|
|
281
287
|
```
|
|
282
288
|
|
|
283
|
-
|
|
289
|
+
#### トランザクション内での Crud
|
|
284
290
|
|
|
285
291
|
すべてのメソッドに `tx` を渡すことができ、対応するトランザクションのコンテキストへ自動的に切り替わります:
|
|
286
292
|
|
|
@@ -292,7 +298,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
292
298
|
})
|
|
293
299
|
```
|
|
294
300
|
|
|
295
|
-
|
|
301
|
+
### BaseReldbCrudRepository
|
|
296
302
|
|
|
297
303
|
業務リポジトリーは `BaseReldbCrudRepository` を継承し、`fields` でフィールドの対応を定義します。テーブル作成と型変換は自動です:
|
|
298
304
|
|
|
@@ -359,7 +365,7 @@ if (txResult.success) {
|
|
|
359
365
|
}
|
|
360
366
|
```
|
|
361
367
|
|
|
362
|
-
|
|
368
|
+
### JSON パス操作
|
|
363
369
|
|
|
364
370
|
`reldb.json` で、複数のデータベースに共通する JSON パス操作の SQL 式を作成します。
|
|
365
371
|
|
|
@@ -375,7 +381,7 @@ if (txResult.success) {
|
|
|
375
381
|
|
|
376
382
|
すべてのメソッドは `{ sql, params }` の断片を返し、`reldb.sql.query` / `reldb.sql.execute` に直接組み込めます。
|
|
377
383
|
|
|
378
|
-
|
|
384
|
+
#### extract — JSON フィールド値の取得
|
|
379
385
|
|
|
380
386
|
```ts
|
|
381
387
|
// 用于 WHERE 条件
|
|
@@ -393,7 +399,7 @@ const result = await reldb.sql.query(
|
|
|
393
399
|
)
|
|
394
400
|
```
|
|
395
401
|
|
|
396
|
-
|
|
402
|
+
#### set — JSON パスの設定(作成または置換)
|
|
397
403
|
|
|
398
404
|
```ts
|
|
399
405
|
const { sql, params } = reldb.json.set('settings', '$.theme', 'dark')
|
|
@@ -403,7 +409,7 @@ await reldb.sql.execute(
|
|
|
403
409
|
)
|
|
404
410
|
```
|
|
405
411
|
|
|
406
|
-
|
|
412
|
+
#### insert — JSON パスへの挿入(未存在の場合のみ)
|
|
407
413
|
|
|
408
414
|
```ts
|
|
409
415
|
const { sql, params } = reldb.json.insert('data', '$.firstSeen', new Date().toISOString())
|
|
@@ -413,7 +419,7 @@ await reldb.sql.execute(
|
|
|
413
419
|
)
|
|
414
420
|
```
|
|
415
421
|
|
|
416
|
-
|
|
422
|
+
#### remove — JSON パスの削除
|
|
417
423
|
|
|
418
424
|
```ts
|
|
419
425
|
const { sql, params } = reldb.json.remove('settings', '$.deprecated')
|
|
@@ -423,7 +429,7 @@ await reldb.sql.execute(
|
|
|
423
429
|
)
|
|
424
430
|
```
|
|
425
431
|
|
|
426
|
-
|
|
432
|
+
#### merge — JSON オブジェクトのマージ(RFC 7396 Merge Patch)
|
|
427
433
|
|
|
428
434
|
```ts
|
|
429
435
|
// 覆盖已有键、保留未提及键、将 null 键删除
|
|
@@ -437,7 +443,7 @@ await reldb.sql.execute(
|
|
|
437
443
|
)
|
|
438
444
|
```
|
|
439
445
|
|
|
440
|
-
|
|
446
|
+
#### 完全な使用例
|
|
441
447
|
|
|
442
448
|
```ts
|
|
443
449
|
// 建表(含 JSON 列)
|
|
@@ -472,7 +478,7 @@ const { sql: rmSql, params: rmParams } = reldb.json.remove('meta', '$.reviewer')
|
|
|
472
478
|
await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...rmParams, 1])
|
|
473
479
|
```
|
|
474
480
|
|
|
475
|
-
|
|
481
|
+
### エラー処理
|
|
476
482
|
|
|
477
483
|
すべての操作は `HaiResult<T>` を返し、`result.success` で成功・失敗を判定します。
|
|
478
484
|
|
|
@@ -507,7 +513,7 @@ if (!result.success) {
|
|
|
507
513
|
| `HaiReldbError.DDL_FAILED` | `hai:reldb:020` | DDL 操作に失敗 |
|
|
508
514
|
| `HaiReldbError.CONFIG_ERROR` | `hai:reldb:013` | 設定エラー |
|
|
509
515
|
|
|
510
|
-
##
|
|
516
|
+
## 開発とテスト
|
|
511
517
|
|
|
512
518
|
```bash
|
|
513
519
|
pnpm --filter @h-ai/reldb test
|
|
@@ -515,6 +521,6 @@ pnpm --filter @h-ai/reldb test
|
|
|
515
521
|
|
|
516
522
|
> MySQL/PostgreSQL のテストには Docker が必要です。
|
|
517
523
|
|
|
518
|
-
##
|
|
524
|
+
## ライセンス
|
|
519
525
|
|
|
520
526
|
Apache-2.0
|
package/README.md
CHANGED
|
@@ -4,21 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
[框架首页](../../README.md) · 相关模块:[@h-ai/core](../core/README.md)
|
|
6
6
|
|
|
7
|
+
## 简介
|
|
8
|
+
|
|
7
9
|
关系型数据库访问模块,通过统一的 `reldb` 对象访问 SQLite、PostgreSQL、MySQL。
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## 核心功能清单
|
|
12
|
+
|
|
13
|
+
- 用同一套 API 连接 SQLite、PostgreSQL 和 MySQL。
|
|
14
|
+
- 执行参数化 SQL,创建表和索引,读取分页结果。
|
|
15
|
+
- 使用事务、CRUD 与 Repository 组织业务读写。
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
- PostgreSQL
|
|
13
|
-
- MySQL
|
|
17
|
+
## 使用入门
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
### 快速开始
|
|
16
20
|
|
|
17
21
|
```ts
|
|
18
22
|
import { HaiReldbError, reldb } from '@h-ai/reldb'
|
|
19
23
|
|
|
20
24
|
// 初始化
|
|
21
|
-
await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
25
|
+
const initialized = await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
26
|
+
if (!initialized.success)
|
|
27
|
+
throw new Error(initialized.error.message)
|
|
22
28
|
|
|
23
29
|
// DDL
|
|
24
30
|
await reldb.ddl.createTable('users', {
|
|
@@ -55,7 +61,7 @@ await reldb.close()
|
|
|
55
61
|
|
|
56
62
|
`reldb.config` 返回的是**脱敏后的配置快照**:数据库 `password` 与 URL 内嵌凭证会自动隐藏,便于排查状态但不应用于回放原始连接信息。
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
### 配置
|
|
59
65
|
|
|
60
66
|
支持连接字符串(`url`)或分字段(`host/port/database/user/password`)。
|
|
61
67
|
|
|
@@ -78,7 +84,7 @@ await reldb.init({
|
|
|
78
84
|
})
|
|
79
85
|
```
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
#### SQLite 执行方式(worker 异步)
|
|
82
88
|
|
|
83
89
|
better-sqlite3 是同步驱动,查询会阻塞 Node 主线程事件循环。WAL 只能改善数据库锁并发,无法避免同步调用占用主线程。需要卸载这一阻塞时,可把 SQLite 切到专用 worker 线程执行(单连接,opt-in,默认 `sync`):
|
|
84
90
|
|
|
@@ -96,7 +102,7 @@ await reldb.init({
|
|
|
96
102
|
|
|
97
103
|
worker 模式下查询、写入与事务均在 worker 线程串行执行;公共 API 与返回值与同步模式完全一致。
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
#### 操作日志
|
|
100
106
|
|
|
101
107
|
可在数据库配置中通过 `operationLog` 开启 SQL 操作日志。日志在 reldb provider-base 的 DML 操作层统一输出,不在 `reldb-main.ts` 或各 raw provider 中重复包装。
|
|
102
108
|
|
|
@@ -127,11 +133,11 @@ await reldb.init({
|
|
|
127
133
|
})
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
### 事务
|
|
131
137
|
|
|
132
138
|
三种使用方式:
|
|
133
139
|
|
|
134
|
-
|
|
140
|
+
#### wrap(自动提交/回滚)
|
|
135
141
|
|
|
136
142
|
```ts
|
|
137
143
|
const result = await reldb.tx.wrap(async (tx) => {
|
|
@@ -142,7 +148,7 @@ const result = await reldb.tx.wrap(async (tx) => {
|
|
|
142
148
|
// 回调正常返回 → 自动提交;抛异常 → 自动回滚
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
#### begin + commit(分步提交)
|
|
146
152
|
|
|
147
153
|
```ts
|
|
148
154
|
const txResult = await reldb.tx.begin()
|
|
@@ -154,7 +160,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户2'])
|
|
|
154
160
|
await tx.commit()
|
|
155
161
|
```
|
|
156
162
|
|
|
157
|
-
|
|
163
|
+
#### begin + rollback(分步回滚)
|
|
158
164
|
|
|
159
165
|
```ts
|
|
160
166
|
const txResult = await reldb.tx.begin()
|
|
@@ -166,7 +172,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
|
|
|
166
172
|
await tx.rollback()
|
|
167
173
|
```
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
#### 事务内使用 batch / queryPage
|
|
170
176
|
|
|
171
177
|
```ts
|
|
172
178
|
await reldb.tx.wrap(async (tx) => {
|
|
@@ -182,7 +188,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
182
188
|
})
|
|
183
189
|
```
|
|
184
190
|
|
|
185
|
-
|
|
191
|
+
### Crud
|
|
186
192
|
|
|
187
193
|
通过 `reldb.crud.table()` 创建单表 CRUD 仓库,免写 SQL:
|
|
188
194
|
|
|
@@ -197,7 +203,7 @@ const userCrud = reldb.crud.table<{ id: number, name: string, email: string }>({
|
|
|
197
203
|
})
|
|
198
204
|
```
|
|
199
205
|
|
|
200
|
-
|
|
206
|
+
#### create / createMany
|
|
201
207
|
|
|
202
208
|
```ts
|
|
203
209
|
// 单条插入
|
|
@@ -211,7 +217,7 @@ await userCrud.createMany([
|
|
|
211
217
|
])
|
|
212
218
|
```
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
#### createOrUpdate
|
|
215
221
|
|
|
216
222
|
主键存在时更新,否则创建(upsert):
|
|
217
223
|
|
|
@@ -230,7 +236,7 @@ await userCrud.createOrUpdate({ id: 1, name: '张三', email: 'test@example.com'
|
|
|
230
236
|
|
|
231
237
|
`BaseReldbCrudRepository` 自动获取 `dbType`,无需手动传入。
|
|
232
238
|
|
|
233
|
-
|
|
239
|
+
#### findById / getById / findAll / findPage
|
|
234
240
|
|
|
235
241
|
```ts
|
|
236
242
|
// 主键查找(不存在时返回 null)
|
|
@@ -260,14 +266,14 @@ const page = await userCrud.findPage({
|
|
|
260
266
|
// page.data → { items: [...], total: 100, page: 1, pageSize: 20 }
|
|
261
267
|
```
|
|
262
268
|
|
|
263
|
-
|
|
269
|
+
#### updateById / deleteById
|
|
264
270
|
|
|
265
271
|
```ts
|
|
266
272
|
await userCrud.updateById(1, { name: '李四' })
|
|
267
273
|
await userCrud.deleteById(1)
|
|
268
274
|
```
|
|
269
275
|
|
|
270
|
-
|
|
276
|
+
#### count / exists / existsById
|
|
271
277
|
|
|
272
278
|
```ts
|
|
273
279
|
const total = await userCrud.count({ where: 'name LIKE ?', params: ['%张%'] })
|
|
@@ -280,7 +286,7 @@ const found = await userCrud.existsById(1)
|
|
|
280
286
|
// found.data → true
|
|
281
287
|
```
|
|
282
288
|
|
|
283
|
-
|
|
289
|
+
#### 事务中使用 Crud
|
|
284
290
|
|
|
285
291
|
所有方法均支持传入 `tx` 参数,自动路由到事务上下文:
|
|
286
292
|
|
|
@@ -292,7 +298,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
292
298
|
})
|
|
293
299
|
```
|
|
294
300
|
|
|
295
|
-
|
|
301
|
+
### BaseReldbCrudRepository
|
|
296
302
|
|
|
297
303
|
业务仓库继承 `BaseReldbCrudRepository`,通过 `fields` 定义字段映射,自动建表、类型转换:
|
|
298
304
|
|
|
@@ -359,7 +365,7 @@ if (txResult.success) {
|
|
|
359
365
|
}
|
|
360
366
|
```
|
|
361
367
|
|
|
362
|
-
|
|
368
|
+
### JSON 路径操作
|
|
363
369
|
|
|
364
370
|
通过 `reldb.json` 构建跨数据库统一的 JSON 路径操作 SQL 表达式。
|
|
365
371
|
|
|
@@ -375,7 +381,7 @@ if (txResult.success) {
|
|
|
375
381
|
|
|
376
382
|
所有方法返回 `{ sql, params }` 片段,可直接嵌入 `reldb.sql.query` / `reldb.sql.execute`。
|
|
377
383
|
|
|
378
|
-
|
|
384
|
+
#### extract — 提取 JSON 字段值
|
|
379
385
|
|
|
380
386
|
```ts
|
|
381
387
|
// 用于 WHERE 条件
|
|
@@ -393,7 +399,7 @@ const result = await reldb.sql.query(
|
|
|
393
399
|
)
|
|
394
400
|
```
|
|
395
401
|
|
|
396
|
-
|
|
402
|
+
#### set — 设置 JSON 字段路径(创建或替换)
|
|
397
403
|
|
|
398
404
|
```ts
|
|
399
405
|
const { sql, params } = reldb.json.set('settings', '$.theme', 'dark')
|
|
@@ -403,7 +409,7 @@ await reldb.sql.execute(
|
|
|
403
409
|
)
|
|
404
410
|
```
|
|
405
411
|
|
|
406
|
-
|
|
412
|
+
#### insert — 插入 JSON 字段路径(仅当路径不存在时)
|
|
407
413
|
|
|
408
414
|
```ts
|
|
409
415
|
const { sql, params } = reldb.json.insert('data', '$.firstSeen', new Date().toISOString())
|
|
@@ -413,7 +419,7 @@ await reldb.sql.execute(
|
|
|
413
419
|
)
|
|
414
420
|
```
|
|
415
421
|
|
|
416
|
-
|
|
422
|
+
#### remove — 删除 JSON 字段路径
|
|
417
423
|
|
|
418
424
|
```ts
|
|
419
425
|
const { sql, params } = reldb.json.remove('settings', '$.deprecated')
|
|
@@ -423,7 +429,7 @@ await reldb.sql.execute(
|
|
|
423
429
|
)
|
|
424
430
|
```
|
|
425
431
|
|
|
426
|
-
|
|
432
|
+
#### merge — 合并 JSON 对象(RFC 7396 Merge Patch)
|
|
427
433
|
|
|
428
434
|
```ts
|
|
429
435
|
// 覆盖已有键、保留未提及键、将 null 键删除
|
|
@@ -437,7 +443,7 @@ await reldb.sql.execute(
|
|
|
437
443
|
)
|
|
438
444
|
```
|
|
439
445
|
|
|
440
|
-
|
|
446
|
+
#### 完整示例
|
|
441
447
|
|
|
442
448
|
```ts
|
|
443
449
|
// 建表(含 JSON 列)
|
|
@@ -472,7 +478,7 @@ const { sql: rmSql, params: rmParams } = reldb.json.remove('meta', '$.reviewer')
|
|
|
472
478
|
await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...rmParams, 1])
|
|
473
479
|
```
|
|
474
480
|
|
|
475
|
-
|
|
481
|
+
### 错误处理
|
|
476
482
|
|
|
477
483
|
所有操作返回 `HaiResult<T>`,通过 `result.success` 判断成功或失败。
|
|
478
484
|
|
|
@@ -507,7 +513,7 @@ if (!result.success) {
|
|
|
507
513
|
| `HaiReldbError.DDL_FAILED` | `hai:reldb:020` | DDL 操作失败 |
|
|
508
514
|
| `HaiReldbError.CONFIG_ERROR` | `hai:reldb:013` | 配置错误 |
|
|
509
515
|
|
|
510
|
-
##
|
|
516
|
+
## 开发测试
|
|
511
517
|
|
|
512
518
|
```bash
|
|
513
519
|
pnpm --filter @h-ai/reldb test
|
|
@@ -515,6 +521,6 @@ pnpm --filter @h-ai/reldb test
|
|
|
515
521
|
|
|
516
522
|
> MySQL/PostgreSQL 测试需要 Docker。
|
|
517
523
|
|
|
518
|
-
##
|
|
524
|
+
## 许可证
|
|
519
525
|
|
|
520
526
|
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/reldb",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.67",
|
|
5
5
|
"description": "Hai Framework relational database module for SQLite, PostgreSQL, and MySQL.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
26
|
+
"@h-ai/core": "0.1.0-alpha.67",
|
|
27
27
|
"better-sqlite3": "^12.11.1",
|
|
28
28
|
"zod": "^4.6.5"
|
|
29
29
|
},
|