@h-ai/reldb 0.1.0-alpha8 → 1.0.0-beta.1

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/dist/index.js CHANGED
@@ -1,173 +1,127 @@
1
- import { z } from 'zod';
2
- import { core, ok, err } from '@h-ai/core';
1
+ import { ReldbConfigSchema, MysqlPoolConfigSchema } from './chunk-SDS2ZT3V.js';
2
+ export { MysqlConfigSchema, MysqlOptionsSchema, MysqlPoolConfigSchema, MysqlSslConfigSchema, PoolConfigSchema, PostgresqlConfigSchema, ReldbConfigSchema, ReldbOperationLogConfigSchema, ReldbTypeSchema, SqliteConfigSchema, SqliteOptionsSchema, SslConfigSchema } from './chunk-SDS2ZT3V.js';
3
+ import { core, ok, err, HaiCommonError } from '@h-ai/core';
4
+ import process from 'process';
3
5
  import { createRequire } from 'module';
4
-
5
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
- }) : x)(function(x) {
8
- if (typeof require !== "undefined") return require.apply(this, arguments);
9
- throw Error('Dynamic require of "' + x + '" is not supported');
10
- });
11
- var ReldbTypeSchema = z.enum(["sqlite", "postgresql", "mysql"]);
12
- var PoolConfigSchema = z.object({
13
- /** 最小连接数(默认 1) */
14
- min: z.number().int().min(0).default(1),
15
- /** 最大连接数(默认 10) */
16
- max: z.number().int().min(1).default(10),
17
- /** 空闲连接超时时间,单位毫秒(默认 30000) */
18
- idleTimeout: z.number().int().min(0).default(3e4),
19
- /** 获取连接超时时间,单位毫秒(默认 10000) */
20
- acquireTimeout: z.number().int().min(0).default(1e4)
21
- });
22
- var SslConfigSchema = z.union([
23
- z.boolean(),
24
- z.enum(["require", "prefer", "allow", "disable"]),
25
- z.record(z.string(), z.unknown())
26
- ]);
27
- var SqliteOptionsSchema = z.object({
28
- /** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */
29
- walMode: z.boolean().default(true),
30
- /** 是否只读模式(默认 false) */
31
- readonly: z.boolean().default(false)
32
- });
33
- var MysqlOptionsSchema = z.object({
34
- /** 字符集(默认 utf8mb4),推荐使用 utf8mb4 支持完整 Unicode */
35
- charset: z.string().default("utf8mb4"),
36
- /** 时区设置,如 '+08:00' */
37
- timezone: z.string().optional()
38
- });
39
- var NetworkConfigSchema = z.object({
40
- /**
41
- * 连接字符串(可选)
42
- * 如果提供 url,将优先使用,忽略 host/port 等字段
43
- */
44
- url: z.string().optional(),
45
- /** 数据库主机地址(默认 localhost) */
46
- host: z.string().default("localhost"),
47
- /**
48
- * 数据库端口
49
- * - PostgreSQL 默认 5432
50
- * - MySQL 默认 3306
51
- */
52
- port: z.number().int().min(1).max(65535).optional(),
53
- /** 数据库名称 */
54
- database: z.string(),
55
- /** 数据库用户名 */
56
- user: z.string().optional(),
57
- /** 数据库密码 */
58
- password: z.string().optional(),
59
- /** SSL/TLS 连接配置 */
60
- ssl: SslConfigSchema.optional(),
61
- /** 连接池配置 */
62
- pool: PoolConfigSchema.optional()
63
- });
64
- var SqliteConfigSchema = z.object({
65
- type: z.literal("sqlite"),
66
- /** 数据库文件路径(如 './data.db')或 ':memory:'(内存数据库) */
67
- database: z.string(),
68
- /** SQLite 特定选项 */
69
- sqlite: SqliteOptionsSchema.optional()
70
- });
71
- var PostgresqlConfigSchema = NetworkConfigSchema.extend({
72
- type: z.literal("postgresql")
73
- });
74
- var MysqlConfigSchema = NetworkConfigSchema.extend({
75
- type: z.literal("mysql"),
76
- /** MySQL 特定选项 */
77
- mysql: MysqlOptionsSchema.optional()
78
- });
79
- var ReldbConfigSchema = z.discriminatedUnion("type", [
80
- SqliteConfigSchema,
81
- PostgresqlConfigSchema,
82
- MysqlConfigSchema
83
- ]);
6
+ import { isIP } from 'net';
7
+ import { performance as performance$1 } from 'perf_hooks';
8
+ import { setTimeout as setTimeout$1 } from 'timers/promises';
9
+ import { existsSync } from 'fs';
10
+ import { Worker } from 'worker_threads';
84
11
 
85
12
  // messages/en-US.json
86
13
  var en_US_default = {
87
14
  $schema: "https://inlang.com/schema/inlang-message-format",
88
15
  reldb_notInitialized: "Database not initialized, please call db.init() first",
89
- reldb_initFailed: "Failed to initialize database: {error}",
90
- reldb_closeFailed: "Failed to close database: {error}",
91
- reldb_unsupportedType: "Unsupported database type: {type}",
92
- reldb_ddlFailed: "DDL operation failed: {error}",
93
- reldb_queryFailed: "Query failed: {error}",
94
- reldb_executeFailed: "Execute failed: {error}",
95
- reldb_batchFailed: "Batch execute failed: {error}",
96
- reldb_postgresTxFailed: "Transaction failed: {error}",
16
+ reldb_initFailed: "Failed to initialize database",
17
+ reldb_closeFailed: "Failed to close database",
18
+ reldb_ddlFailed: "DDL operation failed",
19
+ reldb_queryFailed: "Query failed",
20
+ reldb_duplicateEntry: "Record already exists",
21
+ reldb_batchFailed: "Batch execute failed",
22
+ reldb_postgresTxFailed: "Transaction failed",
97
23
  reldb_postgresOnlyPostgresql: "PostgreSQL Provider only supports postgresql type",
98
- reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL: {error}",
24
+ reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL",
99
25
  reldb_sqliteOnlySqlite: "SQLite Provider only supports sqlite type",
100
26
  reldb_sqliteNeedPath: "SQLite requires database path (database field)",
101
- reldb_sqliteConnectionFailed: "Failed to connect to SQLite: {error}",
102
- reldb_sqliteTxFailed: "Transaction failed: {error}",
103
- reldb_sqliteCreateTableFailed: "Failed to create table {tableName}: {error}",
104
- reldb_sqliteDropTableFailed: "Failed to drop table {tableName}: {error}",
105
- reldb_sqliteAddColumnFailed: "Failed to add column {columnName} to table {tableName}: {error}",
106
- reldb_sqliteDropColumnFailed: "Failed to drop column {columnName} from table {tableName}: {error}",
107
- reldb_sqliteRenameTableFailed: "Failed to rename table {oldName} to {newName}: {error}",
108
- reldb_sqliteCreateIndexFailed: "Failed to create index {indexName}: {error}",
109
- reldb_sqliteDropIndexFailed: "Failed to drop index {indexName}: {error}",
110
- reldb_sqliteExecDdlFailed: "Failed to execute DDL: {error}",
111
- reldb_sqliteQueryFailed: "Query failed: {error}",
112
- reldb_sqliteExecuteFailed: "Execute failed: {error}",
113
- reldb_sqliteBatchFailed: "Batch execute failed: {error}",
27
+ reldb_sqliteConnectionFailed: "Failed to connect to SQLite",
28
+ reldb_sqliteTxFailed: "Transaction failed",
29
+ reldb_sqliteBatchFailed: "Batch execute failed",
114
30
  reldb_mysqlOnlyMysql: "MySQL Provider only supports mysql type",
115
- reldb_mysqlConnectionFailed: "Failed to connect to MySQL: {error}",
116
- reldb_mysqlTxFailed: "Transaction failed: {error}",
31
+ reldb_mysqlConnectionFailed: "Failed to connect to MySQL",
32
+ reldb_mysqlTxFailed: "Transaction failed",
33
+ reldb_mysqlPoolQueueFull: "MySQL connection acquisition queue is full",
34
+ reldb_mysqlPoolAcquireTimeout: "MySQL connection acquisition deadline exceeded",
35
+ reldb_mysqlPoolReleaseFailed: "MySQL connection release failed; close the database before retrying",
36
+ reldb_mysqlTlsDnsRequired: "MySQL hostname verification requires a DNS hostname matching the certificate; the current driver cannot verify an IP address identity",
117
37
  reldb_crudEmptyPayload: "CRUD payload is empty, please provide data",
118
38
  reldb_crudNoValidColumns: "CRUD payload has no valid columns",
119
39
  reldb_crudRecordNotFound: "Record not found: {id}",
120
40
  reldb_invalidIdentifier: "Invalid SQL identifier: {name} (only letters, digits and underscores allowed)",
121
- reldb_configError: "Database config validation failed: {error}",
122
- reldb_txFailed: "Transaction failed: {error}"
41
+ reldb_configError: "Database config validation failed",
42
+ reldb_txFailed: "Transaction failed",
43
+ reldb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
44
+ reldb_jsonPatchObjectRequired: "JSON merge patch must be an object"
45
+ };
46
+
47
+ // messages/ja-JP.json
48
+ var ja_JP_default = {
49
+ $schema: "https://inlang.com/schema/inlang-message-format",
50
+ reldb_notInitialized: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bdb.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
51
+ reldb_initFailed: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
52
+ reldb_closeFailed: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u7D42\u4E86\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
53
+ reldb_ddlFailed: "DDL\u64CD\u4F5C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
54
+ reldb_queryFailed: "\u30AF\u30A8\u30EA\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
55
+ reldb_duplicateEntry: "\u30EC\u30B3\u30FC\u30C9\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
56
+ reldb_batchFailed: "\u30D0\u30C3\u30C1\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
57
+ reldb_postgresTxFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
58
+ reldb_postgresOnlyPostgresql: "PostgreSQL\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Fpostgresql\u578B\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
59
+ reldb_postgresConnectionFailed: "PostgreSQL\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
60
+ reldb_sqliteOnlySqlite: "SQLite\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Fsqlite\u578B\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
61
+ reldb_sqliteNeedPath: "SQLite\u306B\u306F\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u30D1\u30B9\uFF08database\u30D5\u30A3\u30FC\u30EB\u30C9\uFF09\u304C\u5FC5\u8981\u3067\u3059",
62
+ reldb_sqliteConnectionFailed: "SQLite\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
63
+ reldb_sqliteTxFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
64
+ reldb_sqliteBatchFailed: "\u30D0\u30C3\u30C1\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
65
+ reldb_mysqlOnlyMysql: "MySQL\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Fmysql\u578B\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
66
+ reldb_mysqlConnectionFailed: "MySQL\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
67
+ reldb_mysqlTxFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
68
+ reldb_mysqlPoolQueueFull: "MySQL \u63A5\u7D9A\u53D6\u5F97\u30AD\u30E5\u30FC\u304C\u6E80\u676F\u3067\u3059",
69
+ reldb_mysqlPoolAcquireTimeout: "MySQL \u63A5\u7D9A\u53D6\u5F97\u306E\u5F85\u6A5F\u671F\u9650\u3092\u8D85\u904E\u3057\u307E\u3057\u305F",
70
+ reldb_mysqlPoolReleaseFailed: "MySQL \u63A5\u7D9A\u306E\u89E3\u653E\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u524D\u306B\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u7D42\u4E86\u3057\u3066\u304F\u3060\u3055\u3044",
71
+ reldb_mysqlTlsDnsRequired: "MySQL \u306E\u30DB\u30B9\u30C8\u540D\u691C\u8A3C\u306B\u306F\u8A3C\u660E\u66F8\u3068\u4E00\u81F4\u3059\u308B DNS \u30DB\u30B9\u30C8\u540D\u304C\u5FC5\u8981\u3067\u3059\u3002\u73FE\u5728\u306E\u30C9\u30E9\u30A4\u30D0\u30FC\u3067\u306F IP \u30A2\u30C9\u30EC\u30B9\u306E\u8B58\u5225\u60C5\u5831\u3092\u691C\u8A3C\u3067\u304D\u307E\u305B\u3093",
72
+ reldb_crudEmptyPayload: "CRUD\u30DA\u30A4\u30ED\u30FC\u30C9\u304C\u7A7A\u3067\u3059\u3002\u30C7\u30FC\u30BF\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
73
+ reldb_crudNoValidColumns: "CRUD\u30DA\u30A4\u30ED\u30FC\u30C9\u306B\u6709\u52B9\u306A\u5217\u304C\u3042\u308A\u307E\u305B\u3093",
74
+ reldb_crudRecordNotFound: "\u30EC\u30B3\u30FC\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: {id}",
75
+ reldb_invalidIdentifier: "SQL\u8B58\u5225\u5B50\u304C\u7121\u52B9\u3067\u3059: {name}\uFF08\u82F1\u6570\u5B57\u3068\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\uFF09",
76
+ reldb_configError: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
77
+ reldb_txFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
78
+ reldb_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
79
+ reldb_jsonPatchObjectRequired: "JSON \u30DE\u30FC\u30B8\u30D1\u30C3\u30C1\u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
123
80
  };
124
81
 
125
82
  // messages/zh-CN.json
126
83
  var zh_CN_default = {
127
84
  $schema: "https://inlang.com/schema/inlang-message-format",
128
85
  reldb_notInitialized: "\u6570\u636E\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 db.init()",
129
- reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25: {error}",
130
- reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25: {error}",
131
- reldb_unsupportedType: "\u4E0D\u652F\u6301\u7684\u6570\u636E\u5E93\u7C7B\u578B: {type}",
132
- reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25: {error}",
133
- reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25: {error}",
134
- reldb_executeFailed: "\u6267\u884C\u5931\u8D25: {error}",
135
- reldb_batchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25: {error}",
136
- reldb_postgresTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
86
+ reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25",
87
+ reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25",
88
+ reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25",
89
+ reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25",
90
+ reldb_duplicateEntry: "\u8BB0\u5F55\u5DF2\u5B58\u5728",
91
+ reldb_batchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25",
92
+ reldb_postgresTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
137
93
  reldb_postgresOnlyPostgresql: "PostgreSQL Provider \u4EC5\u652F\u6301 postgresql \u7C7B\u578B",
138
- reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25: {error}",
94
+ reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25",
139
95
  reldb_sqliteOnlySqlite: "SQLite Provider \u4EC5\u652F\u6301 sqlite \u7C7B\u578B",
140
96
  reldb_sqliteNeedPath: "SQLite \u9700\u8981\u63D0\u4F9B\u6570\u636E\u5E93\u8DEF\u5F84\uFF08database \u5B57\u6BB5\uFF09",
141
- reldb_sqliteConnectionFailed: "\u8FDE\u63A5 SQLite \u5931\u8D25: {error}",
142
- reldb_sqliteTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
143
- reldb_sqliteCreateTableFailed: "\u521B\u5EFA\u8868 {tableName} \u5931\u8D25: {error}",
144
- reldb_sqliteDropTableFailed: "\u5220\u9664\u8868 {tableName} \u5931\u8D25: {error}",
145
- reldb_sqliteAddColumnFailed: "\u5411\u8868 {tableName} \u6DFB\u52A0\u5217 {columnName} \u5931\u8D25: {error}",
146
- reldb_sqliteDropColumnFailed: "\u4ECE\u8868 {tableName} \u5220\u9664\u5217 {columnName} \u5931\u8D25: {error}",
147
- reldb_sqliteRenameTableFailed: "\u91CD\u547D\u540D\u8868 {oldName} \u4E3A {newName} \u5931\u8D25: {error}",
148
- reldb_sqliteCreateIndexFailed: "\u521B\u5EFA\u7D22\u5F15 {indexName} \u5931\u8D25: {error}",
149
- reldb_sqliteDropIndexFailed: "\u5220\u9664\u7D22\u5F15 {indexName} \u5931\u8D25: {error}",
150
- reldb_sqliteExecDdlFailed: "\u6267\u884C DDL \u5931\u8D25: {error}",
151
- reldb_sqliteQueryFailed: "\u67E5\u8BE2\u5931\u8D25: {error}",
152
- reldb_sqliteExecuteFailed: "\u6267\u884C\u5931\u8D25: {error}",
153
- reldb_sqliteBatchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25: {error}",
97
+ reldb_sqliteConnectionFailed: "\u8FDE\u63A5 SQLite \u5931\u8D25",
98
+ reldb_sqliteTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
99
+ reldb_sqliteBatchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25",
154
100
  reldb_mysqlOnlyMysql: "MySQL Provider \u4EC5\u652F\u6301 mysql \u7C7B\u578B",
155
- reldb_mysqlConnectionFailed: "\u8FDE\u63A5 MySQL \u5931\u8D25: {error}",
156
- reldb_mysqlTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
101
+ reldb_mysqlConnectionFailed: "\u8FDE\u63A5 MySQL \u5931\u8D25",
102
+ reldb_mysqlTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
103
+ reldb_mysqlPoolQueueFull: "MySQL \u8FDE\u63A5\u83B7\u53D6\u7B49\u5F85\u961F\u5217\u5DF2\u6EE1",
104
+ reldb_mysqlPoolAcquireTimeout: "MySQL \u8FDE\u63A5\u83B7\u53D6\u5DF2\u8D85\u8FC7\u622A\u6B62\u671F\u9650",
105
+ reldb_mysqlPoolReleaseFailed: "MySQL \u8FDE\u63A5\u5F52\u8FD8\u5931\u8D25\uFF0C\u8BF7\u5173\u95ED\u6570\u636E\u5E93\u540E\u91CD\u8BD5",
106
+ reldb_mysqlTlsDnsRequired: "MySQL \u5F53\u524D\u9A71\u52A8\u7684\u4E3B\u673A\u8EAB\u4EFD\u9A8C\u8BC1\u8981\u6C42\u4F7F\u7528\u4E0E\u8BC1\u4E66\u5339\u914D\u7684 DNS \u4E3B\u673A\u540D\uFF0C\u4E0D\u80FD\u9A8C\u8BC1 IP \u5730\u5740\u8EAB\u4EFD",
157
107
  reldb_crudEmptyPayload: "CRUD \u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u63D0\u4F9B\u6570\u636E",
158
108
  reldb_crudNoValidColumns: "CRUD \u6570\u636E\u6CA1\u6709\u6709\u6548\u5217",
159
109
  reldb_crudRecordNotFound: "\u8BB0\u5F55\u4E0D\u5B58\u5728: {id}",
160
110
  reldb_invalidIdentifier: "\u975E\u6CD5\u7684 SQL \u6807\u8BC6\u7B26: {name}\uFF08\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF09",
161
- reldb_configError: "\u6570\u636E\u5E93\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
162
- reldb_txFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}"
111
+ reldb_configError: "\u6570\u636E\u5E93\u914D\u7F6E\u6821\u9A8C\u5931\u8D25",
112
+ reldb_txFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
113
+ reldb_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002",
114
+ reldb_jsonPatchObjectRequired: "JSON \u5408\u5E76\u8865\u4E01\u5FC5\u987B\u662F\u5BF9\u8C61"
163
115
  };
164
116
 
165
117
  // src/reldb-i18n.ts
166
118
  var reldbM = core.i18n.createMessageGetter({
167
119
  "zh-CN": zh_CN_default,
168
- "en-US": en_US_default
120
+ "en-US": en_US_default,
121
+ "ja-JP": ja_JP_default
169
122
  });
170
123
  var ReldbErrorInfo = {
124
+ // ─── 通用 001-009 ───
171
125
  CONNECTION_FAILED: "001:500",
172
126
  QUERY_FAILED: "002:500",
173
127
  CONSTRAINT_VIOLATION: "003:409",
@@ -177,11 +131,13 @@ var ReldbErrorInfo = {
177
131
  DUPLICATE_ENTRY: "007:409",
178
132
  DEADLOCK: "008:500",
179
133
  TIMEOUT: "009:504",
180
- POOL_EXHAUSTED: "010:503",
181
- NOT_INITIALIZED: "011:500",
182
- DDL_FAILED: "012:500",
183
- UNSUPPORTED_TYPE: "013:400",
184
- CONFIG_ERROR: "014:500"
134
+ // ─── 初始化 010-019 ───
135
+ NOT_INITIALIZED: "010:500",
136
+ POOL_EXHAUSTED: "011:503",
137
+ UNSUPPORTED_TYPE: "012:400",
138
+ CONFIG_ERROR: "013:500",
139
+ // ─── 业务操作 020+ ───
140
+ DDL_FAILED: "020:500"
185
141
  };
186
142
  var HaiReldbError = core.error.buildHaiErrorsDef("reldb", ReldbErrorInfo);
187
143
 
@@ -420,12 +376,13 @@ var BaseReldbCrudRepository = class {
420
376
  */
421
377
  mapRow(row) {
422
378
  const result = {};
379
+ const resultRecord = result;
423
380
  for (const field of this.fields) {
424
381
  if (!field.select) {
425
382
  continue;
426
383
  }
427
384
  const value = this.fromDbValue(row[field.columnName], field.def);
428
- result[field.fieldName] = value;
385
+ resultRecord[field.fieldName] = value;
429
386
  }
430
387
  return result;
431
388
  }
@@ -498,7 +455,11 @@ var BaseReldbCrudRepository = class {
498
455
  return payload;
499
456
  }
500
457
  /**
501
- * 等待初始化完成
458
+ * 等待初始化完成(自动建表等)
459
+ *
460
+ * 内置 CRUD 方法均已在执行前调用;自定义方法若通过 `this.sql()` 直接执行原始
461
+ * SQL(尤其是写操作或在事务中),应先 `await this.ensureReady()` 以避免表尚未
462
+ * 建好时(例如刚 dropTable 后、或后端建表较慢时)产生「表不存在」的竞态失败。
502
463
  *
503
464
  * @returns 初始化结果
504
465
  */
@@ -517,6 +478,17 @@ var BaseReldbCrudRepository = class {
517
478
  * @param data - 业务字段与值的映射
518
479
  * @param tx - 可选事务句柄
519
480
  * @returns 插入结果(含 changes、lastInsertRowid)
481
+ * @example
482
+ * ```ts
483
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
484
+ *
485
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
486
+ * const result = await repo.create({ name: 'Alice' })
487
+ * if (!result.success) throw new Error(result.error.message)
488
+ * return result.data
489
+ * }
490
+ * void use
491
+ * ```
520
492
  */
521
493
  async create(data, tx) {
522
494
  const ready = await this.ensureReady();
@@ -537,6 +509,17 @@ var BaseReldbCrudRepository = class {
537
509
  * @param items - 待插入的业务数据数组
538
510
  * @param tx - 可选事务句柄
539
511
  * @returns 批量插入结果
512
+ * @example
513
+ * ```ts
514
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
515
+ *
516
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
517
+ * const result = await repo.createMany([{ name: 'Alice' }, { name: 'Bob' }])
518
+ * if (!result.success) throw new Error(result.error.message)
519
+ * return result.data
520
+ * }
521
+ * void use
522
+ * ```
540
523
  */
541
524
  async createMany(items, tx) {
542
525
  const ready = await this.ensureReady();
@@ -571,6 +554,17 @@ var BaseReldbCrudRepository = class {
571
554
  * @param data - 业务字段与值的映射
572
555
  * @param tx - 可选事务句柄
573
556
  * @returns 执行结果(含 changes)
557
+ * @example
558
+ * ```ts
559
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
560
+ *
561
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
562
+ * const result = await repo.createOrUpdate({ id: 'u1', name: 'Alice' })
563
+ * if (!result.success) throw new Error(result.error.message)
564
+ * return result.data
565
+ * }
566
+ * void use
567
+ * ```
574
568
  */
575
569
  async createOrUpdate(data, tx) {
576
570
  const ready = await this.ensureReady();
@@ -613,6 +607,17 @@ var BaseReldbCrudRepository = class {
613
607
  * @param id - 主键值
614
608
  * @param tx - 可选事务句柄
615
609
  * @returns 业务模型对象或 null(未找到)
610
+ * @example
611
+ * ```ts
612
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
613
+ *
614
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
615
+ * const result = await repo.findById('u1')
616
+ * if (!result.success) throw new Error(result.error.message)
617
+ * return result.data
618
+ * }
619
+ * void use
620
+ * ```
616
621
  */
617
622
  async findById(id, tx) {
618
623
  const ready = await this.ensureReady();
@@ -629,6 +634,17 @@ var BaseReldbCrudRepository = class {
629
634
  * @param id - 主键值
630
635
  * @param tx - 可选事务句柄
631
636
  * @returns 业务模型对象(不存在时返回错误)
637
+ * @example
638
+ * ```ts
639
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
640
+ *
641
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
642
+ * const result = await repo.getById('u1')
643
+ * if (!result.success) throw new Error(result.error.message)
644
+ * return result.data
645
+ * }
646
+ * void use
647
+ * ```
632
648
  */
633
649
  async getById(id, tx) {
634
650
  const ready = await this.ensureReady();
@@ -643,6 +659,17 @@ var BaseReldbCrudRepository = class {
643
659
  * @param options - 查询条件(where、orderBy、limit、offset)
644
660
  * @param tx - 可选事务句柄
645
661
  * @returns 业务模型数组
662
+ * @example
663
+ * ```ts
664
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
665
+ *
666
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
667
+ * const result = await repo.findAll({ where: 'name = ?', params: ['Alice'] })
668
+ * if (!result.success) throw new Error(result.error.message)
669
+ * return result.data
670
+ * }
671
+ * void use
672
+ * ```
646
673
  */
647
674
  async findAll(options, tx) {
648
675
  const ready = await this.ensureReady();
@@ -657,6 +684,17 @@ var BaseReldbCrudRepository = class {
657
684
  * @param options - 分页查询条件(where、orderBy、pagination、overrides)
658
685
  * @param tx - 可选事务句柄
659
686
  * @returns 分页结果(含 items、total、page、pageSize)
687
+ * @example
688
+ * ```ts
689
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
690
+ *
691
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
692
+ * const result = await repo.findPage({ pagination: { page: 1, pageSize: 10 }, orderBy: 'id ASC' })
693
+ * if (!result.success) throw new Error(result.error.message)
694
+ * return result.data
695
+ * }
696
+ * void use
697
+ * ```
660
698
  */
661
699
  async findPage(options, tx) {
662
700
  const ready = await this.ensureReady();
@@ -674,6 +712,17 @@ var BaseReldbCrudRepository = class {
674
712
  * @param data - 待更新的业务字段
675
713
  * @param tx - 可选事务句柄
676
714
  * @returns 更新结果(含 changes)
715
+ * @example
716
+ * ```ts
717
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
718
+ *
719
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
720
+ * const result = await repo.updateById('u1', { name: 'Bob' })
721
+ * if (!result.success) throw new Error(result.error.message)
722
+ * return result.data
723
+ * }
724
+ * void use
725
+ * ```
677
726
  */
678
727
  async updateById(id, data, tx) {
679
728
  const ready = await this.ensureReady();
@@ -692,6 +741,17 @@ var BaseReldbCrudRepository = class {
692
741
  * @param id - 主键值
693
742
  * @param tx - 可选事务句柄
694
743
  * @returns 删除结果(含 changes)
744
+ * @example
745
+ * ```ts
746
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
747
+ *
748
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
749
+ * const result = await repo.deleteById('u1')
750
+ * if (!result.success) throw new Error(result.error.message)
751
+ * return result.data
752
+ * }
753
+ * void use
754
+ * ```
695
755
  */
696
756
  async deleteById(id, tx) {
697
757
  const ready = await this.ensureReady();
@@ -706,6 +766,17 @@ var BaseReldbCrudRepository = class {
706
766
  * @param options - 查询条件(where、params)
707
767
  * @param tx - 可选事务句柄
708
768
  * @returns 记录数
769
+ * @example
770
+ * ```ts
771
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
772
+ *
773
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
774
+ * const result = await repo.count({ where: 'name = ?', params: ['Alice'] })
775
+ * if (!result.success) throw new Error(result.error.message)
776
+ * return result.data
777
+ * }
778
+ * void use
779
+ * ```
709
780
  */
710
781
  async count(options, tx) {
711
782
  const ready = await this.ensureReady();
@@ -720,6 +791,17 @@ var BaseReldbCrudRepository = class {
720
791
  * @param options - 查询条件(where、params)
721
792
  * @param tx - 可选事务句柄
722
793
  * @returns 是否存在
794
+ * @example
795
+ * ```ts
796
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
797
+ *
798
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
799
+ * const result = await repo.exists({ where: 'name = ?', params: ['Alice'] })
800
+ * if (!result.success) throw new Error(result.error.message)
801
+ * return result.data
802
+ * }
803
+ * void use
804
+ * ```
723
805
  */
724
806
  async exists(options, tx) {
725
807
  const ready = await this.ensureReady();
@@ -734,6 +816,17 @@ var BaseReldbCrudRepository = class {
734
816
  * @param id - 主键值
735
817
  * @param tx - 可选事务句柄
736
818
  * @returns 是否存在
819
+ * @example
820
+ * ```ts
821
+ * import type { ReldbCrudRepository } from '@h-ai/reldb'
822
+ *
823
+ * async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
824
+ * const result = await repo.existsById('u1')
825
+ * if (!result.success) throw new Error(result.error.message)
826
+ * return result.data
827
+ * }
828
+ * void use
829
+ * ```
737
830
  */
738
831
  async existsById(id, tx) {
739
832
  const ready = await this.ensureReady();
@@ -745,6 +838,7 @@ var BaseReldbCrudRepository = class {
745
838
  };
746
839
 
747
840
  // src/reldb-json.ts
841
+ var JSON_PATH_OBJECT_SEGMENT_REGEX = /^([^.[]+)/;
748
842
  function parseJsonPath(path) {
749
843
  let remaining = path.startsWith("$") ? path.slice(1) : path;
750
844
  const segments = [];
@@ -763,7 +857,7 @@ function parseJsonPath(path) {
763
857
  remaining = remaining.slice(1);
764
858
  if (remaining.startsWith("["))
765
859
  continue;
766
- const match = remaining.match(/^([^.[]+)/);
860
+ const match = remaining.match(JSON_PATH_OBJECT_SEGMENT_REGEX);
767
861
  if (match) {
768
862
  segments.push(match[1]);
769
863
  remaining = remaining.slice(match[1].length);
@@ -808,6 +902,46 @@ function createSqliteJsonOps() {
808
902
  }
809
903
  };
810
904
  }
905
+ function buildPostgresMergePatch(column, patch) {
906
+ const normalized = JSON.parse(JSON.stringify(patch));
907
+ if (!normalized || typeof normalized !== "object" || Array.isArray(normalized))
908
+ throw new TypeError(reldbM("reldb_jsonPatchObjectRequired"));
909
+ const steps = [];
910
+ const pending = Object.entries(normalized).reverse().map(([key, value]) => ({ path: [key], value }));
911
+ while (pending.length > 0) {
912
+ const entry = pending.pop();
913
+ const object = entry.value !== null && typeof entry.value === "object" && !Array.isArray(entry.value);
914
+ steps.push({ path: entry.path, value: object ? {} : entry.value, object });
915
+ if (object) {
916
+ for (const [key, value] of Object.entries(entry.value).reverse())
917
+ pending.push({ path: [...entry.path, key], value });
918
+ }
919
+ }
920
+ return {
921
+ sql: `(WITH RECURSIVE
922
+ patch_steps AS (
923
+ SELECT ordinality AS step,
924
+ ARRAY(SELECT jsonb_array_elements_text(item->'path')) AS path,
925
+ item->'value' AS value, (item->>'object')::boolean AS object
926
+ FROM jsonb_array_elements(?::jsonb) WITH ORDINALITY AS input(item, ordinality)
927
+ ),
928
+ merged(step, document) AS (
929
+ SELECT 0::bigint, CASE WHEN jsonb_typeof(${column}) = 'object' THEN ${column} ELSE '{}'::jsonb END
930
+ UNION ALL
931
+ SELECT patch_steps.step,
932
+ CASE
933
+ WHEN patch_steps.object THEN jsonb_set(merged.document, patch_steps.path,
934
+ CASE WHEN jsonb_typeof(merged.document #> patch_steps.path) = 'object'
935
+ THEN merged.document #> patch_steps.path ELSE '{}'::jsonb END, true)
936
+ WHEN patch_steps.value = 'null'::jsonb THEN merged.document #- patch_steps.path
937
+ ELSE jsonb_set(merged.document, patch_steps.path, patch_steps.value, true)
938
+ END
939
+ FROM merged JOIN patch_steps ON patch_steps.step = merged.step + 1
940
+ )
941
+ SELECT document FROM merged ORDER BY step DESC LIMIT 1)`,
942
+ params: [JSON.stringify(steps)]
943
+ };
944
+ }
811
945
  function createPostgresJsonOps() {
812
946
  return {
813
947
  extract(column, path) {
@@ -839,10 +973,7 @@ function createPostgresJsonOps() {
839
973
  };
840
974
  },
841
975
  merge(column, patch) {
842
- return {
843
- sql: `${column} || ?::jsonb`,
844
- params: [JSON.stringify(patch)]
845
- };
976
+ return buildPostgresMergePatch(column, patch);
846
977
  }
847
978
  };
848
979
  }
@@ -977,6 +1108,7 @@ function escapeSqlString(value) {
977
1108
 
978
1109
  // src/reldb-crud-kernel.ts
979
1110
  var logger = core.logger.child({ module: "reldb", scope: "crud-kernel" });
1111
+ var ORDER_BY_SEGMENT_REGEX = /^(\w+)(?:\s+(ASC|DESC))?$/i;
980
1112
  function buildSelectColumns(select) {
981
1113
  if (!select || select.length === 0) {
982
1114
  return "*";
@@ -1045,7 +1177,7 @@ function createCrud(ops, config) {
1045
1177
  const parts = orderBy.split(",").map((s) => s.trim()).filter(Boolean);
1046
1178
  const safeParts = [];
1047
1179
  for (const part of parts) {
1048
- const match = part.match(/^(\w+)(?:\s+(ASC|DESC))?$/i);
1180
+ const match = part.match(ORDER_BY_SEGMENT_REGEX);
1049
1181
  if (!match) {
1050
1182
  logger.debug("Skipped invalid orderBy segment", { table, segment: part });
1051
1183
  continue;
@@ -1433,15 +1565,87 @@ function createTxWrap(beginTx) {
1433
1565
  }
1434
1566
 
1435
1567
  // src/providers/reldb-provider-base.ts
1568
+ function summarizeOperationPayload(payload) {
1569
+ try {
1570
+ if (!payload || typeof payload !== "object")
1571
+ return {};
1572
+ const fields = payload;
1573
+ return {
1574
+ ...typeof fields.sql === "string" ? { sqlLength: fields.sql.length } : {},
1575
+ ...Array.isArray(fields.params) ? { parameterCount: fields.params.length } : {},
1576
+ ...Array.isArray(fields.statements) ? { statementCount: fields.statements.length } : {},
1577
+ ...fields.columns && typeof fields.columns === "object" ? { columnCount: Object.keys(fields.columns).length } : {}
1578
+ };
1579
+ } catch {
1580
+ return {};
1581
+ }
1582
+ }
1583
+ function logReldbOperation(ctx, category, operation, payload) {
1584
+ const config = ctx.operationLog?.();
1585
+ if (!config?.[category]) {
1586
+ return;
1587
+ }
1588
+ ctx.logger[config.level]("RelDB operation executed", {
1589
+ category,
1590
+ operation,
1591
+ payload: config.maxLength <= 0 ? "" : JSON.stringify(summarizeOperationPayload(payload)).slice(0, config.maxLength)
1592
+ });
1593
+ }
1436
1594
  async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
1437
1595
  if (!ctx.isConnected()) {
1438
1596
  return err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
1439
1597
  }
1598
+ const failed = (error, original) => {
1599
+ let definition = original ?? errorDef;
1600
+ const traceId = core.context.get().traceId ?? core.context.get().requestId ?? original?.traceId ?? core.id.uuid();
1601
+ const fields = error && typeof error === "object" ? error : {};
1602
+ if (definition.code === HaiReldbError.QUERY_FAILED.code && (["23505", "ER_DUP_ENTRY", "SQLITE_CONSTRAINT_UNIQUE", "SQLITE_CONSTRAINT_PRIMARYKEY"].includes(String(fields.code)) || fields.errno === 1062))
1603
+ definition = HaiReldbError.DUPLICATE_ENTRY;
1604
+ const secrets = /* @__PURE__ */ new Set();
1605
+ const seen = /* @__PURE__ */ new WeakSet();
1606
+ const collect = (value) => {
1607
+ if (typeof value === "string" && value) {
1608
+ secrets.add(value);
1609
+ } else if (typeof value === "number" || typeof value === "bigint") {
1610
+ secrets.add(String(value));
1611
+ } else if (value && typeof value === "object" && !seen.has(value)) {
1612
+ seen.add(value);
1613
+ Object.values(value).forEach(collect);
1614
+ }
1615
+ };
1616
+ collect(errorMeta?.params);
1617
+ const payloads = [errorMeta, ...Array.isArray(errorMeta?.statements) ? errorMeta.statements : []];
1618
+ for (const payload of payloads) {
1619
+ collect(payload?.params);
1620
+ for (const match of (typeof payload?.sql === "string" ? payload.sql : "").matchAll(/'((?:''|[^'])*)'/g))
1621
+ secrets.add(match[1].replaceAll("''", "'"));
1622
+ }
1623
+ const diagnostic = {};
1624
+ for (const key2 of ["name", "message", "stack", "code", "errno", "sqlState", "detail", "hint", "constraint", "schema", "routine", "severity", "table", "column"]) {
1625
+ const value = error instanceof Error && key2 === "message" ? error.message : error instanceof Error && key2 === "stack" ? error.stack : fields[key2];
1626
+ if (typeof value === "string") {
1627
+ if (["code", "sqlState", "schema", "routine", "severity", "constraint", "table", "column"].includes(key2)) {
1628
+ diagnostic[key2] = core.sanitize.sanitizeText(value);
1629
+ } else {
1630
+ const frames = key2 === "stack" ? value.search(/^\s+at\s/m) : -1;
1631
+ const body = frames < 0 ? value : value.slice(0, frames);
1632
+ diagnostic[key2] = core.sanitize.sanitizeText(body, [...secrets]).replace(/'(?:''|[^'])*'/g, "'[REDACTED]'") + (frames < 0 ? "" : core.sanitize.sanitizeText(value.slice(frames)));
1633
+ }
1634
+ } else if (typeof value === "number") {
1635
+ diagnostic[key2] = value;
1636
+ }
1637
+ }
1638
+ if (!Object.keys(diagnostic).length)
1639
+ diagnostic.message = core.sanitize.sanitizeText(String(error), [...secrets]);
1640
+ ctx.logger.error(errorLabel, { ...summarizeOperationPayload(errorMeta), table: errorMeta?.tableName, column: errorMeta?.columnName, errorCode: definition.code, traceId, error: diagnostic });
1641
+ const key = definition.code === HaiReldbError.DDL_FAILED.code ? "reldb_ddlFailed" : definition.code === HaiReldbError.TRANSACTION_FAILED.code ? "reldb_txFailed" : "reldb_queryFailed";
1642
+ return err({ ...definition, traceId, message: definition.code === HaiReldbError.DUPLICATE_ENTRY.code ? reldbM("reldb_duplicateEntry") : original ? core.sanitize.sanitizeText(original.message, [...secrets]) : reldbM(key), cause: diagnostic });
1643
+ };
1440
1644
  try {
1441
- return await fn();
1645
+ const result = await fn();
1646
+ return result.success ? result : failed(result.error.cause ?? result.error, result.error);
1442
1647
  } catch (error) {
1443
- ctx.logger.error(errorLabel, { ...errorMeta, error });
1444
- return err(errorDef, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
1648
+ return failed(error);
1445
1649
  }
1446
1650
  }
1447
1651
  function createBaseDdlOps(ctx, raw) {
@@ -1453,12 +1657,14 @@ function createBaseDdlOps(ctx, raw) {
1453
1657
  const v2 = validateIdentifiers(Object.keys(columns));
1454
1658
  if (!v2.success)
1455
1659
  return Promise.resolve(v2);
1660
+ logReldbOperation(ctx, "write", "ddl.createTable", { tableName, columns, ifNotExists });
1456
1661
  return wrapOp(ctx, () => raw.createTable(tableName, columns, ifNotExists), HaiReldbError.DDL_FAILED, "DDL: createTable failed", { tableName });
1457
1662
  },
1458
1663
  dropTable(tableName, ifExists = true) {
1459
1664
  const v = validateIdentifier(tableName);
1460
1665
  if (!v.success)
1461
1666
  return Promise.resolve(v);
1667
+ logReldbOperation(ctx, "write", "ddl.dropTable", { tableName, ifExists });
1462
1668
  return wrapOp(ctx, () => raw.dropTable(tableName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropTable failed", { tableName });
1463
1669
  },
1464
1670
  addColumn(tableName, columnName, columnDef) {
@@ -1468,6 +1674,7 @@ function createBaseDdlOps(ctx, raw) {
1468
1674
  const v2 = validateIdentifier(columnName);
1469
1675
  if (!v2.success)
1470
1676
  return Promise.resolve(v2);
1677
+ logReldbOperation(ctx, "write", "ddl.addColumn", { tableName, columnName, columnDef });
1471
1678
  return wrapOp(ctx, () => raw.addColumn(tableName, columnName, columnDef), HaiReldbError.DDL_FAILED, "DDL: addColumn failed", { tableName, columnName });
1472
1679
  },
1473
1680
  dropColumn(tableName, columnName) {
@@ -1477,6 +1684,7 @@ function createBaseDdlOps(ctx, raw) {
1477
1684
  const v2 = validateIdentifier(columnName);
1478
1685
  if (!v2.success)
1479
1686
  return Promise.resolve(v2);
1687
+ logReldbOperation(ctx, "write", "ddl.dropColumn", { tableName, columnName });
1480
1688
  return wrapOp(ctx, () => raw.dropColumn(tableName, columnName), HaiReldbError.DDL_FAILED, "DDL: dropColumn failed", { tableName, columnName });
1481
1689
  },
1482
1690
  renameTable(oldName, newName) {
@@ -1486,6 +1694,7 @@ function createBaseDdlOps(ctx, raw) {
1486
1694
  const v2 = validateIdentifier(newName);
1487
1695
  if (!v2.success)
1488
1696
  return Promise.resolve(v2);
1697
+ logReldbOperation(ctx, "write", "ddl.renameTable", { oldName, newName });
1489
1698
  return wrapOp(ctx, () => raw.renameTable(oldName, newName), HaiReldbError.DDL_FAILED, "DDL: renameTable failed", { oldName, newName });
1490
1699
  },
1491
1700
  createIndex(tableName, indexName, indexDef) {
@@ -1498,30 +1707,66 @@ function createBaseDdlOps(ctx, raw) {
1498
1707
  const v3 = validateIdentifiers(indexDef.columns);
1499
1708
  if (!v3.success)
1500
1709
  return Promise.resolve(v3);
1710
+ logReldbOperation(ctx, "write", "ddl.createIndex", { tableName, indexName, indexDef });
1501
1711
  return wrapOp(ctx, () => raw.createIndex(tableName, indexName, indexDef), HaiReldbError.DDL_FAILED, "DDL: createIndex failed", { tableName, indexName });
1502
1712
  },
1503
1713
  dropIndex(indexName, ifExists = true) {
1504
1714
  const v = validateIdentifier(indexName);
1505
1715
  if (!v.success)
1506
1716
  return Promise.resolve(v);
1717
+ logReldbOperation(ctx, "write", "ddl.dropIndex", { indexName, ifExists });
1507
1718
  return wrapOp(ctx, () => raw.dropIndex(indexName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropIndex failed", { indexName });
1508
1719
  },
1509
1720
  raw(sql) {
1510
- return wrapOp(ctx, () => raw.raw(sql), HaiReldbError.DDL_FAILED, "DDL: raw failed");
1721
+ logReldbOperation(ctx, "write", "ddl.raw", { sql });
1722
+ return wrapOp(ctx, () => raw.raw(sql), HaiReldbError.DDL_FAILED, "DDL: raw failed", { sql });
1511
1723
  }
1512
1724
  };
1513
1725
  }
1514
1726
  function createBaseDmlOps(ctx, raw) {
1515
1727
  return {
1516
- query: (sql, params) => wrapOp(ctx, () => raw.query(sql, params), HaiReldbError.QUERY_FAILED, "DML: query failed"),
1517
- get: (sql, params) => wrapOp(ctx, () => raw.get(sql, params), HaiReldbError.QUERY_FAILED, "DML: get failed"),
1518
- execute: (sql, params) => wrapOp(ctx, () => raw.execute(sql, params), HaiReldbError.QUERY_FAILED, "DML: execute failed"),
1519
- batch: (stmts) => wrapOp(ctx, () => raw.batch(stmts), HaiReldbError.QUERY_FAILED, "DML: batch failed"),
1520
- queryPage: (options) => wrapOp(ctx, () => raw.queryPage(options), HaiReldbError.QUERY_FAILED, "DML: queryPage failed")
1728
+ query: (sql, params) => {
1729
+ logReldbOperation(ctx, "read", "query", { sql, params });
1730
+ return wrapOp(ctx, () => raw.query(sql, params), HaiReldbError.QUERY_FAILED, "DML: query failed", { sql, params });
1731
+ },
1732
+ get: (sql, params) => {
1733
+ logReldbOperation(ctx, "read", "get", { sql, params });
1734
+ return wrapOp(ctx, () => raw.get(sql, params), HaiReldbError.QUERY_FAILED, "DML: get failed", { sql, params });
1735
+ },
1736
+ execute: (sql, params) => {
1737
+ logReldbOperation(ctx, "write", "execute", { sql, params });
1738
+ return wrapOp(ctx, () => raw.execute(sql, params), HaiReldbError.QUERY_FAILED, "DML: execute failed", { sql, params });
1739
+ },
1740
+ batch: (statements) => {
1741
+ logReldbOperation(ctx, "write", "batch", { count: statements.length, statements });
1742
+ return wrapOp(ctx, () => raw.batch(statements), HaiReldbError.QUERY_FAILED, "DML: batch failed", { statements });
1743
+ },
1744
+ queryPage: (options) => {
1745
+ logReldbOperation(ctx, "read", "queryPage", options);
1746
+ return wrapOp(ctx, () => raw.queryPage(options), HaiReldbError.QUERY_FAILED, "DML: queryPage failed", { ...options });
1747
+ }
1521
1748
  };
1522
1749
  }
1523
1750
  function createBaseTxManager(ctx, beginTx) {
1524
- const begin = () => wrapOp(ctx, beginTx, HaiReldbError.TRANSACTION_FAILED, "TX: begin failed");
1751
+ const begin = async () => {
1752
+ logReldbOperation(ctx, "write", "tx.begin", {});
1753
+ const result = await wrapOp(ctx, beginTx, HaiReldbError.TRANSACTION_FAILED, "TX: begin failed");
1754
+ if (!result.success) {
1755
+ return result;
1756
+ }
1757
+ const tx = result.data;
1758
+ return ok({
1759
+ ...tx,
1760
+ commit: async () => {
1761
+ logReldbOperation(ctx, "write", "tx.commit", {});
1762
+ return tx.commit();
1763
+ },
1764
+ rollback: async () => {
1765
+ logReldbOperation(ctx, "write", "tx.rollback", {});
1766
+ return tx.rollback();
1767
+ }
1768
+ });
1769
+ };
1525
1770
  return {
1526
1771
  begin,
1527
1772
  wrap: createTxWrap(begin)
@@ -1609,9 +1854,149 @@ function buildDefaultDropIndexSql(quoteId, indexName, ifExists) {
1609
1854
  var logger2 = core.logger.child({ module: "reldb", scope: "mysql" });
1610
1855
  function createMysqlProvider() {
1611
1856
  let pool = null;
1857
+ let ready = false;
1858
+ let closing = null;
1859
+ let poolConfig = null;
1860
+ let activeSlots = 0;
1861
+ let slotsDrained = Promise.resolve();
1862
+ let resolveSlots = null;
1863
+ const waiters = [];
1864
+ let currentConfig2 = null;
1865
+ function releaseSlot() {
1866
+ activeSlots--;
1867
+ if (ready && poolConfig) {
1868
+ const maximum = poolConfig.max;
1869
+ while (waiters.length > 0) {
1870
+ if (activeSlots >= maximum)
1871
+ break;
1872
+ waiters.shift().start();
1873
+ }
1874
+ }
1875
+ if (activeSlots === 0) {
1876
+ resolveSlots?.();
1877
+ resolveSlots = null;
1878
+ }
1879
+ }
1880
+ function acquireConnection() {
1881
+ const owned = pool;
1882
+ const limits = poolConfig;
1883
+ if (!ready || !owned || !limits)
1884
+ return Promise.resolve(err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized")));
1885
+ if (activeSlots >= limits.max && waiters.length >= limits.queueLimit)
1886
+ return Promise.resolve(err(HaiReldbError.POOL_EXHAUSTED, reldbM("reldb_mysqlPoolQueueFull")));
1887
+ const deadline = performance$1.now() + limits.acquireTimeout;
1888
+ return new Promise((resolve) => {
1889
+ let completed = false;
1890
+ let timer;
1891
+ const timeoutResult = () => err(HaiReldbError.TIMEOUT, reldbM("reldb_mysqlPoolAcquireTimeout"));
1892
+ const finish = (result) => {
1893
+ if (completed)
1894
+ return;
1895
+ completed = true;
1896
+ clearTimeout(timer);
1897
+ resolve(result);
1898
+ };
1899
+ const waiter = {
1900
+ cancel: () => finish(err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"))),
1901
+ start: () => {
1902
+ if (!ready || performance$1.now() >= deadline) {
1903
+ finish(ready ? timeoutResult() : err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized")));
1904
+ return;
1905
+ }
1906
+ if (activeSlots++ === 0)
1907
+ slotsDrained = new Promise((resolve2) => {
1908
+ resolveSlots = resolve2;
1909
+ });
1910
+ void Promise.resolve().then(() => owned.getConnection()).then((connection) => {
1911
+ if (completed || !ready || performance$1.now() >= deadline) {
1912
+ try {
1913
+ connection.release();
1914
+ } finally {
1915
+ releaseSlot();
1916
+ }
1917
+ finish(ready ? timeoutResult() : err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized")));
1918
+ return;
1919
+ }
1920
+ let released = false;
1921
+ finish(ok({
1922
+ connection,
1923
+ deadline,
1924
+ release: () => {
1925
+ if (released)
1926
+ return;
1927
+ released = true;
1928
+ try {
1929
+ connection.release();
1930
+ } finally {
1931
+ releaseSlot();
1932
+ }
1933
+ }
1934
+ }));
1935
+ }, (error) => {
1936
+ releaseSlot();
1937
+ finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error));
1938
+ }).catch((error) => {
1939
+ ready = false;
1940
+ finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: reldbM("reldb_mysqlPoolReleaseFailed") } }), error));
1941
+ logger2.warn("MySQL connection release failed; pool requires cleanup");
1942
+ });
1943
+ }
1944
+ };
1945
+ timer = setTimeout(() => {
1946
+ const index = waiters.indexOf(waiter);
1947
+ if (index >= 0)
1948
+ waiters.splice(index, 1);
1949
+ finish(timeoutResult());
1950
+ }, limits.acquireTimeout);
1951
+ if (activeSlots < limits.max)
1952
+ waiter.start();
1953
+ else
1954
+ waiters.push(waiter);
1955
+ });
1956
+ }
1957
+ async function withConnection(fn) {
1958
+ const acquired = await acquireConnection();
1959
+ if (!acquired.success)
1960
+ return acquired;
1961
+ try {
1962
+ if (!ready)
1963
+ return err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
1964
+ if (performance$1.now() >= acquired.data.deadline)
1965
+ return err(HaiReldbError.TIMEOUT, reldbM("reldb_mysqlPoolAcquireTimeout"));
1966
+ return await fn(acquired.data.connection);
1967
+ } finally {
1968
+ acquired.data.release();
1969
+ }
1970
+ }
1971
+ function closeResource() {
1972
+ ready = false;
1973
+ currentConfig2 = null;
1974
+ for (const waiter of waiters.splice(0))
1975
+ waiter.cancel();
1976
+ if (closing)
1977
+ return closing;
1978
+ const owned = pool;
1979
+ if (!owned)
1980
+ return Promise.resolve(ok(void 0));
1981
+ closing = (async () => {
1982
+ try {
1983
+ await slotsDrained;
1984
+ await owned.end();
1985
+ pool = null;
1986
+ poolConfig = null;
1987
+ return ok(void 0);
1988
+ } catch (error) {
1989
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
1990
+ }
1991
+ })().finally(() => {
1992
+ closing = null;
1993
+ });
1994
+ return closing;
1995
+ }
1612
1996
  const ctx = {
1613
- isConnected: () => pool !== null,
1614
- logger: logger2
1997
+ isConnected: () => ready && pool !== null,
1998
+ logger: logger2,
1999
+ operationLog: () => currentConfig2?.operationLog
1615
2000
  };
1616
2001
  function mysqlQuoteId(name) {
1617
2002
  return `\`${name}\``;
@@ -1619,17 +2004,15 @@ function createMysqlProvider() {
1619
2004
  function mysqlTxErrorMessage(detail) {
1620
2005
  return reldbM("reldb_mysqlTxFailed", { params: { error: detail } });
1621
2006
  }
1622
- async function findIndexTableName(queryFn, indexName) {
1623
- try {
1624
- const [rows] = await queryFn(
2007
+ async function findIndexTableName(indexName) {
2008
+ return withConnection(async (connection) => {
2009
+ const [rows] = await connection.query(
1625
2010
  "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME = ? LIMIT 1",
1626
2011
  [indexName]
1627
2012
  );
1628
2013
  const data = rows;
1629
2014
  return ok(data[0]?.name ?? null);
1630
- } catch (error) {
1631
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
1632
- }
2015
+ });
1633
2016
  }
1634
2017
  function mapMysqlType(def) {
1635
2018
  switch (def.type) {
@@ -1637,6 +2020,8 @@ function createMysqlProvider() {
1637
2020
  return "VARCHAR(255)";
1638
2021
  case "INTEGER":
1639
2022
  return def.autoIncrement ? "BIGINT" : "INT";
2023
+ case "BIGINT":
2024
+ return "BIGINT";
1640
2025
  case "REAL":
1641
2026
  return "DOUBLE";
1642
2027
  case "BLOB":
@@ -1706,39 +2091,39 @@ function createMysqlProvider() {
1706
2091
  const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
1707
2092
  return `CREATE TABLE ${ifNotExistsClause}${quotedTable} (${columnDefs.join(", ")}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
1708
2093
  }
2094
+ function executeDdl(sql) {
2095
+ return withConnection(async (connection) => {
2096
+ await connection.query(sql);
2097
+ return ok(void 0);
2098
+ });
2099
+ }
1709
2100
  const rawDdl = {
1710
2101
  async createTable(name, columns, ifNotExists = true) {
1711
2102
  const quotedTable = mysqlQuoteId(name);
1712
2103
  const sql = mysqlBuildCreateTableSql(quotedTable, columns, ifNotExists);
1713
- await pool.query(sql);
1714
- return ok(void 0);
2104
+ return executeDdl(sql);
1715
2105
  },
1716
2106
  async dropTable(name, ifExists) {
1717
2107
  const ifExistsClause = ifExists ? "IF EXISTS " : "";
1718
- await pool.query(`DROP TABLE ${ifExistsClause}${mysqlQuoteId(name)}`);
1719
- return ok(void 0);
2108
+ return executeDdl(`DROP TABLE ${ifExistsClause}${mysqlQuoteId(name)}`);
1720
2109
  },
1721
2110
  async addColumn(table, column, def) {
1722
2111
  const colSql = mysqlBuildColumnSql(column, def);
1723
- await pool.query(`ALTER TABLE ${mysqlQuoteId(table)} ADD COLUMN ${colSql}`);
1724
- return ok(void 0);
2112
+ return executeDdl(`ALTER TABLE ${mysqlQuoteId(table)} ADD COLUMN ${colSql}`);
1725
2113
  },
1726
2114
  async dropColumn(table, column) {
1727
- await pool.query(`ALTER TABLE ${mysqlQuoteId(table)} DROP COLUMN ${mysqlQuoteId(column)}`);
1728
- return ok(void 0);
2115
+ return executeDdl(`ALTER TABLE ${mysqlQuoteId(table)} DROP COLUMN ${mysqlQuoteId(column)}`);
1729
2116
  },
1730
2117
  async renameTable(oldName, newName) {
1731
- await pool.query(`RENAME TABLE ${mysqlQuoteId(oldName)} TO ${mysqlQuoteId(newName)}`);
1732
- return ok(void 0);
2118
+ return executeDdl(`RENAME TABLE ${mysqlQuoteId(oldName)} TO ${mysqlQuoteId(newName)}`);
1733
2119
  },
1734
2120
  async createIndex(table, index, def) {
1735
2121
  const uniqueClause = def.unique ? "UNIQUE " : "";
1736
2122
  const columns = def.columns.map((c) => mysqlQuoteId(c)).join(", ");
1737
- await pool.query(`CREATE ${uniqueClause}INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(table)} (${columns})`);
1738
- return ok(void 0);
2123
+ return executeDdl(`CREATE ${uniqueClause}INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(table)} (${columns})`);
1739
2124
  },
1740
2125
  async dropIndex(index, ifExists) {
1741
- const tableResult = await findIndexTableName((s, v) => pool.query(s, v), index);
2126
+ const tableResult = await findIndexTableName(index);
1742
2127
  if (!tableResult.success)
1743
2128
  return tableResult;
1744
2129
  if (!tableResult.data) {
@@ -1746,58 +2131,58 @@ function createMysqlProvider() {
1746
2131
  return ok(void 0);
1747
2132
  return err(HaiReldbError.DDL_FAILED, reldbM("reldb_ddlFailed", { params: { error: `index not found: ${index}` } }));
1748
2133
  }
1749
- await pool.query(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
1750
- return ok(void 0);
2134
+ return executeDdl(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
1751
2135
  },
1752
2136
  async raw(sql) {
1753
- await pool.query(sql);
1754
- return ok(void 0);
2137
+ return executeDdl(sql);
1755
2138
  }
1756
2139
  };
1757
2140
  const rawDml = {
1758
2141
  async query(sql, params) {
1759
- const [rows] = await pool.query(sql, params);
1760
- return ok(rows);
2142
+ return withConnection(async (connection) => {
2143
+ const [rows] = await connection.query(sql, params);
2144
+ return ok(rows);
2145
+ });
1761
2146
  },
1762
2147
  async get(sql, params) {
1763
- const [rows] = await pool.query(sql, params);
1764
- return ok(rows[0] ?? null);
2148
+ return withConnection(async (connection) => {
2149
+ const [rows] = await connection.query(sql, params);
2150
+ return ok(rows[0] ?? null);
2151
+ });
1765
2152
  },
1766
2153
  async execute(sql, params) {
1767
- const [result] = await pool.execute(sql, params);
1768
- return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
2154
+ return withConnection(async (connection) => {
2155
+ const [result] = await connection.execute({ sql, values: params });
2156
+ return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
2157
+ });
1769
2158
  },
1770
2159
  async batch(statements) {
1771
- let connection = null;
1772
- try {
1773
- connection = await pool.getConnection();
1774
- await connection.beginTransaction();
1775
- for (const { sql: s, params } of statements) {
1776
- await connection.execute(s, params);
1777
- }
1778
- await connection.commit();
1779
- return ok(void 0);
1780
- } catch (error) {
1781
- if (connection) {
2160
+ return withConnection(async (connection) => {
2161
+ try {
2162
+ await connection.beginTransaction();
2163
+ for (const { sql: s, params } of statements) {
2164
+ await connection.execute({ sql: s, values: params });
2165
+ }
2166
+ await connection.commit();
2167
+ return ok(void 0);
2168
+ } catch (error) {
1782
2169
  await connection.rollback().catch(() => {
1783
2170
  });
2171
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
1784
2172
  }
1785
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
1786
- } finally {
1787
- if (connection) {
1788
- connection.release();
1789
- }
1790
- }
2173
+ });
1791
2174
  },
1792
2175
  async queryPage(options) {
1793
- const result = await queryPageAsync(
1794
- async (sql, params) => {
1795
- const [rows] = await pool.query(sql, params);
1796
- return rows;
1797
- },
1798
- options
1799
- );
1800
- return ok(result);
2176
+ return withConnection(async (connection) => {
2177
+ const result = await queryPageAsync(
2178
+ async (sql, params) => {
2179
+ const [rows] = await connection.query(sql, params);
2180
+ return rows;
2181
+ },
2182
+ options
2183
+ );
2184
+ return ok(result);
2185
+ });
1801
2186
  }
1802
2187
  };
1803
2188
  function createMysqlTxDmlOps(conn) {
@@ -1811,12 +2196,12 @@ function createMysqlProvider() {
1811
2196
  return ok(rows[0] ?? null);
1812
2197
  },
1813
2198
  async execute(sql, params) {
1814
- const [result] = await conn.execute(sql, params);
2199
+ const [result] = await conn.execute({ sql, values: params });
1815
2200
  return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
1816
2201
  },
1817
2202
  async batch(statements) {
1818
2203
  for (const { sql: s, params } of statements) {
1819
- await conn.execute(s, params);
2204
+ await conn.execute({ sql: s, values: params });
1820
2205
  }
1821
2206
  return ok(void 0);
1822
2207
  },
@@ -1833,25 +2218,36 @@ function createMysqlProvider() {
1833
2218
  };
1834
2219
  }
1835
2220
  async function beginTx() {
1836
- let connection = null;
2221
+ const acquired = await acquireConnection();
2222
+ if (!acquired.success)
2223
+ return acquired;
2224
+ let connection = acquired.data.connection;
1837
2225
  try {
1838
- connection = await pool.getConnection();
2226
+ if (!ready || performance$1.now() >= acquired.data.deadline) {
2227
+ acquired.data.release();
2228
+ return ready ? err(HaiReldbError.TIMEOUT, reldbM("reldb_mysqlPoolAcquireTimeout")) : err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
2229
+ }
1839
2230
  await connection.beginTransaction();
1840
2231
  } catch (error) {
1841
- if (connection) {
1842
- connection.release();
1843
- }
2232
+ acquired.data.release();
1844
2233
  return err(HaiReldbError.TRANSACTION_FAILED, mysqlTxErrorMessage(String(error)), error);
1845
2234
  }
1846
2235
  const txDmlOps = createMysqlTxDmlOps(connection);
1847
- return ok(createTxHandle(txDmlOps, {
2236
+ const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => connection !== null }, txDmlOps);
2237
+ return ok(createTxHandle(guardedTxDmlOps, {
1848
2238
  commit: async () => {
1849
2239
  await connection.commit();
1850
2240
  },
1851
2241
  rollback: async () => {
1852
2242
  await connection.rollback();
1853
2243
  },
1854
- release: () => connection.release(),
2244
+ release: () => {
2245
+ try {
2246
+ acquired.data.release();
2247
+ } finally {
2248
+ connection = null;
2249
+ }
2250
+ },
1855
2251
  errorMessage: mysqlTxErrorMessage
1856
2252
  }));
1857
2253
  }
@@ -1861,42 +2257,44 @@ function createMysqlProvider() {
1861
2257
  if (config.type !== "mysql") {
1862
2258
  return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_mysqlOnlyMysql"));
1863
2259
  }
2260
+ const previousCleanup = await closeResource();
2261
+ if (!previousCleanup.success)
2262
+ return previousCleanup;
2263
+ const host = config.url ? new URL(config.url).hostname : config.host;
2264
+ if (config.ssl && config.ssl.rejectUnauthorized && config.ssl.verifyIdentity && isIP(host.replace(/^\[|\]$/g, ""))) {
2265
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_mysqlTlsDnsRequired"));
2266
+ }
2267
+ if (config.ssl && (!config.ssl.rejectUnauthorized || !config.ssl.verifyIdentity)) {
2268
+ logger2.warn("MySQL TLS server verification is explicitly disabled", {
2269
+ certificateVerification: config.ssl.rejectUnauthorized,
2270
+ hostnameVerification: config.ssl.rejectUnauthorized && config.ssl.verifyIdentity
2271
+ });
2272
+ }
1864
2273
  try {
1865
- const mysql = __require("mysql2/promise");
2274
+ const mysql = createRequire(import.meta.url)("mysql2/promise");
2275
+ poolConfig = config.pool ?? MysqlPoolConfigSchema.parse({});
1866
2276
  pool = mysql.createPool({
1867
- uri: config.url,
1868
- host: config.host,
1869
- port: config.port,
1870
- database: config.database,
1871
- user: config.user,
1872
- password: config.password,
2277
+ // mysql2 会用非空的分字段覆盖 URI;URL 模式不能传 schema 默认 host 或冲突字段。
2278
+ ...config.url ? { uri: config.url } : { host: config.host, port: config.port, database: config.database, user: config.user, password: config.password },
1873
2279
  ssl: config.ssl,
1874
- connectionLimit: config.pool?.max ?? 10,
1875
- waitForConnections: true,
1876
- queueLimit: 0,
2280
+ connectionLimit: poolConfig.max,
2281
+ // 排队由本 Provider 管理,超时项可直接移除;驱动绝不保留取消后的等待者。
2282
+ waitForConnections: false,
2283
+ connectTimeout: poolConfig.acquireTimeout,
1877
2284
  charset: config.mysql?.charset ?? "utf8mb4"
1878
2285
  });
1879
2286
  await pool.query("SELECT 1");
1880
- logger2.info("Connected to MySQL", { host: config.host, port: config.port, database: config.database });
2287
+ ready = true;
2288
+ currentConfig2 = config;
2289
+ logger2.info("Connected to MySQL", config.url ? { target: core.sanitize.sanitizeUrl(config.url) } : { host: config.host, port: config.port, database: config.database });
1881
2290
  return ok(void 0);
1882
2291
  } catch (error) {
1883
- pool = null;
1884
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
2292
+ const cleanup = await closeResource();
2293
+ const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
2294
+ return cleanup.success || result.success ? result : err({ ...result.error, ext: { cleanupPending: true, cleanupError: cleanup.error } });
1885
2295
  }
1886
2296
  },
1887
- async close() {
1888
- if (pool) {
1889
- try {
1890
- await pool.end();
1891
- } catch (error) {
1892
- pool = null;
1893
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
1894
- }
1895
- pool = null;
1896
- logger2.info("Disconnected from MySQL");
1897
- }
1898
- return ok(void 0);
1899
- },
2297
+ close: closeResource,
1900
2298
  isConnected: () => ctx.isConnected(),
1901
2299
  ddl: createBaseDdlOps(ctx, rawDdl),
1902
2300
  sql: dmlOps,
@@ -1905,15 +2303,143 @@ function createMysqlProvider() {
1905
2303
  };
1906
2304
  }
1907
2305
  var logger3 = core.logger.child({ module: "reldb", scope: "postgres" });
2306
+ var PG_DOLLAR_TAG_REGEX = /^\$([A-Z_]\w*)?\$/i;
1908
2307
  function createPostgresProvider() {
1909
2308
  let pool = null;
2309
+ let ready = false;
2310
+ let closing = null;
2311
+ let activeTransactions = 0;
2312
+ let transactionsDrained = Promise.resolve();
2313
+ let resolveTransactions = null;
2314
+ function retainTransaction() {
2315
+ if (activeTransactions++ === 0)
2316
+ transactionsDrained = new Promise((resolve) => {
2317
+ resolveTransactions = resolve;
2318
+ });
2319
+ return () => {
2320
+ if (--activeTransactions === 0) {
2321
+ resolveTransactions?.();
2322
+ resolveTransactions = null;
2323
+ }
2324
+ };
2325
+ }
2326
+ let currentConfig2 = null;
2327
+ function closeResource() {
2328
+ ready = false;
2329
+ currentConfig2 = null;
2330
+ if (closing)
2331
+ return closing;
2332
+ const owned = pool;
2333
+ if (!owned)
2334
+ return Promise.resolve(ok(void 0));
2335
+ closing = (async () => {
2336
+ try {
2337
+ await transactionsDrained;
2338
+ await owned.end();
2339
+ pool = null;
2340
+ return ok(void 0);
2341
+ } catch (error) {
2342
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2343
+ }
2344
+ })().finally(() => {
2345
+ closing = null;
2346
+ });
2347
+ return closing;
2348
+ }
1910
2349
  const ctx = {
1911
- isConnected: () => pool !== null,
1912
- logger: logger3
2350
+ isConnected: () => ready && pool !== null,
2351
+ logger: logger3,
2352
+ operationLog: () => currentConfig2?.operationLog
1913
2353
  };
1914
2354
  function convertPlaceholders(sql) {
2355
+ let out = "";
1915
2356
  let index = 0;
1916
- return sql.replace(/\?/g, () => `$${++index}`);
2357
+ let i = 0;
2358
+ const len = sql.length;
2359
+ while (i < len) {
2360
+ const ch = sql[i];
2361
+ if (ch === "'") {
2362
+ out += ch;
2363
+ i++;
2364
+ while (i < len) {
2365
+ const c = sql[i];
2366
+ out += c;
2367
+ i++;
2368
+ if (c === "'") {
2369
+ if (sql[i] === "'") {
2370
+ out += sql[i];
2371
+ i++;
2372
+ continue;
2373
+ }
2374
+ break;
2375
+ }
2376
+ }
2377
+ continue;
2378
+ }
2379
+ if (ch === '"') {
2380
+ out += ch;
2381
+ i++;
2382
+ while (i < len) {
2383
+ const c = sql[i];
2384
+ out += c;
2385
+ i++;
2386
+ if (c === '"') {
2387
+ if (sql[i] === '"') {
2388
+ out += sql[i];
2389
+ i++;
2390
+ continue;
2391
+ }
2392
+ break;
2393
+ }
2394
+ }
2395
+ continue;
2396
+ }
2397
+ if (ch === "$") {
2398
+ const tagMatch = sql.slice(i).match(PG_DOLLAR_TAG_REGEX);
2399
+ if (tagMatch) {
2400
+ const tag = tagMatch[0];
2401
+ const end = sql.indexOf(tag, i + tag.length);
2402
+ if (end === -1) {
2403
+ out += sql.slice(i);
2404
+ i = len;
2405
+ } else {
2406
+ out += sql.slice(i, end + tag.length);
2407
+ i = end + tag.length;
2408
+ }
2409
+ continue;
2410
+ }
2411
+ }
2412
+ if (ch === "-" && sql[i + 1] === "-") {
2413
+ const nl = sql.indexOf("\n", i);
2414
+ if (nl === -1) {
2415
+ out += sql.slice(i);
2416
+ i = len;
2417
+ } else {
2418
+ out += sql.slice(i, nl + 1);
2419
+ i = nl + 1;
2420
+ }
2421
+ continue;
2422
+ }
2423
+ if (ch === "/" && sql[i + 1] === "*") {
2424
+ const endIdx = sql.indexOf("*/", i + 2);
2425
+ if (endIdx === -1) {
2426
+ out += sql.slice(i);
2427
+ i = len;
2428
+ } else {
2429
+ out += sql.slice(i, endIdx + 2);
2430
+ i = endIdx + 2;
2431
+ }
2432
+ continue;
2433
+ }
2434
+ if (ch === "?") {
2435
+ out += `$${++index}`;
2436
+ i++;
2437
+ continue;
2438
+ }
2439
+ out += ch;
2440
+ i++;
2441
+ }
2442
+ return out;
1917
2443
  }
1918
2444
  function pgTxErrorMessage(detail) {
1919
2445
  return reldbM("reldb_postgresTxFailed", { params: { error: detail } });
@@ -1924,6 +2450,8 @@ function createPostgresProvider() {
1924
2450
  return "TEXT";
1925
2451
  case "INTEGER":
1926
2452
  return def.autoIncrement ? "BIGSERIAL" : "INTEGER";
2453
+ case "BIGINT":
2454
+ return "BIGINT";
1927
2455
  case "REAL":
1928
2456
  return "DOUBLE PRECISION";
1929
2457
  case "BLOB":
@@ -1957,11 +2485,23 @@ function createPostgresProvider() {
1957
2485
  const result = await queryFn(convertPlaceholders(sql), params);
1958
2486
  return result.rows;
1959
2487
  }
2488
+ async function executeStatement(sql, params) {
2489
+ try {
2490
+ return await pool.query(sql, params);
2491
+ } catch (error) {
2492
+ const kind = /^\s*CREATE\s+(TABLE|(?:UNIQUE\s+)?INDEX)\s+IF\s+NOT\s+EXISTS\s+/i.exec(sql)?.[1]?.toUpperCase();
2493
+ const single = !sql.trim().replace(/;$/, "").includes(";");
2494
+ 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"));
2495
+ if (!kind || !single || !catalogConflict)
2496
+ throw error;
2497
+ return pool.query(sql, params);
2498
+ }
2499
+ }
1960
2500
  const rawDdl = {
1961
2501
  async createTable(name, columns, ifNotExists = true) {
1962
2502
  const quotedTable = quoteIdentifier(name);
1963
2503
  const sql = buildDefaultCreateTableSql(pgBuildColumnSql, quotedTable, columns, ifNotExists);
1964
- await pool.query(sql);
2504
+ await executeStatement(sql);
1965
2505
  return ok(void 0);
1966
2506
  },
1967
2507
  async dropTable(name, ifExists) {
@@ -1984,7 +2524,7 @@ function createPostgresProvider() {
1984
2524
  },
1985
2525
  async createIndex(table, index, def) {
1986
2526
  const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
1987
- await pool.query(sql);
2527
+ await executeStatement(sql);
1988
2528
  return ok(void 0);
1989
2529
  },
1990
2530
  async dropIndex(index, ifExists = true) {
@@ -1993,7 +2533,7 @@ function createPostgresProvider() {
1993
2533
  return ok(void 0);
1994
2534
  },
1995
2535
  async raw(sql) {
1996
- await pool.query(sql);
2536
+ await executeStatement(sql);
1997
2537
  return ok(void 0);
1998
2538
  }
1999
2539
  };
@@ -2007,7 +2547,7 @@ function createPostgresProvider() {
2007
2547
  return ok(rows[0] ?? null);
2008
2548
  },
2009
2549
  async execute(sql, params) {
2010
- const result = await pool.query(convertPlaceholders(sql), params);
2550
+ const result = await executeStatement(convertPlaceholders(sql), params);
2011
2551
  return ok({ changes: result.rowCount ?? 0 });
2012
2552
  },
2013
2553
  async batch(statements) {
@@ -2078,24 +2618,35 @@ function createPostgresProvider() {
2078
2618
  }
2079
2619
  async function beginTx() {
2080
2620
  let client = null;
2621
+ const releaseOwnership = retainTransaction();
2081
2622
  try {
2082
2623
  client = await pool.connect();
2083
2624
  await client.query("BEGIN");
2084
2625
  } catch (error) {
2085
- if (client) {
2086
- client.release();
2626
+ try {
2627
+ client?.release();
2628
+ } finally {
2629
+ releaseOwnership();
2087
2630
  }
2088
2631
  return err(HaiReldbError.TRANSACTION_FAILED, pgTxErrorMessage(String(error)), error);
2089
2632
  }
2090
2633
  const txDmlOps = createPgTxDmlOps(client);
2091
- return ok(createTxHandle(txDmlOps, {
2634
+ const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => client !== null }, txDmlOps);
2635
+ return ok(createTxHandle(guardedTxDmlOps, {
2092
2636
  commit: async () => {
2093
2637
  await client.query("COMMIT");
2094
2638
  },
2095
2639
  rollback: async () => {
2096
2640
  await client.query("ROLLBACK");
2097
2641
  },
2098
- release: () => client.release(),
2642
+ release: () => {
2643
+ try {
2644
+ client.release();
2645
+ } finally {
2646
+ client = null;
2647
+ releaseOwnership();
2648
+ }
2649
+ },
2099
2650
  errorMessage: pgTxErrorMessage
2100
2651
  }));
2101
2652
  }
@@ -2105,8 +2656,11 @@ function createPostgresProvider() {
2105
2656
  if (config.type !== "postgresql") {
2106
2657
  return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_postgresOnlyPostgresql"));
2107
2658
  }
2659
+ const previousCleanup = await closeResource();
2660
+ if (!previousCleanup.success)
2661
+ return previousCleanup;
2108
2662
  try {
2109
- const { Pool } = __require("pg");
2663
+ const { Pool } = createRequire(import.meta.url)("pg");
2110
2664
  pool = new Pool({
2111
2665
  connectionString: config.url,
2112
2666
  host: config.host,
@@ -2120,27 +2674,34 @@ function createPostgresProvider() {
2120
2674
  idleTimeoutMillis: config.pool?.idleTimeout,
2121
2675
  connectionTimeoutMillis: config.pool?.acquireTimeout
2122
2676
  });
2123
- await pool.query("SELECT 1");
2677
+ pool.on("error", (error) => {
2678
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z0-9_]{1,32}$/.test(error.code) ? error.code : void 0;
2679
+ logger3.warn("PostgreSQL idle connection error; pool will replace connection", {
2680
+ code,
2681
+ name: error instanceof Error ? "Error" : "UnknownError"
2682
+ });
2683
+ });
2684
+ for (let attempt = 0; ; attempt++) {
2685
+ try {
2686
+ await pool.query("SELECT 1");
2687
+ break;
2688
+ } catch (error) {
2689
+ if (attempt >= 2 || typeof error !== "object" || error === null || !("code" in error) || error.code !== "EAI_AGAIN")
2690
+ throw error;
2691
+ await setTimeout$1(250 * (attempt + 1));
2692
+ }
2693
+ }
2694
+ ready = true;
2695
+ currentConfig2 = config;
2124
2696
  logger3.info("Connected to PostgreSQL", { host: config.host, port: config.port, database: config.database });
2125
2697
  return ok(void 0);
2126
2698
  } catch (error) {
2127
- pool = null;
2128
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2699
+ const cleanup = await closeResource();
2700
+ const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2701
+ return cleanup.success || result.success ? result : err({ ...result.error, ext: { cleanupPending: true, cleanupError: cleanup.error } });
2129
2702
  }
2130
2703
  },
2131
- async close() {
2132
- if (pool) {
2133
- try {
2134
- await pool.end();
2135
- } catch (error) {
2136
- pool = null;
2137
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2138
- }
2139
- pool = null;
2140
- logger3.info("Disconnected from PostgreSQL");
2141
- }
2142
- return ok(void 0);
2143
- },
2704
+ close: closeResource,
2144
2705
  isConnected: () => ctx.isConnected(),
2145
2706
  ddl: createBaseDdlOps(ctx, rawDdl),
2146
2707
  sql: dmlOps,
@@ -2149,10 +2710,234 @@ function createPostgresProvider() {
2149
2710
  };
2150
2711
  }
2151
2712
  var require2 = createRequire(import.meta.url);
2713
+ function createSyncSqliteEngine(options) {
2714
+ const Database = require2("better-sqlite3");
2715
+ const db = new Database(options.database, { readonly: options.readonly });
2716
+ let initialized = false;
2717
+ return {
2718
+ async initialize() {
2719
+ if (!initialized && options.walMode !== false) {
2720
+ const deadline = performance.now() + 5e3;
2721
+ let contention;
2722
+ try {
2723
+ for (; ; ) {
2724
+ if (contention && performance.now() >= deadline)
2725
+ throw contention;
2726
+ db.pragma(`busy_timeout = ${Math.max(1, Math.ceil(deadline - performance.now()))}`);
2727
+ try {
2728
+ db.pragma("journal_mode = WAL");
2729
+ break;
2730
+ } catch (error) {
2731
+ if (!error || typeof error !== "object" || !("code" in error) || typeof error.code !== "string" || !/^SQLITE_BUSY(?:_|$)/.test(error.code) || performance.now() >= deadline) {
2732
+ throw error;
2733
+ }
2734
+ contention = error;
2735
+ await setTimeout$1(Math.min(25, Math.max(1, deadline - performance.now())));
2736
+ }
2737
+ }
2738
+ } finally {
2739
+ db.pragma("busy_timeout = 5000");
2740
+ }
2741
+ }
2742
+ initialized = true;
2743
+ },
2744
+ async all(sql, params) {
2745
+ const stmt = db.prepare(sql);
2746
+ return params ? stmt.all(...params) : stmt.all();
2747
+ },
2748
+ async get(sql, params) {
2749
+ const stmt = db.prepare(sql);
2750
+ return (params ? stmt.get(...params) : stmt.get()) ?? null;
2751
+ },
2752
+ async run(sql, params) {
2753
+ const stmt = db.prepare(sql);
2754
+ const result = params ? stmt.run(...params) : stmt.run();
2755
+ return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
2756
+ },
2757
+ async exec(sql) {
2758
+ db.exec(sql);
2759
+ },
2760
+ async batch(statements) {
2761
+ const tx = db.transaction((items) => {
2762
+ for (const item of items) {
2763
+ const stmt = db.prepare(item.sql);
2764
+ if (item.params)
2765
+ stmt.run(...item.params);
2766
+ else stmt.run();
2767
+ }
2768
+ });
2769
+ tx(statements);
2770
+ },
2771
+ async close() {
2772
+ initialized = false;
2773
+ if (db.open) {
2774
+ db.close();
2775
+ }
2776
+ },
2777
+ isOpen: () => initialized && db.open
2778
+ };
2779
+ }
2780
+ function resolveWorkerUrl() {
2781
+ const candidates = [
2782
+ // 源码 / 测试:引擎与 worker 同目录。
2783
+ new URL("./reldb-sqlite-worker.mjs", import.meta.url),
2784
+ // 打包:引擎被并入 dist/index.js,worker 复制到 dist/providers/sqlite/。
2785
+ new URL("./providers/sqlite/reldb-sqlite-worker.mjs", import.meta.url)
2786
+ ];
2787
+ for (const candidate of candidates) {
2788
+ if (existsSync(candidate))
2789
+ return candidate;
2790
+ }
2791
+ return candidates[0];
2792
+ }
2793
+ function createWorkerSqliteEngine(options, connectTimeoutMs) {
2794
+ const worker = new Worker(resolveWorkerUrl(), {
2795
+ workerData: { database: options.database, readonly: options.readonly, walMode: options.walMode }
2796
+ });
2797
+ let seq = 0;
2798
+ let open = false;
2799
+ let exited = false;
2800
+ let closing = null;
2801
+ const pending = /* @__PURE__ */ new Map();
2802
+ function rejectAll(error) {
2803
+ for (const entry of pending.values())
2804
+ entry.reject(error);
2805
+ pending.clear();
2806
+ }
2807
+ worker.on("message", (msg) => {
2808
+ if (!("id" in msg))
2809
+ return;
2810
+ const entry = pending.get(msg.id);
2811
+ if (!entry)
2812
+ return;
2813
+ pending.delete(msg.id);
2814
+ if (msg.ok)
2815
+ entry.resolve(msg.result);
2816
+ else entry.reject(new Error(msg.error?.message ?? "sqlite worker error"));
2817
+ });
2818
+ worker.on("error", (error) => {
2819
+ open = false;
2820
+ rejectAll(error instanceof Error ? error : new Error(String(error)));
2821
+ });
2822
+ worker.on("exit", (code) => {
2823
+ exited = true;
2824
+ open = false;
2825
+ if (pending.size > 0)
2826
+ rejectAll(new Error(`sqlite worker exited unexpectedly with code ${code}`));
2827
+ });
2828
+ function initialize() {
2829
+ return new Promise((resolve, reject) => {
2830
+ const timer = setTimeout(() => {
2831
+ finish(new Error(`sqlite worker init timed out after ${connectTimeoutMs}ms`));
2832
+ }, connectTimeoutMs);
2833
+ function finish(error) {
2834
+ clearTimeout(timer);
2835
+ worker.off("message", onHandshake);
2836
+ worker.off("error", onError);
2837
+ worker.off("exit", onExit);
2838
+ if (error) {
2839
+ reject(error);
2840
+ } else {
2841
+ open = true;
2842
+ resolve();
2843
+ }
2844
+ }
2845
+ function onError(error) {
2846
+ finish(error);
2847
+ }
2848
+ function onExit(code) {
2849
+ finish(new Error(`sqlite worker exited before ready with code ${code}`));
2850
+ }
2851
+ function onHandshake(msg) {
2852
+ if (msg?.type === "ready") {
2853
+ finish();
2854
+ } else if (msg?.type === "init-error") {
2855
+ finish(new Error(msg.error?.message ?? "sqlite worker init failed"));
2856
+ }
2857
+ }
2858
+ worker.on("message", onHandshake);
2859
+ worker.once("error", onError);
2860
+ worker.once("exit", onExit);
2861
+ if (exited)
2862
+ finish(new Error("sqlite worker exited before initialization"));
2863
+ });
2864
+ }
2865
+ function post(op, payload) {
2866
+ if (!open)
2867
+ return Promise.reject(new Error("sqlite worker is not open"));
2868
+ const id = ++seq;
2869
+ return new Promise((resolve, reject) => {
2870
+ pending.set(id, { resolve, reject });
2871
+ worker.postMessage({ id, op, ...payload });
2872
+ });
2873
+ }
2874
+ return {
2875
+ initialize,
2876
+ // 边界反序列化:worker 通过结构化克隆回传,行/结果类型在此断言。
2877
+ async all(sql, params) {
2878
+ return await post("all", { sql, params });
2879
+ },
2880
+ async get(sql, params) {
2881
+ return await post("get", { sql, params });
2882
+ },
2883
+ async run(sql, params) {
2884
+ return await post("run", { sql, params });
2885
+ },
2886
+ async exec(sql) {
2887
+ await post("exec", { sql });
2888
+ },
2889
+ async batch(statements) {
2890
+ await post("batch", { statements });
2891
+ },
2892
+ close() {
2893
+ if (closing)
2894
+ return closing;
2895
+ const wasOpen = open;
2896
+ const gracefulClose = wasOpen ? post("close", {}) : Promise.resolve();
2897
+ open = false;
2898
+ closing = (async () => {
2899
+ await gracefulClose.catch(() => void 0);
2900
+ if (!exited)
2901
+ await worker.terminate();
2902
+ })().finally(() => {
2903
+ closing = null;
2904
+ });
2905
+ return closing;
2906
+ },
2907
+ isOpen: () => open
2908
+ };
2909
+ }
2910
+
2911
+ // src/providers/reldb-provider-sqlite.ts
2152
2912
  var logger4 = core.logger.child({ module: "reldb", scope: "sqlite" });
2153
2913
  function createSqliteProvider() {
2154
- let database = null;
2914
+ let engine = null;
2915
+ let ready = false;
2916
+ let closing = null;
2155
2917
  let txChain = Promise.resolve();
2918
+ let currentConfig2 = null;
2919
+ function closeResource() {
2920
+ ready = false;
2921
+ currentConfig2 = null;
2922
+ if (closing)
2923
+ return closing;
2924
+ const owned = engine;
2925
+ if (!owned)
2926
+ return Promise.resolve(ok(void 0));
2927
+ closing = (async () => {
2928
+ try {
2929
+ await txChain.catch(() => void 0);
2930
+ await owned.close();
2931
+ engine = null;
2932
+ return ok(void 0);
2933
+ } catch (error) {
2934
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2935
+ }
2936
+ })().finally(() => {
2937
+ closing = null;
2938
+ });
2939
+ return closing;
2940
+ }
2156
2941
  const TX_LOCK_TIMEOUT_MS = 3e4;
2157
2942
  async function acquireTxLock() {
2158
2943
  let release;
@@ -2178,8 +2963,9 @@ function createSqliteProvider() {
2178
2963
  return release;
2179
2964
  }
2180
2965
  const ctx = {
2181
- isConnected: () => database !== null && database.open,
2182
- logger: logger4
2966
+ isConnected: () => ready && engine !== null && engine.isOpen(),
2967
+ logger: logger4,
2968
+ operationLog: () => currentConfig2?.operationLog
2183
2969
  };
2184
2970
  function mapSqliteType(def) {
2185
2971
  switch (def.type) {
@@ -2187,6 +2973,7 @@ function createSqliteProvider() {
2187
2973
  case "JSON":
2188
2974
  return "TEXT";
2189
2975
  case "INTEGER":
2976
+ case "BIGINT":
2190
2977
  case "BOOLEAN":
2191
2978
  return "INTEGER";
2192
2979
  case "REAL":
@@ -2220,71 +3007,59 @@ function createSqliteProvider() {
2220
3007
  async createTable(name, columns, ifNotExists = true) {
2221
3008
  const quotedTable = quoteIdentifier(name);
2222
3009
  const sql = buildDefaultCreateTableSql(sqliteBuildColumnSql, quotedTable, columns, ifNotExists);
2223
- database.exec(sql);
3010
+ await engine.exec(sql);
2224
3011
  return ok(void 0);
2225
3012
  },
2226
3013
  async dropTable(name, ifExists) {
2227
3014
  const ifExistsClause = ifExists ? "IF EXISTS " : "";
2228
- database.exec(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
3015
+ await engine.exec(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
2229
3016
  return ok(void 0);
2230
3017
  },
2231
3018
  async addColumn(table, column, def) {
2232
3019
  const colSql = sqliteBuildColumnSql(column, def);
2233
- database.exec(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
3020
+ await engine.exec(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
2234
3021
  return ok(void 0);
2235
3022
  },
2236
3023
  async dropColumn(table, column) {
2237
- database.exec(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
3024
+ await engine.exec(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
2238
3025
  return ok(void 0);
2239
3026
  },
2240
3027
  async renameTable(oldName, newName) {
2241
- database.exec(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
3028
+ await engine.exec(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
2242
3029
  return ok(void 0);
2243
3030
  },
2244
3031
  async createIndex(table, index, def) {
2245
3032
  const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
2246
- database.exec(sql);
3033
+ await engine.exec(sql);
2247
3034
  return ok(void 0);
2248
3035
  },
2249
3036
  async dropIndex(index, ifExists = true) {
2250
3037
  const sql = buildDefaultDropIndexSql(quoteIdentifier, index, ifExists);
2251
- database.exec(sql);
3038
+ await engine.exec(sql);
2252
3039
  return ok(void 0);
2253
3040
  },
2254
3041
  async raw(sql) {
2255
- database.exec(sql);
3042
+ await engine.exec(sql);
2256
3043
  return ok(void 0);
2257
3044
  }
2258
3045
  };
2259
3046
  const rawDml = {
2260
3047
  async query(sql, params) {
2261
- const stmt = database.prepare(sql);
2262
- const rows = params ? stmt.all(...params) : stmt.all();
3048
+ const rows = await engine.all(sql, params);
2263
3049
  return ok(rows);
2264
3050
  },
2265
3051
  async get(sql, params) {
2266
- const stmt = database.prepare(sql);
2267
- const row = params ? stmt.get(...params) : stmt.get();
3052
+ const row = await engine.get(sql, params);
2268
3053
  return ok(row ?? null);
2269
3054
  },
2270
3055
  async execute(sql, params) {
2271
- const stmt = database.prepare(sql);
2272
- const result = params ? stmt.run(...params) : stmt.run();
2273
- return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
3056
+ const result = await engine.run(sql, params);
3057
+ return ok(result);
2274
3058
  },
2275
3059
  async batch(statements) {
2276
3060
  const releaseTxLock = await acquireTxLock();
2277
3061
  try {
2278
- const db = database;
2279
- const transaction = db.transaction(() => {
2280
- for (const { sql: s, params } of statements) {
2281
- const stmt = db.prepare(s);
2282
- if (params)
2283
- stmt.run(...params);
2284
- else stmt.run();
2285
- }
2286
- });
2287
- transaction();
3062
+ await engine.batch(statements);
2288
3063
  return ok(void 0);
2289
3064
  } catch (error) {
2290
3065
  return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_sqliteBatchFailed", { params: { error: String(error) } }), error);
@@ -2294,45 +3069,32 @@ function createSqliteProvider() {
2294
3069
  },
2295
3070
  async queryPage(options) {
2296
3071
  const result = await queryPageAsync(
2297
- async (sqlStr, params) => {
2298
- const stmt = database.prepare(sqlStr);
2299
- return params ? stmt.all(...params) : stmt.all();
2300
- },
3072
+ (sqlStr, params) => engine.all(sqlStr, params),
2301
3073
  options
2302
3074
  );
2303
3075
  return ok(result);
2304
3076
  }
2305
3077
  };
2306
- function createSqliteTxDmlOps(db) {
3078
+ function createSqliteTxDmlOps(txEngine) {
2307
3079
  return {
2308
3080
  async query(sql, params) {
2309
- const stmt = db.prepare(sql);
2310
- return ok(params ? stmt.all(...params) : stmt.all());
3081
+ return ok(await txEngine.all(sql, params));
2311
3082
  },
2312
3083
  async get(sql, params) {
2313
- const stmt = db.prepare(sql);
2314
- return ok((params ? stmt.get(...params) : stmt.get()) ?? null);
3084
+ return ok(await txEngine.get(sql, params) ?? null);
2315
3085
  },
2316
3086
  async execute(sql, params) {
2317
- const stmt = db.prepare(sql);
2318
- const result = params ? stmt.run(...params) : stmt.run();
2319
- return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
3087
+ return ok(await txEngine.run(sql, params));
2320
3088
  },
2321
3089
  async batch(statements) {
2322
3090
  for (const { sql: s, params } of statements) {
2323
- const stmt = db.prepare(s);
2324
- if (params)
2325
- stmt.run(...params);
2326
- else stmt.run();
3091
+ await txEngine.run(s, params);
2327
3092
  }
2328
3093
  return ok(void 0);
2329
3094
  },
2330
3095
  async queryPage(options) {
2331
3096
  const result = await queryPageAsync(
2332
- async (sqlStr, params) => {
2333
- const stmt = db.prepare(sqlStr);
2334
- return params ? stmt.all(...params) : stmt.all();
2335
- },
3097
+ (sqlStr, params) => txEngine.all(sqlStr, params),
2336
3098
  options
2337
3099
  );
2338
3100
  return ok(result);
@@ -2340,7 +3102,7 @@ function createSqliteProvider() {
2340
3102
  };
2341
3103
  }
2342
3104
  async function beginTx() {
2343
- const db = database;
3105
+ const txEngine = engine;
2344
3106
  let released = false;
2345
3107
  const releaseTxLock = await acquireTxLock();
2346
3108
  const finishTransaction = () => {
@@ -2350,18 +3112,19 @@ function createSqliteProvider() {
2350
3112
  }
2351
3113
  };
2352
3114
  try {
2353
- db.exec("BEGIN TRANSACTION");
3115
+ await txEngine.exec("BEGIN TRANSACTION");
2354
3116
  } catch (error) {
2355
3117
  finishTransaction();
2356
3118
  return err(HaiReldbError.TRANSACTION_FAILED, sqliteTxErrorMessage(String(error)), error);
2357
3119
  }
2358
- const txDmlOps = createSqliteTxDmlOps(db);
2359
- return ok(createTxHandle(txDmlOps, {
3120
+ const txDmlOps = createSqliteTxDmlOps(txEngine);
3121
+ const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => txEngine.isOpen() }, txDmlOps);
3122
+ return ok(createTxHandle(guardedTxDmlOps, {
2360
3123
  commit: async () => {
2361
- db.exec("COMMIT");
3124
+ await txEngine.exec("COMMIT");
2362
3125
  },
2363
3126
  rollback: async () => {
2364
- db.exec("ROLLBACK");
3127
+ await txEngine.exec("ROLLBACK");
2365
3128
  },
2366
3129
  release: () => finishTransaction(),
2367
3130
  errorMessage: sqliteTxErrorMessage
@@ -2376,38 +3139,35 @@ function createSqliteProvider() {
2376
3139
  if (!config.database) {
2377
3140
  return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sqliteNeedPath"));
2378
3141
  }
3142
+ const previousCleanup = await closeResource();
3143
+ if (!previousCleanup.success)
3144
+ return previousCleanup;
2379
3145
  try {
2380
- const Database = require2("better-sqlite3");
2381
3146
  const sqliteOptions = {
2382
3147
  walMode: true,
2383
3148
  readonly: false,
3149
+ executor: "sync",
3150
+ workerConnectTimeoutMs: 1e4,
2384
3151
  ...config.sqlite ?? {}
2385
3152
  };
2386
- database = new Database(config.database, {
2387
- readonly: sqliteOptions.readonly ?? false
2388
- });
2389
- if (sqliteOptions.walMode !== false) {
2390
- database.pragma("journal_mode = WAL");
2391
- }
2392
- logger4.info("Connected to SQLite", { database: config.database });
3153
+ const engineOptions = {
3154
+ database: config.database,
3155
+ readonly: sqliteOptions.readonly ?? false,
3156
+ walMode: sqliteOptions.walMode !== false
3157
+ };
3158
+ engine = sqliteOptions.executor === "worker" ? createWorkerSqliteEngine(engineOptions, sqliteOptions.workerConnectTimeoutMs ?? 1e4) : createSyncSqliteEngine(engineOptions);
3159
+ await engine.initialize();
3160
+ ready = true;
3161
+ currentConfig2 = config;
3162
+ logger4.info("Connected to SQLite", { database: config.database, executor: sqliteOptions.executor });
2393
3163
  return ok(void 0);
2394
3164
  } catch (error) {
2395
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2396
- }
2397
- },
2398
- async close() {
2399
- if (database) {
2400
- try {
2401
- database.close();
2402
- } catch (error) {
2403
- database = null;
2404
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2405
- }
2406
- database = null;
2407
- logger4.info("Disconnected from SQLite");
3165
+ const cleanup = await closeResource();
3166
+ const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
3167
+ return cleanup.success || result.success ? result : err({ ...result.error, ext: { cleanupPending: true, cleanupError: cleanup.error } });
2408
3168
  }
2409
- return ok(void 0);
2410
3169
  },
3170
+ close: closeResource,
2411
3171
  isConnected: () => ctx.isConnected(),
2412
3172
  ddl: createBaseDdlOps(ctx, rawDdl),
2413
3173
  sql: dmlOps,
@@ -2422,6 +3182,43 @@ var currentProvider = null;
2422
3182
  var currentConfig = null;
2423
3183
  var currentJsonOps = null;
2424
3184
  var initInProgress = false;
3185
+ var pendingProvider = null;
3186
+ var initCompletion = null;
3187
+ var closingPromise = null;
3188
+ async function closeOwnedProvider() {
3189
+ if (currentProvider) {
3190
+ pendingProvider = currentProvider;
3191
+ currentProvider = null;
3192
+ }
3193
+ currentConfig = null;
3194
+ currentJsonOps = null;
3195
+ if (!pendingProvider)
3196
+ return ok(void 0);
3197
+ try {
3198
+ const result = await pendingProvider.close();
3199
+ if (!result.success)
3200
+ return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true } });
3201
+ pendingProvider = null;
3202
+ return ok(void 0);
3203
+ } catch (error) {
3204
+ logger5.error("Provider cleanup failed; retaining resource for retry", { error });
3205
+ return err({ ...HaiReldbError.CONNECTION_FAILED, message: reldbM("reldb_closeFailed", {
3206
+ params: { error: error instanceof Error ? error.message : String(error) }
3207
+ }), cause: error, ext: { cleanupPending: true } });
3208
+ }
3209
+ }
3210
+ async function failInitialization(result) {
3211
+ const cleanup = await closeOwnedProvider();
3212
+ if (result.success)
3213
+ return result;
3214
+ if (cleanup.success) {
3215
+ const ext = { ...result.error.ext };
3216
+ delete ext.cleanupPending;
3217
+ delete ext.cleanupError;
3218
+ return err({ ...result.error, ext: Object.keys(ext).length > 0 ? ext : void 0 });
3219
+ }
3220
+ return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } });
3221
+ }
2425
3222
  function createProvider(config) {
2426
3223
  switch (config.type) {
2427
3224
  case "sqlite":
@@ -2450,18 +3247,29 @@ var reldb = {
2450
3247
  *
2451
3248
  * @param config - 数据库配置(允许部分字段,内部会补齐默认值)
2452
3249
  * @returns 初始化结果,失败时包含错误信息
3250
+ * @example
3251
+ * ```ts
3252
+ * import { reldb } from '@h-ai/reldb'
3253
+ *
3254
+ * const result = await reldb.init({ type: 'sqlite', database: ':memory:' })
3255
+ * if (!result.success) throw new Error(result.error.message)
3256
+ * void result.data
3257
+ * ```
2453
3258
  */
2454
3259
  async init(config) {
2455
- if (initInProgress) {
3260
+ if (initInProgress || closingPromise) {
2456
3261
  logger5.warn("RelDB module init is already in progress, skipping concurrent call");
2457
3262
  return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: "init already in progress" } }));
2458
3263
  }
2459
3264
  initInProgress = true;
3265
+ let resolveInit;
3266
+ initCompletion = new Promise((resolve) => {
3267
+ resolveInit = resolve;
3268
+ });
2460
3269
  try {
2461
- if (currentProvider) {
2462
- logger5.warn("RelDB module is already initialized, reinitializing");
2463
- await reldb.close();
2464
- }
3270
+ const cleanup = await closeOwnedProvider();
3271
+ if (!cleanup.success)
3272
+ return cleanup;
2465
3273
  logger5.info("Initializing RelDB module");
2466
3274
  const parseResult = ReldbConfigSchema.safeParse(config);
2467
3275
  if (!parseResult.success) {
@@ -2469,16 +3277,20 @@ var reldb = {
2469
3277
  return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
2470
3278
  }
2471
3279
  const parsed = parseResult.data;
3280
+ if (core.module.requiresSharedState(process.env) && parsed.type === "sqlite")
3281
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sharedBackendRequired"));
2472
3282
  try {
2473
3283
  const provider = createProvider(parsed);
3284
+ pendingProvider = provider;
2474
3285
  const connectResult = await provider.connect(parsed);
2475
3286
  if (!connectResult.success) {
2476
3287
  logger5.error("RelDB module initialization failed", {
2477
3288
  code: connectResult.error.code,
2478
3289
  message: connectResult.error.message
2479
3290
  });
2480
- return connectResult;
3291
+ return await failInitialization(connectResult);
2481
3292
  }
3293
+ pendingProvider = null;
2482
3294
  currentProvider = provider;
2483
3295
  currentConfig = parsed;
2484
3296
  currentJsonOps = createJsonOps(parsed.type);
@@ -2486,10 +3298,12 @@ var reldb = {
2486
3298
  return ok(void 0);
2487
3299
  } catch (error) {
2488
3300
  logger5.error("RelDB module initialization failed", { error });
2489
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
3301
+ return await failInitialization(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error));
2490
3302
  }
2491
3303
  } finally {
2492
3304
  initInProgress = false;
3305
+ initCompletion = null;
3306
+ resolveInit();
2493
3307
  }
2494
3308
  },
2495
3309
  /**
@@ -2521,9 +3335,14 @@ var reldb = {
2521
3335
  get tx() {
2522
3336
  return currentProvider?.tx ?? notInitializedTx;
2523
3337
  },
2524
- /** 获取当前配置(未初始化时为 null) */
3338
+ /** 获取当前脱敏后的配置快照(未初始化时为 null) */
2525
3339
  get config() {
2526
- return currentConfig;
3340
+ if (!currentConfig)
3341
+ return null;
3342
+ const snapshot = core.sanitize.sanitizeSensitiveFields(currentConfig);
3343
+ if ("ssl" in snapshot && snapshot.ssl && typeof snapshot.ssl === "object" && "key" in snapshot.ssl)
3344
+ snapshot.ssl.key = core.sanitize.defaultSensitiveReplacement;
3345
+ return snapshot;
2527
3346
  },
2528
3347
  /** 检查是否已初始化 */
2529
3348
  get isInitialized() {
@@ -2569,35 +3388,123 @@ var reldb = {
2569
3388
  /**
2570
3389
  * 关闭数据库连接
2571
3390
  *
2572
- * 多次调用安全,未初始化时直接返回。
3391
+ * 等待初始化和在途事务,停止接收新操作;失败时保留资源供 close/init 重试。
3392
+ * @returns 所有资源释放成功才返回成功;并发关闭共享同一清理过程。
3393
+ * @example
3394
+ * ```ts
3395
+ * import { reldb } from '@h-ai/reldb'
3396
+ *
3397
+ * const result = await reldb.close()
3398
+ * if (!result.success) throw new Error(result.error.message)
3399
+ * void result.data
3400
+ * ```
2573
3401
  */
2574
3402
  async close() {
2575
- if (!currentProvider) {
2576
- currentConfig = null;
2577
- currentJsonOps = null;
2578
- logger5.info("RelDB module already closed, skipping");
2579
- return ok(void 0);
2580
- }
2581
- logger5.info("Closing RelDB module");
2582
- try {
2583
- const closeResult = await currentProvider.close();
2584
- if (!closeResult.success) {
2585
- logger5.error("RelDB module close failed", { code: closeResult.error.code, message: closeResult.error.message });
2586
- return closeResult;
2587
- }
2588
- logger5.info("RelDB module closed");
2589
- return ok(void 0);
2590
- } catch (error) {
2591
- logger5.error("RelDB module close failed", { error });
2592
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_closeFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
2593
- } finally {
2594
- currentProvider = null;
2595
- currentConfig = null;
2596
- currentJsonOps = null;
3403
+ if (!closingPromise) {
3404
+ closingPromise = (async () => {
3405
+ if (initCompletion)
3406
+ await initCompletion;
3407
+ return await closeOwnedProvider();
3408
+ })().finally(() => {
3409
+ closingPromise = null;
3410
+ });
2597
3411
  }
3412
+ return await closingPromise;
2598
3413
  }
2599
3414
  };
3415
+ async function schemaColumns(db, table, sql = db.sql) {
3416
+ if (!/^[a-z][a-z0-9_]{0,62}$/.test(table))
3417
+ return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
3418
+ const type = db.config?.type;
3419
+ 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]);
3420
+ return found.success ? ok(found.data.map((row) => row.name)) : found;
3421
+ }
3422
+ async function migrateSchema(db, module, steps, options = {}) {
3423
+ if (!db.isInitialized || !/^[a-z][a-z0-9-]{0,62}$/.test(module) || steps.some((step, index) => step.version !== index + 1))
3424
+ return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
3425
+ const table = "hai_schema_migrations";
3426
+ const targetVersion = steps.length;
3427
+ const failed = (cause) => err({ ...HaiCommonError.INIT_FAILED, message: core.i18n.coreM("core_runtimeStartFailed", { params: { module } }), ext: { module, phase: "migration" }, cause });
3428
+ async function inspect(sql) {
3429
+ const columns = await schemaColumns(db, table, sql);
3430
+ if (!columns.success)
3431
+ return failed(columns.error);
3432
+ const row = columns.data.length ? await sql.get(`SELECT version FROM ${table} WHERE module=?`, [module]) : ok(null);
3433
+ if (!row.success)
3434
+ return failed(row.error);
3435
+ const currentVersion = Number(row.data?.version ?? 0);
3436
+ if (currentVersion > targetVersion)
3437
+ return failed();
3438
+ const changes = [];
3439
+ for (const step of steps) {
3440
+ const needed = await step.needed(sql);
3441
+ if (!needed.success)
3442
+ return failed(needed.error);
3443
+ if (needed.data)
3444
+ changes.push(step.name);
3445
+ }
3446
+ return ok({ module, currentVersion, targetVersion, changes, requiresExclusiveAccess: changes.length > 0 });
3447
+ }
3448
+ if (options.dryRun)
3449
+ return inspect(db.sql);
3450
+ const created = await db.sql.execute(`CREATE TABLE IF NOT EXISTS ${table} (module VARCHAR(64) PRIMARY KEY, version INTEGER NOT NULL)`);
3451
+ if (!created.success)
3452
+ return failed(created.error);
3453
+ 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]);
3454
+ if (!seeded.success)
3455
+ return failed(seeded.error);
3456
+ const started = await db.tx.begin();
3457
+ if (!started.success)
3458
+ return failed(started.error);
3459
+ const tx = started.data;
3460
+ let mysqlLock = false;
3461
+ let committed = false;
3462
+ try {
3463
+ if (db.config?.type === "mysql") {
3464
+ const lock = await tx.get("SELECT GET_LOCK(?,30) AS acquired", [`hai-schema:${module}`]);
3465
+ if (!lock.success || Number(lock.data?.acquired) !== 1)
3466
+ return failed(lock.success ? new Error("Migration lock unavailable") : lock.error);
3467
+ mysqlLock = true;
3468
+ }
3469
+ const locked = await tx.execute(`UPDATE ${table} SET version=version WHERE module=?`, [module]);
3470
+ if (!locked.success)
3471
+ return failed(locked.error);
3472
+ const plan = await inspect(tx);
3473
+ if (!plan.success)
3474
+ return plan;
3475
+ for (const step of steps) {
3476
+ const needed = await step.needed(tx);
3477
+ if (!needed.success)
3478
+ return failed(needed.error);
3479
+ if (needed.data) {
3480
+ const applied = await step.apply(tx);
3481
+ if (!applied.success)
3482
+ return failed(applied.error);
3483
+ const remaining = await step.needed(tx);
3484
+ if (!remaining.success || remaining.data)
3485
+ return failed(remaining.success ? new Error("Migration step did not converge") : remaining.error);
3486
+ }
3487
+ }
3488
+ const recorded = await tx.execute(`UPDATE ${table} SET version=? WHERE module=?`, [targetVersion, module]);
3489
+ if (!recorded.success)
3490
+ return failed(recorded.error);
3491
+ if (mysqlLock) {
3492
+ await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
3493
+ mysqlLock = false;
3494
+ }
3495
+ const result = await tx.commit();
3496
+ committed = result.success;
3497
+ return result.success ? plan : failed(result.error);
3498
+ } catch (error) {
3499
+ return failed(error);
3500
+ } finally {
3501
+ if (mysqlLock)
3502
+ await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
3503
+ if (!committed)
3504
+ await tx.rollback();
3505
+ }
3506
+ }
2600
3507
 
2601
- export { BaseReldbCrudRepository, HaiReldbError, MysqlConfigSchema, MysqlOptionsSchema, PoolConfigSchema, PostgresqlConfigSchema, ReldbConfigSchema, ReldbTypeSchema, SqliteConfigSchema, SqliteOptionsSchema, SslConfigSchema, createJsonOps, escapeSqlString, parseJsonPath, quoteIdentifier, reldb, validateIdentifier, validateIdentifiers };
3508
+ export { BaseReldbCrudRepository, HaiReldbError, createJsonOps, escapeSqlString, migrateSchema, parseJsonPath, quoteIdentifier, reldb, schemaColumns, validateIdentifier, validateIdentifiers };
2602
3509
  //# sourceMappingURL=index.js.map
2603
3510
  //# sourceMappingURL=index.js.map