@h-ai/reldb 1.0.0-beta.21 → 1.0.0-beta.23

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
 
@@ -788,7 +787,7 @@ Common error codes:
788
787
  | `HaiReldbError.TIMEOUT` | `hai:reldb:009` | MySQL acquisition deadline exceeded |
789
788
  | `HaiReldbError.POOL_EXHAUSTED` | `hai:reldb:011` | MySQL acquisition queue is full |
790
789
 
791
- Custom SQL methods in `BaseReldbCrudRepository` subclasses must check `await this.ensureReady()`. Construction starts table creation; service init must await candidate readiness before publication. PostgreSQL retries a single autocommit `CREATE TABLE/INDEX IF NOT EXISTS` once only for `23505/pg_class_relname_nsp_index` catalog contention (also `pg_type_typname_nsp_index` for tables), or a table row-type race reported as `42710` with server routine `heap_create_with_catalog` / `TypeCreate`, consistently through DDL and SQL entry points. A failed retry remains an error. Transactions, business uniqueness errors and other failures are not retried. Initial connection probing attempts temporary DNS `EAI_AGAIN` failures at most three times, with 250/500 ms delays; business SQL is never replayed.
790
+ Custom SQL methods in `BaseReldbCrudRepository` subclasses must check `await this.ensureReady()`. Construction starts table creation; service init must await candidate readiness before publication. PostgreSQL retries a single autocommit `CREATE TABLE/INDEX IF NOT EXISTS` once only for `23505/pg_class_relname_nsp_index` catalog contention (also `pg_type_typname_nsp_index` for tables), or table-creation races reported as `42710/heap_create_with_catalog`, `42710/TypeCreate`, or `42P07/heap_create_with_catalog`. DDL and SQL entry points both return the error if the retry fails. Transactions, business uniqueness errors and other failures are not retried. Initial connection probing attempts temporary DNS `EAI_AGAIN` failures at most three times, with 250/500 ms delays; business SQL is never replayed.
792
791
 
793
792
  ### Error messages and correlated logs
794
793
 
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
 
@@ -788,7 +787,7 @@ if (!result.success) {
788
787
  | `HaiReldbError.TIMEOUT` | `hai:reldb:009` | MySQL 接続取得の期限超過 |
789
788
  | `HaiReldbError.POOL_EXHAUSTED` | `hai:reldb:011` | MySQL 接続待機キューが満杯 |
790
789
 
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 は再送しません。
790
+ `BaseReldbCrudRepository` の派生クラスで独自 SQL を実行する前に `await this.ensureReady()` を確認します。構築時に建表を開始するだけなので、サービス init は候補の準備完了後に公開します。PostgreSQL の自動コミットによる単一の `CREATE TABLE/INDEX IF NOT EXISTS` は、`23505/pg_class_relname_nsp_index`(建表は `pg_type_typname_nsp_index` も対象)の競合、または建表時の `42710/heap_create_with_catalog`、`42710/TypeCreate`、`42P07/heap_create_with_catalog` のカタログ競合に限り、DDL/SQL の両入口で一度だけ再実行します。再試行も失敗した場合はエラーを返します。トランザクション、業務の一意制約、その他の失敗は再試行しません。初期接続確認の一時 DNS エラー `EAI_AGAIN` は 250/500 ms 間隔で最大三回試行し、業務 SQL は再送しません。
792
791
 
793
792
  ### エラー表示とログの関連付け
794
793
 
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
 
@@ -788,7 +787,7 @@ if (!result.success) {
788
787
  | `HaiReldbError.TIMEOUT` | `hai:reldb:009` | MySQL 连接获取超过截止期限 |
789
788
  | `HaiReldbError.POOL_EXHAUSTED` | `hai:reldb:011` | MySQL 连接等待队列已满 |
790
789
 
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`)目录竞争,或建表时 `42710` 且服务端 routine 为 `heap_create_with_catalog` / `TypeCreate` 的行类型竞争重执行一次;重试仍失败则保留错误,DDL 与 SQL 入口一致;事务、业务唯一约束及其他错误不自动重试。初始化连接探测仅对临时 DNS 错误 `EAI_AGAIN` 最多尝试三次,间隔 250/500 ms;不重放业务 SQL。
790
+ 派生 `BaseReldbCrudRepository` 的自定义 SQL 方法先检查 `await this.ensureReady()`;构造函数启动自动建表不等于仓库就绪,服务 init 必须等待候选仓库的就绪入口再发布。PostgreSQL 自动提交的单条 `CREATE TABLE/INDEX IF NOT EXISTS` 仅对 `23505/pg_class_relname_nsp_index`(建表还包括 `pg_type_typname_nsp_index`)目录竞争,以及建表时 `42710/heap_create_with_catalog`、`42710/TypeCreate` 或 `42P07/heap_create_with_catalog` 的目录竞争重执行一次。DDL 和 SQL 入口均保留重试后的错误。事务、业务唯一约束及其他错误不自动重试。初始化连接探测仅对临时 DNS 错误 `EAI_AGAIN` 最多尝试三次,间隔 250/500 ms;不重放业务 SQL。
792
791
 
793
792
  ### 错误提示与关联日志
794
793
 
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
  *
@@ -2638,7 +2656,7 @@ function createPostgresProvider() {
2638
2656
  } catch (error) {
2639
2657
  const kind = /^\s*CREATE\s+(TABLE|(?:UNIQUE\s+)?INDEX)\s+IF\s+NOT\s+EXISTS\s+/i.exec(sql)?.[1]?.toUpperCase();
2640
2658
  const single = !sql.trim().replace(/;$/, "").includes(";");
2641
- const catalogConflict = typeof error === "object" && error !== null && "code" in error && (error.code === "23505" && "schema" in error && error.schema === "pg_catalog" && "constraint" in error && (error.constraint === "pg_class_relname_nsp_index" || kind === "TABLE" && error.constraint === "pg_type_typname_nsp_index") || kind === "TABLE" && error.code === "42710" && "routine" in error && (error.routine === "heap_create_with_catalog" || error.routine === "TypeCreate"));
2659
+ const catalogConflict = typeof error === "object" && error !== null && "code" in error && (error.code === "23505" && "schema" in error && error.schema === "pg_catalog" && "constraint" in error && (error.constraint === "pg_class_relname_nsp_index" || kind === "TABLE" && error.constraint === "pg_type_typname_nsp_index") || kind === "TABLE" && "routine" in error && (error.routine === "heap_create_with_catalog" && (error.code === "42P07" || error.code === "42710") || error.routine === "TypeCreate" && error.code === "42710"));
2642
2660
  if (!kind || !single || !catalogConflict)
2643
2661
  throw error;
2644
2662
  return pool.query(sql, params);
@@ -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)