@h-ai/reldb 1.0.0-beta.17 → 1.0.0-beta.19
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 +82 -14
- package/README.ja.md +82 -14
- package/README.md +82 -14
- package/dist/{chunk-SDS2ZT3V.js → chunk-7OE27ZH3.js} +18 -6
- package/dist/chunk-7OE27ZH3.js.map +1 -0
- package/dist/index.d.ts +56 -54
- package/dist/index.js +392 -159
- package/dist/index.js.map +1 -1
- package/dist/providers/sqlite/reldb-sqlite-worker.mjs +23 -6
- package/dist/schemas.d.ts +2 -1
- package/dist/schemas.js +1 -1
- package/package.json +10 -10
- package/dist/chunk-SDS2ZT3V.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -129,7 +129,11 @@ MySQL `close()` rejects new and queued requests immediately, then waits for borr
|
|
|
129
129
|
MySQL accepts a `mysql://` URL without split connection fields; `database` is required without a URL. URL host/port/database/user/password take precedence over conflicting split fields. `pool`, `mysql.charset`, `ssl` and `operationLog` remain independent options. Query strings and fragments are rejected, so hidden URL parameters cannot change TLS or pool policy. Connection logs sanitize URLs. This example assumes `HAI_DB_URL` is set:
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
|
-
const initialized = await reldb.init({
|
|
132
|
+
const initialized = await reldb.init({
|
|
133
|
+
type: 'mysql',
|
|
134
|
+
url: process.env.HAI_DB_URL,
|
|
135
|
+
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 }
|
|
136
|
+
})
|
|
133
137
|
if (!initialized.success)
|
|
134
138
|
throw new Error(initialized.error.message)
|
|
135
139
|
```
|
|
@@ -144,7 +148,12 @@ Omitting `ssl` or using `false` disables TLS. Enabling it requires a strict obje
|
|
|
144
148
|
|
|
145
149
|
```ts
|
|
146
150
|
// Supply trustedCaPem from deployment trust configuration, not an arbitrary peer certificate.
|
|
147
|
-
const initialized = await reldb.init({
|
|
151
|
+
const initialized = await reldb.init({
|
|
152
|
+
type: 'mysql',
|
|
153
|
+
host: 'db.example.com',
|
|
154
|
+
database: 'app',
|
|
155
|
+
ssl: { ca: trustedCaPem }
|
|
156
|
+
})
|
|
148
157
|
if (!initialized.success)
|
|
149
158
|
throw new Error(initialized.error.message)
|
|
150
159
|
```
|
|
@@ -204,6 +213,8 @@ Operation logs contain fixed operation names and SQL length, parameter count, an
|
|
|
204
213
|
|
|
205
214
|
### Transactions
|
|
206
215
|
|
|
216
|
+
A transaction has one finishing owner. Once commit or rollback starts, new queries, duplicate commits, and concurrent rollbacks immediately return TRANSACTION_FAILED; the connection is released once. Await the first finishing result rather than using concurrent completion as recovery.
|
|
217
|
+
|
|
207
218
|
Three approaches:
|
|
208
219
|
|
|
209
220
|
#### wrap (automatic commit/rollback)
|
|
@@ -272,8 +283,14 @@ if (!rolledBack.success)
|
|
|
272
283
|
```ts
|
|
273
284
|
await reldb.tx.wrap(async (tx) => {
|
|
274
285
|
const inserted = await tx.batch([
|
|
275
|
-
{
|
|
276
|
-
|
|
286
|
+
{
|
|
287
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
288
|
+
params: ['Alice']
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
292
|
+
params: ['Bob']
|
|
293
|
+
},
|
|
277
294
|
])
|
|
278
295
|
if (!inserted.success)
|
|
279
296
|
throw new Error(inserted.error.message, { cause: inserted.error })
|
|
@@ -376,10 +393,16 @@ await userCrud.deleteById(1)
|
|
|
376
393
|
#### count / exists / existsById
|
|
377
394
|
|
|
378
395
|
```ts
|
|
379
|
-
const total = await userCrud.count({
|
|
396
|
+
const total = await userCrud.count({
|
|
397
|
+
where: 'name LIKE ?',
|
|
398
|
+
params: ['%张%']
|
|
399
|
+
})
|
|
380
400
|
// total.data → 5
|
|
381
401
|
|
|
382
|
-
const has = await userCrud.exists({
|
|
402
|
+
const has = await userCrud.exists({
|
|
403
|
+
where: 'email = ?',
|
|
404
|
+
params: ['test@example.com']
|
|
405
|
+
})
|
|
383
406
|
// has.data → true
|
|
384
407
|
|
|
385
408
|
const found = await userCrud.existsById(1)
|
|
@@ -426,11 +449,46 @@ interface UserRow {
|
|
|
426
449
|
}
|
|
427
450
|
|
|
428
451
|
const USER_FIELDS: ReldbCrudFieldDefinition[] = [
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
452
|
+
{
|
|
453
|
+
fieldName: 'id',
|
|
454
|
+
columnName: 'id',
|
|
455
|
+
def: { type: 'INTEGER', primaryKey: true, autoIncrement: true },
|
|
456
|
+
select: true,
|
|
457
|
+
create: false,
|
|
458
|
+
update: false
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
fieldName: 'name',
|
|
462
|
+
columnName: 'name',
|
|
463
|
+
def: { type: 'TEXT', notNull: true },
|
|
464
|
+
select: true,
|
|
465
|
+
create: true,
|
|
466
|
+
update: true
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
fieldName: 'email',
|
|
470
|
+
columnName: 'email',
|
|
471
|
+
def: { type: 'TEXT', notNull: true },
|
|
472
|
+
select: true,
|
|
473
|
+
create: true,
|
|
474
|
+
update: true
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
fieldName: 'createdAt',
|
|
478
|
+
columnName: 'created_at',
|
|
479
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
480
|
+
select: true,
|
|
481
|
+
create: true,
|
|
482
|
+
update: false
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
fieldName: 'updatedAt',
|
|
486
|
+
columnName: 'updated_at',
|
|
487
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
488
|
+
select: true,
|
|
489
|
+
create: true,
|
|
490
|
+
update: false
|
|
491
|
+
},
|
|
434
492
|
]
|
|
435
493
|
|
|
436
494
|
class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
@@ -440,7 +498,11 @@ class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
|
440
498
|
|
|
441
499
|
/** 自定义查询方法 */
|
|
442
500
|
async findByEmail(email: string) {
|
|
443
|
-
return this.findAll({
|
|
501
|
+
return this.findAll({
|
|
502
|
+
where: 'email = ?',
|
|
503
|
+
params: [email],
|
|
504
|
+
limit: 1
|
|
505
|
+
})
|
|
444
506
|
}
|
|
445
507
|
|
|
446
508
|
/** 自定义方法中使用 this.sql(tx) 自动路由到事务 */
|
|
@@ -568,7 +630,13 @@ await reldb.ddl.createTable('products', {
|
|
|
568
630
|
// 插入初始 JSON
|
|
569
631
|
await reldb.sql.execute(
|
|
570
632
|
`INSERT INTO products (meta) VALUES (?)`,
|
|
571
|
-
[
|
|
633
|
+
[
|
|
634
|
+
JSON.stringify({
|
|
635
|
+
status: 'draft',
|
|
636
|
+
tags: ['new'],
|
|
637
|
+
version: 1
|
|
638
|
+
})
|
|
639
|
+
],
|
|
572
640
|
)
|
|
573
641
|
|
|
574
642
|
// 设置字段
|
|
@@ -636,7 +704,7 @@ Production requires PostgreSQL/MySQL. SQLite, including disk files, is for devel
|
|
|
636
704
|
|
|
637
705
|
## Module schema migrations
|
|
638
706
|
|
|
639
|
-
`schemaColumns(
|
|
707
|
+
`reldb.schemaColumns(table, sql?)` inspects real columns. `reldb.migrateSchema(module, steps, { dryRun: true })` creates no ledger. Modules own their steps. Execution journals versions in `hai_schema_migrations`; SQLite/PostgreSQL are transactional, while MySQL implicit DDL commits require reentrant steps. Failures do not advance versions; retries inspect real state. Older code rejects newer ledgers. Stop old writers and back up DB/session cache first. Rollback restores compatible database and binaries together. See the [upgrade guide](../../docs/usage/README.en.md).
|
|
640
708
|
|
|
641
709
|
## Error messages and correlated logs
|
|
642
710
|
|
package/README.ja.md
CHANGED
|
@@ -129,7 +129,11 @@ MySQL の `close()` は新規・待機リクエストを直ちに拒否し、借
|
|
|
129
129
|
MySQL は `mysql://` の `url` だけでも接続でき、URL がなければ `database` が必須です。URL の host/port/database/user/password を優先し、競合する個別フィールドは接続に使用しません。`pool`、`mysql.charset`、`ssl`、`operationLog` は独立して適用します。query/fragment は拒否し、URL 内の隠れた設定で TLS やプールを変更できません。接続ログの URL は機密情報を伏せます。以下は `HAI_DB_URL` の設定を前提とします:
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
|
-
const initialized = await reldb.init({
|
|
132
|
+
const initialized = await reldb.init({
|
|
133
|
+
type: 'mysql',
|
|
134
|
+
url: process.env.HAI_DB_URL,
|
|
135
|
+
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 }
|
|
136
|
+
})
|
|
133
137
|
if (!initialized.success)
|
|
134
138
|
throw new Error(initialized.error.message)
|
|
135
139
|
```
|
|
@@ -144,7 +148,12 @@ MySQL の空 `url`(空の環境変数を含む)は host/port/database/user/p
|
|
|
144
148
|
|
|
145
149
|
```ts
|
|
146
150
|
// trustedCaPem は配置時の信頼設定から供給し、任意の接続先証明書をそのまま信頼しません。
|
|
147
|
-
const initialized = await reldb.init({
|
|
151
|
+
const initialized = await reldb.init({
|
|
152
|
+
type: 'mysql',
|
|
153
|
+
host: 'db.example.com',
|
|
154
|
+
database: 'app',
|
|
155
|
+
ssl: { ca: trustedCaPem }
|
|
156
|
+
})
|
|
148
157
|
if (!initialized.success)
|
|
149
158
|
throw new Error(initialized.error.message)
|
|
150
159
|
```
|
|
@@ -204,6 +213,8 @@ await reldb.init({
|
|
|
204
213
|
|
|
205
214
|
### トランザクション
|
|
206
215
|
|
|
216
|
+
トランザクションの終了処理を所有できるのは一つの操作だけです。commit または rollback の開始後、新規クエリ、重複 commit、並行 rollback は直ちに TRANSACTION_FAILED を返し、接続は一度だけ返却します。復旧目的で並行終了を試みず、最初の終了結果を待ってください。
|
|
217
|
+
|
|
207
218
|
3 種類の使用方法:
|
|
208
219
|
|
|
209
220
|
#### wrap(自動コミット/ロールバック)
|
|
@@ -272,8 +283,14 @@ if (!rolledBack.success)
|
|
|
272
283
|
```ts
|
|
273
284
|
await reldb.tx.wrap(async (tx) => {
|
|
274
285
|
const inserted = await tx.batch([
|
|
275
|
-
{
|
|
276
|
-
|
|
286
|
+
{
|
|
287
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
288
|
+
params: ['Alice']
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
292
|
+
params: ['Bob']
|
|
293
|
+
},
|
|
277
294
|
])
|
|
278
295
|
if (!inserted.success)
|
|
279
296
|
throw new Error(inserted.error.message, { cause: inserted.error })
|
|
@@ -376,10 +393,16 @@ await userCrud.deleteById(1)
|
|
|
376
393
|
#### count / exists / existsById
|
|
377
394
|
|
|
378
395
|
```ts
|
|
379
|
-
const total = await userCrud.count({
|
|
396
|
+
const total = await userCrud.count({
|
|
397
|
+
where: 'name LIKE ?',
|
|
398
|
+
params: ['%张%']
|
|
399
|
+
})
|
|
380
400
|
// total.data → 5
|
|
381
401
|
|
|
382
|
-
const has = await userCrud.exists({
|
|
402
|
+
const has = await userCrud.exists({
|
|
403
|
+
where: 'email = ?',
|
|
404
|
+
params: ['test@example.com']
|
|
405
|
+
})
|
|
383
406
|
// has.data → true
|
|
384
407
|
|
|
385
408
|
const found = await userCrud.existsById(1)
|
|
@@ -426,11 +449,46 @@ interface UserRow {
|
|
|
426
449
|
}
|
|
427
450
|
|
|
428
451
|
const USER_FIELDS: ReldbCrudFieldDefinition[] = [
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
452
|
+
{
|
|
453
|
+
fieldName: 'id',
|
|
454
|
+
columnName: 'id',
|
|
455
|
+
def: { type: 'INTEGER', primaryKey: true, autoIncrement: true },
|
|
456
|
+
select: true,
|
|
457
|
+
create: false,
|
|
458
|
+
update: false
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
fieldName: 'name',
|
|
462
|
+
columnName: 'name',
|
|
463
|
+
def: { type: 'TEXT', notNull: true },
|
|
464
|
+
select: true,
|
|
465
|
+
create: true,
|
|
466
|
+
update: true
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
fieldName: 'email',
|
|
470
|
+
columnName: 'email',
|
|
471
|
+
def: { type: 'TEXT', notNull: true },
|
|
472
|
+
select: true,
|
|
473
|
+
create: true,
|
|
474
|
+
update: true
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
fieldName: 'createdAt',
|
|
478
|
+
columnName: 'created_at',
|
|
479
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
480
|
+
select: true,
|
|
481
|
+
create: true,
|
|
482
|
+
update: false
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
fieldName: 'updatedAt',
|
|
486
|
+
columnName: 'updated_at',
|
|
487
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
488
|
+
select: true,
|
|
489
|
+
create: true,
|
|
490
|
+
update: false
|
|
491
|
+
},
|
|
434
492
|
]
|
|
435
493
|
|
|
436
494
|
class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
@@ -440,7 +498,11 @@ class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
|
440
498
|
|
|
441
499
|
/** 自定义查询方法 */
|
|
442
500
|
async findByEmail(email: string) {
|
|
443
|
-
return this.findAll({
|
|
501
|
+
return this.findAll({
|
|
502
|
+
where: 'email = ?',
|
|
503
|
+
params: [email],
|
|
504
|
+
limit: 1
|
|
505
|
+
})
|
|
444
506
|
}
|
|
445
507
|
|
|
446
508
|
/** 自定义方法中使用 this.sql(tx) 自动路由到事务 */
|
|
@@ -568,7 +630,13 @@ await reldb.ddl.createTable('products', {
|
|
|
568
630
|
// 插入初始 JSON
|
|
569
631
|
await reldb.sql.execute(
|
|
570
632
|
`INSERT INTO products (meta) VALUES (?)`,
|
|
571
|
-
[
|
|
633
|
+
[
|
|
634
|
+
JSON.stringify({
|
|
635
|
+
status: 'draft',
|
|
636
|
+
tags: ['new'],
|
|
637
|
+
version: 1
|
|
638
|
+
})
|
|
639
|
+
],
|
|
572
640
|
)
|
|
573
641
|
|
|
574
642
|
// 设置字段
|
|
@@ -636,7 +704,7 @@ if (!result.success) {
|
|
|
636
704
|
|
|
637
705
|
## モジュールのスキーマ移行
|
|
638
706
|
|
|
639
|
-
`schemaColumns(
|
|
707
|
+
`reldb.schemaColumns(table, sql?)` は実際の列を読み取ります。`reldb.migrateSchema(module, steps, { dryRun: true })` は台帳を作成しません。各モジュールが手順を所有し、`hai_schema_migrations` に版を記録します。SQLite/PostgreSQL はトランザクション、MySQL の暗黙 DDL コミットには再入可能な手順が必要です。失敗時は版を進めず、実構造を再確認して再試行します。新しい台帳へのダウングレードは拒否します。旧書込を停止し DB/セッションキャッシュをバックアップしてください。復旧は互換 DB とバイナリを一緒に戻します。[更新ガイド](../../docs/usage/README.ja.md)を参照。
|
|
640
708
|
|
|
641
709
|
## エラー表示とログの関連付け
|
|
642
710
|
|
package/README.md
CHANGED
|
@@ -129,7 +129,11 @@ MySQL `close()` 立即拒绝新请求并结束排队等待,随后等待已借
|
|
|
129
129
|
MySQL 支持仅传 `url`(`mysql://`),未给 URL 时 `database` 必填。URL 中的 host/port/database/user/password 优先,冲突的分字段不参与连接;`pool`、`mysql.charset`、`ssl` 和 `operationLog` 独立适用。URL 仅接受连接地址,query/fragment 会被拒绝,不能用隐藏 URL 参数改变 TLS 或池策略。URL 模式的连接日志会脱敏。以下示例假定已设置 `HAI_DB_URL`:
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
|
-
const initialized = await reldb.init({
|
|
132
|
+
const initialized = await reldb.init({
|
|
133
|
+
type: 'mysql',
|
|
134
|
+
url: process.env.HAI_DB_URL,
|
|
135
|
+
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 }
|
|
136
|
+
})
|
|
133
137
|
if (!initialized.success)
|
|
134
138
|
throw new Error(initialized.error.message)
|
|
135
139
|
```
|
|
@@ -144,7 +148,12 @@ MySQL 的空 `url`(例如空环境变量占位)表示使用 host/port/databa
|
|
|
144
148
|
|
|
145
149
|
```ts
|
|
146
150
|
// trustedCaPem 由部署时的受信 CA 提供,不是任意远端返回的证书。
|
|
147
|
-
const initialized = await reldb.init({
|
|
151
|
+
const initialized = await reldb.init({
|
|
152
|
+
type: 'mysql',
|
|
153
|
+
host: 'db.example.com',
|
|
154
|
+
database: 'app',
|
|
155
|
+
ssl: { ca: trustedCaPem }
|
|
156
|
+
})
|
|
148
157
|
if (!initialized.success)
|
|
149
158
|
throw new Error(initialized.error.message)
|
|
150
159
|
```
|
|
@@ -204,6 +213,8 @@ await reldb.init({
|
|
|
204
213
|
|
|
205
214
|
### 事务
|
|
206
215
|
|
|
216
|
+
一个事务只允许一个结束操作。`commit()` 或 `rollback()` 开始后,新查询、重复提交和并发回滚立即返回 TRANSACTION_FAILED;连接只归还一次。调用方仍须等待首次结束结果,不能把并发结束当作故障恢复。
|
|
217
|
+
|
|
207
218
|
三种使用方式:
|
|
208
219
|
|
|
209
220
|
#### wrap(自动提交/回滚)
|
|
@@ -272,8 +283,14 @@ if (!rolledBack.success)
|
|
|
272
283
|
```ts
|
|
273
284
|
await reldb.tx.wrap(async (tx) => {
|
|
274
285
|
const inserted = await tx.batch([
|
|
275
|
-
{
|
|
276
|
-
|
|
286
|
+
{
|
|
287
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
288
|
+
params: ['Alice']
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
292
|
+
params: ['Bob']
|
|
293
|
+
},
|
|
277
294
|
])
|
|
278
295
|
if (!inserted.success)
|
|
279
296
|
throw new Error(inserted.error.message, { cause: inserted.error })
|
|
@@ -376,10 +393,16 @@ await userCrud.deleteById(1)
|
|
|
376
393
|
#### count / exists / existsById
|
|
377
394
|
|
|
378
395
|
```ts
|
|
379
|
-
const total = await userCrud.count({
|
|
396
|
+
const total = await userCrud.count({
|
|
397
|
+
where: 'name LIKE ?',
|
|
398
|
+
params: ['%张%']
|
|
399
|
+
})
|
|
380
400
|
// total.data → 5
|
|
381
401
|
|
|
382
|
-
const has = await userCrud.exists({
|
|
402
|
+
const has = await userCrud.exists({
|
|
403
|
+
where: 'email = ?',
|
|
404
|
+
params: ['test@example.com']
|
|
405
|
+
})
|
|
383
406
|
// has.data → true
|
|
384
407
|
|
|
385
408
|
const found = await userCrud.existsById(1)
|
|
@@ -426,11 +449,46 @@ interface UserRow {
|
|
|
426
449
|
}
|
|
427
450
|
|
|
428
451
|
const USER_FIELDS: ReldbCrudFieldDefinition[] = [
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
452
|
+
{
|
|
453
|
+
fieldName: 'id',
|
|
454
|
+
columnName: 'id',
|
|
455
|
+
def: { type: 'INTEGER', primaryKey: true, autoIncrement: true },
|
|
456
|
+
select: true,
|
|
457
|
+
create: false,
|
|
458
|
+
update: false
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
fieldName: 'name',
|
|
462
|
+
columnName: 'name',
|
|
463
|
+
def: { type: 'TEXT', notNull: true },
|
|
464
|
+
select: true,
|
|
465
|
+
create: true,
|
|
466
|
+
update: true
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
fieldName: 'email',
|
|
470
|
+
columnName: 'email',
|
|
471
|
+
def: { type: 'TEXT', notNull: true },
|
|
472
|
+
select: true,
|
|
473
|
+
create: true,
|
|
474
|
+
update: true
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
fieldName: 'createdAt',
|
|
478
|
+
columnName: 'created_at',
|
|
479
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
480
|
+
select: true,
|
|
481
|
+
create: true,
|
|
482
|
+
update: false
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
fieldName: 'updatedAt',
|
|
486
|
+
columnName: 'updated_at',
|
|
487
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
488
|
+
select: true,
|
|
489
|
+
create: true,
|
|
490
|
+
update: false
|
|
491
|
+
},
|
|
434
492
|
]
|
|
435
493
|
|
|
436
494
|
class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
@@ -440,7 +498,11 @@ class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
|
440
498
|
|
|
441
499
|
/** 自定义查询方法 */
|
|
442
500
|
async findByEmail(email: string) {
|
|
443
|
-
return this.findAll({
|
|
501
|
+
return this.findAll({
|
|
502
|
+
where: 'email = ?',
|
|
503
|
+
params: [email],
|
|
504
|
+
limit: 1
|
|
505
|
+
})
|
|
444
506
|
}
|
|
445
507
|
|
|
446
508
|
/** 自定义方法中使用 this.sql(tx) 自动路由到事务 */
|
|
@@ -568,7 +630,13 @@ await reldb.ddl.createTable('products', {
|
|
|
568
630
|
// 插入初始 JSON
|
|
569
631
|
await reldb.sql.execute(
|
|
570
632
|
`INSERT INTO products (meta) VALUES (?)`,
|
|
571
|
-
[
|
|
633
|
+
[
|
|
634
|
+
JSON.stringify({
|
|
635
|
+
status: 'draft',
|
|
636
|
+
tags: ['new'],
|
|
637
|
+
version: 1
|
|
638
|
+
})
|
|
639
|
+
],
|
|
572
640
|
)
|
|
573
641
|
|
|
574
642
|
// 设置字段
|
|
@@ -636,7 +704,7 @@ if (!result.success) {
|
|
|
636
704
|
|
|
637
705
|
## 模块架构迁移
|
|
638
706
|
|
|
639
|
-
`schemaColumns(
|
|
707
|
+
`reldb.schemaColumns(table, sql?)` 只读检查真实结构;`reldb.migrateSchema(module, steps, { dryRun: true })` 不创建账本。版本步骤由所属模块维护,业务不修改框架内部表。执行使用 `hai_schema_migrations`,SQLite/PostgreSQL 整体事务,MySQL DDL 隐式提交因此步骤必须可重入;失败不推进版本,重新检查真实结构后续做,拒绝向低版本降级。升级前停止所有旧写入并备份 DB/会话缓存;回退需恢复兼容数据库与旧代码,不能只换二进制。详见 [升级指南](../../docs/usage/README.md)。
|
|
640
708
|
|
|
641
709
|
## 错误提示与关联日志
|
|
642
710
|
|
|
@@ -32,7 +32,12 @@ var MysqlPoolConfigSchema = z.object({
|
|
|
32
32
|
}).strict();
|
|
33
33
|
var SslConfigSchema = z.union([
|
|
34
34
|
z.boolean(),
|
|
35
|
-
z.enum([
|
|
35
|
+
z.enum([
|
|
36
|
+
"require",
|
|
37
|
+
"prefer",
|
|
38
|
+
"allow",
|
|
39
|
+
"disable"
|
|
40
|
+
]),
|
|
36
41
|
z.record(z.string(), z.unknown())
|
|
37
42
|
]);
|
|
38
43
|
var MysqlSslConfigSchema = z.union([
|
|
@@ -48,7 +53,9 @@ var MysqlSslConfigSchema = z.union([
|
|
|
48
53
|
cert: z.string().min(1).optional(),
|
|
49
54
|
/** MySQL 客户端私钥,PEM 内容;必须同时提供 cert */
|
|
50
55
|
key: z.string().min(1).optional()
|
|
51
|
-
}).strict().refine((ssl) => Boolean(ssl.cert) === Boolean(ssl.key), {
|
|
56
|
+
}).strict().refine((ssl) => Boolean(ssl.cert) === Boolean(ssl.key), {
|
|
57
|
+
path: ["key"]
|
|
58
|
+
})
|
|
52
59
|
]);
|
|
53
60
|
var SqliteOptionsSchema = z.object({
|
|
54
61
|
/** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */
|
|
@@ -110,7 +117,10 @@ var PostgresqlConfigSchema = NetworkConfigSchema.extend({
|
|
|
110
117
|
var MysqlConfigSchema = NetworkConfigSchema.extend({
|
|
111
118
|
type: z.literal("mysql"),
|
|
112
119
|
/** 网络数据库URL;MySQL空串表示使用分字段,非空仅mysql://且拒绝query/fragment,优先于host/port/database/user/password */
|
|
113
|
-
url: z.union([
|
|
120
|
+
url: z.union([
|
|
121
|
+
z.literal("").transform(() => void 0),
|
|
122
|
+
z.url({ protocol: /^mysql$/ }).refine((value) => !value.includes("?") && !value.includes("#"))
|
|
123
|
+
]).optional(),
|
|
114
124
|
/** 数据库:SQLite文件路径或:memory:;PostgreSQL必填名称;MySQL无URL时必填,URL模式从URL读取 */
|
|
115
125
|
database: z.string().min(1).optional(),
|
|
116
126
|
/** TLS:PostgreSQL使用其模式;MySQL仅false或严格对象,空对象默认校验证书链和DNS身份 */
|
|
@@ -120,7 +130,9 @@ var MysqlConfigSchema = NetworkConfigSchema.extend({
|
|
|
120
130
|
/** MySQL 特定选项 */
|
|
121
131
|
mysql: MysqlOptionsSchema.optional(),
|
|
122
132
|
...OperationLogConfigFieldSchema
|
|
123
|
-
}).refine((config) => Boolean(config.url) || Boolean(config.database), {
|
|
133
|
+
}).refine((config) => Boolean(config.url) || Boolean(config.database), {
|
|
134
|
+
path: ["database"]
|
|
135
|
+
});
|
|
124
136
|
var ReldbConfigSchema = z.discriminatedUnion("type", [
|
|
125
137
|
SqliteConfigSchema,
|
|
126
138
|
PostgresqlConfigSchema,
|
|
@@ -128,5 +140,5 @@ var ReldbConfigSchema = z.discriminatedUnion("type", [
|
|
|
128
140
|
]);
|
|
129
141
|
|
|
130
142
|
export { MysqlConfigSchema, MysqlOptionsSchema, MysqlPoolConfigSchema, MysqlSslConfigSchema, PoolConfigSchema, PostgresqlConfigSchema, ReldbConfigSchema, ReldbOperationLogConfigSchema, ReldbTypeSchema, SqliteConfigSchema, SqliteOptionsSchema, SslConfigSchema };
|
|
131
|
-
//# sourceMappingURL=chunk-
|
|
132
|
-
//# sourceMappingURL=chunk-
|
|
143
|
+
//# sourceMappingURL=chunk-7OE27ZH3.js.map
|
|
144
|
+
//# sourceMappingURL=chunk-7OE27ZH3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reldb-config.ts"],"names":[],"mappings":"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","file":"chunk-7OE27ZH3.js","sourcesContent":["/**\n * @h-ai/reldb — 数据库配置 Schema\n *\n * 本文件定义数据库模块的配置结构,使用 Zod 进行运行时校验。\n * @module reldb-config\n */\n\nimport { z } from 'zod'\n// ─── 数据库配置 Schema ───\n\n/**\n * 数据库类型枚举\n *\n * 支持的数据库类型:\n * - `sqlite` - SQLite 嵌入式数据库(使用 better-sqlite3)\n * - `postgresql` - PostgreSQL 数据库(使用 pg)\n * - `mysql` - MySQL 数据库(使用 mysql2)\n * @example\n * ```ts\n * import { ReldbTypeSchema } from '@h-ai/reldb/schemas'\n *\n * const value = ReldbTypeSchema.parse('postgresql')\n * void value\n * ```\n */\nexport const ReldbTypeSchema = z.enum(['sqlite', 'postgresql', 'mysql'])\n\n/**\n * 数据库类型\n * @example\n * ```ts\n * import type { DbType } from '@h-ai/reldb/schemas'\n *\n * const value: DbType = 'postgresql'\n * void value\n * ```\n */\nexport type DbType = z.infer<typeof ReldbTypeSchema>\n\n/**\n * ReldbOperationLogConfigSchema 的数据约束与操作契约。\n * @example\n * ```ts\n * import { ReldbOperationLogConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = ReldbOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 100, level: 'debug' })\n * void value\n * ```\n */\nexport const ReldbOperationLogConfigSchema = z.object({\n read: z.boolean().default(false),\n write: z.boolean().default(false),\n /** 安全数量摘要的最大字符数;0 表示省略摘要,永不记录 SQL 原文或参数值。 */\n maxLength: z.number().int().min(0).default(1000),\n level: z.enum(['info', 'debug', 'trace']).default('debug'),\n})\n\n/**\n * ReldbOperationLogConfig 的数据约束与操作契约。\n * @example\n * ```ts\n * import { ReldbOperationLogConfigSchema, type ReldbOperationLogConfig } from '@h-ai/reldb/schemas'\n *\n * const value: ReldbOperationLogConfig = ReldbOperationLogConfigSchema.parse({ read: false, write: true, maxLength: 100, level: 'debug' })\n * void value\n * ```\n */\nexport type ReldbOperationLogConfig = z.infer<typeof ReldbOperationLogConfigSchema>\n\nconst OperationLogConfigFieldSchema = {\n operationLog: ReldbOperationLogConfigSchema.optional(),\n}\n\n/**\n * PostgreSQL 连接池配置 Schema\n *\n * min 是已建立连接的保留下限,不主动预建连接。\n * MySQL 使用独立的 MysqlPoolConfigSchema;SQLite 不使用连接池。\n * @example\n * ```ts\n * import { PoolConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = PoolConfigSchema.parse({ max: 10, acquireTimeout: 5000 })\n * void value\n * ```\n */\nexport const PoolConfigSchema = z.object({\n /** PostgreSQL 已建立连接的最小保留数(默认 1,不主动预建) */\n min: z.number().int().min(0).default(1),\n /** 最大连接数(默认 10) */\n max: z.number().int().min(1).default(10),\n /** PostgreSQL 空闲连接超时,毫秒(默认 30000;0 禁用) */\n idleTimeout: z.number().int().min(0).default(30000),\n /** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */\n acquireTimeout: z.number().int().min(0).default(10000),\n})\n\n/**\n * PostgreSQL 连接池配置类型。\n * @example\n * ```ts\n * import { PoolConfigSchema, type PoolConfig } from '@h-ai/reldb/schemas'\n *\n * const value: PoolConfig = PoolConfigSchema.parse({ max: 10, acquireTimeout: 5000 })\n * void value\n * ```\n */\nexport type PoolConfig = z.infer<typeof PoolConfigSchema>\n\n/**\n * MySQL 连接池配置:有界 FIFO 等待,拒绝没有可靠驱动语义的 min/idleTimeout。\n *\n * acquireTimeout 覆盖排队和建立连接,不是 SQL 执行时限。驱动建连也使用此值作为 connectTimeout。\n * 空闲连接由 mysql2/服务端管理;本模块不承诺最小保留数或可配置的空闲回收期限。\n * @example\n * ```ts\n * import { MysqlPoolConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = MysqlPoolConfigSchema.parse({ max: 10, acquireTimeout: 5000, queueLimit: 100 })\n * void value\n * ```\n */\nexport const MysqlPoolConfigSchema = z.object({\n /** 最大连接数(默认 10) */\n max: z.number().int().min(1).default(10),\n /** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */\n acquireTimeout: z.number().int().min(1).max(2147483647).default(10000),\n /** MySQL 等待队列最大请求数(默认 1000;正整数,不支持无限队列) */\n queueLimit: z.number().int().min(1).default(1000),\n}).strict()\n\n/**\n * MySQL 解析后的连接池配置。\n * @example\n * ```ts\n * import { MysqlPoolConfigSchema, type MysqlPoolConfig } from '@h-ai/reldb/schemas'\n *\n * const value: MysqlPoolConfig = MysqlPoolConfigSchema.parse({ max: 10, acquireTimeout: 5000, queueLimit: 100 })\n * void value\n * ```\n */\nexport type MysqlPoolConfig = z.infer<typeof MysqlPoolConfigSchema>\n\n/**\n * PostgreSQL SSL 配置 Schema\n *\n * 支持多种配置方式:\n * - `true/false` - 简单开关\n * - `'require'/'prefer'/'allow'/'disable'` - SSL 模式\n * - 自定义对象 - 详细 SSL 配置\n * @example\n * ```ts\n * import { SslConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = SslConfigSchema.parse({ rejectUnauthorized: true })\n * void value\n * ```\n */\nexport const SslConfigSchema = z.union([\n z.boolean(),\n z.enum([\n 'require',\n 'prefer',\n 'allow',\n 'disable',\n ]),\n z.record(z.string(), z.unknown()),\n])\n\n/**\n * PostgreSQL SSL 配置类型\n * @example\n * ```ts\n * import { SslConfigSchema, type SslConfig } from '@h-ai/reldb/schemas'\n *\n * const value: SslConfig = SslConfigSchema.parse({ rejectUnauthorized: true })\n * void value\n * ```\n */\nexport type SslConfig = z.infer<typeof SslConfigSchema>\n\n/**\n * MySQL TLS 配置;空对象默认校验证书链和 DNS 主机身份,只接收明确支持的 PEM 字段。\n *\n * cert/key 必须成对。verifyIdentity 在当前驱动仅支持 DNS 主机,不能宣称验证 IP 身份。\n * 显式关闭验证会记录风险日志;本模块不提供任意 TLS 参数或回调透传。\n * @example\n * ```ts\n * import { MysqlSslConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = MysqlSslConfigSchema.parse({ rejectUnauthorized: true, verifyIdentity: true })\n * void value\n * ```\n */\nexport const MysqlSslConfigSchema = z.union([\n z.literal(false),\n z.object({\n /** MySQL 是否校验证书链(默认 true);false 明确放弃服务端认证 */\n rejectUnauthorized: z.boolean().default(true),\n /** MySQL 是否验证 DNS 主机身份(默认 true);仅证书链验证开启时有效 */\n verifyIdentity: z.boolean().default(true),\n /** MySQL 信任的 CA,PEM 内容;省略时使用 Node 信任库 */\n ca: z.string().min(1).optional(),\n /** MySQL 客户端证书,PEM 内容;必须同时提供 key */\n cert: z.string().min(1).optional(),\n /** MySQL 客户端私钥,PEM 内容;必须同时提供 cert */\n key: z.string().min(1).optional(),\n }).strict().refine(ssl => Boolean(ssl.cert) === Boolean(ssl.key), {\n path: ['key'],\n }),\n])\n\n/**\n * SQLite 特定选项 Schema\n * @example\n * ```ts\n * import { SqliteOptionsSchema } from '@h-ai/reldb/schemas'\n *\n * const value = SqliteOptionsSchema.parse({ walMode: true, executor: 'worker' })\n * void value\n * ```\n */\nexport const SqliteOptionsSchema = z.object({\n /** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */\n walMode: z.boolean().default(true),\n /** 是否只读模式(默认 false) */\n readonly: z.boolean().default(false),\n /**\n * 执行方式(默认 sync)。\n *\n * - `sync`:主线程直接执行 better-sqlite3(同步阻塞主线程事件循环)。\n * - `worker`:在专用 worker 线程持有唯一连接执行,把阻塞移出主线程。\n * 注意 WAL 只改善数据库锁并发,无法避免 better-sqlite3 同步调用占用调用线程。\n */\n executor: z.enum(['sync', 'worker']).default('sync'),\n /** worker 执行方式下等待连接打开的超时(毫秒,默认 10000) */\n workerConnectTimeoutMs: z.number().int().positive().default(10_000),\n})\n\n/**\n * MySQL 特定选项 Schema\n * @example\n * ```ts\n * import { MysqlOptionsSchema } from '@h-ai/reldb/schemas'\n *\n * const value = MysqlOptionsSchema.parse({ charset: 'utf8mb4' })\n * void value\n * ```\n */\nexport const MysqlOptionsSchema = z.object({\n /** 字符集(默认 utf8mb4),推荐使用 utf8mb4 支持完整 Unicode */\n charset: z.string().default('utf8mb4'),\n}).strict()\n\n// ─── 网络数据库共享字段 ───\n\nconst NetworkConfigSchema = z.object({\n /**\n * 连接字符串(可选)\n * 如果提供 url,将优先使用,忽略 host/port 等字段\n */\n url: z.string().optional(),\n\n /** 数据库主机地址(默认 localhost) */\n host: z.string().default('localhost'),\n\n /**\n * 数据库端口\n * - PostgreSQL 默认 5432\n * - MySQL 默认 3306\n */\n port: z.number().int().min(1).max(65535).optional(),\n\n /** 数据库名称 */\n database: z.string(),\n\n /** 数据库用户名 */\n user: z.string().optional(),\n\n /** 数据库密码 */\n password: z.string().optional(),\n\n /** SSL/TLS 连接配置 */\n ssl: SslConfigSchema.optional(),\n\n /** 连接池配置 */\n pool: PoolConfigSchema.optional(),\n})\n\n// ─── 各数据库类型配置 Schema ───\n\n/**\n * SQLite 配置 Schema\n * @example\n * ```ts\n * import { SqliteConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = SqliteConfigSchema.parse({ type: 'sqlite', database: './data/app.db' })\n * void value\n * ```\n */\nexport const SqliteConfigSchema = z.object({\n type: z.literal('sqlite'),\n /** 数据库文件路径(如 './data.db')或 ':memory:'(内存数据库) */\n database: z.string(),\n /** SQLite 特定选项 */\n sqlite: SqliteOptionsSchema.optional(),\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * PostgreSQL 配置 Schema\n * @example\n * ```ts\n * import { PostgresqlConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = PostgresqlConfigSchema.parse({ type: 'postgresql', database: 'app', host: 'db', pool: { max: 10 } })\n * void value\n * ```\n */\nexport const PostgresqlConfigSchema = NetworkConfigSchema.extend({\n type: z.literal('postgresql'),\n ...OperationLogConfigFieldSchema,\n})\n\n/**\n * MySQL 配置 Schema\n * @example\n * ```ts\n * import { MysqlConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = MysqlConfigSchema.parse({ type: 'mysql', database: 'app', host: 'db', pool: { max: 10, queueLimit: 100 } })\n * void value\n * ```\n */\nexport const MysqlConfigSchema = NetworkConfigSchema.extend({\n type: z.literal('mysql'),\n /** 网络数据库URL;MySQL空串表示使用分字段,非空仅mysql://且拒绝query/fragment,优先于host/port/database/user/password */\n url: z.union([\n z.literal('').transform(() => undefined),\n z.url({ protocol: /^mysql$/ }).refine(value => !value.includes('?') && !value.includes('#')),\n ]).optional(),\n /** 数据库:SQLite文件路径或:memory:;PostgreSQL必填名称;MySQL无URL时必填,URL模式从URL读取 */\n database: z.string().min(1).optional(),\n /** TLS:PostgreSQL使用其模式;MySQL仅false或严格对象,空对象默认校验证书链和DNS身份 */\n ssl: MysqlSslConfigSchema.optional(),\n /** 连接池配置:PostgreSQL支持min/idleTimeout;MySQL仅max/acquireTimeout/queueLimit,拒绝其他字段 */\n pool: MysqlPoolConfigSchema.optional(),\n /** MySQL 特定选项 */\n mysql: MysqlOptionsSchema.optional(),\n ...OperationLogConfigFieldSchema,\n}).refine(config => Boolean(config.url) || Boolean(config.database), {\n path: ['database'],\n})\n\n/**\n * 统一数据库配置 Schema(判别联合体)\n *\n * 根据 `type` 字段区分不同数据库类型的配置。\n * @example\n * ```ts\n * import { ReldbConfigSchema } from '@h-ai/reldb/schemas'\n *\n * const value = ReldbConfigSchema.parse({ type: 'postgresql', database: 'app', host: 'db' })\n * void value\n * ```\n */\nexport const ReldbConfigSchema = z.discriminatedUnion('type', [\n SqliteConfigSchema,\n PostgresqlConfigSchema,\n MysqlConfigSchema,\n])\n\n/**\n * 数据库配置类型(判别联合体)\n * @example\n * ```ts\n * import { ReldbConfigSchema, type ReldbConfig } from '@h-ai/reldb/schemas'\n *\n * const value: ReldbConfig = ReldbConfigSchema.parse({ type: 'postgresql', database: 'app', host: 'db' })\n * void value\n * ```\n */\nexport type ReldbConfig = z.infer<typeof ReldbConfigSchema>\n\n/**\n * SQLite 配置类型\n * @example\n * ```ts\n * import { SqliteConfigSchema, type SqliteConfig } from '@h-ai/reldb/schemas'\n *\n * const value: SqliteConfig = SqliteConfigSchema.parse({ type: 'sqlite', database: './data/app.db' })\n * void value\n * ```\n */\nexport type SqliteConfig = z.infer<typeof SqliteConfigSchema>\n\n/**\n * PostgreSQL 配置类型\n * @example\n * ```ts\n * import { PostgresqlConfigSchema, type PostgresqlConfig } from '@h-ai/reldb/schemas'\n *\n * const value: PostgresqlConfig = PostgresqlConfigSchema.parse({ type: 'postgresql', database: 'app', host: 'db', pool: { max: 10 } })\n * void value\n * ```\n */\nexport type PostgresqlConfig = z.infer<typeof PostgresqlConfigSchema>\n\n/**\n * MySQL 配置类型\n * @example\n * ```ts\n * import { MysqlConfigSchema, type MysqlConfig } from '@h-ai/reldb/schemas'\n *\n * const value: MysqlConfig = MysqlConfigSchema.parse({ type: 'mysql', database: 'app', host: 'db', pool: { max: 10, queueLimit: 100 } })\n * void value\n * ```\n */\nexport type MysqlConfig = z.infer<typeof MysqlConfigSchema>\n\n/**\n * 数据库配置输入类型(用于 init 等入口)\n *\n * 说明:Zod 的 default 会让输入端字段可省略,但输出端字段为必填。\n * 因此对外 API(如 reldb.init)更适合接收 ReldbConfigInput。\n * @example\n * ```ts\n * import type { ReldbConfigInput } from '@h-ai/reldb/schemas'\n *\n * const value: ReldbConfigInput = { type: 'postgresql', host: 'db', database: 'app' }\n * void value\n * ```\n */\nexport type ReldbConfigInput = z.input<typeof ReldbConfigSchema>\n"]}
|