@h-ai/reldb 1.0.0-beta.2 → 1.0.0-beta.20
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 +295 -143
- package/README.ja.md +295 -143
- package/README.md +295 -143
- 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.ja.md
CHANGED
|
@@ -4,33 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/core](../core/README.ja.md)
|
|
6
6
|
|
|
7
|
-
## 概要
|
|
8
|
-
|
|
9
7
|
共通の `reldb` オブジェクトで SQLite、PostgreSQL、MySQL を操作するリレーショナルデータベースモジュールです。
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
const closed = await reldb.close()
|
|
15
|
-
if (!closed.success) {
|
|
16
|
-
// 障害への対処後に一度再試行し、再失敗はアプリへ報告します。
|
|
17
|
-
const retried = await reldb.close()
|
|
18
|
-
if (!retried.success)
|
|
19
|
-
throw new Error(retried.error.message)
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
終了処理は既存トランザクションのコミット/ロールバックと解放を待ちます。アプリはトランザクションを終了させてください。SQLite の WAL 設定と worker のハンドシェイクも初期化リソースとして管理します。PostgreSQL の切断済みアイドル接続はプールが除去し、次のクエリで置き換えます。プロセスは終了せず、ログには安全なエラー識別子のみを記録します。
|
|
24
|
-
|
|
25
|
-
## 主な機能
|
|
26
|
-
|
|
27
|
-
- 共通 API で SQLite・PostgreSQL・MySQL に接続。
|
|
28
|
-
- パラメーター付き SQL、テーブル・索引管理、ページ取得。
|
|
29
|
-
- トランザクション、CRUD、Repository による業務の読み書き。
|
|
30
|
-
|
|
31
|
-
## 使い始める
|
|
32
|
-
|
|
33
|
-
### クイックスタート
|
|
9
|
+
## クイックスタート
|
|
34
10
|
|
|
35
11
|
```ts
|
|
36
12
|
import { HaiReldbError, reldb } from '@h-ai/reldb'
|
|
@@ -75,135 +51,110 @@ await reldb.close()
|
|
|
75
51
|
|
|
76
52
|
`reldb.config` は**機密情報をマスクした設定スナップショット**を返します。DB の `password`、URL 内の認証情報、TLS クライアント秘密鍵 `ssl.key` を非表示にします。状態確認用であり、元の接続情報の再利用には使わないでください。
|
|
77
53
|
|
|
78
|
-
|
|
54
|
+
## 機能
|
|
79
55
|
|
|
80
|
-
|
|
56
|
+
接続はヘルスチェック成功後に公開します。初期化失敗時は候補リソースを解放し、解放にも失敗した場合は初期化エラーの `error.ext.cleanupPending` が `true` になります。`reldb.close()` は `Promise<HaiResult<void>>` を返し、初期化を待機して並行する終了処理をまとめます。解放開始後は `isInitialized` が `false` となり、設定と操作は利用できません。解放に失敗したリソースは保持され、次の `close()` または `init()` で再試行します。解放が成功するまで代替接続は作成しません。終了結果を必ず確認してください。
|
|
81
57
|
|
|
82
58
|
```ts
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
59
|
+
const closed = await reldb.close()
|
|
60
|
+
if (!closed.success) {
|
|
61
|
+
// 障害への対処後に一度再試行し、再失敗はアプリへ報告します。
|
|
62
|
+
const retried = await reldb.close()
|
|
63
|
+
if (!retried.success)
|
|
64
|
+
throw new Error(retried.error.message)
|
|
65
|
+
}
|
|
89
66
|
```
|
|
90
67
|
|
|
91
|
-
|
|
68
|
+
終了処理は既存トランザクションのコミット/ロールバックと解放を待ちます。アプリはトランザクションを終了させてください。SQLite の WAL 設定と worker のハンドシェイクも初期化リソースとして管理します。PostgreSQL の切断済みアイドル接続はプールが除去し、次のクエリで置き換えます。プロセスは終了せず、ログには安全なエラー識別子のみを記録します。
|
|
92
69
|
|
|
93
|
-
|
|
94
|
-
// SQLite
|
|
95
|
-
await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
70
|
+
### 操作と入出力
|
|
96
71
|
|
|
97
|
-
|
|
98
|
-
await reldb.init({ type: 'postgresql', url: 'postgres://user:pass@localhost:5432/mydb', database: 'mydb' })
|
|
72
|
+
本節の完全な初期化手順を先に実行します。例の業務値はホストが検証・認可し、非同期領域結果は success を確認してから data を読みます。void はデータなし完了、null は不存在、同期ファクトリー/ツールは表の型を直接返します。
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
await reldb.init({
|
|
102
|
-
type: 'mysql',
|
|
103
|
-
host: 'localhost',
|
|
104
|
-
port: 3306,
|
|
105
|
-
database: 'mydb',
|
|
106
|
-
user: 'admin',
|
|
107
|
-
password: 'secret',
|
|
108
|
-
mysql: { charset: 'utf8mb4' },
|
|
109
|
-
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 },
|
|
110
|
-
})
|
|
111
|
-
```
|
|
74
|
+
表・列・索引・SQL は信頼するコード、params は業務値の束縛、steps は移行手順、options は事前確認/ページ指定。
|
|
112
75
|
|
|
113
|
-
####
|
|
76
|
+
#### reldb.ddl
|
|
114
77
|
|
|
115
|
-
|
|
78
|
+
テーブル・列・索引を管理。信頼する識別子、定義または DDL を渡し、成功は void、構造エラーは失敗。
|
|
116
79
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
80
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
81
|
+
| ----------------------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------ |
|
|
82
|
+
| `reldb.ddl.createTable` | tableName, columns, ifNotExists | `void` | `await reldb.ddl.createTable('orders', { id: { type: 'TEXT', primaryKey: true } })` |
|
|
83
|
+
| `reldb.ddl.dropTable` | tableName, ifExists | `void` | `await reldb.ddl.dropTable('orders_archive', true)` |
|
|
84
|
+
| `reldb.ddl.addColumn` | tableName, columnName, columnDef | `void` | `await reldb.ddl.addColumn('orders', 'note', { type: 'TEXT' })` |
|
|
85
|
+
| `reldb.ddl.dropColumn` | tableName, columnName | `void` | `await reldb.ddl.dropColumn('orders', 'note')` |
|
|
86
|
+
| `reldb.ddl.renameTable` | oldName, newName | `void` | `await reldb.ddl.renameTable('orders_draft', 'orders')` |
|
|
87
|
+
| `reldb.ddl.createIndex` | tableName, indexName, indexDef | `void` | `await reldb.ddl.createIndex('orders', 'orders_status', { columns: ['status'] })` |
|
|
88
|
+
| `reldb.ddl.dropIndex` | indexName, ifExists | `void` | `await reldb.ddl.dropIndex('orders_status', true)` |
|
|
89
|
+
| `reldb.ddl.raw` | sql | `void` | `await reldb.ddl.raw('CREATE TABLE IF NOT EXISTS app_marker (id TEXT PRIMARY KEY)')` |
|
|
124
90
|
|
|
125
|
-
|
|
91
|
+
#### reldb.sql
|
|
126
92
|
|
|
127
|
-
|
|
93
|
+
params で値を束縛して業務データを操作。順に行配列、先頭行/null、書込結果、void、ページを返す。batch は文の配列。
|
|
128
94
|
|
|
129
|
-
|
|
95
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
96
|
+
| --------------------- | ----------- | -------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `reldb.sql.query` | sql, params | `T[]` | `await reldb.sql.query('SELECT id FROM orders WHERE status = ?', ['pending'])` |
|
|
98
|
+
| `reldb.sql.get` | sql, params | `T \| null` | `await reldb.sql.get('SELECT id FROM orders WHERE id = ?', ['o1'])` |
|
|
99
|
+
| `reldb.sql.execute` | sql, params | `ExecuteResult` | `await reldb.sql.execute('UPDATE orders SET status = ? WHERE id = ?', ['paid', 'o1'])` |
|
|
100
|
+
| `reldb.sql.batch` | statements | `void` | `await reldb.sql.batch([{ sql: 'DELETE FROM orders WHERE id = ?', params: ['o1'] }])` |
|
|
101
|
+
| `reldb.sql.queryPage` | options | `PaginatedResult<T>` | `await reldb.sql.queryPage({ sql: 'SELECT id FROM orders ORDER BY id', pagination: { page: 1, pageSize: 20 } })` |
|
|
130
102
|
|
|
131
|
-
|
|
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
|
-
```
|
|
103
|
+
#### reldb.pagination
|
|
136
104
|
|
|
137
|
-
|
|
105
|
+
ページ処理。normalize はページ指定と上限から正規化値、build は items・total・ページ指定からページオブジェクトを直接返す。
|
|
138
106
|
|
|
139
|
-
|
|
107
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
108
|
+
| ---------------------------- | ------------------------ | ---------------------- | ---------------------------------------------------------- |
|
|
109
|
+
| `reldb.pagination.normalize` | options, overrides | `NormalizedPagination` | `reldb.pagination.normalize({ page: 1, pageSize: 20 })` |
|
|
110
|
+
| `reldb.pagination.build` | items, total, pagination | `PaginatedResult<T>` | `reldb.pagination.build([], 0, { page: 1, pageSize: 20 })` |
|
|
140
111
|
|
|
141
|
-
|
|
112
|
+
#### reldb
|
|
142
113
|
|
|
143
|
-
|
|
114
|
+
永続化の準備。テーブル名から列名、モジュール名・手順・dryRun から移行計画を取得。適用前にバックアップし競合書込を停止。
|
|
144
115
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
throw new Error(initialized.error.message)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
TLS テストには OpenSSL(Windows は Git 同梱版も可)とローカルのコンテナー転送が必要です。各実行で CA/秘密鍵を生成・削除し、信頼済み DNS、誤った CA/主機名の拒否、`REQUIRE X509` 認証、実際の `Ssl_cipher` を検証します。検証無効の接続を認証済み TLS の証拠にはしません。
|
|
116
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
117
|
+
| --------------------- | ---------------------- | --------------------- | ----------------------------------------------------------------------- |
|
|
118
|
+
| `reldb.schemaColumns` | table, sql | `string[]` | `await reldb.schemaColumns('orders')` |
|
|
119
|
+
| `reldb.migrateSchema` | module, steps, options | `SchemaMigrationPlan` | `await reldb.migrateSchema('orders', migrationSteps, { dryRun: true })` |
|
|
153
120
|
|
|
154
|
-
####
|
|
121
|
+
#### reldb.tx
|
|
155
122
|
|
|
156
|
-
|
|
123
|
+
原子的な書込。begin は query/get/execute/batch/queryPage/commit/rollback を持つハンドル、wrap はコールバックの結果を返す。失敗 HaiResult は明示的にロールバックする。
|
|
157
124
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
sqlite: {
|
|
163
|
-
executor: 'worker', // 'sync'(默认)| 'worker'
|
|
164
|
-
walMode: true, // 默认 true
|
|
165
|
-
workerConnectTimeoutMs: 10000, // worker 打开连接超时,默认 10000
|
|
166
|
-
},
|
|
167
|
-
})
|
|
168
|
-
```
|
|
125
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
126
|
+
| ---------------- | -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
|
+
| `reldb.tx.begin` | 引数なし | `DmlWithTxOperations` | `await reldb.tx.begin()` |
|
|
128
|
+
| `reldb.tx.wrap` | fn | `T` | `await reldb.tx.wrap(async tx => { const found = await tx.get('SELECT 1 AS value'); if (!found.success) throw new Error(found.error.message); return found.data })` |
|
|
169
129
|
|
|
170
|
-
|
|
130
|
+
#### reldb.crud
|
|
171
131
|
|
|
172
|
-
|
|
132
|
+
業務エンティティのテーブル・フィールド対応から CRUD とページ処理のリポジトリを直接作成。DB 接続は露出しない。
|
|
173
133
|
|
|
174
|
-
|
|
134
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
135
|
+
| ------------------ | -------- | ---------------------------- | ----------------------------------------- |
|
|
136
|
+
| `reldb.crud.table` | config | `ReldbCrudRepository<TItem>` | `reldb.crud.table(orderRepositoryConfig)` |
|
|
175
137
|
|
|
176
|
-
|
|
177
|
-
# config/_db.yml
|
|
178
|
-
type: sqlite
|
|
179
|
-
database: ./data/app.db
|
|
180
|
-
operationLog:
|
|
181
|
-
read: false # query / get / queryPage
|
|
182
|
-
write: false # DDL, execute / batch, tx.begin / tx.commit / tx.rollback
|
|
183
|
-
maxLength: 1000 # 安全な件数サマリーの最大長。0は省略。SQL本文と値は記録しない
|
|
184
|
-
level: debug # info | debug | trace,默认 debug
|
|
185
|
-
```
|
|
138
|
+
#### reldb.json
|
|
186
139
|
|
|
187
|
-
|
|
188
|
-
`HAI_DB_DATABASE`、`HAI_DB_OPERATIONLOG_LEVEL` などの規約に沿う変数が YAML より優先されます。
|
|
140
|
+
方言共通の JSON 式。信頼する列名、JSON パス、値/patch から SQL 式と束縛値を直接作成し、ここでは実行しない。
|
|
189
141
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
maxLength: 500,
|
|
198
|
-
level: 'debug',
|
|
199
|
-
},
|
|
200
|
-
})
|
|
201
|
-
```
|
|
142
|
+
| 入口 | 入力引数 | 成功結果 | 呼出し例 |
|
|
143
|
+
| -------------------- | ------------------- | ------------- | --------------------------------------------------- |
|
|
144
|
+
| `reldb.json.extract` | column, path | `JsonSqlExpr` | `reldb.json.extract('payload', '$.status')` |
|
|
145
|
+
| `reldb.json.set` | column, path, value | `JsonSqlExpr` | `reldb.json.set('payload', '$.status', 'paid')` |
|
|
146
|
+
| `reldb.json.insert` | column, path, value | `JsonSqlExpr` | `reldb.json.insert('payload', '$.note', 'checked')` |
|
|
147
|
+
| `reldb.json.remove` | column, path | `JsonSqlExpr` | `reldb.json.remove('payload', '$.note')` |
|
|
148
|
+
| `reldb.json.merge` | column, patch | `JsonSqlExpr` | `reldb.json.merge('payload', { status: 'paid' })` |
|
|
202
149
|
|
|
203
|
-
|
|
150
|
+
- 共通 API で SQLite・PostgreSQL・MySQL に接続。
|
|
151
|
+
- パラメーター付き SQL、テーブル・索引管理、ページ取得。
|
|
152
|
+
- トランザクション、CRUD、Repository による業務の読み書き。
|
|
204
153
|
|
|
205
154
|
### トランザクション
|
|
206
155
|
|
|
156
|
+
トランザクションの終了処理を所有できるのは一つの操作だけです。commit または rollback の開始後、新規クエリ、重複 commit、並行 rollback は直ちに TRANSACTION_FAILED を返し、接続は一度だけ返却します。復旧目的で並行終了を試みず、最初の終了結果を待ってください。
|
|
157
|
+
|
|
207
158
|
3 種類の使用方法:
|
|
208
159
|
|
|
209
160
|
#### wrap(自動コミット/ロールバック)
|
|
@@ -272,8 +223,14 @@ if (!rolledBack.success)
|
|
|
272
223
|
```ts
|
|
273
224
|
await reldb.tx.wrap(async (tx) => {
|
|
274
225
|
const inserted = await tx.batch([
|
|
275
|
-
{
|
|
276
|
-
|
|
226
|
+
{
|
|
227
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
228
|
+
params: ['Alice']
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
sql: 'INSERT INTO users (name) VALUES (?)',
|
|
232
|
+
params: ['Bob']
|
|
233
|
+
},
|
|
277
234
|
])
|
|
278
235
|
if (!inserted.success)
|
|
279
236
|
throw new Error(inserted.error.message, { cause: inserted.error })
|
|
@@ -376,10 +333,16 @@ await userCrud.deleteById(1)
|
|
|
376
333
|
#### count / exists / existsById
|
|
377
334
|
|
|
378
335
|
```ts
|
|
379
|
-
const total = await userCrud.count({
|
|
336
|
+
const total = await userCrud.count({
|
|
337
|
+
where: 'name LIKE ?',
|
|
338
|
+
params: ['%张%']
|
|
339
|
+
})
|
|
380
340
|
// total.data → 5
|
|
381
341
|
|
|
382
|
-
const has = await userCrud.exists({
|
|
342
|
+
const has = await userCrud.exists({
|
|
343
|
+
where: 'email = ?',
|
|
344
|
+
params: ['test@example.com']
|
|
345
|
+
})
|
|
383
346
|
// has.data → true
|
|
384
347
|
|
|
385
348
|
const found = await userCrud.existsById(1)
|
|
@@ -426,11 +389,46 @@ interface UserRow {
|
|
|
426
389
|
}
|
|
427
390
|
|
|
428
391
|
const USER_FIELDS: ReldbCrudFieldDefinition[] = [
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
392
|
+
{
|
|
393
|
+
fieldName: 'id',
|
|
394
|
+
columnName: 'id',
|
|
395
|
+
def: { type: 'INTEGER', primaryKey: true, autoIncrement: true },
|
|
396
|
+
select: true,
|
|
397
|
+
create: false,
|
|
398
|
+
update: false
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
fieldName: 'name',
|
|
402
|
+
columnName: 'name',
|
|
403
|
+
def: { type: 'TEXT', notNull: true },
|
|
404
|
+
select: true,
|
|
405
|
+
create: true,
|
|
406
|
+
update: true
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
fieldName: 'email',
|
|
410
|
+
columnName: 'email',
|
|
411
|
+
def: { type: 'TEXT', notNull: true },
|
|
412
|
+
select: true,
|
|
413
|
+
create: true,
|
|
414
|
+
update: true
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
fieldName: 'createdAt',
|
|
418
|
+
columnName: 'created_at',
|
|
419
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
420
|
+
select: true,
|
|
421
|
+
create: true,
|
|
422
|
+
update: false
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
fieldName: 'updatedAt',
|
|
426
|
+
columnName: 'updated_at',
|
|
427
|
+
def: { type: 'TIMESTAMP', notNull: true },
|
|
428
|
+
select: true,
|
|
429
|
+
create: true,
|
|
430
|
+
update: false
|
|
431
|
+
},
|
|
434
432
|
]
|
|
435
433
|
|
|
436
434
|
class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
@@ -440,7 +438,11 @@ class UserRepository extends BaseReldbCrudRepository<UserRow> {
|
|
|
440
438
|
|
|
441
439
|
/** 自定义查询方法 */
|
|
442
440
|
async findByEmail(email: string) {
|
|
443
|
-
return this.findAll({
|
|
441
|
+
return this.findAll({
|
|
442
|
+
where: 'email = ?',
|
|
443
|
+
params: [email],
|
|
444
|
+
limit: 1
|
|
445
|
+
})
|
|
444
446
|
}
|
|
445
447
|
|
|
446
448
|
/** 自定义方法中使用 this.sql(tx) 自动路由到事务 */
|
|
@@ -554,7 +556,7 @@ await reldb.sql.execute(
|
|
|
554
556
|
)
|
|
555
557
|
```
|
|
556
558
|
|
|
557
|
-
`json.merge(column, patch)` は三つのバックエンドで RFC 7396 のオブジェクトパッチを実装します。未変更の入れ子キーを保持し、null が指定したキーを削除し、配列を全体置換します。PostgreSQL はパラメーター化した再帰 CTE を単一 SQL 内で適用し、関数の事前インストールやアプリ側の読み取り後の書き戻しを必要としません。パッチのルートは JSON シリアライズ可能なオブジェクトに限ります。列式は開発者の静的 SQL で、パスと値はパラメーターです。[RFC 7396](https://www.rfc-editor.org/rfc/rfc7396)・[PostgreSQL JSON 演算](https://www.postgresql.org/docs/16/functions-json.html)
|
|
559
|
+
`json.merge(column, patch)` は三つのバックエンドで RFC 7396 のオブジェクトパッチを実装します。未変更の入れ子キーを保持し、null が指定したキーを削除し、配列を全体置換します。PostgreSQL はパラメーター化した再帰 CTE を単一 SQL 内で適用し、関数の事前インストールやアプリ側の読み取り後の書き戻しを必要としません。パッチのルートは JSON シリアライズ可能なオブジェクトに限ります。列式は開発者の静的 SQL で、パスと値はパラメーターです。[RFC 7396](https://www.rfc-editor.org/rfc/rfc7396)・[PostgreSQL JSON 演算](https://www.postgresql.org/docs/16/functions-json.html).
|
|
558
560
|
|
|
559
561
|
#### 完全な使用例
|
|
560
562
|
|
|
@@ -568,7 +570,13 @@ await reldb.ddl.createTable('products', {
|
|
|
568
570
|
// 插入初始 JSON
|
|
569
571
|
await reldb.sql.execute(
|
|
570
572
|
`INSERT INTO products (meta) VALUES (?)`,
|
|
571
|
-
[
|
|
573
|
+
[
|
|
574
|
+
JSON.stringify({
|
|
575
|
+
status: 'draft',
|
|
576
|
+
tags: ['new'],
|
|
577
|
+
version: 1
|
|
578
|
+
})
|
|
579
|
+
],
|
|
572
580
|
)
|
|
573
581
|
|
|
574
582
|
// 设置字段
|
|
@@ -591,7 +599,159 @@ const { sql: rmSql, params: rmParams } = reldb.json.remove('meta', '$.reviewer')
|
|
|
591
599
|
await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...rmParams, 1])
|
|
592
600
|
```
|
|
593
601
|
|
|
594
|
-
###
|
|
602
|
+
### 本番の共有状態
|
|
603
|
+
|
|
604
|
+
本番は PostgreSQL/MySQL が必須です。ファイルを含む SQLite は開発・明示的テスト用であり、ホスト間の共有状態を保証しません。 NODE_ENV または HAI_CORE_ENV が production の場合、初期化成功前に検証します。明示的な HAI_E2E=1/HAI_E2E_MOCK=1 テストのみ除外します。 [状態・並行制御・復旧範囲(中国語)](../../.agents/instructions/distributed-state.md).
|
|
605
|
+
|
|
606
|
+
### モジュールのスキーマ移行
|
|
607
|
+
|
|
608
|
+
`reldb.schemaColumns(table, sql?)` は実際の列を読み取ります。`reldb.migrateSchema(module, steps, { dryRun: true })` は台帳を作成しません。各モジュールが手順を所有し、`hai_schema_migrations` に版を記録します。SQLite/PostgreSQL はトランザクション、MySQL の暗黙 DDL コミットには再入可能な手順が必要です。失敗時は版を進めず、実構造を再確認して再試行します。新しい台帳へのダウングレードは拒否します。旧書込を停止し DB/セッションキャッシュをバックアップしてください。復旧は互換 DB とバイナリを一緒に戻します。[更新ガイド](../../docs/usage/README.ja.md)を参照。
|
|
609
|
+
|
|
610
|
+
## 設定
|
|
611
|
+
|
|
612
|
+
ライフサイクル。init にバックエンド/業務設定を渡し成功後に使用します。借用依存先の解放前に close の完了を待ち、正確な戻り値は例と公開署名に従います。
|
|
613
|
+
|
|
614
|
+
`@h-ai/reldb/schemas` は同じ設定 Schema と型を Zod のみで公開し、DB インスタンスやドライバーを読み込みません。CLI やブラウザーで設定検証に利用できます。MySQL TLS は `false` または厳密なオブジェクトのみで、`{}` は証明書チェーンと DNS ID を検証します。PostgreSQL の真偽値・文字列モード・オブジェクト契約は維持します。
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
import { ReldbConfigSchema } from '@h-ai/reldb/schemas'
|
|
618
|
+
|
|
619
|
+
const checked = ReldbConfigSchema.safeParse({ type: 'mysql', database: 'shop', ssl: {} })
|
|
620
|
+
if (!checked.success)
|
|
621
|
+
throw new Error('Invalid database configuration')
|
|
622
|
+
// checked.data.ssl: { rejectUnauthorized: true, verifyIdentity: true }
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
接続文字列(`url`)または個別フィールド(`host/port/database/user/password`)を指定できます。
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
// SQLite
|
|
629
|
+
await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
630
|
+
|
|
631
|
+
// PostgreSQL
|
|
632
|
+
await reldb.init({ type: 'postgresql', url: 'postgres://user:pass@localhost:5432/mydb', database: 'mydb' })
|
|
633
|
+
|
|
634
|
+
// MySQL
|
|
635
|
+
await reldb.init({
|
|
636
|
+
type: 'mysql',
|
|
637
|
+
host: 'localhost',
|
|
638
|
+
port: 3306,
|
|
639
|
+
database: 'mydb',
|
|
640
|
+
user: 'admin',
|
|
641
|
+
password: 'secret',
|
|
642
|
+
mysql: { charset: 'utf8mb4' },
|
|
643
|
+
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 },
|
|
644
|
+
})
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### 接続プールと MySQL URL
|
|
648
|
+
|
|
649
|
+
MySQL の `pool` は `max`、`acquireTimeout`、`queueLimit` のみを受け付け、省略時もそれぞれ 10、10000 ミリ秒、1000 を使用します。`min`、`idleTimeout` などの未対応フィールドと未実装の `mysql.timezone` は設定検証で拒否します。アイドル接続は mysql2/サーバーによる切断または `close()` で解放し、最小保持数や設定可能なアイドル回収期限は保証しません。[mysql2 3.24.5 の設定](https://github.com/sidorares/node-mysql2/blob/v3.24.5/typings/mysql/lib/Pool.d.ts)は PostgreSQL と異なります。
|
|
650
|
+
|
|
651
|
+
| フィールド | PostgreSQL(`pool` 指定時の既定値) | MySQL |
|
|
652
|
+
| ---------------- | ----------------------------------- | -------------------------------------- |
|
|
653
|
+
| `max` | 10 | 10、借用中と接続確立中の合計上限 |
|
|
654
|
+
| `min` | 1、既存接続の保持下限。事前作成なし | 未対応、拒否 |
|
|
655
|
+
| `idleTimeout` | 30000 ミリ秒、0 は無効化 | 未対応、拒否 |
|
|
656
|
+
| `acquireTimeout` | 10000 ミリ秒、0 は無効化 | 10000 ミリ秒、1–2147483647 の整数 |
|
|
657
|
+
| `queueLimit` | 未対応 | 1000、待機リクエスト数の上限、正の整数 |
|
|
658
|
+
|
|
659
|
+
PostgreSQL で `pool` を省略した場合は pg の既定値を使用します。保持と回収の仕様は [pg.Pool](https://node-postgres.com/apis/pool) を参照してください。MySQL の SQL、DDL、CRUD、バッチ、トランザクションは共通の有限 FIFO 待機キューを使います。待機と接続確立に一つの期限を適用し、超過時は `TIMEOUT`、満杯時は `POOL_EXHAUSTED` を返します。期限切れ項目は削除し、遅れて取得した接続は SQL を実行せず解放します。確立処理が完了するまで容量枠を保持します。受領時と処理開始前にも期限を確認しますが、開始済み SQL/トランザクションの実行時間は制限しません。
|
|
660
|
+
|
|
661
|
+
MySQL の `close()` は新規・待機リクエストを直ちに拒否し、借用接続、トランザクション、接続確立の完了後にプールを閉じます。`connectTimeout` にも同じ値を使うため、その期限や既存処理を待つ場合があります。アプリはトランザクションを終了してください。環境変数は `HAI_DB_POOL_*` です。`MIN` / `IDLETIMEOUT` は PostgreSQL 専用で、MySQL の `QUEUELIMIT` は待機総数を制限します。
|
|
662
|
+
|
|
663
|
+
MySQL は `mysql://` の `url` だけでも接続でき、URL がなければ `database` が必須です。URL の host/port/database/user/password を優先し、競合する個別フィールドは接続に使用しません。`pool`、`mysql.charset`、`ssl`、`operationLog` は独立して適用します。query/fragment は拒否し、URL 内の隠れた設定で TLS やプールを変更できません。接続ログの URL は機密情報を伏せます。以下は `HAI_DB_URL` の設定を前提とします:
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
const initialized = await reldb.init({
|
|
667
|
+
type: 'mysql',
|
|
668
|
+
url: process.env.HAI_DB_URL,
|
|
669
|
+
pool: { max: 10, acquireTimeout: 5000, queueLimit: 100 }
|
|
670
|
+
})
|
|
671
|
+
if (!initialized.success)
|
|
672
|
+
throw new Error(initialized.error.message)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
MySQL の空 `url`(空の環境変数を含む)は host/port/database/user/password を使用します。空でない URL は厳密に検証します。
|
|
676
|
+
|
|
677
|
+
#### MySQL TLS
|
|
678
|
+
|
|
679
|
+
`ssl` を省略するか `false` にすると TLS を使用しません。有効化には厳密なオブジェクトを指定し、空オブジェクトの既定値は `{ rejectUnauthorized: true, verifyIdentity: true }` です。`ca` は信頼する CA の PEM 内容で、省略時は Node の信頼ストアを使います。任意のクライアント `cert` / `key` も PEM 内容で、必ず対で指定します。未知の TLS フィールド、`true`、PostgreSQL の文字列モードは拒否し、任意の設定やコールバックは透過しません。
|
|
680
|
+
|
|
681
|
+
[mysql2 3.24.5 の主機名検証](https://github.com/sidorares/node-mysql2/blob/v3.24.5/lib/base/connection.js)は DNS servername に依存します。証明書と名前の両方を検証する場合、IP 接続先は `CONFIG_ERROR` とし、証明書の DNS SAN に一致するホスト名を使います。明示的な `verifyIdentity: false` は証明書チェーンのみを検証します。`rejectUnauthorized: false` はサーバー認証を放棄し、暗号化だけでは相手の身元を確認できません。どちらかの検証を無効にすると固定のリスク警告を記録し、安全な構成とは扱いません。
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
// trustedCaPem は配置時の信頼設定から供給し、任意の接続先証明書をそのまま信頼しません。
|
|
685
|
+
const initialized = await reldb.init({
|
|
686
|
+
type: 'mysql',
|
|
687
|
+
host: 'db.example.com',
|
|
688
|
+
database: 'app',
|
|
689
|
+
ssl: { ca: trustedCaPem }
|
|
690
|
+
})
|
|
691
|
+
if (!initialized.success)
|
|
692
|
+
throw new Error(initialized.error.message)
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
TLS テストには OpenSSL(Windows は Git 同梱版も可)とローカルのコンテナー転送が必要です。各実行で CA/秘密鍵を生成・削除し、信頼済み DNS、誤った CA/主機名の拒否、`REQUIRE X509` 認証、実際の `Ssl_cipher` を検証します。検証無効の接続を認証済み TLS の証拠にはしません。
|
|
696
|
+
|
|
697
|
+
#### SQLite の実行方式(非同期 worker)
|
|
698
|
+
|
|
699
|
+
better-sqlite3 は同期ドライバーなので、クエリは Node のメインイベントループをブロックします。WAL は DB ロックの並行性を改善しますが、同期呼び出しによるブロックは解消しません。専用 worker スレッドで SQLite を実行して処理を移せます(単一接続、任意設定、既定は `sync`):
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
await reldb.init({
|
|
703
|
+
type: 'sqlite',
|
|
704
|
+
database: './data.db',
|
|
705
|
+
sqlite: {
|
|
706
|
+
executor: 'worker', // 'sync'(默认)| 'worker'
|
|
707
|
+
walMode: true, // 默认 true
|
|
708
|
+
workerConnectTimeoutMs: 10000, // worker 打开连接超时,默认 10000
|
|
709
|
+
},
|
|
710
|
+
})
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
worker モードではクエリ、書き込み、トランザクションを worker スレッドで順番に実行します。公開 API と戻り値は同期モードと同じです。
|
|
714
|
+
|
|
715
|
+
#### 操作ログ
|
|
716
|
+
|
|
717
|
+
DB 設定の `operationLog` で SQL 操作ログを有効にできます。ログは reldb provider-base の DML 層で出力され、`reldb-main.ts` や各 raw provider で重複してラップしません。
|
|
718
|
+
|
|
719
|
+
```yaml
|
|
720
|
+
# config/_db.yml
|
|
721
|
+
type: sqlite
|
|
722
|
+
database: ./data/app.db
|
|
723
|
+
operationLog:
|
|
724
|
+
read: false # query / get / queryPage
|
|
725
|
+
write: false # DDL, execute / batch, tx.begin / tx.commit / tx.rollback
|
|
726
|
+
maxLength: 1000 # 安全な件数サマリーの最大長。0は省略。SQL本文と値は記録しない
|
|
727
|
+
level: debug # info | debug | trace,默认 debug
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
`core.config.load('db', ...)` で読み込む場合、`HAI_DB_TYPE`、
|
|
731
|
+
`HAI_DB_DATABASE`、`HAI_DB_OPERATIONLOG_LEVEL` などの規約に沿う変数が YAML より優先されます。
|
|
732
|
+
|
|
733
|
+
```ts
|
|
734
|
+
await reldb.init({
|
|
735
|
+
type: 'sqlite',
|
|
736
|
+
database: './data.db',
|
|
737
|
+
operationLog: {
|
|
738
|
+
read: true,
|
|
739
|
+
write: true,
|
|
740
|
+
maxLength: 500,
|
|
741
|
+
level: 'debug',
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
操作ログには固定の操作名、SQL 長、パラメーター数、バッチ件数を記録します。失敗時は、エラーコード、traceId、許可リストに含まれるマスキング済みのドライバーメッセージとスタックも保持します。SQL 全文、バインド値、接続設定をそのまま記録することはありません。
|
|
747
|
+
|
|
748
|
+
設定はホストが読込・検証してから渡します。秘密はサーバーの非公開環境に保存し、利用場面に応じてバックエンド・容量・期限・ログを選びます。変更後は再初期化の結果を確認してください。
|
|
749
|
+
|
|
750
|
+
[項目別の用途・適用場面・例](../../docs/usage/configuration/data.md).
|
|
751
|
+
|
|
752
|
+
## エラー処理
|
|
753
|
+
|
|
754
|
+
領域操作は result.success を確認し、失敗時はモジュールのエラー定義と安全な説明を使います。コード・関連 ID・秘匿した原因をサーバーログに残します。同期ツール・ファクトリー・ストリーム反復・ライフサイクルは各署名に従い、Promise 完了だけで成功と判断しません。回復条件は各機能を参照してください。
|
|
595
755
|
|
|
596
756
|
すべての操作は `HaiResult<T>` を返し、`result.success` で成功・失敗を判定します。
|
|
597
757
|
|
|
@@ -630,15 +790,7 @@ if (!result.success) {
|
|
|
630
790
|
|
|
631
791
|
`BaseReldbCrudRepository` の派生クラスで独自 SQL を実行する前に `await this.ensureReady()` を確認します。構築時に建表を開始するだけなので、サービス init は候補の準備完了後に公開します。PostgreSQL の自動コミットによる単一の `CREATE TABLE/INDEX IF NOT EXISTS` は、`23505/pg_class_relname_nsp_index`(建表は `pg_type_typname_nsp_index` も対象)の競合、または建表時にサーバー routine が `heap_create_with_catalog` / `TypeCreate` である `42710` の行型競合に限り、DDL/SQL の両入口で一度だけ再実行します。再試行も失敗した場合はエラーを返します。トランザクション、業務の一意制約、その他の失敗は再試行しません。初期接続確認の一時 DNS エラー `EAI_AGAIN` は 250/500 ms 間隔で最大三回試行し、業務 SQL は再送しません。
|
|
632
792
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
本番は PostgreSQL/MySQL が必須です。ファイルを含む SQLite は開発・明示的テスト用であり、ホスト間の共有状態を保証しません。 NODE_ENV または HAI_CORE_ENV が production の場合、初期化成功前に検証します。明示的な HAI_E2E=1/HAI_E2E_MOCK=1 テストのみ除外します。 [状態・並行制御・復旧範囲(中国語)](../../.agents/instructions/distributed-state.md)。
|
|
636
|
-
|
|
637
|
-
## モジュールのスキーマ移行
|
|
638
|
-
|
|
639
|
-
`schemaColumns(reldb, table, sql?)` は実際の列を読み取ります。`migrateSchema(reldb, module, steps, { dryRun: true })` は台帳を作成しません。各モジュールが手順を所有し、`hai_schema_migrations` に版を記録します。SQLite/PostgreSQL はトランザクション、MySQL の暗黙 DDL コミットには再入可能な手順が必要です。失敗時は版を進めず、実構造を再確認して再試行します。新しい台帳へのダウングレードは拒否します。旧書込を停止し DB/セッションキャッシュをバックアップしてください。復旧は互換 DB とバイナリを一緒に戻します。[更新ガイド](../../docs/usage/README.ja.md)を参照。
|
|
640
|
-
|
|
641
|
-
## エラー表示とログの関連付け
|
|
793
|
+
### エラー表示とログの関連付け
|
|
642
794
|
|
|
643
795
|
一意制約違反は `HaiReldbError.DUPLICATE_ENTRY`(409)に統一し、タイムアウトや待機列満杯など既存のコードは保持します。翻訳済みメッセージやドライバー文字列ではなくコードで分岐してください。ログには code/schema/routine/constraint とスタック行番号を残し、数値のバインド値でこれらの診断識別子を隠しません。
|
|
644
796
|
|