@h-ai/reldb 1.0.0-beta.22 → 1.0.0-beta.24

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 CHANGED
@@ -113,10 +113,9 @@ Pagination: normalize accepts page options and limits and returns normalized opt
113
113
 
114
114
  Prepare module persistence: table name returns columns; module name, steps and dryRun return a migration plan. Back up and stop conflicting writers before applying.
115
115
 
116
- | Entry | Input parameters | Successful result | Call example |
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 })` |
116
+ | Entry | Input parameters | Successful result | Call example |
117
+ | --------------------- | ---------------- | ----------------- | ------------------------------------- |
118
+ | `reldb.schemaColumns` | table, sql | `string[]` | `await reldb.schemaColumns('orders')` |
120
119
 
121
120
  #### reldb.tx
122
121
 
@@ -603,9 +602,9 @@ await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...
603
602
 
604
603
  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).
605
604
 
606
- ### Module schema migrations
605
+ ### Inspect table structure
607
606
 
608
- `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).
607
+ `reldb.schemaColumns(table, sql?)` reads the actual column names. Pass the transaction SQL handle to inspect the structure within the current transaction. Framework modules validate their own schemas during initialization. Applications manage their business tables.
609
608
 
610
609
  ## Configuration
611
610
 
package/README.ja.md CHANGED
@@ -113,10 +113,9 @@ params で値を束縛して業務データを操作。順に行配列、先頭
113
113
 
114
114
  永続化の準備。テーブル名から列名、モジュール名・手順・dryRun から移行計画を取得。適用前にバックアップし競合書込を停止。
115
115
 
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 })` |
116
+ | 入口 | 入力引数 | 成功結果 | 呼出し例 |
117
+ | --------------------- | ---------- | ---------- | ------------------------------------- |
118
+ | `reldb.schemaColumns` | table, sql | `string[]` | `await reldb.schemaColumns('orders')` |
120
119
 
121
120
  #### reldb.tx
122
121
 
@@ -603,9 +602,9 @@ await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...
603
602
 
604
603
  本番は PostgreSQL/MySQL が必須です。ファイルを含む SQLite は開発・明示的テスト用であり、ホスト間の共有状態を保証しません。 NODE_ENV または HAI_CORE_ENV が production の場合、初期化成功前に検証します。明示的な HAI_E2E=1/HAI_E2E_MOCK=1 テストのみ除外します。 [状態・並行制御・復旧範囲(中国語)](../../.agents/instructions/distributed-state.md).
605
604
 
606
- ### モジュールのスキーマ移行
605
+ ### テーブル構造の確認
607
606
 
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)を参照。
607
+ `reldb.schemaColumns(table, sql?)` で実際の列名を読み取れます。トランザクションの SQL ハンドルを渡すと、同じトランザクション内で構造を確認できます。各フレームワークモジュールは初期化時に自身のテーブル構造を検証し、業務テーブルはアプリが管理します。
609
608
 
610
609
  ## 設定
611
610
 
package/README.md CHANGED
@@ -113,10 +113,9 @@ table/column/index 名必须来自可信代码;sql 是可信语句,params
113
113
 
114
114
  模块持久化准备。输入表名得到列名数组;输入模块名、迁移步骤和 dryRun 得到迁移计划。执行前备份并停止冲突写入。
115
115
 
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 })` |
116
+ | 入口 | 输入参数 | 成功结果 | 调用示例 |
117
+ | --------------------- | ---------- | ---------- | ------------------------------------- |
118
+ | `reldb.schemaColumns` | table, sql | `string[]` | `await reldb.schemaColumns('orders')` |
120
119
 
121
120
  #### reldb.tx
122
121
 
@@ -603,9 +602,9 @@ await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...
603
602
 
604
603
  生产仅使用 PostgreSQL/MySQL;SQLite(含磁盘文件)只用于开发/明确测试,不能作为跨主机共享状态。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化发布成功前校验;仅 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试可豁免。 [状态、并发和恢复边界](../../.agents/instructions/distributed-state.md).
605
604
 
606
- ### 模块架构迁移
605
+ ### 检查数据表结构
607
606
 
608
- `reldb.schemaColumns(table, sql?)` 只读检查真实结构;`reldb.migrateSchema(module, steps, { dryRun: true })` 不创建账本。版本步骤由所属模块维护,业务不修改框架内部表。执行使用 `hai_schema_migrations`,SQLite/PostgreSQL 整体事务,MySQL DDL 隐式提交因此步骤必须可重入;失败不推进版本,重新检查真实结构后续做,拒绝向低版本降级。升级前停止所有旧写入并备份 DB/会话缓存;回退需恢复兼容数据库与旧代码,不能只换二进制。详见 [升级指南](../../docs/usage/README.md).
607
+ `reldb.schemaColumns(table, sql?)` 只读查询实际列名。传入当前事务的 SQL 操作句柄,可在同一事务中检查表结构。框架模块初始化时校验各自的表结构,业务表由应用管理。
609
608
 
610
609
  ## 配置
611
610
 
package/dist/index.d.ts CHANGED
@@ -192,38 +192,6 @@ declare function parseJsonPath(path: string): string[];
192
192
  */
193
193
  declare function createJsonOps(dbType: 'sqlite' | 'postgresql' | 'mysql'): ReldbJsonOps;
194
194
 
195
- /** 模块维护自己的迁移;这里只提供跨数据库列检查、版本账本和执行边界。 */
196
-
197
- /**
198
- * 一个可重入的模块升级步骤;只接收代码中固定的 SQL 与表名。
199
- * @example
200
- * ```ts
201
- * import type { SchemaMigration } from '@h-ai/reldb'
202
- * const steps: SchemaMigration[] = []
203
- * ```
204
- */
205
- interface SchemaMigration {
206
- version: number;
207
- name: string;
208
- needed: (sql: DmlOperations) => Promise<HaiResult<boolean>>;
209
- apply: (sql: DmlOperations) => Promise<HaiResult<unknown>>;
210
- }
211
- /**
212
- * 只读预检结果;生产升级需先停旧写入并备份数据库与共享缓存。
213
- * @example
214
- * ```ts
215
- * import type { SchemaMigrationPlan } from '@h-ai/reldb'
216
- * function changes(plan: SchemaMigrationPlan) { return plan.changes }
217
- * ```
218
- */
219
- interface SchemaMigrationPlan {
220
- module: string;
221
- currentVersion: number;
222
- targetVersion: number;
223
- changes: string[];
224
- requiresExclusiveAccess: boolean;
225
- }
226
-
227
195
  /**
228
196
  * HaiReldbError 的数据约束与操作契约。
229
197
  * @example
@@ -240,7 +208,6 @@ declare const HaiReldbError: {
240
208
  QUERY_FAILED: _h_ai_core.HaiErrorDef;
241
209
  CONSTRAINT_VIOLATION: _h_ai_core.HaiErrorDef;
242
210
  TRANSACTION_FAILED: _h_ai_core.HaiErrorDef;
243
- MIGRATION_FAILED: _h_ai_core.HaiErrorDef;
244
211
  RECORD_NOT_FOUND: _h_ai_core.HaiErrorDef;
245
212
  DUPLICATE_ENTRY: _h_ai_core.HaiErrorDef;
246
213
  DEADLOCK: _h_ai_core.HaiErrorDef;
@@ -989,18 +956,6 @@ interface ReldbFunctions {
989
956
  readonly crud: CrudManager;
990
957
  /** 事务管理器 */
991
958
  readonly tx: TxManager;
992
- /**
993
- * 预检或执行模块维护的版本迁移;绑定当前数据库,不接受其它模块实例。
994
- * @example
995
- * ```ts
996
- * import { reldb } from '@h-ai/reldb'
997
- * const plan = await reldb.migrateSchema('example', [], { dryRun: true })
998
- * if (!plan.success) throw new Error(plan.error.message)
999
- * ```
1000
- */
1001
- migrateSchema: (module: string, steps: readonly SchemaMigration[], options?: {
1002
- dryRun?: boolean;
1003
- }) => Promise<HaiResult<SchemaMigrationPlan>>;
1004
959
  /**
1005
960
  * 只读查询当前数据库真实列名;sql 可指定当前事务,不创建表。
1006
961
  * @example
@@ -1216,6 +1171,8 @@ declare abstract class BaseReldbCrudRepository<TItem> implements ReldbCrudReposi
1216
1171
  * @returns 初始化结果
1217
1172
  */
1218
1173
  protected ensureReady(): Promise<HaiResult<void>>;
1174
+ /** 验证框架仓库使用当前字段定义,结构不符时停止发布仓库。 */
1175
+ protected ensureSchema(): Promise<HaiResult<void>>;
1219
1176
  /**
1220
1177
  * 创建单条记录
1221
1178
  *
@@ -1590,4 +1547,4 @@ declare function quoteIdentifier(name: string): string;
1590
1547
  declare function escapeSqlString(value: string): string;
1591
1548
 
1592
1549
  export { BaseReldbCrudRepository, DbType, HaiReldbError, ReldbConfig, ReldbConfigInput, createJsonOps, escapeSqlString, parseJsonPath, quoteIdentifier, reldb, validateIdentifier, validateIdentifiers };
1593
- export type { BaseReldbCrudRepositoryConfig, ColumnType, CrudConfig, CrudManager, CrudPageOptions, CrudQueryOptions, DdlOperations, DmlOperations, DmlWithTxOperations, ExecuteResult, JsonSqlExpr, NormalizedPagination, PaginationOverrides, PaginationQueryOptions, QueryRow, ReldbColumnDef, ReldbCrudCountOptions, ReldbCrudFieldDefinition, ReldbCrudRepository, ReldbFunctions, ReldbIndexDef, ReldbJsonOps, ReldbTableDef, SchemaMigration, SchemaMigrationPlan, TxManager, TxWrapCallback };
1550
+ export type { BaseReldbCrudRepositoryConfig, ColumnType, CrudConfig, CrudManager, CrudPageOptions, CrudQueryOptions, DdlOperations, DmlOperations, DmlWithTxOperations, ExecuteResult, JsonSqlExpr, NormalizedPagination, PaginationOverrides, PaginationQueryOptions, QueryRow, ReldbColumnDef, ReldbCrudCountOptions, ReldbCrudFieldDefinition, ReldbCrudRepository, ReldbFunctions, ReldbIndexDef, ReldbJsonOps, ReldbTableDef, TxManager, TxWrapCallback };
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ var en_US_default = {
16
16
  reldb_initFailed: "Failed to initialize database",
17
17
  reldb_closeFailed: "Failed to close database",
18
18
  reldb_ddlFailed: "DDL operation failed",
19
+ reldb_schemaMismatch: "Table {table} does not match its current schema",
19
20
  reldb_queryFailed: "Query failed",
20
21
  reldb_duplicateEntry: "Record already exists",
21
22
  reldb_batchFailed: "Batch execute failed",
@@ -51,6 +52,7 @@ var ja_JP_default = {
51
52
  reldb_initFailed: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
52
53
  reldb_closeFailed: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u7D42\u4E86\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
53
54
  reldb_ddlFailed: "DDL\u64CD\u4F5C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
55
+ reldb_schemaMismatch: "\u30C6\u30FC\u30D6\u30EB {table} \u304C\u73FE\u5728\u306E\u30B9\u30AD\u30FC\u30DE\u5B9A\u7FA9\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093",
54
56
  reldb_queryFailed: "\u30AF\u30A8\u30EA\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
55
57
  reldb_duplicateEntry: "\u30EC\u30B3\u30FC\u30C9\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
56
58
  reldb_batchFailed: "\u30D0\u30C3\u30C1\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
@@ -86,6 +88,7 @@ var zh_CN_default = {
86
88
  reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25",
87
89
  reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25",
88
90
  reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25",
91
+ reldb_schemaMismatch: "\u6570\u636E\u8868 {table} \u4E0E\u5F53\u524D\u7ED3\u6784\u5B9A\u4E49\u4E0D\u7B26",
89
92
  reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25",
90
93
  reldb_duplicateEntry: "\u8BB0\u5F55\u5DF2\u5B58\u5728",
91
94
  reldb_batchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25",
@@ -126,7 +129,6 @@ var ReldbErrorInfo = {
126
129
  QUERY_FAILED: "002:500",
127
130
  CONSTRAINT_VIOLATION: "003:409",
128
131
  TRANSACTION_FAILED: "004:500",
129
- MIGRATION_FAILED: "005:500",
130
132
  RECORD_NOT_FOUND: "006:404",
131
133
  DUPLICATE_ENTRY: "007:409",
132
134
  DEADLOCK: "008:500",
@@ -473,6 +475,22 @@ var BaseReldbCrudRepository = class {
473
475
  }
474
476
  return ok(void 0);
475
477
  }
478
+ /** 验证框架仓库使用当前字段定义,结构不符时停止发布仓库。 */
479
+ async ensureSchema() {
480
+ const ready = await this.ensureReady();
481
+ if (!ready.success)
482
+ return ready;
483
+ const columns = await this.db.schemaColumns(this.table);
484
+ if (!columns.success)
485
+ return columns;
486
+ const names = new Set(columns.data);
487
+ if (names.size !== this.fields.length || this.fields.some((field) => !names.has(field.columnName))) {
488
+ return err(HaiReldbError.DDL_FAILED, reldbM("reldb_schemaMismatch", {
489
+ params: { table: this.table }
490
+ }));
491
+ }
492
+ return ok(void 0);
493
+ }
476
494
  /**
477
495
  * 创建单条记录
478
496
  *
@@ -3363,106 +3381,6 @@ async function schemaColumns(db, table, sql = db.sql) {
3363
3381
  const found = type === "sqlite" ? await sql.query(`PRAGMA table_info(${table})`) : await sql.query(`SELECT column_name AS name FROM information_schema.columns WHERE table_schema=${type === "mysql" ? "DATABASE()" : "current_schema()"} AND table_name=?`, [table]);
3364
3382
  return found.success ? ok(found.data.map((row) => row.name)) : found;
3365
3383
  }
3366
- async function migrateSchema(db, module, steps, options = {}) {
3367
- if (!db.isInitialized || !/^[a-z][a-z0-9-]{0,62}$/.test(module) || steps.some((step, index) => step.version !== index + 1))
3368
- return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
3369
- const table = "hai_schema_migrations";
3370
- const targetVersion = steps.length;
3371
- const failed = (cause) => err({
3372
- ...HaiCommonError.INIT_FAILED,
3373
- message: core.i18n.coreM("core_runtimeStartFailed", {
3374
- params: { module }
3375
- }),
3376
- ext: { module, phase: "migration" },
3377
- cause
3378
- });
3379
- async function inspect(sql) {
3380
- const columns = await schemaColumns(db, table, sql);
3381
- if (!columns.success)
3382
- return failed(columns.error);
3383
- const row = columns.data.length ? await sql.get(`SELECT version FROM ${table} WHERE module=?`, [module]) : ok(null);
3384
- if (!row.success)
3385
- return failed(row.error);
3386
- const currentVersion = Number(row.data?.version ?? 0);
3387
- if (currentVersion > targetVersion)
3388
- return failed();
3389
- const changes = [];
3390
- for (const step of steps) {
3391
- const needed = await step.needed(sql);
3392
- if (!needed.success)
3393
- return failed(needed.error);
3394
- if (needed.data)
3395
- changes.push(step.name);
3396
- }
3397
- return ok({
3398
- module,
3399
- currentVersion,
3400
- targetVersion,
3401
- changes,
3402
- requiresExclusiveAccess: changes.length > 0
3403
- });
3404
- }
3405
- if (options.dryRun)
3406
- return inspect(db.sql);
3407
- const created = await db.sql.execute(`CREATE TABLE IF NOT EXISTS ${table} (module VARCHAR(64) PRIMARY KEY, version INTEGER NOT NULL)`);
3408
- if (!created.success)
3409
- return failed(created.error);
3410
- const seeded = await db.sql.execute(db.config?.type === "mysql" ? `INSERT IGNORE INTO ${table}(module,version) VALUES (?,0)` : `INSERT INTO ${table}(module,version) VALUES (?,0) ON CONFLICT(module) DO NOTHING`, [module]);
3411
- if (!seeded.success)
3412
- return failed(seeded.error);
3413
- const started = await db.tx.begin();
3414
- if (!started.success)
3415
- return failed(started.error);
3416
- const tx = started.data;
3417
- let mysqlLock = false;
3418
- let committed = false;
3419
- try {
3420
- if (db.config?.type === "mysql") {
3421
- const lock = await tx.get("SELECT GET_LOCK(?,30) AS acquired", [`hai-schema:${module}`]);
3422
- if (!lock.success || Number(lock.data?.acquired) !== 1) {
3423
- return failed(lock.success ? new Error("Migration lock unavailable") : lock.error);
3424
- }
3425
- mysqlLock = true;
3426
- }
3427
- const locked = await tx.execute(`UPDATE ${table} SET version=version WHERE module=?`, [module]);
3428
- if (!locked.success)
3429
- return failed(locked.error);
3430
- const plan = await inspect(tx);
3431
- if (!plan.success)
3432
- return plan;
3433
- for (const step of steps) {
3434
- const needed = await step.needed(tx);
3435
- if (!needed.success)
3436
- return failed(needed.error);
3437
- if (needed.data) {
3438
- const applied = await step.apply(tx);
3439
- if (!applied.success)
3440
- return failed(applied.error);
3441
- const remaining = await step.needed(tx);
3442
- if (!remaining.success || remaining.data) {
3443
- return failed(remaining.success ? new Error("Migration step did not converge") : remaining.error);
3444
- }
3445
- }
3446
- }
3447
- const recorded = await tx.execute(`UPDATE ${table} SET version=? WHERE module=?`, [targetVersion, module]);
3448
- if (!recorded.success)
3449
- return failed(recorded.error);
3450
- if (mysqlLock) {
3451
- await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
3452
- mysqlLock = false;
3453
- }
3454
- const result = await tx.commit();
3455
- committed = result.success;
3456
- return result.success ? plan : failed(result.error);
3457
- } catch (error) {
3458
- return failed(error);
3459
- } finally {
3460
- if (mysqlLock)
3461
- await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
3462
- if (!committed)
3463
- await tx.rollback();
3464
- }
3465
- }
3466
3384
 
3467
3385
  // src/reldb-main.ts
3468
3386
  var logger5 = core.logger.child({ module: "reldb", scope: "main" });
@@ -3648,12 +3566,6 @@ var reldb = {
3648
3566
  get tx() {
3649
3567
  return currentProvider?.tx ?? notInitializedTx;
3650
3568
  },
3651
- /** 迁移操作绑定当前实例,调用方只提供模块自己的步骤。 */
3652
- migrateSchema(module, steps, options) {
3653
- if (!currentProvider)
3654
- return Promise.resolve(notInitialized.result());
3655
- return migrateSchema(reldb, module, steps, options);
3656
- },
3657
3569
  /** 列检查复用当前连接或显式传入的事务。 */
3658
3570
  schemaColumns(table, sql) {
3659
3571
  if (!currentProvider)