@h-ai/reldb 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +132 -43
- package/README.ja.md +133 -44
- package/README.md +133 -44
- package/dist/chunk-SDS2ZT3V.js +132 -0
- package/dist/chunk-SDS2ZT3V.js.map +1 -0
- package/dist/index.d.ts +586 -632
- package/dist/index.js +762 -327
- package/dist/index.js.map +1 -1
- package/dist/providers/sqlite/reldb-sqlite-worker.mjs +101 -70
- package/dist/schemas.d.ts +490 -0
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +7 -2
package/README.en.md
CHANGED
|
@@ -4,21 +4,41 @@
|
|
|
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
|
+
Connections become available only after a successful health check. Failed initialization disposes its candidate; `error.ext.cleanupPending === true` identifies cleanup that still needs retry. `reldb.close()` returns `Promise<HaiResult<void>>`, waits for initialization, and coalesces concurrent close calls. Once cleanup starts, `isInitialized` is false and configuration/operations are unavailable. A failed cleanup retains the resource: the next `close()` or `init()` retries disposal before allocating a replacement. Always inspect the close result.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const closed = await reldb.close()
|
|
15
|
+
if (!closed.success) {
|
|
16
|
+
// Retry once after addressing the cleanup failure; report another failure.
|
|
17
|
+
const retried = await reldb.close()
|
|
18
|
+
if (!retried.success)
|
|
19
|
+
throw new Error(retried.error.message)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Close waits for borrowed transactions to commit/roll back and release their handles; applications must finish transactions for shutdown to complete. SQLite WAL setup and worker handshakes are part of the owned initialization chain. PostgreSQL removes disconnected idle clients and replaces them on later queries without terminating the process; logs contain safe error identifiers rather than raw driver errors.
|
|
24
|
+
|
|
25
|
+
## Core features
|
|
26
|
+
|
|
27
|
+
- Connect to SQLite, PostgreSQL and MySQL through one API.
|
|
28
|
+
- Run parameterized SQL, manage tables/indexes and read paginated results.
|
|
29
|
+
- Organize application reads and writes with transactions, CRUD and repositories.
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
- PostgreSQL
|
|
13
|
-
- MySQL
|
|
31
|
+
## Getting started
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
### Quick start
|
|
16
34
|
|
|
17
35
|
```ts
|
|
18
36
|
import { HaiReldbError, reldb } from '@h-ai/reldb'
|
|
19
37
|
|
|
20
38
|
// 初始化
|
|
21
|
-
await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
39
|
+
const initialized = await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
40
|
+
if (!initialized.success)
|
|
41
|
+
throw new Error(initialized.error.message)
|
|
22
42
|
|
|
23
43
|
// DDL
|
|
24
44
|
await reldb.ddl.createTable('users', {
|
|
@@ -53,9 +73,20 @@ const user = await userCrud.findById(1)
|
|
|
53
73
|
await reldb.close()
|
|
54
74
|
```
|
|
55
75
|
|
|
56
|
-
`reldb.config` returns a **redacted configuration snapshot**. Database `password
|
|
76
|
+
`reldb.config` returns a **redacted configuration snapshot**. Database `password`, URL credentials and the TLS client private key `ssl.key` are hidden. Use it to inspect state, not to replay connection details.
|
|
57
77
|
|
|
58
|
-
|
|
78
|
+
### Configuration
|
|
79
|
+
|
|
80
|
+
`@h-ai/reldb/schemas` exports the same configuration schemas and types with only Zod, without loading the database singleton or drivers. CLI and browser code can validate configuration through this entry. MySQL TLS accepts `false` or a strict object; `{}` verifies the certificate chain and DNS identity by default. PostgreSQL retains its boolean, string mode and object contract.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { ReldbConfigSchema } from '@h-ai/reldb/schemas'
|
|
84
|
+
|
|
85
|
+
const checked = ReldbConfigSchema.safeParse({ type: 'mysql', database: 'shop', ssl: {} })
|
|
86
|
+
if (!checked.success)
|
|
87
|
+
throw new Error('Invalid database configuration')
|
|
88
|
+
// checked.data.ssl: { rejectUnauthorized: true, verifyIdentity: true }
|
|
89
|
+
```
|
|
59
90
|
|
|
60
91
|
Use either a connection string (`url`) or separate fields (`host/port/database/user/password`).
|
|
61
92
|
|
|
@@ -64,7 +95,7 @@ Use either a connection string (`url`) or separate fields (`host/port/database/u
|
|
|
64
95
|
await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
65
96
|
|
|
66
97
|
// PostgreSQL
|
|
67
|
-
await reldb.init({ type: 'postgresql', url: 'postgres://user:pass@localhost:5432/mydb' })
|
|
98
|
+
await reldb.init({ type: 'postgresql', url: 'postgres://user:pass@localhost:5432/mydb', database: 'mydb' })
|
|
68
99
|
|
|
69
100
|
// MySQL
|
|
70
101
|
await reldb.init({
|
|
@@ -75,10 +106,52 @@ await reldb.init({
|
|
|
75
106
|
user: 'admin',
|
|
76
107
|
password: 'secret',
|
|
77
108
|
mysql: { charset: 'utf8mb4' },
|
|
109
|
+
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 },
|
|
78
110
|
})
|
|
79
111
|
```
|
|
80
112
|
|
|
81
|
-
|
|
113
|
+
#### Connection pools and MySQL URLs
|
|
114
|
+
|
|
115
|
+
MySQL `pool` accepts only `max`, `acquireTimeout` and `queueLimit`, defaulting to 10, 10000 milliseconds and 1000 even when omitted. Unsupported pool fields such as `min` and `idleTimeout`, and the unimplemented `mysql.timezone`, fail configuration validation. Idle connections remain subject to mysql2/server disconnection and `close()`; the module promises no MySQL minimum retention or configurable idle expiry. [mysql2 3.24.5 options](https://github.com/sidorares/node-mysql2/blob/v3.24.5/typings/mysql/lib/Pool.d.ts) differ from PostgreSQL.
|
|
116
|
+
|
|
117
|
+
| Field | PostgreSQL (defaults when `pool` is provided) | MySQL |
|
|
118
|
+
| ---------------- | ------------------------------------------------------------- | --------------------------------------------------- |
|
|
119
|
+
| `max` | 10 | 10, counting borrowed and connecting slots together |
|
|
120
|
+
| `min` | 1, retention floor for established connections; no prewarming | Unsupported; rejected |
|
|
121
|
+
| `idleTimeout` | 30000 ms; 0 disables | Unsupported; rejected |
|
|
122
|
+
| `acquireTimeout` | 10000 ms; 0 disables | 10000 ms; integer from 1 to 2147483647 |
|
|
123
|
+
| `queueLimit` | Unsupported | 1000; positive integer limiting queued requests |
|
|
124
|
+
|
|
125
|
+
PostgreSQL uses pg defaults when `pool` is omitted; see [pg.Pool](https://node-postgres.com/apis/pool) for retention semantics. All MySQL SQL, DDL, CRUD, batch and transaction entry points use a bounded FIFO acquisition queue. Waiting and establishment share one deadline: expiry returns `TIMEOUT`, and a full queue returns `POOL_EXHAUSTED`. Expired waiters are removed; late connections are released without executing SQL and retain their capacity slot until settled. The deadline is checked on delivery and before starting work. It does not limit SQL or transactions that have already started.
|
|
126
|
+
|
|
127
|
+
MySQL `close()` rejects new and queued requests immediately, then waits for borrowed connections, transactions and pending establishment before ending the pool. The driver uses the same value for `connectTimeout`; shutdown may wait for that timeout or existing work to finish. Applications must finish transactions. Environment variables use `HAI_DB_POOL_*`: `MIN` and `IDLETIMEOUT` apply only to PostgreSQL; MySQL `QUEUELIMIT` bounds queued requests.
|
|
128
|
+
|
|
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
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const initialized = await reldb.init({ type: 'mysql', url: process.env.HAI_DB_URL, pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 } })
|
|
133
|
+
if (!initialized.success)
|
|
134
|
+
throw new Error(initialized.error.message)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
An empty MySQL `url` (including an empty environment placeholder) selects host/port/database/user/password; nonempty URLs remain strictly validated.
|
|
138
|
+
|
|
139
|
+
#### MySQL TLS
|
|
140
|
+
|
|
141
|
+
Omitting `ssl` or using `false` disables TLS. Enabling it requires a strict object; an empty object defaults to `{ rejectUnauthorized: true, verifyIdentity: true }`. `ca` contains trusted CA PEM text, or Node's trust store is used when omitted. Optional client `cert` and `key` must contain PEM text and be provided together. Unknown TLS fields, `true` and PostgreSQL string modes are rejected; arbitrary options and callbacks are not forwarded.
|
|
142
|
+
|
|
143
|
+
[mysql2 3.24.5 identity checks](https://github.com/sidorares/node-mysql2/blob/v3.24.5/lib/base/connection.js) depend on a DNS servername. With both certificate and identity checks enabled, IP targets return `CONFIG_ERROR`; use a hostname matching the certificate DNS SAN. Explicit `verifyIdentity: false` retains only chain verification. `rejectUnauthorized: false` abandons server authentication; encryption alone does not authenticate the server. Disabling either check emits a fixed risk warning and is not reported as a secure configuration.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// Supply trustedCaPem from deployment trust configuration, not an arbitrary peer certificate.
|
|
147
|
+
const initialized = await reldb.init({ type: 'mysql', host: 'db.example.com', database: 'app', ssl: { ca: trustedCaPem } })
|
|
148
|
+
if (!initialized.success)
|
|
149
|
+
throw new Error(initialized.error.message)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
TLS tests require OpenSSL (Git for Windows includes one) and local container port forwarding. Each run generates and removes its own CA/private keys and checks trusted DNS, wrong CA/hostname rejection, `REQUIRE X509` client authentication and actual session `Ssl_cipher`. Disabling verification is not accepted as proof of authenticated TLS.
|
|
153
|
+
|
|
154
|
+
#### SQLite execution (asynchronous worker)
|
|
82
155
|
|
|
83
156
|
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
157
|
|
|
@@ -96,7 +169,7 @@ await reldb.init({
|
|
|
96
169
|
|
|
97
170
|
In worker mode, queries, writes, and transactions run serially on the worker thread. Public APIs and return values remain identical to synchronous mode.
|
|
98
171
|
|
|
99
|
-
|
|
172
|
+
#### Operation logging
|
|
100
173
|
|
|
101
174
|
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
175
|
|
|
@@ -107,7 +180,7 @@ database: ./data/app.db
|
|
|
107
180
|
operationLog:
|
|
108
181
|
read: false # query / get / queryPage
|
|
109
182
|
write: false # DDL, execute / batch, tx.begin / tx.commit / tx.rollback
|
|
110
|
-
maxLength: 1000 #
|
|
183
|
+
maxLength: 1000 # maximum numeric summary length; 0 omits the summary, SQL text and values are never logged
|
|
111
184
|
level: debug # info | debug | trace,默认 debug
|
|
112
185
|
```
|
|
113
186
|
|
|
@@ -127,11 +200,13 @@ await reldb.init({
|
|
|
127
200
|
})
|
|
128
201
|
```
|
|
129
202
|
|
|
130
|
-
|
|
203
|
+
Operation logs contain only fixed operation names and numeric SQL/parameter/batch counts. SQL text, values and driver error content are excluded, including failure paths.
|
|
204
|
+
|
|
205
|
+
### Transactions
|
|
131
206
|
|
|
132
207
|
Three approaches:
|
|
133
208
|
|
|
134
|
-
|
|
209
|
+
#### wrap (automatic commit/rollback)
|
|
135
210
|
|
|
136
211
|
```ts
|
|
137
212
|
const result = await reldb.tx.wrap(async (tx) => {
|
|
@@ -142,7 +217,7 @@ const result = await reldb.tx.wrap(async (tx) => {
|
|
|
142
217
|
// 回调正常返回 → 自动提交;抛异常 → 自动回滚
|
|
143
218
|
```
|
|
144
219
|
|
|
145
|
-
|
|
220
|
+
#### begin + commit (explicit commit)
|
|
146
221
|
|
|
147
222
|
```ts
|
|
148
223
|
const txResult = await reldb.tx.begin()
|
|
@@ -154,7 +229,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户2'])
|
|
|
154
229
|
await tx.commit()
|
|
155
230
|
```
|
|
156
231
|
|
|
157
|
-
|
|
232
|
+
#### begin + rollback (explicit rollback)
|
|
158
233
|
|
|
159
234
|
```ts
|
|
160
235
|
const txResult = await reldb.tx.begin()
|
|
@@ -166,7 +241,7 @@ await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
|
|
|
166
241
|
await tx.rollback()
|
|
167
242
|
```
|
|
168
243
|
|
|
169
|
-
|
|
244
|
+
#### batch / queryPage inside transactions
|
|
170
245
|
|
|
171
246
|
```ts
|
|
172
247
|
await reldb.tx.wrap(async (tx) => {
|
|
@@ -182,7 +257,7 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
182
257
|
})
|
|
183
258
|
```
|
|
184
259
|
|
|
185
|
-
|
|
260
|
+
### Crud
|
|
186
261
|
|
|
187
262
|
Create a single-table CRUD repository with `reldb.crud.table()`, without writing SQL:
|
|
188
263
|
|
|
@@ -197,7 +272,7 @@ const userCrud = reldb.crud.table<{ id: number, name: string, email: string }>({
|
|
|
197
272
|
})
|
|
198
273
|
```
|
|
199
274
|
|
|
200
|
-
|
|
275
|
+
#### create / createMany
|
|
201
276
|
|
|
202
277
|
```ts
|
|
203
278
|
// 单条插入
|
|
@@ -211,7 +286,7 @@ await userCrud.createMany([
|
|
|
211
286
|
])
|
|
212
287
|
```
|
|
213
288
|
|
|
214
|
-
|
|
289
|
+
#### createOrUpdate
|
|
215
290
|
|
|
216
291
|
Update when the primary key exists; otherwise create (upsert):
|
|
217
292
|
|
|
@@ -230,7 +305,7 @@ await userCrud.createOrUpdate({ id: 1, name: '张三', email: 'test@example.com'
|
|
|
230
305
|
|
|
231
306
|
`BaseReldbCrudRepository` obtains `dbType` automatically; no manual argument is needed.
|
|
232
307
|
|
|
233
|
-
|
|
308
|
+
#### findById / getById / findAll / findPage
|
|
234
309
|
|
|
235
310
|
```ts
|
|
236
311
|
// 主键查找(不存在时返回 null)
|
|
@@ -260,14 +335,14 @@ const page = await userCrud.findPage({
|
|
|
260
335
|
// page.data → { items: [...], total: 100, page: 1, pageSize: 20 }
|
|
261
336
|
```
|
|
262
337
|
|
|
263
|
-
|
|
338
|
+
#### updateById / deleteById
|
|
264
339
|
|
|
265
340
|
```ts
|
|
266
341
|
await userCrud.updateById(1, { name: '李四' })
|
|
267
342
|
await userCrud.deleteById(1)
|
|
268
343
|
```
|
|
269
344
|
|
|
270
|
-
|
|
345
|
+
#### count / exists / existsById
|
|
271
346
|
|
|
272
347
|
```ts
|
|
273
348
|
const total = await userCrud.count({ where: 'name LIKE ?', params: ['%张%'] })
|
|
@@ -280,7 +355,7 @@ const found = await userCrud.existsById(1)
|
|
|
280
355
|
// found.data → true
|
|
281
356
|
```
|
|
282
357
|
|
|
283
|
-
|
|
358
|
+
#### Crud inside transactions
|
|
284
359
|
|
|
285
360
|
All methods accept `tx` and automatically route operations to that transaction context:
|
|
286
361
|
|
|
@@ -292,10 +367,12 @@ await reldb.tx.wrap(async (tx) => {
|
|
|
292
367
|
})
|
|
293
368
|
```
|
|
294
369
|
|
|
295
|
-
|
|
370
|
+
### BaseReldbCrudRepository
|
|
296
371
|
|
|
297
372
|
Business repositories extend `BaseReldbCrudRepository` and define field mappings with `fields` for automatic table creation and type conversion:
|
|
298
373
|
|
|
374
|
+
Use `BIGINT` for Unix milliseconds (64-bit INTEGER on SQLite, BIGINT on PostgreSQL/MySQL). A regular INTEGER on PostgreSQL/MySQL cannot hold current epoch milliseconds. Raw SQL/CRUD and base repository reads preserve driver BIGINT types: number/string/bigint. Business repositories requiring number must validate safe integers before conversion to avoid silent precision loss. The base repository reads TIMESTAMP fields as Date.
|
|
375
|
+
|
|
299
376
|
```ts
|
|
300
377
|
import type { DmlWithTxOperations, ReldbCrudFieldDefinition } from '@h-ai/reldb'
|
|
301
378
|
import { BaseReldbCrudRepository, reldb } from '@h-ai/reldb'
|
|
@@ -359,7 +436,7 @@ if (txResult.success) {
|
|
|
359
436
|
}
|
|
360
437
|
```
|
|
361
438
|
|
|
362
|
-
|
|
439
|
+
### JSON path operations
|
|
363
440
|
|
|
364
441
|
Use `reldb.json` to build SQL expressions for consistent JSON path operations across databases.
|
|
365
442
|
|
|
@@ -375,7 +452,7 @@ Paths follow SQL/JSON Path (`$.key` / `$.key.subkey` / `$[0]`); each backend map
|
|
|
375
452
|
|
|
376
453
|
All methods return `{ sql, params }` fragments that can be embedded in `reldb.sql.query` / `reldb.sql.execute`.
|
|
377
454
|
|
|
378
|
-
|
|
455
|
+
#### extract — read a JSON field value
|
|
379
456
|
|
|
380
457
|
```ts
|
|
381
458
|
// 用于 WHERE 条件
|
|
@@ -393,7 +470,7 @@ const result = await reldb.sql.query(
|
|
|
393
470
|
)
|
|
394
471
|
```
|
|
395
472
|
|
|
396
|
-
|
|
473
|
+
#### set — set a JSON path (create or replace)
|
|
397
474
|
|
|
398
475
|
```ts
|
|
399
476
|
const { sql, params } = reldb.json.set('settings', '$.theme', 'dark')
|
|
@@ -403,7 +480,7 @@ await reldb.sql.execute(
|
|
|
403
480
|
)
|
|
404
481
|
```
|
|
405
482
|
|
|
406
|
-
|
|
483
|
+
#### insert — insert at a JSON path (only when absent)
|
|
407
484
|
|
|
408
485
|
```ts
|
|
409
486
|
const { sql, params } = reldb.json.insert('data', '$.firstSeen', new Date().toISOString())
|
|
@@ -413,7 +490,7 @@ await reldb.sql.execute(
|
|
|
413
490
|
)
|
|
414
491
|
```
|
|
415
492
|
|
|
416
|
-
|
|
493
|
+
#### remove — remove a JSON path
|
|
417
494
|
|
|
418
495
|
```ts
|
|
419
496
|
const { sql, params } = reldb.json.remove('settings', '$.deprecated')
|
|
@@ -423,7 +500,7 @@ await reldb.sql.execute(
|
|
|
423
500
|
)
|
|
424
501
|
```
|
|
425
502
|
|
|
426
|
-
|
|
503
|
+
#### merge — merge JSON objects (RFC 7396 Merge Patch)
|
|
427
504
|
|
|
428
505
|
```ts
|
|
429
506
|
// 覆盖已有键、保留未提及键、将 null 键删除
|
|
@@ -437,7 +514,9 @@ await reldb.sql.execute(
|
|
|
437
514
|
)
|
|
438
515
|
```
|
|
439
516
|
|
|
440
|
-
|
|
517
|
+
`json.merge(column, patch)` implements RFC 7396 object patches on all three backends: preserve untouched nested keys, delete keys targeted by null, and replace arrays as a whole. PostgreSQL applies parameterized steps in a recursive CTE within one SQL statement, with no installed function or application read-modify-write. The patch root must be a JSON-serializable object; arbitrary JSON patch roots are outside this contract. Column expressions are developer-owned SQL; paths and values remain parameters. See [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) and [PostgreSQL JSON operators](https://www.postgresql.org/docs/16/functions-json.html).
|
|
518
|
+
|
|
519
|
+
#### Complete example
|
|
441
520
|
|
|
442
521
|
```ts
|
|
443
522
|
// 建表(含 JSON 列)
|
|
@@ -472,7 +551,7 @@ const { sql: rmSql, params: rmParams } = reldb.json.remove('meta', '$.reviewer')
|
|
|
472
551
|
await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...rmParams, 1])
|
|
473
552
|
```
|
|
474
553
|
|
|
475
|
-
|
|
554
|
+
### Error handling
|
|
476
555
|
|
|
477
556
|
All operations return `HaiResult<T>`; check `result.success` for success or failure.
|
|
478
557
|
|
|
@@ -497,17 +576,25 @@ if (!result.success) {
|
|
|
497
576
|
|
|
498
577
|
Common error codes:
|
|
499
578
|
|
|
500
|
-
| Error code | code | Description
|
|
501
|
-
| ------------------------------------ | --------------- |
|
|
502
|
-
| `HaiReldbError.NOT_INITIALIZED` | `hai:reldb:010` | Not initialized
|
|
503
|
-
| `HaiReldbError.CONNECTION_FAILED` | `hai:reldb:001` | Connection failed
|
|
504
|
-
| `HaiReldbError.QUERY_FAILED` | `hai:reldb:002` | Query failed
|
|
505
|
-
| `HaiReldbError.CONSTRAINT_VIOLATION` | `hai:reldb:003` | Constraint violation
|
|
506
|
-
| `HaiReldbError.TRANSACTION_FAILED` | `hai:reldb:004` | Transaction failed
|
|
507
|
-
| `HaiReldbError.DDL_FAILED` | `hai:reldb:020` | DDL operation failed
|
|
508
|
-
| `HaiReldbError.CONFIG_ERROR` | `hai:reldb:013` | Invalid configuration
|
|
579
|
+
| Error code | code | Description |
|
|
580
|
+
| ------------------------------------ | --------------- | ----------------------------------- |
|
|
581
|
+
| `HaiReldbError.NOT_INITIALIZED` | `hai:reldb:010` | Not initialized |
|
|
582
|
+
| `HaiReldbError.CONNECTION_FAILED` | `hai:reldb:001` | Connection failed |
|
|
583
|
+
| `HaiReldbError.QUERY_FAILED` | `hai:reldb:002` | Query failed |
|
|
584
|
+
| `HaiReldbError.CONSTRAINT_VIOLATION` | `hai:reldb:003` | Constraint violation |
|
|
585
|
+
| `HaiReldbError.TRANSACTION_FAILED` | `hai:reldb:004` | Transaction failed |
|
|
586
|
+
| `HaiReldbError.DDL_FAILED` | `hai:reldb:020` | DDL operation failed |
|
|
587
|
+
| `HaiReldbError.CONFIG_ERROR` | `hai:reldb:013` | Invalid configuration |
|
|
588
|
+
| `HaiReldbError.TIMEOUT` | `hai:reldb:009` | MySQL acquisition deadline exceeded |
|
|
589
|
+
| `HaiReldbError.POOL_EXHAUSTED` | `hai:reldb:011` | MySQL acquisition queue is full |
|
|
509
590
|
|
|
510
|
-
|
|
591
|
+
Custom SQL methods in `BaseReldbCrudRepository` subclasses must check `await this.ensureReady()`. Construction starts table creation; service init must await candidate readiness before publication. PostgreSQL concurrent first `CREATE TABLE IF NOT EXISTS` retries the same DDL once only for `23505/pg_type_typname_nsp_index` catalog contention; other errors remain failures.
|
|
592
|
+
|
|
593
|
+
## Production shared state
|
|
594
|
+
|
|
595
|
+
Production requires PostgreSQL/MySQL. SQLite, including disk files, is for development/explicit tests and does not provide cross-host shared state. Initialization checks apply when NODE_ENV or HAI_CORE_ENV is production; only explicit HAI_E2E=1/HAI_E2E_MOCK=1 tests are exempt. [State, concurrency and recovery (Chinese)](../../.agents/instructions/distributed-state.md)。
|
|
596
|
+
|
|
597
|
+
## Development and testing
|
|
511
598
|
|
|
512
599
|
```bash
|
|
513
600
|
pnpm --filter @h-ai/reldb test
|
|
@@ -515,6 +602,8 @@ pnpm --filter @h-ai/reldb test
|
|
|
515
602
|
|
|
516
603
|
> MySQL/PostgreSQL tests require Docker.
|
|
517
604
|
|
|
605
|
+
Source JSDoc follows the [shared conventions](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证): public examples use published entry points, and internal examples are checked in their module scope. After building, run `pnpm check:jsdoc --package reldb`; compiling examples does not replace backend behavior tests.
|
|
606
|
+
|
|
518
607
|
## License
|
|
519
608
|
|
|
520
609
|
Apache-2.0
|