@h-ai/reldb 0.1.0-alpha9 → 1.0.0-beta.2

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,147 +1,124 @@
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_ddlFailed: "DDL operation failed: {error}",
92
- reldb_queryFailed: "Query failed: {error}",
93
- reldb_batchFailed: "Batch execute failed: {error}",
94
- 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",
95
23
  reldb_postgresOnlyPostgresql: "PostgreSQL Provider only supports postgresql type",
96
- reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL: {error}",
24
+ reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL",
97
25
  reldb_sqliteOnlySqlite: "SQLite Provider only supports sqlite type",
98
26
  reldb_sqliteNeedPath: "SQLite requires database path (database field)",
99
- reldb_sqliteConnectionFailed: "Failed to connect to SQLite: {error}",
100
- reldb_sqliteTxFailed: "Transaction failed: {error}",
101
- 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",
102
30
  reldb_mysqlOnlyMysql: "MySQL Provider only supports mysql type",
103
- reldb_mysqlConnectionFailed: "Failed to connect to MySQL: {error}",
104
- 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",
105
37
  reldb_crudEmptyPayload: "CRUD payload is empty, please provide data",
106
38
  reldb_crudNoValidColumns: "CRUD payload has no valid columns",
107
39
  reldb_crudRecordNotFound: "Record not found: {id}",
108
40
  reldb_invalidIdentifier: "Invalid SQL identifier: {name} (only letters, digits and underscores allowed)",
109
- reldb_configError: "Database config validation failed: {error}",
110
- 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"
111
80
  };
112
81
 
113
82
  // messages/zh-CN.json
114
83
  var zh_CN_default = {
115
84
  $schema: "https://inlang.com/schema/inlang-message-format",
116
85
  reldb_notInitialized: "\u6570\u636E\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 db.init()",
117
- reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25: {error}",
118
- reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25: {error}",
119
- reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25: {error}",
120
- reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25: {error}",
121
- reldb_batchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25: {error}",
122
- 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",
123
93
  reldb_postgresOnlyPostgresql: "PostgreSQL Provider \u4EC5\u652F\u6301 postgresql \u7C7B\u578B",
124
- reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25: {error}",
94
+ reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25",
125
95
  reldb_sqliteOnlySqlite: "SQLite Provider \u4EC5\u652F\u6301 sqlite \u7C7B\u578B",
126
96
  reldb_sqliteNeedPath: "SQLite \u9700\u8981\u63D0\u4F9B\u6570\u636E\u5E93\u8DEF\u5F84\uFF08database \u5B57\u6BB5\uFF09",
127
- reldb_sqliteConnectionFailed: "\u8FDE\u63A5 SQLite \u5931\u8D25: {error}",
128
- reldb_sqliteTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
129
- 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",
130
100
  reldb_mysqlOnlyMysql: "MySQL Provider \u4EC5\u652F\u6301 mysql \u7C7B\u578B",
131
- reldb_mysqlConnectionFailed: "\u8FDE\u63A5 MySQL \u5931\u8D25: {error}",
132
- 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",
133
107
  reldb_crudEmptyPayload: "CRUD \u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u63D0\u4F9B\u6570\u636E",
134
108
  reldb_crudNoValidColumns: "CRUD \u6570\u636E\u6CA1\u6709\u6709\u6548\u5217",
135
109
  reldb_crudRecordNotFound: "\u8BB0\u5F55\u4E0D\u5B58\u5728: {id}",
136
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",
137
- reldb_configError: "\u6570\u636E\u5E93\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
138
- 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"
139
115
  };
140
116
 
141
117
  // src/reldb-i18n.ts
142
118
  var reldbM = core.i18n.createMessageGetter({
143
119
  "zh-CN": zh_CN_default,
144
- "en-US": en_US_default
120
+ "en-US": en_US_default,
121
+ "ja-JP": ja_JP_default
145
122
  });
146
123
  var ReldbErrorInfo = {
147
124
  // ─── 通用 001-009 ───
@@ -399,12 +376,13 @@ var BaseReldbCrudRepository = class {
399
376
  */
400
377
  mapRow(row) {
401
378
  const result = {};
379
+ const resultRecord = result;
402
380
  for (const field of this.fields) {
403
381
  if (!field.select) {
404
382
  continue;
405
383
  }
406
384
  const value = this.fromDbValue(row[field.columnName], field.def);
407
- result[field.fieldName] = value;
385
+ resultRecord[field.fieldName] = value;
408
386
  }
409
387
  return result;
410
388
  }
@@ -477,7 +455,11 @@ var BaseReldbCrudRepository = class {
477
455
  return payload;
478
456
  }
479
457
  /**
480
- * 等待初始化完成
458
+ * 等待初始化完成(自动建表等)
459
+ *
460
+ * 内置 CRUD 方法均已在执行前调用;自定义方法若通过 `this.sql()` 直接执行原始
461
+ * SQL(尤其是写操作或在事务中),应先 `await this.ensureReady()` 以避免表尚未
462
+ * 建好时(例如刚 dropTable 后、或后端建表较慢时)产生「表不存在」的竞态失败。
481
463
  *
482
464
  * @returns 初始化结果
483
465
  */
@@ -496,6 +478,17 @@ var BaseReldbCrudRepository = class {
496
478
  * @param data - 业务字段与值的映射
497
479
  * @param tx - 可选事务句柄
498
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
+ * ```
499
492
  */
500
493
  async create(data, tx) {
501
494
  const ready = await this.ensureReady();
@@ -516,6 +509,17 @@ var BaseReldbCrudRepository = class {
516
509
  * @param items - 待插入的业务数据数组
517
510
  * @param tx - 可选事务句柄
518
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
+ * ```
519
523
  */
520
524
  async createMany(items, tx) {
521
525
  const ready = await this.ensureReady();
@@ -550,6 +554,17 @@ var BaseReldbCrudRepository = class {
550
554
  * @param data - 业务字段与值的映射
551
555
  * @param tx - 可选事务句柄
552
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
+ * ```
553
568
  */
554
569
  async createOrUpdate(data, tx) {
555
570
  const ready = await this.ensureReady();
@@ -592,6 +607,17 @@ var BaseReldbCrudRepository = class {
592
607
  * @param id - 主键值
593
608
  * @param tx - 可选事务句柄
594
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
+ * ```
595
621
  */
596
622
  async findById(id, tx) {
597
623
  const ready = await this.ensureReady();
@@ -608,6 +634,17 @@ var BaseReldbCrudRepository = class {
608
634
  * @param id - 主键值
609
635
  * @param tx - 可选事务句柄
610
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
+ * ```
611
648
  */
612
649
  async getById(id, tx) {
613
650
  const ready = await this.ensureReady();
@@ -622,6 +659,17 @@ var BaseReldbCrudRepository = class {
622
659
  * @param options - 查询条件(where、orderBy、limit、offset)
623
660
  * @param tx - 可选事务句柄
624
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
+ * ```
625
673
  */
626
674
  async findAll(options, tx) {
627
675
  const ready = await this.ensureReady();
@@ -636,6 +684,17 @@ var BaseReldbCrudRepository = class {
636
684
  * @param options - 分页查询条件(where、orderBy、pagination、overrides)
637
685
  * @param tx - 可选事务句柄
638
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
+ * ```
639
698
  */
640
699
  async findPage(options, tx) {
641
700
  const ready = await this.ensureReady();
@@ -653,6 +712,17 @@ var BaseReldbCrudRepository = class {
653
712
  * @param data - 待更新的业务字段
654
713
  * @param tx - 可选事务句柄
655
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
+ * ```
656
726
  */
657
727
  async updateById(id, data, tx) {
658
728
  const ready = await this.ensureReady();
@@ -671,6 +741,17 @@ var BaseReldbCrudRepository = class {
671
741
  * @param id - 主键值
672
742
  * @param tx - 可选事务句柄
673
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
+ * ```
674
755
  */
675
756
  async deleteById(id, tx) {
676
757
  const ready = await this.ensureReady();
@@ -685,6 +766,17 @@ var BaseReldbCrudRepository = class {
685
766
  * @param options - 查询条件(where、params)
686
767
  * @param tx - 可选事务句柄
687
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
+ * ```
688
780
  */
689
781
  async count(options, tx) {
690
782
  const ready = await this.ensureReady();
@@ -699,6 +791,17 @@ var BaseReldbCrudRepository = class {
699
791
  * @param options - 查询条件(where、params)
700
792
  * @param tx - 可选事务句柄
701
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
+ * ```
702
805
  */
703
806
  async exists(options, tx) {
704
807
  const ready = await this.ensureReady();
@@ -713,6 +816,17 @@ var BaseReldbCrudRepository = class {
713
816
  * @param id - 主键值
714
817
  * @param tx - 可选事务句柄
715
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
+ * ```
716
830
  */
717
831
  async existsById(id, tx) {
718
832
  const ready = await this.ensureReady();
@@ -724,6 +838,7 @@ var BaseReldbCrudRepository = class {
724
838
  };
725
839
 
726
840
  // src/reldb-json.ts
841
+ var JSON_PATH_OBJECT_SEGMENT_REGEX = /^([^.[]+)/;
727
842
  function parseJsonPath(path) {
728
843
  let remaining = path.startsWith("$") ? path.slice(1) : path;
729
844
  const segments = [];
@@ -742,7 +857,7 @@ function parseJsonPath(path) {
742
857
  remaining = remaining.slice(1);
743
858
  if (remaining.startsWith("["))
744
859
  continue;
745
- const match = remaining.match(/^([^.[]+)/);
860
+ const match = remaining.match(JSON_PATH_OBJECT_SEGMENT_REGEX);
746
861
  if (match) {
747
862
  segments.push(match[1]);
748
863
  remaining = remaining.slice(match[1].length);
@@ -787,6 +902,46 @@ function createSqliteJsonOps() {
787
902
  }
788
903
  };
789
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
+ }
790
945
  function createPostgresJsonOps() {
791
946
  return {
792
947
  extract(column, path) {
@@ -818,10 +973,7 @@ function createPostgresJsonOps() {
818
973
  };
819
974
  },
820
975
  merge(column, patch) {
821
- return {
822
- sql: `${column} || ?::jsonb`,
823
- params: [JSON.stringify(patch)]
824
- };
976
+ return buildPostgresMergePatch(column, patch);
825
977
  }
826
978
  };
827
979
  }
@@ -956,6 +1108,7 @@ function escapeSqlString(value) {
956
1108
 
957
1109
  // src/reldb-crud-kernel.ts
958
1110
  var logger = core.logger.child({ module: "reldb", scope: "crud-kernel" });
1111
+ var ORDER_BY_SEGMENT_REGEX = /^(\w+)(?:\s+(ASC|DESC))?$/i;
959
1112
  function buildSelectColumns(select) {
960
1113
  if (!select || select.length === 0) {
961
1114
  return "*";
@@ -1024,7 +1177,7 @@ function createCrud(ops, config) {
1024
1177
  const parts = orderBy.split(",").map((s) => s.trim()).filter(Boolean);
1025
1178
  const safeParts = [];
1026
1179
  for (const part of parts) {
1027
- const match = part.match(/^(\w+)(?:\s+(ASC|DESC))?$/i);
1180
+ const match = part.match(ORDER_BY_SEGMENT_REGEX);
1028
1181
  if (!match) {
1029
1182
  logger.debug("Skipped invalid orderBy segment", { table, segment: part });
1030
1183
  continue;
@@ -1412,15 +1565,87 @@ function createTxWrap(beginTx) {
1412
1565
  }
1413
1566
 
1414
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
+ }
1415
1594
  async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
1416
1595
  if (!ctx.isConnected()) {
1417
1596
  return err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
1418
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
+ };
1419
1644
  try {
1420
- return await fn();
1645
+ const result = await fn();
1646
+ return result.success ? result : failed(result.error.cause ?? result.error, result.error);
1421
1647
  } catch (error) {
1422
- ctx.logger.error(errorLabel, { ...errorMeta, error });
1423
- return err(errorDef, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
1648
+ return failed(error);
1424
1649
  }
1425
1650
  }
1426
1651
  function createBaseDdlOps(ctx, raw) {
@@ -1432,12 +1657,14 @@ function createBaseDdlOps(ctx, raw) {
1432
1657
  const v2 = validateIdentifiers(Object.keys(columns));
1433
1658
  if (!v2.success)
1434
1659
  return Promise.resolve(v2);
1660
+ logReldbOperation(ctx, "write", "ddl.createTable", { tableName, columns, ifNotExists });
1435
1661
  return wrapOp(ctx, () => raw.createTable(tableName, columns, ifNotExists), HaiReldbError.DDL_FAILED, "DDL: createTable failed", { tableName });
1436
1662
  },
1437
1663
  dropTable(tableName, ifExists = true) {
1438
1664
  const v = validateIdentifier(tableName);
1439
1665
  if (!v.success)
1440
1666
  return Promise.resolve(v);
1667
+ logReldbOperation(ctx, "write", "ddl.dropTable", { tableName, ifExists });
1441
1668
  return wrapOp(ctx, () => raw.dropTable(tableName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropTable failed", { tableName });
1442
1669
  },
1443
1670
  addColumn(tableName, columnName, columnDef) {
@@ -1447,6 +1674,7 @@ function createBaseDdlOps(ctx, raw) {
1447
1674
  const v2 = validateIdentifier(columnName);
1448
1675
  if (!v2.success)
1449
1676
  return Promise.resolve(v2);
1677
+ logReldbOperation(ctx, "write", "ddl.addColumn", { tableName, columnName, columnDef });
1450
1678
  return wrapOp(ctx, () => raw.addColumn(tableName, columnName, columnDef), HaiReldbError.DDL_FAILED, "DDL: addColumn failed", { tableName, columnName });
1451
1679
  },
1452
1680
  dropColumn(tableName, columnName) {
@@ -1456,6 +1684,7 @@ function createBaseDdlOps(ctx, raw) {
1456
1684
  const v2 = validateIdentifier(columnName);
1457
1685
  if (!v2.success)
1458
1686
  return Promise.resolve(v2);
1687
+ logReldbOperation(ctx, "write", "ddl.dropColumn", { tableName, columnName });
1459
1688
  return wrapOp(ctx, () => raw.dropColumn(tableName, columnName), HaiReldbError.DDL_FAILED, "DDL: dropColumn failed", { tableName, columnName });
1460
1689
  },
1461
1690
  renameTable(oldName, newName) {
@@ -1465,6 +1694,7 @@ function createBaseDdlOps(ctx, raw) {
1465
1694
  const v2 = validateIdentifier(newName);
1466
1695
  if (!v2.success)
1467
1696
  return Promise.resolve(v2);
1697
+ logReldbOperation(ctx, "write", "ddl.renameTable", { oldName, newName });
1468
1698
  return wrapOp(ctx, () => raw.renameTable(oldName, newName), HaiReldbError.DDL_FAILED, "DDL: renameTable failed", { oldName, newName });
1469
1699
  },
1470
1700
  createIndex(tableName, indexName, indexDef) {
@@ -1477,30 +1707,66 @@ function createBaseDdlOps(ctx, raw) {
1477
1707
  const v3 = validateIdentifiers(indexDef.columns);
1478
1708
  if (!v3.success)
1479
1709
  return Promise.resolve(v3);
1710
+ logReldbOperation(ctx, "write", "ddl.createIndex", { tableName, indexName, indexDef });
1480
1711
  return wrapOp(ctx, () => raw.createIndex(tableName, indexName, indexDef), HaiReldbError.DDL_FAILED, "DDL: createIndex failed", { tableName, indexName });
1481
1712
  },
1482
1713
  dropIndex(indexName, ifExists = true) {
1483
1714
  const v = validateIdentifier(indexName);
1484
1715
  if (!v.success)
1485
1716
  return Promise.resolve(v);
1717
+ logReldbOperation(ctx, "write", "ddl.dropIndex", { indexName, ifExists });
1486
1718
  return wrapOp(ctx, () => raw.dropIndex(indexName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropIndex failed", { indexName });
1487
1719
  },
1488
1720
  raw(sql) {
1489
- 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 });
1490
1723
  }
1491
1724
  };
1492
1725
  }
1493
1726
  function createBaseDmlOps(ctx, raw) {
1494
1727
  return {
1495
- query: (sql, params) => wrapOp(ctx, () => raw.query(sql, params), HaiReldbError.QUERY_FAILED, "DML: query failed"),
1496
- get: (sql, params) => wrapOp(ctx, () => raw.get(sql, params), HaiReldbError.QUERY_FAILED, "DML: get failed"),
1497
- execute: (sql, params) => wrapOp(ctx, () => raw.execute(sql, params), HaiReldbError.QUERY_FAILED, "DML: execute failed"),
1498
- batch: (stmts) => wrapOp(ctx, () => raw.batch(stmts), HaiReldbError.QUERY_FAILED, "DML: batch failed"),
1499
- 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
+ }
1500
1748
  };
1501
1749
  }
1502
1750
  function createBaseTxManager(ctx, beginTx) {
1503
- 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
+ };
1504
1770
  return {
1505
1771
  begin,
1506
1772
  wrap: createTxWrap(begin)
@@ -1588,9 +1854,149 @@ function buildDefaultDropIndexSql(quoteId, indexName, ifExists) {
1588
1854
  var logger2 = core.logger.child({ module: "reldb", scope: "mysql" });
1589
1855
  function createMysqlProvider() {
1590
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
+ }
1591
1996
  const ctx = {
1592
- isConnected: () => pool !== null,
1593
- logger: logger2
1997
+ isConnected: () => ready && pool !== null,
1998
+ logger: logger2,
1999
+ operationLog: () => currentConfig2?.operationLog
1594
2000
  };
1595
2001
  function mysqlQuoteId(name) {
1596
2002
  return `\`${name}\``;
@@ -1598,17 +2004,15 @@ function createMysqlProvider() {
1598
2004
  function mysqlTxErrorMessage(detail) {
1599
2005
  return reldbM("reldb_mysqlTxFailed", { params: { error: detail } });
1600
2006
  }
1601
- async function findIndexTableName(queryFn, indexName) {
1602
- try {
1603
- const [rows] = await queryFn(
2007
+ async function findIndexTableName(indexName) {
2008
+ return withConnection(async (connection) => {
2009
+ const [rows] = await connection.query(
1604
2010
  "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME = ? LIMIT 1",
1605
2011
  [indexName]
1606
2012
  );
1607
2013
  const data = rows;
1608
2014
  return ok(data[0]?.name ?? null);
1609
- } catch (error) {
1610
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
1611
- }
2015
+ });
1612
2016
  }
1613
2017
  function mapMysqlType(def) {
1614
2018
  switch (def.type) {
@@ -1616,6 +2020,8 @@ function createMysqlProvider() {
1616
2020
  return "VARCHAR(255)";
1617
2021
  case "INTEGER":
1618
2022
  return def.autoIncrement ? "BIGINT" : "INT";
2023
+ case "BIGINT":
2024
+ return "BIGINT";
1619
2025
  case "REAL":
1620
2026
  return "DOUBLE";
1621
2027
  case "BLOB":
@@ -1685,39 +2091,39 @@ function createMysqlProvider() {
1685
2091
  const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
1686
2092
  return `CREATE TABLE ${ifNotExistsClause}${quotedTable} (${columnDefs.join(", ")}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
1687
2093
  }
2094
+ function executeDdl(sql) {
2095
+ return withConnection(async (connection) => {
2096
+ await connection.query(sql);
2097
+ return ok(void 0);
2098
+ });
2099
+ }
1688
2100
  const rawDdl = {
1689
2101
  async createTable(name, columns, ifNotExists = true) {
1690
2102
  const quotedTable = mysqlQuoteId(name);
1691
2103
  const sql = mysqlBuildCreateTableSql(quotedTable, columns, ifNotExists);
1692
- await pool.query(sql);
1693
- return ok(void 0);
2104
+ return executeDdl(sql);
1694
2105
  },
1695
2106
  async dropTable(name, ifExists) {
1696
2107
  const ifExistsClause = ifExists ? "IF EXISTS " : "";
1697
- await pool.query(`DROP TABLE ${ifExistsClause}${mysqlQuoteId(name)}`);
1698
- return ok(void 0);
2108
+ return executeDdl(`DROP TABLE ${ifExistsClause}${mysqlQuoteId(name)}`);
1699
2109
  },
1700
2110
  async addColumn(table, column, def) {
1701
2111
  const colSql = mysqlBuildColumnSql(column, def);
1702
- await pool.query(`ALTER TABLE ${mysqlQuoteId(table)} ADD COLUMN ${colSql}`);
1703
- return ok(void 0);
2112
+ return executeDdl(`ALTER TABLE ${mysqlQuoteId(table)} ADD COLUMN ${colSql}`);
1704
2113
  },
1705
2114
  async dropColumn(table, column) {
1706
- await pool.query(`ALTER TABLE ${mysqlQuoteId(table)} DROP COLUMN ${mysqlQuoteId(column)}`);
1707
- return ok(void 0);
2115
+ return executeDdl(`ALTER TABLE ${mysqlQuoteId(table)} DROP COLUMN ${mysqlQuoteId(column)}`);
1708
2116
  },
1709
2117
  async renameTable(oldName, newName) {
1710
- await pool.query(`RENAME TABLE ${mysqlQuoteId(oldName)} TO ${mysqlQuoteId(newName)}`);
1711
- return ok(void 0);
2118
+ return executeDdl(`RENAME TABLE ${mysqlQuoteId(oldName)} TO ${mysqlQuoteId(newName)}`);
1712
2119
  },
1713
2120
  async createIndex(table, index, def) {
1714
2121
  const uniqueClause = def.unique ? "UNIQUE " : "";
1715
2122
  const columns = def.columns.map((c) => mysqlQuoteId(c)).join(", ");
1716
- await pool.query(`CREATE ${uniqueClause}INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(table)} (${columns})`);
1717
- return ok(void 0);
2123
+ return executeDdl(`CREATE ${uniqueClause}INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(table)} (${columns})`);
1718
2124
  },
1719
2125
  async dropIndex(index, ifExists) {
1720
- const tableResult = await findIndexTableName((s, v) => pool.query(s, v), index);
2126
+ const tableResult = await findIndexTableName(index);
1721
2127
  if (!tableResult.success)
1722
2128
  return tableResult;
1723
2129
  if (!tableResult.data) {
@@ -1725,58 +2131,58 @@ function createMysqlProvider() {
1725
2131
  return ok(void 0);
1726
2132
  return err(HaiReldbError.DDL_FAILED, reldbM("reldb_ddlFailed", { params: { error: `index not found: ${index}` } }));
1727
2133
  }
1728
- await pool.query(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
1729
- return ok(void 0);
2134
+ return executeDdl(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
1730
2135
  },
1731
2136
  async raw(sql) {
1732
- await pool.query(sql);
1733
- return ok(void 0);
2137
+ return executeDdl(sql);
1734
2138
  }
1735
2139
  };
1736
2140
  const rawDml = {
1737
2141
  async query(sql, params) {
1738
- const [rows] = await pool.query(sql, params);
1739
- return ok(rows);
2142
+ return withConnection(async (connection) => {
2143
+ const [rows] = await connection.query(sql, params);
2144
+ return ok(rows);
2145
+ });
1740
2146
  },
1741
2147
  async get(sql, params) {
1742
- const [rows] = await pool.query(sql, params);
1743
- 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
+ });
1744
2152
  },
1745
2153
  async execute(sql, params) {
1746
- const [result] = await pool.execute(sql, params);
1747
- 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
+ });
1748
2158
  },
1749
2159
  async batch(statements) {
1750
- let connection = null;
1751
- try {
1752
- connection = await pool.getConnection();
1753
- await connection.beginTransaction();
1754
- for (const { sql: s, params } of statements) {
1755
- await connection.execute(s, params);
1756
- }
1757
- await connection.commit();
1758
- return ok(void 0);
1759
- } catch (error) {
1760
- 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) {
1761
2169
  await connection.rollback().catch(() => {
1762
2170
  });
2171
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
1763
2172
  }
1764
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
1765
- } finally {
1766
- if (connection) {
1767
- connection.release();
1768
- }
1769
- }
2173
+ });
1770
2174
  },
1771
2175
  async queryPage(options) {
1772
- const result = await queryPageAsync(
1773
- async (sql, params) => {
1774
- const [rows] = await pool.query(sql, params);
1775
- return rows;
1776
- },
1777
- options
1778
- );
1779
- 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
+ });
1780
2186
  }
1781
2187
  };
1782
2188
  function createMysqlTxDmlOps(conn) {
@@ -1790,12 +2196,12 @@ function createMysqlProvider() {
1790
2196
  return ok(rows[0] ?? null);
1791
2197
  },
1792
2198
  async execute(sql, params) {
1793
- const [result] = await conn.execute(sql, params);
2199
+ const [result] = await conn.execute({ sql, values: params });
1794
2200
  return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
1795
2201
  },
1796
2202
  async batch(statements) {
1797
2203
  for (const { sql: s, params } of statements) {
1798
- await conn.execute(s, params);
2204
+ await conn.execute({ sql: s, values: params });
1799
2205
  }
1800
2206
  return ok(void 0);
1801
2207
  },
@@ -1812,25 +2218,36 @@ function createMysqlProvider() {
1812
2218
  };
1813
2219
  }
1814
2220
  async function beginTx() {
1815
- let connection = null;
2221
+ const acquired = await acquireConnection();
2222
+ if (!acquired.success)
2223
+ return acquired;
2224
+ let connection = acquired.data.connection;
1816
2225
  try {
1817
- 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
+ }
1818
2230
  await connection.beginTransaction();
1819
2231
  } catch (error) {
1820
- if (connection) {
1821
- connection.release();
1822
- }
2232
+ acquired.data.release();
1823
2233
  return err(HaiReldbError.TRANSACTION_FAILED, mysqlTxErrorMessage(String(error)), error);
1824
2234
  }
1825
2235
  const txDmlOps = createMysqlTxDmlOps(connection);
1826
- return ok(createTxHandle(txDmlOps, {
2236
+ const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => connection !== null }, txDmlOps);
2237
+ return ok(createTxHandle(guardedTxDmlOps, {
1827
2238
  commit: async () => {
1828
2239
  await connection.commit();
1829
2240
  },
1830
2241
  rollback: async () => {
1831
2242
  await connection.rollback();
1832
2243
  },
1833
- release: () => connection.release(),
2244
+ release: () => {
2245
+ try {
2246
+ acquired.data.release();
2247
+ } finally {
2248
+ connection = null;
2249
+ }
2250
+ },
1834
2251
  errorMessage: mysqlTxErrorMessage
1835
2252
  }));
1836
2253
  }
@@ -1840,42 +2257,44 @@ function createMysqlProvider() {
1840
2257
  if (config.type !== "mysql") {
1841
2258
  return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_mysqlOnlyMysql"));
1842
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
+ }
1843
2273
  try {
1844
- const mysql = __require("mysql2/promise");
2274
+ const mysql = createRequire(import.meta.url)("mysql2/promise");
2275
+ poolConfig = config.pool ?? MysqlPoolConfigSchema.parse({});
1845
2276
  pool = mysql.createPool({
1846
- uri: config.url,
1847
- host: config.host,
1848
- port: config.port,
1849
- database: config.database,
1850
- user: config.user,
1851
- 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 },
1852
2279
  ssl: config.ssl,
1853
- connectionLimit: config.pool?.max ?? 10,
1854
- waitForConnections: true,
1855
- queueLimit: 0,
2280
+ connectionLimit: poolConfig.max,
2281
+ // 排队由本 Provider 管理,超时项可直接移除;驱动绝不保留取消后的等待者。
2282
+ waitForConnections: false,
2283
+ connectTimeout: poolConfig.acquireTimeout,
1856
2284
  charset: config.mysql?.charset ?? "utf8mb4"
1857
2285
  });
1858
2286
  await pool.query("SELECT 1");
1859
- 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 });
1860
2290
  return ok(void 0);
1861
2291
  } catch (error) {
1862
- pool = null;
1863
- 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 } });
1864
2295
  }
1865
2296
  },
1866
- async close() {
1867
- if (pool) {
1868
- try {
1869
- await pool.end();
1870
- } catch (error) {
1871
- pool = null;
1872
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
1873
- }
1874
- pool = null;
1875
- logger2.info("Disconnected from MySQL");
1876
- }
1877
- return ok(void 0);
1878
- },
2297
+ close: closeResource,
1879
2298
  isConnected: () => ctx.isConnected(),
1880
2299
  ddl: createBaseDdlOps(ctx, rawDdl),
1881
2300
  sql: dmlOps,
@@ -1884,15 +2303,143 @@ function createMysqlProvider() {
1884
2303
  };
1885
2304
  }
1886
2305
  var logger3 = core.logger.child({ module: "reldb", scope: "postgres" });
2306
+ var PG_DOLLAR_TAG_REGEX = /^\$([A-Z_]\w*)?\$/i;
1887
2307
  function createPostgresProvider() {
1888
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
+ }
1889
2349
  const ctx = {
1890
- isConnected: () => pool !== null,
1891
- logger: logger3
2350
+ isConnected: () => ready && pool !== null,
2351
+ logger: logger3,
2352
+ operationLog: () => currentConfig2?.operationLog
1892
2353
  };
1893
2354
  function convertPlaceholders(sql) {
2355
+ let out = "";
1894
2356
  let index = 0;
1895
- 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;
1896
2443
  }
1897
2444
  function pgTxErrorMessage(detail) {
1898
2445
  return reldbM("reldb_postgresTxFailed", { params: { error: detail } });
@@ -1903,6 +2450,8 @@ function createPostgresProvider() {
1903
2450
  return "TEXT";
1904
2451
  case "INTEGER":
1905
2452
  return def.autoIncrement ? "BIGSERIAL" : "INTEGER";
2453
+ case "BIGINT":
2454
+ return "BIGINT";
1906
2455
  case "REAL":
1907
2456
  return "DOUBLE PRECISION";
1908
2457
  case "BLOB":
@@ -1936,11 +2485,23 @@ function createPostgresProvider() {
1936
2485
  const result = await queryFn(convertPlaceholders(sql), params);
1937
2486
  return result.rows;
1938
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
+ }
1939
2500
  const rawDdl = {
1940
2501
  async createTable(name, columns, ifNotExists = true) {
1941
2502
  const quotedTable = quoteIdentifier(name);
1942
2503
  const sql = buildDefaultCreateTableSql(pgBuildColumnSql, quotedTable, columns, ifNotExists);
1943
- await pool.query(sql);
2504
+ await executeStatement(sql);
1944
2505
  return ok(void 0);
1945
2506
  },
1946
2507
  async dropTable(name, ifExists) {
@@ -1963,7 +2524,7 @@ function createPostgresProvider() {
1963
2524
  },
1964
2525
  async createIndex(table, index, def) {
1965
2526
  const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
1966
- await pool.query(sql);
2527
+ await executeStatement(sql);
1967
2528
  return ok(void 0);
1968
2529
  },
1969
2530
  async dropIndex(index, ifExists = true) {
@@ -1972,7 +2533,7 @@ function createPostgresProvider() {
1972
2533
  return ok(void 0);
1973
2534
  },
1974
2535
  async raw(sql) {
1975
- await pool.query(sql);
2536
+ await executeStatement(sql);
1976
2537
  return ok(void 0);
1977
2538
  }
1978
2539
  };
@@ -1986,7 +2547,7 @@ function createPostgresProvider() {
1986
2547
  return ok(rows[0] ?? null);
1987
2548
  },
1988
2549
  async execute(sql, params) {
1989
- const result = await pool.query(convertPlaceholders(sql), params);
2550
+ const result = await executeStatement(convertPlaceholders(sql), params);
1990
2551
  return ok({ changes: result.rowCount ?? 0 });
1991
2552
  },
1992
2553
  async batch(statements) {
@@ -2057,24 +2618,35 @@ function createPostgresProvider() {
2057
2618
  }
2058
2619
  async function beginTx() {
2059
2620
  let client = null;
2621
+ const releaseOwnership = retainTransaction();
2060
2622
  try {
2061
2623
  client = await pool.connect();
2062
2624
  await client.query("BEGIN");
2063
2625
  } catch (error) {
2064
- if (client) {
2065
- client.release();
2626
+ try {
2627
+ client?.release();
2628
+ } finally {
2629
+ releaseOwnership();
2066
2630
  }
2067
2631
  return err(HaiReldbError.TRANSACTION_FAILED, pgTxErrorMessage(String(error)), error);
2068
2632
  }
2069
2633
  const txDmlOps = createPgTxDmlOps(client);
2070
- return ok(createTxHandle(txDmlOps, {
2634
+ const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => client !== null }, txDmlOps);
2635
+ return ok(createTxHandle(guardedTxDmlOps, {
2071
2636
  commit: async () => {
2072
2637
  await client.query("COMMIT");
2073
2638
  },
2074
2639
  rollback: async () => {
2075
2640
  await client.query("ROLLBACK");
2076
2641
  },
2077
- release: () => client.release(),
2642
+ release: () => {
2643
+ try {
2644
+ client.release();
2645
+ } finally {
2646
+ client = null;
2647
+ releaseOwnership();
2648
+ }
2649
+ },
2078
2650
  errorMessage: pgTxErrorMessage
2079
2651
  }));
2080
2652
  }
@@ -2084,8 +2656,11 @@ function createPostgresProvider() {
2084
2656
  if (config.type !== "postgresql") {
2085
2657
  return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_postgresOnlyPostgresql"));
2086
2658
  }
2659
+ const previousCleanup = await closeResource();
2660
+ if (!previousCleanup.success)
2661
+ return previousCleanup;
2087
2662
  try {
2088
- const { Pool } = __require("pg");
2663
+ const { Pool } = createRequire(import.meta.url)("pg");
2089
2664
  pool = new Pool({
2090
2665
  connectionString: config.url,
2091
2666
  host: config.host,
@@ -2099,27 +2674,34 @@ function createPostgresProvider() {
2099
2674
  idleTimeoutMillis: config.pool?.idleTimeout,
2100
2675
  connectionTimeoutMillis: config.pool?.acquireTimeout
2101
2676
  });
2102
- 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;
2103
2696
  logger3.info("Connected to PostgreSQL", { host: config.host, port: config.port, database: config.database });
2104
2697
  return ok(void 0);
2105
2698
  } catch (error) {
2106
- pool = null;
2107
- 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 } });
2108
2702
  }
2109
2703
  },
2110
- async close() {
2111
- if (pool) {
2112
- try {
2113
- await pool.end();
2114
- } catch (error) {
2115
- pool = null;
2116
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2117
- }
2118
- pool = null;
2119
- logger3.info("Disconnected from PostgreSQL");
2120
- }
2121
- return ok(void 0);
2122
- },
2704
+ close: closeResource,
2123
2705
  isConnected: () => ctx.isConnected(),
2124
2706
  ddl: createBaseDdlOps(ctx, rawDdl),
2125
2707
  sql: dmlOps,
@@ -2128,10 +2710,234 @@ function createPostgresProvider() {
2128
2710
  };
2129
2711
  }
2130
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
2131
2912
  var logger4 = core.logger.child({ module: "reldb", scope: "sqlite" });
2132
2913
  function createSqliteProvider() {
2133
- let database = null;
2914
+ let engine = null;
2915
+ let ready = false;
2916
+ let closing = null;
2134
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
+ }
2135
2941
  const TX_LOCK_TIMEOUT_MS = 3e4;
2136
2942
  async function acquireTxLock() {
2137
2943
  let release;
@@ -2157,8 +2963,9 @@ function createSqliteProvider() {
2157
2963
  return release;
2158
2964
  }
2159
2965
  const ctx = {
2160
- isConnected: () => database !== null && database.open,
2161
- logger: logger4
2966
+ isConnected: () => ready && engine !== null && engine.isOpen(),
2967
+ logger: logger4,
2968
+ operationLog: () => currentConfig2?.operationLog
2162
2969
  };
2163
2970
  function mapSqliteType(def) {
2164
2971
  switch (def.type) {
@@ -2166,6 +2973,7 @@ function createSqliteProvider() {
2166
2973
  case "JSON":
2167
2974
  return "TEXT";
2168
2975
  case "INTEGER":
2976
+ case "BIGINT":
2169
2977
  case "BOOLEAN":
2170
2978
  return "INTEGER";
2171
2979
  case "REAL":
@@ -2199,71 +3007,59 @@ function createSqliteProvider() {
2199
3007
  async createTable(name, columns, ifNotExists = true) {
2200
3008
  const quotedTable = quoteIdentifier(name);
2201
3009
  const sql = buildDefaultCreateTableSql(sqliteBuildColumnSql, quotedTable, columns, ifNotExists);
2202
- database.exec(sql);
3010
+ await engine.exec(sql);
2203
3011
  return ok(void 0);
2204
3012
  },
2205
3013
  async dropTable(name, ifExists) {
2206
3014
  const ifExistsClause = ifExists ? "IF EXISTS " : "";
2207
- database.exec(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
3015
+ await engine.exec(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
2208
3016
  return ok(void 0);
2209
3017
  },
2210
3018
  async addColumn(table, column, def) {
2211
3019
  const colSql = sqliteBuildColumnSql(column, def);
2212
- database.exec(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
3020
+ await engine.exec(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
2213
3021
  return ok(void 0);
2214
3022
  },
2215
3023
  async dropColumn(table, column) {
2216
- database.exec(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
3024
+ await engine.exec(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
2217
3025
  return ok(void 0);
2218
3026
  },
2219
3027
  async renameTable(oldName, newName) {
2220
- database.exec(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
3028
+ await engine.exec(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
2221
3029
  return ok(void 0);
2222
3030
  },
2223
3031
  async createIndex(table, index, def) {
2224
3032
  const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
2225
- database.exec(sql);
3033
+ await engine.exec(sql);
2226
3034
  return ok(void 0);
2227
3035
  },
2228
3036
  async dropIndex(index, ifExists = true) {
2229
3037
  const sql = buildDefaultDropIndexSql(quoteIdentifier, index, ifExists);
2230
- database.exec(sql);
3038
+ await engine.exec(sql);
2231
3039
  return ok(void 0);
2232
3040
  },
2233
3041
  async raw(sql) {
2234
- database.exec(sql);
3042
+ await engine.exec(sql);
2235
3043
  return ok(void 0);
2236
3044
  }
2237
3045
  };
2238
3046
  const rawDml = {
2239
3047
  async query(sql, params) {
2240
- const stmt = database.prepare(sql);
2241
- const rows = params ? stmt.all(...params) : stmt.all();
3048
+ const rows = await engine.all(sql, params);
2242
3049
  return ok(rows);
2243
3050
  },
2244
3051
  async get(sql, params) {
2245
- const stmt = database.prepare(sql);
2246
- const row = params ? stmt.get(...params) : stmt.get();
3052
+ const row = await engine.get(sql, params);
2247
3053
  return ok(row ?? null);
2248
3054
  },
2249
3055
  async execute(sql, params) {
2250
- const stmt = database.prepare(sql);
2251
- const result = params ? stmt.run(...params) : stmt.run();
2252
- return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
3056
+ const result = await engine.run(sql, params);
3057
+ return ok(result);
2253
3058
  },
2254
3059
  async batch(statements) {
2255
3060
  const releaseTxLock = await acquireTxLock();
2256
3061
  try {
2257
- const db = database;
2258
- const transaction = db.transaction(() => {
2259
- for (const { sql: s, params } of statements) {
2260
- const stmt = db.prepare(s);
2261
- if (params)
2262
- stmt.run(...params);
2263
- else stmt.run();
2264
- }
2265
- });
2266
- transaction();
3062
+ await engine.batch(statements);
2267
3063
  return ok(void 0);
2268
3064
  } catch (error) {
2269
3065
  return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_sqliteBatchFailed", { params: { error: String(error) } }), error);
@@ -2273,45 +3069,32 @@ function createSqliteProvider() {
2273
3069
  },
2274
3070
  async queryPage(options) {
2275
3071
  const result = await queryPageAsync(
2276
- async (sqlStr, params) => {
2277
- const stmt = database.prepare(sqlStr);
2278
- return params ? stmt.all(...params) : stmt.all();
2279
- },
3072
+ (sqlStr, params) => engine.all(sqlStr, params),
2280
3073
  options
2281
3074
  );
2282
3075
  return ok(result);
2283
3076
  }
2284
3077
  };
2285
- function createSqliteTxDmlOps(db) {
3078
+ function createSqliteTxDmlOps(txEngine) {
2286
3079
  return {
2287
3080
  async query(sql, params) {
2288
- const stmt = db.prepare(sql);
2289
- return ok(params ? stmt.all(...params) : stmt.all());
3081
+ return ok(await txEngine.all(sql, params));
2290
3082
  },
2291
3083
  async get(sql, params) {
2292
- const stmt = db.prepare(sql);
2293
- return ok((params ? stmt.get(...params) : stmt.get()) ?? null);
3084
+ return ok(await txEngine.get(sql, params) ?? null);
2294
3085
  },
2295
3086
  async execute(sql, params) {
2296
- const stmt = db.prepare(sql);
2297
- const result = params ? stmt.run(...params) : stmt.run();
2298
- return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
3087
+ return ok(await txEngine.run(sql, params));
2299
3088
  },
2300
3089
  async batch(statements) {
2301
3090
  for (const { sql: s, params } of statements) {
2302
- const stmt = db.prepare(s);
2303
- if (params)
2304
- stmt.run(...params);
2305
- else stmt.run();
3091
+ await txEngine.run(s, params);
2306
3092
  }
2307
3093
  return ok(void 0);
2308
3094
  },
2309
3095
  async queryPage(options) {
2310
3096
  const result = await queryPageAsync(
2311
- async (sqlStr, params) => {
2312
- const stmt = db.prepare(sqlStr);
2313
- return params ? stmt.all(...params) : stmt.all();
2314
- },
3097
+ (sqlStr, params) => txEngine.all(sqlStr, params),
2315
3098
  options
2316
3099
  );
2317
3100
  return ok(result);
@@ -2319,7 +3102,7 @@ function createSqliteProvider() {
2319
3102
  };
2320
3103
  }
2321
3104
  async function beginTx() {
2322
- const db = database;
3105
+ const txEngine = engine;
2323
3106
  let released = false;
2324
3107
  const releaseTxLock = await acquireTxLock();
2325
3108
  const finishTransaction = () => {
@@ -2329,18 +3112,19 @@ function createSqliteProvider() {
2329
3112
  }
2330
3113
  };
2331
3114
  try {
2332
- db.exec("BEGIN TRANSACTION");
3115
+ await txEngine.exec("BEGIN TRANSACTION");
2333
3116
  } catch (error) {
2334
3117
  finishTransaction();
2335
3118
  return err(HaiReldbError.TRANSACTION_FAILED, sqliteTxErrorMessage(String(error)), error);
2336
3119
  }
2337
- const txDmlOps = createSqliteTxDmlOps(db);
2338
- return ok(createTxHandle(txDmlOps, {
3120
+ const txDmlOps = createSqliteTxDmlOps(txEngine);
3121
+ const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => txEngine.isOpen() }, txDmlOps);
3122
+ return ok(createTxHandle(guardedTxDmlOps, {
2339
3123
  commit: async () => {
2340
- db.exec("COMMIT");
3124
+ await txEngine.exec("COMMIT");
2341
3125
  },
2342
3126
  rollback: async () => {
2343
- db.exec("ROLLBACK");
3127
+ await txEngine.exec("ROLLBACK");
2344
3128
  },
2345
3129
  release: () => finishTransaction(),
2346
3130
  errorMessage: sqliteTxErrorMessage
@@ -2355,38 +3139,35 @@ function createSqliteProvider() {
2355
3139
  if (!config.database) {
2356
3140
  return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sqliteNeedPath"));
2357
3141
  }
3142
+ const previousCleanup = await closeResource();
3143
+ if (!previousCleanup.success)
3144
+ return previousCleanup;
2358
3145
  try {
2359
- const Database = require2("better-sqlite3");
2360
3146
  const sqliteOptions = {
2361
3147
  walMode: true,
2362
3148
  readonly: false,
3149
+ executor: "sync",
3150
+ workerConnectTimeoutMs: 1e4,
2363
3151
  ...config.sqlite ?? {}
2364
3152
  };
2365
- database = new Database(config.database, {
2366
- readonly: sqliteOptions.readonly ?? false
2367
- });
2368
- if (sqliteOptions.walMode !== false) {
2369
- database.pragma("journal_mode = WAL");
2370
- }
2371
- 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 });
2372
3163
  return ok(void 0);
2373
3164
  } catch (error) {
2374
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2375
- }
2376
- },
2377
- async close() {
2378
- if (database) {
2379
- try {
2380
- database.close();
2381
- } catch (error) {
2382
- database = null;
2383
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2384
- }
2385
- database = null;
2386
- 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 } });
2387
3168
  }
2388
- return ok(void 0);
2389
3169
  },
3170
+ close: closeResource,
2390
3171
  isConnected: () => ctx.isConnected(),
2391
3172
  ddl: createBaseDdlOps(ctx, rawDdl),
2392
3173
  sql: dmlOps,
@@ -2401,6 +3182,43 @@ var currentProvider = null;
2401
3182
  var currentConfig = null;
2402
3183
  var currentJsonOps = null;
2403
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
+ }
2404
3222
  function createProvider(config) {
2405
3223
  switch (config.type) {
2406
3224
  case "sqlite":
@@ -2429,18 +3247,29 @@ var reldb = {
2429
3247
  *
2430
3248
  * @param config - 数据库配置(允许部分字段,内部会补齐默认值)
2431
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
+ * ```
2432
3258
  */
2433
3259
  async init(config) {
2434
- if (initInProgress) {
3260
+ if (initInProgress || closingPromise) {
2435
3261
  logger5.warn("RelDB module init is already in progress, skipping concurrent call");
2436
3262
  return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: "init already in progress" } }));
2437
3263
  }
2438
3264
  initInProgress = true;
3265
+ let resolveInit;
3266
+ initCompletion = new Promise((resolve) => {
3267
+ resolveInit = resolve;
3268
+ });
2439
3269
  try {
2440
- if (currentProvider) {
2441
- logger5.warn("RelDB module is already initialized, reinitializing");
2442
- await reldb.close();
2443
- }
3270
+ const cleanup = await closeOwnedProvider();
3271
+ if (!cleanup.success)
3272
+ return cleanup;
2444
3273
  logger5.info("Initializing RelDB module");
2445
3274
  const parseResult = ReldbConfigSchema.safeParse(config);
2446
3275
  if (!parseResult.success) {
@@ -2448,16 +3277,20 @@ var reldb = {
2448
3277
  return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
2449
3278
  }
2450
3279
  const parsed = parseResult.data;
3280
+ if (core.module.requiresSharedState(process.env) && parsed.type === "sqlite")
3281
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sharedBackendRequired"));
2451
3282
  try {
2452
3283
  const provider = createProvider(parsed);
3284
+ pendingProvider = provider;
2453
3285
  const connectResult = await provider.connect(parsed);
2454
3286
  if (!connectResult.success) {
2455
3287
  logger5.error("RelDB module initialization failed", {
2456
3288
  code: connectResult.error.code,
2457
3289
  message: connectResult.error.message
2458
3290
  });
2459
- return connectResult;
3291
+ return await failInitialization(connectResult);
2460
3292
  }
3293
+ pendingProvider = null;
2461
3294
  currentProvider = provider;
2462
3295
  currentConfig = parsed;
2463
3296
  currentJsonOps = createJsonOps(parsed.type);
@@ -2465,10 +3298,12 @@ var reldb = {
2465
3298
  return ok(void 0);
2466
3299
  } catch (error) {
2467
3300
  logger5.error("RelDB module initialization failed", { error });
2468
- 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));
2469
3302
  }
2470
3303
  } finally {
2471
3304
  initInProgress = false;
3305
+ initCompletion = null;
3306
+ resolveInit();
2472
3307
  }
2473
3308
  },
2474
3309
  /**
@@ -2500,9 +3335,14 @@ var reldb = {
2500
3335
  get tx() {
2501
3336
  return currentProvider?.tx ?? notInitializedTx;
2502
3337
  },
2503
- /** 获取当前配置(未初始化时为 null) */
3338
+ /** 获取当前脱敏后的配置快照(未初始化时为 null) */
2504
3339
  get config() {
2505
- 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;
2506
3346
  },
2507
3347
  /** 检查是否已初始化 */
2508
3348
  get isInitialized() {
@@ -2548,35 +3388,123 @@ var reldb = {
2548
3388
  /**
2549
3389
  * 关闭数据库连接
2550
3390
  *
2551
- * 多次调用安全,未初始化时直接返回。
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
+ * ```
2552
3401
  */
2553
3402
  async close() {
2554
- if (!currentProvider) {
2555
- currentConfig = null;
2556
- currentJsonOps = null;
2557
- logger5.info("RelDB module already closed, skipping");
2558
- return ok(void 0);
2559
- }
2560
- logger5.info("Closing RelDB module");
2561
- try {
2562
- const closeResult = await currentProvider.close();
2563
- if (!closeResult.success) {
2564
- logger5.error("RelDB module close failed", { code: closeResult.error.code, message: closeResult.error.message });
2565
- return closeResult;
2566
- }
2567
- logger5.info("RelDB module closed");
2568
- return ok(void 0);
2569
- } catch (error) {
2570
- logger5.error("RelDB module close failed", { error });
2571
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_closeFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
2572
- } finally {
2573
- currentProvider = null;
2574
- currentConfig = null;
2575
- currentJsonOps = null;
3403
+ if (!closingPromise) {
3404
+ closingPromise = (async () => {
3405
+ if (initCompletion)
3406
+ await initCompletion;
3407
+ return await closeOwnedProvider();
3408
+ })().finally(() => {
3409
+ closingPromise = null;
3410
+ });
2576
3411
  }
3412
+ return await closingPromise;
2577
3413
  }
2578
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
+ }
2579
3507
 
2580
- 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 };
2581
3509
  //# sourceMappingURL=index.js.map
2582
3510
  //# sourceMappingURL=index.js.map