@h-ai/reldb 0.1.0-alpha5

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 ADDED
@@ -0,0 +1,2603 @@
1
+ import { z } from 'zod';
2
+ import { core, ok, err } from '@h-ai/core';
3
+ 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
+ ]);
84
+
85
+ // messages/en-US.json
86
+ var en_US_default = {
87
+ $schema: "https://inlang.com/schema/inlang-message-format",
88
+ reldb_notInitialized: "Database not initialized, please call db.init() first",
89
+ reldb_initFailed: "Failed to initialize database: {error}",
90
+ reldb_closeFailed: "Failed to close database: {error}",
91
+ reldb_unsupportedType: "Unsupported database type: {type}",
92
+ reldb_ddlFailed: "DDL operation failed: {error}",
93
+ reldb_queryFailed: "Query failed: {error}",
94
+ reldb_executeFailed: "Execute failed: {error}",
95
+ reldb_batchFailed: "Batch execute failed: {error}",
96
+ reldb_postgresTxFailed: "Transaction failed: {error}",
97
+ reldb_postgresOnlyPostgresql: "PostgreSQL Provider only supports postgresql type",
98
+ reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL: {error}",
99
+ reldb_sqliteOnlySqlite: "SQLite Provider only supports sqlite type",
100
+ reldb_sqliteNeedPath: "SQLite requires database path (database field)",
101
+ reldb_sqliteConnectionFailed: "Failed to connect to SQLite: {error}",
102
+ reldb_sqliteTxFailed: "Transaction failed: {error}",
103
+ reldb_sqliteCreateTableFailed: "Failed to create table {tableName}: {error}",
104
+ reldb_sqliteDropTableFailed: "Failed to drop table {tableName}: {error}",
105
+ reldb_sqliteAddColumnFailed: "Failed to add column {columnName} to table {tableName}: {error}",
106
+ reldb_sqliteDropColumnFailed: "Failed to drop column {columnName} from table {tableName}: {error}",
107
+ reldb_sqliteRenameTableFailed: "Failed to rename table {oldName} to {newName}: {error}",
108
+ reldb_sqliteCreateIndexFailed: "Failed to create index {indexName}: {error}",
109
+ reldb_sqliteDropIndexFailed: "Failed to drop index {indexName}: {error}",
110
+ reldb_sqliteExecDdlFailed: "Failed to execute DDL: {error}",
111
+ reldb_sqliteQueryFailed: "Query failed: {error}",
112
+ reldb_sqliteExecuteFailed: "Execute failed: {error}",
113
+ reldb_sqliteBatchFailed: "Batch execute failed: {error}",
114
+ reldb_mysqlOnlyMysql: "MySQL Provider only supports mysql type",
115
+ reldb_mysqlConnectionFailed: "Failed to connect to MySQL: {error}",
116
+ reldb_mysqlTxFailed: "Transaction failed: {error}",
117
+ reldb_crudEmptyPayload: "CRUD payload is empty, please provide data",
118
+ reldb_crudNoValidColumns: "CRUD payload has no valid columns",
119
+ reldb_crudRecordNotFound: "Record not found: {id}",
120
+ reldb_invalidIdentifier: "Invalid SQL identifier: {name} (only letters, digits and underscores allowed)",
121
+ reldb_configError: "Database config validation failed: {error}",
122
+ reldb_txFailed: "Transaction failed: {error}"
123
+ };
124
+
125
+ // messages/zh-CN.json
126
+ var zh_CN_default = {
127
+ $schema: "https://inlang.com/schema/inlang-message-format",
128
+ reldb_notInitialized: "\u6570\u636E\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 db.init()",
129
+ reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25: {error}",
130
+ reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25: {error}",
131
+ reldb_unsupportedType: "\u4E0D\u652F\u6301\u7684\u6570\u636E\u5E93\u7C7B\u578B: {type}",
132
+ reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25: {error}",
133
+ reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25: {error}",
134
+ reldb_executeFailed: "\u6267\u884C\u5931\u8D25: {error}",
135
+ reldb_batchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25: {error}",
136
+ reldb_postgresTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
137
+ reldb_postgresOnlyPostgresql: "PostgreSQL Provider \u4EC5\u652F\u6301 postgresql \u7C7B\u578B",
138
+ reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25: {error}",
139
+ reldb_sqliteOnlySqlite: "SQLite Provider \u4EC5\u652F\u6301 sqlite \u7C7B\u578B",
140
+ reldb_sqliteNeedPath: "SQLite \u9700\u8981\u63D0\u4F9B\u6570\u636E\u5E93\u8DEF\u5F84\uFF08database \u5B57\u6BB5\uFF09",
141
+ reldb_sqliteConnectionFailed: "\u8FDE\u63A5 SQLite \u5931\u8D25: {error}",
142
+ reldb_sqliteTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
143
+ reldb_sqliteCreateTableFailed: "\u521B\u5EFA\u8868 {tableName} \u5931\u8D25: {error}",
144
+ reldb_sqliteDropTableFailed: "\u5220\u9664\u8868 {tableName} \u5931\u8D25: {error}",
145
+ reldb_sqliteAddColumnFailed: "\u5411\u8868 {tableName} \u6DFB\u52A0\u5217 {columnName} \u5931\u8D25: {error}",
146
+ reldb_sqliteDropColumnFailed: "\u4ECE\u8868 {tableName} \u5220\u9664\u5217 {columnName} \u5931\u8D25: {error}",
147
+ reldb_sqliteRenameTableFailed: "\u91CD\u547D\u540D\u8868 {oldName} \u4E3A {newName} \u5931\u8D25: {error}",
148
+ reldb_sqliteCreateIndexFailed: "\u521B\u5EFA\u7D22\u5F15 {indexName} \u5931\u8D25: {error}",
149
+ reldb_sqliteDropIndexFailed: "\u5220\u9664\u7D22\u5F15 {indexName} \u5931\u8D25: {error}",
150
+ reldb_sqliteExecDdlFailed: "\u6267\u884C DDL \u5931\u8D25: {error}",
151
+ reldb_sqliteQueryFailed: "\u67E5\u8BE2\u5931\u8D25: {error}",
152
+ reldb_sqliteExecuteFailed: "\u6267\u884C\u5931\u8D25: {error}",
153
+ reldb_sqliteBatchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25: {error}",
154
+ reldb_mysqlOnlyMysql: "MySQL Provider \u4EC5\u652F\u6301 mysql \u7C7B\u578B",
155
+ reldb_mysqlConnectionFailed: "\u8FDE\u63A5 MySQL \u5931\u8D25: {error}",
156
+ reldb_mysqlTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}",
157
+ reldb_crudEmptyPayload: "CRUD \u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u63D0\u4F9B\u6570\u636E",
158
+ reldb_crudNoValidColumns: "CRUD \u6570\u636E\u6CA1\u6709\u6709\u6548\u5217",
159
+ reldb_crudRecordNotFound: "\u8BB0\u5F55\u4E0D\u5B58\u5728: {id}",
160
+ reldb_invalidIdentifier: "\u975E\u6CD5\u7684 SQL \u6807\u8BC6\u7B26: {name}\uFF08\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF09",
161
+ reldb_configError: "\u6570\u636E\u5E93\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
162
+ reldb_txFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25: {error}"
163
+ };
164
+
165
+ // src/reldb-i18n.ts
166
+ var reldbM = core.i18n.createMessageGetter({
167
+ "zh-CN": zh_CN_default,
168
+ "en-US": en_US_default
169
+ });
170
+ var ReldbErrorInfo = {
171
+ CONNECTION_FAILED: "001:500",
172
+ QUERY_FAILED: "002:500",
173
+ CONSTRAINT_VIOLATION: "003:409",
174
+ TRANSACTION_FAILED: "004:500",
175
+ MIGRATION_FAILED: "005:500",
176
+ RECORD_NOT_FOUND: "006:404",
177
+ DUPLICATE_ENTRY: "007:409",
178
+ DEADLOCK: "008:500",
179
+ TIMEOUT: "009:504",
180
+ POOL_EXHAUSTED: "010:503",
181
+ NOT_INITIALIZED: "011:500",
182
+ DDL_FAILED: "012:500",
183
+ UNSUPPORTED_TYPE: "013:400",
184
+ CONFIG_ERROR: "014:500"
185
+ };
186
+ var HaiReldbError = core.error.buildHaiErrorsDef("reldb", ReldbErrorInfo);
187
+
188
+ // src/reldb-crud-repository.ts
189
+ var BaseReldbCrudRepository = class {
190
+ /** 底层 CRUD 仓库实例(基于 reldb-crud-kernel 创建) */
191
+ crud;
192
+ /** 数据库服务对象 */
193
+ db;
194
+ /** 字段定义列表 */
195
+ fields;
196
+ /** 主键字段名(对象侧) */
197
+ idField;
198
+ /** 主键列名(数据库侧) */
199
+ idColumn;
200
+ /** 主键生成器 */
201
+ generateId;
202
+ /** 当前时间提供者(默认 Date.now) */
203
+ nowProvider;
204
+ /** 初始化 Promise(自动建表等) */
205
+ initPromise;
206
+ /** 可查询列 */
207
+ selectColumns;
208
+ /** 可插入列 */
209
+ createColumns;
210
+ /** 可更新列 */
211
+ updateColumns;
212
+ /** 表名 */
213
+ table;
214
+ /** 当前数据库类型(延迟读取,确保获取初始化后的值) */
215
+ get dbType() {
216
+ return this.db.config.type;
217
+ }
218
+ /**
219
+ * 创建 BaseReldbCrudRepository
220
+ *
221
+ * 初始化字段映射、构建表结构(默认自动建表)并配置底层 CRUD 仓库。
222
+ *
223
+ * @param db - 数据库服务对象
224
+ * @param config - BaseReldbCrudRepository 配置(表名、字段定义、主键策略等)
225
+ */
226
+ constructor(db, config) {
227
+ this.db = db;
228
+ this.table = config.table;
229
+ this.fields = config.fields;
230
+ this.idColumn = config.idColumn ?? "id";
231
+ this.idField = config.idField ?? this.resolveIdField(config.fields, this.idColumn);
232
+ this.generateId = config.generateId ?? this.defaultIdGenerator();
233
+ this.nowProvider = config.nowProvider ?? (() => Date.now());
234
+ const tableDef = this.buildReldbTableDef(config.fields);
235
+ const createTable = config.createTableIfNotExists !== false ? this.db.ddl.createTable(config.table, tableDef, true) : Promise.resolve(ok(void 0));
236
+ this.initPromise = createTable;
237
+ this.selectColumns = this.fields.filter((field) => field.select).map((field) => field.columnName);
238
+ this.createColumns = this.fields.filter((field) => field.create).map((field) => field.columnName);
239
+ const updateColumns = this.fields.filter((field) => field.update).map((field) => field.columnName);
240
+ const updatedAtField = this.fields.find((field) => this.isUpdatedAtField(field));
241
+ this.updateColumns = updatedAtField && !updateColumns.includes(updatedAtField.columnName) ? [...updateColumns, updatedAtField.columnName] : updateColumns;
242
+ this.crud = this.db.crud.table({
243
+ table: config.table,
244
+ idColumn: this.idColumn,
245
+ select: this.selectColumns,
246
+ createColumns: this.createColumns,
247
+ updateColumns: this.updateColumns,
248
+ mapRow: (row) => this.mapRow(row),
249
+ get dbType() {
250
+ return db.config.type;
251
+ }
252
+ });
253
+ }
254
+ /**
255
+ * 获取 SQL 操作对象(自动选择 reldb.sql 或 tx)
256
+ *
257
+ * 当传入事务句柄时,自动使用事务内 DataOperations;否则使用 reldb.sql。
258
+ *
259
+ * @param tx - 可选事务句柄
260
+ * @returns DataOperations 实例
261
+ */
262
+ sql(tx) {
263
+ return tx ?? this.db.sql;
264
+ }
265
+ /**
266
+ * 解析主键字段
267
+ *
268
+ * 优先使用声明为主键的字段;如果未声明主键,则回退到列名匹配。
269
+ *
270
+ * @param fields - 字段定义列表
271
+ * @param idColumn - 主键列名
272
+ * @returns 主键字段名(可能为空)
273
+ */
274
+ resolveIdField(fields, idColumn) {
275
+ const primary = fields.find((field) => field.def.primaryKey);
276
+ if (primary) {
277
+ return primary.fieldName;
278
+ }
279
+ const byColumn = fields.find((field) => field.columnName === idColumn);
280
+ return byColumn?.fieldName;
281
+ }
282
+ /**
283
+ * 生成默认主键生成器
284
+ *
285
+ * 在运行环境支持 `crypto.randomUUID` 时返回 UUID 生成函数,否则返回 undefined。
286
+ */
287
+ defaultIdGenerator() {
288
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
289
+ return () => crypto.randomUUID();
290
+ }
291
+ return void 0;
292
+ }
293
+ /**
294
+ * 构建表结构定义
295
+ *
296
+ * @param fields - 字段定义列表
297
+ * @returns 表结构定义
298
+ */
299
+ buildReldbTableDef(fields) {
300
+ const entries = fields.map((field) => [field.columnName, this.normalizeReldbColumnDef(field.def)]);
301
+ return Object.fromEntries(entries);
302
+ }
303
+ /**
304
+ * 归一化列定义
305
+ *
306
+ * 用于处理不同数据库对默认值的兼容差异(例如 BOOLEAN)。
307
+ */
308
+ normalizeReldbColumnDef(def) {
309
+ if (def.defaultValue === void 0 || def.defaultValue === null) {
310
+ return def;
311
+ }
312
+ if (def.type !== "BOOLEAN" || typeof def.defaultValue === "string") {
313
+ return def;
314
+ }
315
+ const normalized = { ...def };
316
+ if (this.dbType === "postgresql") {
317
+ normalized.defaultValue = Boolean(def.defaultValue);
318
+ return normalized;
319
+ }
320
+ normalized.defaultValue = def.defaultValue ? 1 : 0;
321
+ return normalized;
322
+ }
323
+ /**
324
+ * 判断是否为创建时间字段
325
+ */
326
+ isCreatedAtField(field) {
327
+ return field.fieldName === "createdAt" || field.columnName === "created_at";
328
+ }
329
+ /**
330
+ * 判断是否为更新时间字段
331
+ */
332
+ isUpdatedAtField(field) {
333
+ return field.fieldName === "updatedAt" || field.columnName === "updated_at";
334
+ }
335
+ /**
336
+ * 将数据库值转换为业务值
337
+ *
338
+ * @param value - 数据库原始值
339
+ * @param def - 字段定义
340
+ * @returns 业务侧值(可能为 undefined)
341
+ */
342
+ fromDbValue(value, def) {
343
+ if (value === null || value === void 0) {
344
+ return void 0;
345
+ }
346
+ if (def.type === "BOOLEAN") {
347
+ return Boolean(value);
348
+ }
349
+ if (def.type === "TIMESTAMP") {
350
+ if (value instanceof Date) {
351
+ return value;
352
+ }
353
+ if (typeof value === "number") {
354
+ return new Date(value);
355
+ }
356
+ const parsed = Date.parse(String(value));
357
+ return Number.isNaN(parsed) ? void 0 : new Date(parsed);
358
+ }
359
+ if (def.type === "JSON") {
360
+ if (typeof value === "string") {
361
+ try {
362
+ return JSON.parse(value);
363
+ } catch {
364
+ return void 0;
365
+ }
366
+ }
367
+ return value;
368
+ }
369
+ return value;
370
+ }
371
+ /**
372
+ * 将业务值转换为数据库值
373
+ *
374
+ * @param value - 业务侧值
375
+ * @param def - 字段定义
376
+ * @returns 适配数据库的值
377
+ */
378
+ toDbValue(value, def) {
379
+ if (value === void 0) {
380
+ return void 0;
381
+ }
382
+ if (value === null) {
383
+ return null;
384
+ }
385
+ if (def.type === "BOOLEAN") {
386
+ if (this.dbType === "postgresql") {
387
+ return Boolean(value);
388
+ }
389
+ return value ? 1 : 0;
390
+ }
391
+ if (def.type === "TIMESTAMP") {
392
+ if (this.dbType === "sqlite") {
393
+ if (value instanceof Date) {
394
+ return value.getTime();
395
+ }
396
+ if (typeof value === "number") {
397
+ return value;
398
+ }
399
+ const parsed = Date.parse(String(value));
400
+ return Number.isNaN(parsed) ? value : parsed;
401
+ }
402
+ if (value instanceof Date) {
403
+ return value;
404
+ }
405
+ if (typeof value === "number") {
406
+ return new Date(value);
407
+ }
408
+ return value;
409
+ }
410
+ if (def.type === "JSON") {
411
+ if (typeof value === "string") {
412
+ return value;
413
+ }
414
+ return JSON.stringify(value);
415
+ }
416
+ return value;
417
+ }
418
+ /**
419
+ * 将查询行映射为业务模型
420
+ */
421
+ mapRow(row) {
422
+ const result = {};
423
+ for (const field of this.fields) {
424
+ if (!field.select) {
425
+ continue;
426
+ }
427
+ const value = this.fromDbValue(row[field.columnName], field.def);
428
+ result[field.fieldName] = value;
429
+ }
430
+ return result;
431
+ }
432
+ /**
433
+ * 构建创建数据的列值映射
434
+ *
435
+ * 处理主键生成、默认值、createdAt/updatedAt 填充等规则。
436
+ */
437
+ buildCreatePayload(data) {
438
+ const payload = {};
439
+ const now = this.nowProvider();
440
+ let generatedId;
441
+ const idFieldDef = this.fields.find((field) => field.fieldName === this.idField);
442
+ if (this.idField && !idFieldDef?.def.autoIncrement) {
443
+ const currentId = data[this.idField];
444
+ if (currentId !== void 0) ; else if (this.generateId) {
445
+ generatedId = this.generateId();
446
+ }
447
+ }
448
+ for (const field of this.fields) {
449
+ if (!field.create) {
450
+ continue;
451
+ }
452
+ let value = data[field.fieldName];
453
+ if (value === void 0 && field.def.autoIncrement) {
454
+ continue;
455
+ }
456
+ if (field.fieldName === this.idField && value === void 0 && generatedId !== void 0) {
457
+ value = generatedId;
458
+ }
459
+ if (value === void 0) {
460
+ if (this.isCreatedAtField(field) || this.isUpdatedAtField(field)) {
461
+ value = now;
462
+ } else if (field.def.defaultValue !== void 0 && typeof field.def.defaultValue !== "string") {
463
+ value = field.def.defaultValue;
464
+ }
465
+ }
466
+ if (value === void 0) {
467
+ continue;
468
+ }
469
+ payload[field.columnName] = this.toDbValue(value, field.def);
470
+ }
471
+ return payload;
472
+ }
473
+ /**
474
+ * 构建更新数据的列值映射
475
+ *
476
+ * 当没有任何可更新字段时返回 null。
477
+ */
478
+ buildUpdatePayload(data) {
479
+ const payload = {};
480
+ const now = this.nowProvider();
481
+ for (const field of this.fields) {
482
+ if (!field.update) {
483
+ continue;
484
+ }
485
+ const value = data[field.fieldName];
486
+ if (value === void 0) {
487
+ continue;
488
+ }
489
+ payload[field.columnName] = this.toDbValue(value, field.def);
490
+ }
491
+ if (Object.keys(payload).length === 0) {
492
+ return null;
493
+ }
494
+ const updatedAtField = this.fields.find((field) => this.isUpdatedAtField(field));
495
+ if (updatedAtField) {
496
+ payload[updatedAtField.columnName] = this.toDbValue(now, updatedAtField.def);
497
+ }
498
+ return payload;
499
+ }
500
+ /**
501
+ * 等待初始化完成
502
+ *
503
+ * @returns 初始化结果
504
+ */
505
+ async ensureReady() {
506
+ const result = await this.initPromise;
507
+ if (!result.success) {
508
+ return result;
509
+ }
510
+ return ok(void 0);
511
+ }
512
+ /**
513
+ * 创建单条记录
514
+ *
515
+ * 自动处理主键生成、createdAt/updatedAt 填充、字段类型转换。
516
+ *
517
+ * @param data - 业务字段与值的映射
518
+ * @param tx - 可选事务句柄
519
+ * @returns 插入结果(含 changes、lastInsertRowid)
520
+ */
521
+ async create(data, tx) {
522
+ const ready = await this.ensureReady();
523
+ if (!ready.success) {
524
+ return ready;
525
+ }
526
+ const payload = this.buildCreatePayload(data);
527
+ if (Object.keys(payload).length === 0) {
528
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudEmptyPayload"));
529
+ }
530
+ return this.crud.create(payload, tx);
531
+ }
532
+ /**
533
+ * 批量创建记录
534
+ *
535
+ * 每条记录均通过 buildCreatePayload 处理,然后以 batch 方式写入。
536
+ *
537
+ * @param items - 待插入的业务数据数组
538
+ * @param tx - 可选事务句柄
539
+ * @returns 批量插入结果
540
+ */
541
+ async createMany(items, tx) {
542
+ const ready = await this.ensureReady();
543
+ if (!ready.success) {
544
+ return ready;
545
+ }
546
+ const payloads = items.map((item) => this.buildCreatePayload(item));
547
+ return this.crud.createMany(payloads, tx);
548
+ }
549
+ /**
550
+ * 创建或更新单条记录(upsert)
551
+ *
552
+ * 主键存在时更新可更新字段,否则插入新记录。
553
+ * 自动处理主键生成、createdAt/updatedAt 填充、字段类型转换。
554
+ * 更新时仅写入用户显式提供的可更新字段,不会用默认值覆盖已有数据。
555
+ *
556
+ * ### 为什么不复用 this.crud.createOrUpdate(kernel)
557
+ *
558
+ * Kernel 的 createOrUpdate 使用 `excluded.col` / `VALUES(col)` 引用 INSERT 值作为
559
+ * UPDATE 值,即 INSERT 和 UPDATE 共享同一组数据。但 Repository 层的 INSERT 负载和
560
+ * UPDATE 负载语义不同:
561
+ *
562
+ * - **INSERT 负载**(buildCreatePayload):包含自动生成的主键、createdAt、updatedAt、
563
+ * 字段默认值(如 `status: 'active'`)等应用层填充值。
564
+ * - **UPDATE 负载**(buildUpdatePayload):仅包含用户显式传入的可更新字段 + updatedAt,
565
+ * 不包含 createdAt(避免覆盖原始创建时间)、不包含应用默认值(避免覆盖已有数据)。
566
+ *
567
+ * 若复用 kernel,冲突更新时会将 INSERT 的 createdAt、默认值等一并写入,破坏已有记录。
568
+ * 因此 Repository 自行构建两份独立负载,使用 `col = ?` 参数化占位分别传入 INSERT 值
569
+ * 和 UPDATE 值,通过 this.sql(tx).execute() 直接执行。
570
+ *
571
+ * @param data - 业务字段与值的映射
572
+ * @param tx - 可选事务句柄
573
+ * @returns 执行结果(含 changes)
574
+ */
575
+ async createOrUpdate(data, tx) {
576
+ const ready = await this.ensureReady();
577
+ if (!ready.success) {
578
+ return ready;
579
+ }
580
+ const dbType = this.dbType;
581
+ const createPayload = this.buildCreatePayload(data);
582
+ if (this.idField && data[this.idField] !== void 0) {
583
+ const idFieldDef = this.fields.find((f) => f.fieldName === this.idField);
584
+ if (idFieldDef && !(idFieldDef.columnName in createPayload)) {
585
+ createPayload[idFieldDef.columnName] = this.toDbValue(data[this.idField], idFieldDef.def);
586
+ }
587
+ }
588
+ if (Object.keys(createPayload).length === 0) {
589
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudEmptyPayload"));
590
+ }
591
+ const insertColumns = Object.keys(createPayload);
592
+ const insertValues = Object.values(createPayload);
593
+ const placeholders = insertColumns.map(() => "?").join(", ");
594
+ const updatePayload = this.buildUpdatePayload(data);
595
+ if (!updatePayload || Object.keys(updatePayload).length === 0) {
596
+ const sql2 = `INSERT INTO ${this.table} (${insertColumns.join(", ")}) VALUES (${placeholders})`;
597
+ return this.sql(tx).execute(sql2, insertValues);
598
+ }
599
+ const updateColumns = Object.keys(updatePayload);
600
+ const updateValues = Object.values(updatePayload);
601
+ const updateSet = updateColumns.map((col) => `${col} = ?`).join(", ");
602
+ let sql;
603
+ if (dbType === "mysql") {
604
+ sql = `INSERT INTO ${this.table} (${insertColumns.join(", ")}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updateSet}`;
605
+ } else {
606
+ sql = `INSERT INTO ${this.table} (${insertColumns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${this.idColumn}) DO UPDATE SET ${updateSet}`;
607
+ }
608
+ return this.sql(tx).execute(sql, [...insertValues, ...updateValues]);
609
+ }
610
+ /**
611
+ * 根据主键查找单条记录
612
+ *
613
+ * @param id - 主键值
614
+ * @param tx - 可选事务句柄
615
+ * @returns 业务模型对象或 null(未找到)
616
+ */
617
+ async findById(id, tx) {
618
+ const ready = await this.ensureReady();
619
+ if (!ready.success) {
620
+ return ready;
621
+ }
622
+ return this.crud.findById(id, tx);
623
+ }
624
+ /**
625
+ * 根据主键获取单条记录(必须存在)
626
+ *
627
+ * 与 findById 不同,当记录不存在时返回 RECORD_NOT_FOUND 错误。
628
+ *
629
+ * @param id - 主键值
630
+ * @param tx - 可选事务句柄
631
+ * @returns 业务模型对象(不存在时返回错误)
632
+ */
633
+ async getById(id, tx) {
634
+ const ready = await this.ensureReady();
635
+ if (!ready.success) {
636
+ return ready;
637
+ }
638
+ return this.crud.getById(id, tx);
639
+ }
640
+ /**
641
+ * 条件查询多条记录
642
+ *
643
+ * @param options - 查询条件(where、orderBy、limit、offset)
644
+ * @param tx - 可选事务句柄
645
+ * @returns 业务模型数组
646
+ */
647
+ async findAll(options, tx) {
648
+ const ready = await this.ensureReady();
649
+ if (!ready.success) {
650
+ return ready;
651
+ }
652
+ return this.crud.findAll(options, tx);
653
+ }
654
+ /**
655
+ * 分页查询记录
656
+ *
657
+ * @param options - 分页查询条件(where、orderBy、pagination、overrides)
658
+ * @param tx - 可选事务句柄
659
+ * @returns 分页结果(含 items、total、page、pageSize)
660
+ */
661
+ async findPage(options, tx) {
662
+ const ready = await this.ensureReady();
663
+ if (!ready.success) {
664
+ return ready;
665
+ }
666
+ return this.crud.findPage(options, tx);
667
+ }
668
+ /**
669
+ * 根据主键更新记录
670
+ *
671
+ * 自动填充 updatedAt 字段。无可更新字段时返回 CONFIG_ERROR。
672
+ *
673
+ * @param id - 主键值
674
+ * @param data - 待更新的业务字段
675
+ * @param tx - 可选事务句柄
676
+ * @returns 更新结果(含 changes)
677
+ */
678
+ async updateById(id, data, tx) {
679
+ const ready = await this.ensureReady();
680
+ if (!ready.success) {
681
+ return ready;
682
+ }
683
+ const payload = this.buildUpdatePayload(data);
684
+ if (!payload) {
685
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudEmptyPayload"));
686
+ }
687
+ return this.crud.updateById(id, payload, tx);
688
+ }
689
+ /**
690
+ * 根据主键删除记录
691
+ *
692
+ * @param id - 主键值
693
+ * @param tx - 可选事务句柄
694
+ * @returns 删除结果(含 changes)
695
+ */
696
+ async deleteById(id, tx) {
697
+ const ready = await this.ensureReady();
698
+ if (!ready.success) {
699
+ return ready;
700
+ }
701
+ return this.crud.deleteById(id, tx);
702
+ }
703
+ /**
704
+ * 统计符合条件的记录数
705
+ *
706
+ * @param options - 查询条件(where、params)
707
+ * @param tx - 可选事务句柄
708
+ * @returns 记录数
709
+ */
710
+ async count(options, tx) {
711
+ const ready = await this.ensureReady();
712
+ if (!ready.success) {
713
+ return ready;
714
+ }
715
+ return this.crud.count(options, tx);
716
+ }
717
+ /**
718
+ * 检查是否存在符合条件的记录
719
+ *
720
+ * @param options - 查询条件(where、params)
721
+ * @param tx - 可选事务句柄
722
+ * @returns 是否存在
723
+ */
724
+ async exists(options, tx) {
725
+ const ready = await this.ensureReady();
726
+ if (!ready.success) {
727
+ return ready;
728
+ }
729
+ return this.crud.exists(options, tx);
730
+ }
731
+ /**
732
+ * 根据主键检查记录是否存在
733
+ *
734
+ * @param id - 主键值
735
+ * @param tx - 可选事务句柄
736
+ * @returns 是否存在
737
+ */
738
+ async existsById(id, tx) {
739
+ const ready = await this.ensureReady();
740
+ if (!ready.success) {
741
+ return ready;
742
+ }
743
+ return this.crud.existsById(id, tx);
744
+ }
745
+ };
746
+
747
+ // src/reldb-json.ts
748
+ function parseJsonPath(path) {
749
+ let remaining = path.startsWith("$") ? path.slice(1) : path;
750
+ const segments = [];
751
+ while (remaining.length > 0) {
752
+ if (remaining.startsWith("[")) {
753
+ const end = remaining.indexOf("]");
754
+ if (end === -1)
755
+ break;
756
+ let segment = remaining.slice(1, end);
757
+ if (segment.startsWith('"') && segment.endsWith('"') || segment.startsWith("'") && segment.endsWith("'")) {
758
+ segment = segment.slice(1, -1);
759
+ }
760
+ segments.push(segment);
761
+ remaining = remaining.slice(end + 1);
762
+ } else if (remaining.startsWith(".")) {
763
+ remaining = remaining.slice(1);
764
+ if (remaining.startsWith("["))
765
+ continue;
766
+ const match = remaining.match(/^([^.[]+)/);
767
+ if (match) {
768
+ segments.push(match[1]);
769
+ remaining = remaining.slice(match[1].length);
770
+ }
771
+ } else {
772
+ break;
773
+ }
774
+ }
775
+ return segments;
776
+ }
777
+ function createSqliteJsonOps() {
778
+ return {
779
+ extract(column, path) {
780
+ return {
781
+ sql: `json_extract(${column}, ?)`,
782
+ params: [path]
783
+ };
784
+ },
785
+ set(column, path, value) {
786
+ return {
787
+ sql: `json_set(${column}, ?, json(?))`,
788
+ params: [path, JSON.stringify(value)]
789
+ };
790
+ },
791
+ insert(column, path, value) {
792
+ return {
793
+ sql: `json_insert(${column}, ?, json(?))`,
794
+ params: [path, JSON.stringify(value)]
795
+ };
796
+ },
797
+ remove(column, path) {
798
+ return {
799
+ sql: `json_remove(${column}, ?)`,
800
+ params: [path]
801
+ };
802
+ },
803
+ merge(column, patch) {
804
+ return {
805
+ sql: `json_patch(${column}, ?)`,
806
+ params: [JSON.stringify(patch)]
807
+ };
808
+ }
809
+ };
810
+ }
811
+ function createPostgresJsonOps() {
812
+ return {
813
+ extract(column, path) {
814
+ const segments = parseJsonPath(path);
815
+ return {
816
+ sql: `${column} #> ?::text[]`,
817
+ params: [segments]
818
+ };
819
+ },
820
+ set(column, path, value) {
821
+ const segments = parseJsonPath(path);
822
+ return {
823
+ sql: `jsonb_set(${column}, ?::text[], ?::jsonb)`,
824
+ params: [segments, JSON.stringify(value)]
825
+ };
826
+ },
827
+ insert(column, path, value) {
828
+ const segments = parseJsonPath(path);
829
+ return {
830
+ sql: `jsonb_insert(${column}, ?::text[], ?::jsonb)`,
831
+ params: [segments, JSON.stringify(value)]
832
+ };
833
+ },
834
+ remove(column, path) {
835
+ const segments = parseJsonPath(path);
836
+ return {
837
+ sql: `${column} #- ?::text[]`,
838
+ params: [segments]
839
+ };
840
+ },
841
+ merge(column, patch) {
842
+ return {
843
+ sql: `${column} || ?::jsonb`,
844
+ params: [JSON.stringify(patch)]
845
+ };
846
+ }
847
+ };
848
+ }
849
+ function createMysqlJsonOps() {
850
+ return {
851
+ extract(column, path) {
852
+ return {
853
+ sql: `JSON_EXTRACT(${column}, ?)`,
854
+ params: [path]
855
+ };
856
+ },
857
+ set(column, path, value) {
858
+ return {
859
+ sql: `JSON_SET(${column}, ?, CAST(? AS JSON))`,
860
+ params: [path, JSON.stringify(value)]
861
+ };
862
+ },
863
+ insert(column, path, value) {
864
+ return {
865
+ sql: `JSON_INSERT(${column}, ?, CAST(? AS JSON))`,
866
+ params: [path, JSON.stringify(value)]
867
+ };
868
+ },
869
+ remove(column, path) {
870
+ return {
871
+ sql: `JSON_REMOVE(${column}, ?)`,
872
+ params: [path]
873
+ };
874
+ },
875
+ merge(column, patch) {
876
+ return {
877
+ sql: `JSON_MERGE_PATCH(${column}, CAST(? AS JSON))`,
878
+ params: [JSON.stringify(patch)]
879
+ };
880
+ }
881
+ };
882
+ }
883
+ function createJsonOps(dbType) {
884
+ switch (dbType) {
885
+ case "sqlite":
886
+ return createSqliteJsonOps();
887
+ case "postgresql":
888
+ return createPostgresJsonOps();
889
+ case "mysql":
890
+ return createMysqlJsonOps();
891
+ }
892
+ }
893
+
894
+ // src/reldb-pagination.ts
895
+ var DEFAULT_PAGE = 1;
896
+ var DEFAULT_PAGE_SIZE = 20;
897
+ var MAX_PAGE_SIZE = 200;
898
+ function normalizePage(value, fallback) {
899
+ if (!value || value < 1) {
900
+ return fallback;
901
+ }
902
+ return Math.floor(value);
903
+ }
904
+ function normalizePageSize(value, fallback, maxSize) {
905
+ if (!value || value < 1) {
906
+ return fallback;
907
+ }
908
+ return Math.min(Math.floor(value), maxSize);
909
+ }
910
+ function normalizePagination(options, overrides) {
911
+ const defaultPage = overrides?.defaultPage ?? DEFAULT_PAGE;
912
+ const defaultPageSize = overrides?.defaultPageSize ?? DEFAULT_PAGE_SIZE;
913
+ const maxPageSize = overrides?.maxPageSize ?? MAX_PAGE_SIZE;
914
+ const page = normalizePage(options?.page, defaultPage);
915
+ const pageSize = normalizePageSize(options?.pageSize, defaultPageSize, maxPageSize);
916
+ const offset = (page - 1) * pageSize;
917
+ return {
918
+ page,
919
+ pageSize,
920
+ offset,
921
+ limit: pageSize
922
+ };
923
+ }
924
+ function buildPaginatedResult(items, total, pagination2) {
925
+ return {
926
+ items,
927
+ total,
928
+ page: pagination2.page,
929
+ pageSize: pagination2.pageSize
930
+ };
931
+ }
932
+ var pagination = {
933
+ normalize: normalizePagination,
934
+ build: buildPaginatedResult
935
+ };
936
+ function parseCount(row) {
937
+ if (!row) {
938
+ return 0;
939
+ }
940
+ if ("total" in row) {
941
+ return Number(row.total ?? 0);
942
+ }
943
+ if ("__total__" in row) {
944
+ return Number(row.__total__ ?? 0);
945
+ }
946
+ if ("cnt" in row) {
947
+ return Number(row.cnt ?? 0);
948
+ }
949
+ const value = Object.values(row)[0];
950
+ if (typeof value === "bigint") {
951
+ return Number(value);
952
+ }
953
+ return Number(value ?? 0);
954
+ }
955
+ var VALID_IDENTIFIER_RE = /^[a-z_]\w{0,127}$/i;
956
+ function validateIdentifier(name) {
957
+ if (VALID_IDENTIFIER_RE.test(name)) {
958
+ return ok(name);
959
+ }
960
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_invalidIdentifier", { params: { name } }));
961
+ }
962
+ function validateIdentifiers(names) {
963
+ for (const name of names) {
964
+ const result = validateIdentifier(name);
965
+ if (!result.success) {
966
+ return err(result.error);
967
+ }
968
+ }
969
+ return ok(void 0);
970
+ }
971
+ function quoteIdentifier(name) {
972
+ return `"${name.replace(/"/g, '""')}"`;
973
+ }
974
+ function escapeSqlString(value) {
975
+ return value.replace(/'/g, "''");
976
+ }
977
+
978
+ // src/reldb-crud-kernel.ts
979
+ var logger = core.logger.child({ module: "reldb", scope: "crud-kernel" });
980
+ function buildSelectColumns(select) {
981
+ if (!select || select.length === 0) {
982
+ return "*";
983
+ }
984
+ return select.join(", ");
985
+ }
986
+ function pickColumns(data, allowList) {
987
+ const keys = Object.keys(data);
988
+ const columns = allowList ? keys.filter((key) => allowList.includes(key)) : keys;
989
+ const values = columns.map((key) => data[key]);
990
+ return { columns, values };
991
+ }
992
+ function createFailReldbCrudRepository(failedResult) {
993
+ const failResult = () => Promise.resolve(failedResult);
994
+ return {
995
+ create: () => failResult(),
996
+ createMany: () => failResult(),
997
+ createOrUpdate: () => failResult(),
998
+ findById: () => failResult(),
999
+ getById: () => failResult(),
1000
+ findAll: () => failResult(),
1001
+ findPage: () => failResult(),
1002
+ updateById: () => failResult(),
1003
+ deleteById: () => failResult(),
1004
+ count: () => failResult(),
1005
+ exists: () => failResult(),
1006
+ existsById: () => failResult()
1007
+ };
1008
+ }
1009
+ function createCrud(ops, config) {
1010
+ const table = config.table;
1011
+ const idColumn = config.idColumn ?? "id";
1012
+ const selectColumns = buildSelectColumns(config.select);
1013
+ const mapRow = config.mapRow ?? ((row) => row);
1014
+ const dbType = config.dbType;
1015
+ const tableValid = validateIdentifier(table);
1016
+ if (!tableValid.success) {
1017
+ return createFailReldbCrudRepository(tableValid);
1018
+ }
1019
+ const idColumnValid = validateIdentifier(idColumn);
1020
+ if (!idColumnValid.success) {
1021
+ return createFailReldbCrudRepository(idColumnValid);
1022
+ }
1023
+ if (config.select) {
1024
+ const selectValid = validateIdentifiers(config.select);
1025
+ if (!selectValid.success) {
1026
+ return createFailReldbCrudRepository(selectValid);
1027
+ }
1028
+ }
1029
+ if (config.createColumns) {
1030
+ const createColsValid = validateIdentifiers(config.createColumns);
1031
+ if (!createColsValid.success) {
1032
+ return createFailReldbCrudRepository(createColsValid);
1033
+ }
1034
+ }
1035
+ if (config.updateColumns) {
1036
+ const updateColsValid = validateIdentifiers(config.updateColumns);
1037
+ if (!updateColsValid.success) {
1038
+ return createFailReldbCrudRepository(updateColsValid);
1039
+ }
1040
+ }
1041
+ const buildWhereClause = (where) => where ? ` WHERE ${where}` : "";
1042
+ const buildOrderClause = (orderBy) => {
1043
+ if (!orderBy)
1044
+ return "";
1045
+ const parts = orderBy.split(",").map((s) => s.trim()).filter(Boolean);
1046
+ const safeParts = [];
1047
+ for (const part of parts) {
1048
+ const match = part.match(/^(\w+)(?:\s+(ASC|DESC))?$/i);
1049
+ if (!match) {
1050
+ logger.debug("Skipped invalid orderBy segment", { table, segment: part });
1051
+ continue;
1052
+ }
1053
+ const colName = match[1];
1054
+ const direction = match[2] ?? "";
1055
+ const colValid = validateIdentifier(colName);
1056
+ if (!colValid.success) {
1057
+ logger.debug("Skipped invalid orderBy identifier", { table, column: colName });
1058
+ continue;
1059
+ }
1060
+ safeParts.push(direction ? `${colName} ${direction.toUpperCase()}` : colName);
1061
+ }
1062
+ if (safeParts.length === 0)
1063
+ return "";
1064
+ return ` ORDER BY ${safeParts.join(", ")}`;
1065
+ };
1066
+ const buildLimitOffset = (limit, offset) => {
1067
+ const parts = [];
1068
+ if (typeof limit === "number") {
1069
+ parts.push(` LIMIT ${limit}`);
1070
+ }
1071
+ if (typeof offset === "number") {
1072
+ parts.push(` OFFSET ${offset}`);
1073
+ }
1074
+ return parts.join("");
1075
+ };
1076
+ const createPayloadError = () => err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudEmptyPayload"));
1077
+ const createColumnsError = () => err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudNoValidColumns"));
1078
+ const resolveOps = (tx) => tx ?? ops;
1079
+ return {
1080
+ /**
1081
+ * 创建单条记录
1082
+ *
1083
+ * @param data - 列名与值的映射(会根据 createColumns 白名单过滤)
1084
+ * @param tx - 可选事务句柄
1085
+ * @returns 插入结果(含 changes、lastInsertRowid)
1086
+ */
1087
+ async create(data, tx) {
1088
+ if (!data || Object.keys(data).length === 0) {
1089
+ return createPayloadError();
1090
+ }
1091
+ const { columns, values } = pickColumns(data, config.createColumns);
1092
+ if (columns.length === 0) {
1093
+ return createColumnsError();
1094
+ }
1095
+ const colValid = validateIdentifiers(columns);
1096
+ if (!colValid.success) {
1097
+ return err(colValid.error);
1098
+ }
1099
+ const placeholders = columns.map(() => "?").join(", ");
1100
+ const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
1101
+ return resolveOps(tx).execute(sql, values);
1102
+ },
1103
+ /**
1104
+ * 批量创建记录
1105
+ *
1106
+ * 所有记录在同一 batch 中执行,任意一条失败则整体失败。
1107
+ *
1108
+ * @param items - 待插入的数据数组
1109
+ * @param tx - 可选事务句柄
1110
+ * @returns 批量插入结果
1111
+ */
1112
+ async createMany(items, tx) {
1113
+ if (!items || items.length === 0) {
1114
+ return ok(void 0);
1115
+ }
1116
+ const statements = [];
1117
+ for (const item of items) {
1118
+ if (!item || Object.keys(item).length === 0) {
1119
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudEmptyPayload"));
1120
+ }
1121
+ const { columns, values } = pickColumns(item, config.createColumns);
1122
+ if (columns.length === 0) {
1123
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_crudNoValidColumns"));
1124
+ }
1125
+ const colValid = validateIdentifiers(columns);
1126
+ if (!colValid.success) {
1127
+ return err(colValid.error);
1128
+ }
1129
+ const placeholders = columns.map(() => "?").join(", ");
1130
+ const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
1131
+ statements.push({ sql, params: values });
1132
+ }
1133
+ return resolveOps(tx).batch(statements);
1134
+ },
1135
+ /**
1136
+ * 创建或更新单条记录(upsert)
1137
+ *
1138
+ * 主键冲突时更新 updateColumns 中存在的列,否则插入新记录。
1139
+ *
1140
+ * @param data - 列名与值的映射
1141
+ * @param tx - 可选事务句柄
1142
+ * @returns 执行结果(含 changes)
1143
+ */
1144
+ async createOrUpdate(data, tx) {
1145
+ if (!data || Object.keys(data).length === 0) {
1146
+ return createPayloadError();
1147
+ }
1148
+ const { columns, values } = pickColumns(data, config.createColumns);
1149
+ if (columns.length === 0) {
1150
+ return createColumnsError();
1151
+ }
1152
+ const colValid = validateIdentifiers(columns);
1153
+ if (!colValid.success) {
1154
+ return err(colValid.error);
1155
+ }
1156
+ const placeholders = columns.map(() => "?").join(", ");
1157
+ const updateCols = (config.updateColumns ?? columns).filter((col) => col !== idColumn && columns.includes(col));
1158
+ if (updateCols.length === 0) {
1159
+ const sql2 = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
1160
+ return resolveOps(tx).execute(sql2, values);
1161
+ }
1162
+ let sql;
1163
+ if (dbType === "mysql") {
1164
+ const updateSet = updateCols.map((col) => `${col} = VALUES(${col})`).join(", ");
1165
+ sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updateSet}`;
1166
+ } else {
1167
+ const updateSet = updateCols.map((col) => `${col} = excluded.${col}`).join(", ");
1168
+ sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${idColumn}) DO UPDATE SET ${updateSet}`;
1169
+ }
1170
+ return resolveOps(tx).execute(sql, values);
1171
+ },
1172
+ /**
1173
+ * 根据主键查找单条记录
1174
+ *
1175
+ * @param id - 主键值
1176
+ * @param tx - 可选事务句柄
1177
+ * @returns 记录对象或 null(未找到)
1178
+ */
1179
+ async findById(id, tx) {
1180
+ const sql = `SELECT ${selectColumns} FROM ${table} WHERE ${idColumn} = ?`;
1181
+ const result = await resolveOps(tx).get(sql, [id]);
1182
+ if (!result.success) {
1183
+ return result;
1184
+ }
1185
+ return ok(result.data ? mapRow(result.data) : null);
1186
+ },
1187
+ /**
1188
+ * 根据主键获取单条记录(必须存在)
1189
+ *
1190
+ * 与 findById 不同,当记录不存在时返回 RECORD_NOT_FOUND 错误。
1191
+ *
1192
+ * @param id - 主键值
1193
+ * @param tx - 可选事务句柄
1194
+ * @returns 记录对象(不存在时返回错误)
1195
+ */
1196
+ async getById(id, tx) {
1197
+ const findResult = await this.findById(id, tx);
1198
+ if (!findResult.success) {
1199
+ return findResult;
1200
+ }
1201
+ if (findResult.data === null) {
1202
+ return err(HaiReldbError.RECORD_NOT_FOUND, reldbM("reldb_crudRecordNotFound", { params: { id: String(id) } }));
1203
+ }
1204
+ return ok(findResult.data);
1205
+ },
1206
+ /**
1207
+ * 条件查询多条记录
1208
+ *
1209
+ * @param options - 查询条件(where、orderBy、limit、offset)
1210
+ * @param tx - 可选事务句柄
1211
+ * @returns 记录数组
1212
+ */
1213
+ async findAll(options = {}, tx) {
1214
+ const whereClause = buildWhereClause(options.where);
1215
+ const orderClause = buildOrderClause(options.orderBy);
1216
+ const limitClause = buildLimitOffset(options.limit, options.offset);
1217
+ const sql = `SELECT ${selectColumns} FROM ${table}${whereClause}${orderClause}${limitClause}`;
1218
+ const result = await resolveOps(tx).query(sql, options.params);
1219
+ if (!result.success) {
1220
+ return result;
1221
+ }
1222
+ return ok(result.data.map((row) => mapRow(row)));
1223
+ },
1224
+ /**
1225
+ * 分页查询记录
1226
+ *
1227
+ * @param options - 分页查询条件(where、orderBy、pagination、overrides)
1228
+ * @param tx - 可选事务句柄
1229
+ * @returns 分页结果(含 items、total、page、pageSize)
1230
+ */
1231
+ async findPage(options, tx) {
1232
+ const whereClause = buildWhereClause(options.where);
1233
+ const orderClause = buildOrderClause(options.orderBy);
1234
+ const sql = `SELECT ${selectColumns} FROM ${table}${whereClause}${orderClause}`;
1235
+ const pageResult = await resolveOps(tx).queryPage({
1236
+ sql,
1237
+ params: options.params,
1238
+ pagination: options.pagination,
1239
+ overrides: options.overrides
1240
+ });
1241
+ if (!pageResult.success) {
1242
+ return pageResult;
1243
+ }
1244
+ return ok({
1245
+ ...pageResult.data,
1246
+ items: pageResult.data.items.map((row) => mapRow(row))
1247
+ });
1248
+ },
1249
+ /**
1250
+ * 根据主键更新记录
1251
+ *
1252
+ * 主键列不可更新,会自动排除。
1253
+ *
1254
+ * @param id - 主键值
1255
+ * @param data - 待更新的列值映射(会根据 updateColumns 白名单过滤)
1256
+ * @param tx - 可选事务句柄
1257
+ * @returns 更新结果(含 changes)
1258
+ */
1259
+ async updateById(id, data, tx) {
1260
+ if (!data || Object.keys(data).length === 0) {
1261
+ return createPayloadError();
1262
+ }
1263
+ const allowList = (config.updateColumns ?? []).filter((column) => column !== idColumn);
1264
+ const { columns } = pickColumns(data, allowList.length > 0 ? allowList : void 0);
1265
+ const filtered = columns.filter((column) => column !== idColumn);
1266
+ if (filtered.length === 0) {
1267
+ return createColumnsError();
1268
+ }
1269
+ const colValid = validateIdentifiers(filtered);
1270
+ if (!colValid.success) {
1271
+ return err(colValid.error);
1272
+ }
1273
+ const setClause = filtered.map((column) => `${column} = ?`).join(", ");
1274
+ const sql = `UPDATE ${table} SET ${setClause} WHERE ${idColumn} = ?`;
1275
+ const params = [...filtered.map((column) => data[column]), id];
1276
+ return resolveOps(tx).execute(sql, params);
1277
+ },
1278
+ /**
1279
+ * 根据主键删除记录
1280
+ *
1281
+ * @param id - 主键值
1282
+ * @param tx - 可选事务句柄
1283
+ * @returns 删除结果(含 changes)
1284
+ */
1285
+ async deleteById(id, tx) {
1286
+ const sql = `DELETE FROM ${table} WHERE ${idColumn} = ?`;
1287
+ return resolveOps(tx).execute(sql, [id]);
1288
+ },
1289
+ /**
1290
+ * 统计符合条件的记录数
1291
+ *
1292
+ * @param options - 查询条件(where、params)
1293
+ * @param tx - 可选事务句柄
1294
+ * @returns 记录数
1295
+ */
1296
+ async count(options = {}, tx) {
1297
+ const whereClause = buildWhereClause(options.where);
1298
+ const sql = `SELECT COUNT(*) as cnt FROM ${table}${whereClause}`;
1299
+ const result = await resolveOps(tx).get(sql, options.params);
1300
+ if (!result.success) {
1301
+ return result;
1302
+ }
1303
+ return ok(parseCount(result.data ?? null));
1304
+ },
1305
+ /**
1306
+ * 检查是否存在符合条件的记录
1307
+ *
1308
+ * @param options - 查询条件(where、params)
1309
+ * @param tx - 可选事务句柄
1310
+ * @returns 是否存在
1311
+ */
1312
+ async exists(options = {}, tx) {
1313
+ const whereClause = buildWhereClause(options.where);
1314
+ const sql = `SELECT 1 as exist_flag FROM ${table}${whereClause} LIMIT 1`;
1315
+ const result = await resolveOps(tx).get(sql, options.params);
1316
+ if (!result.success) {
1317
+ return result;
1318
+ }
1319
+ return ok(Boolean(result.data));
1320
+ },
1321
+ /**
1322
+ * 根据主键检查记录是否存在
1323
+ *
1324
+ * @param id - 主键值
1325
+ * @param tx - 可选事务句柄
1326
+ * @returns 是否存在
1327
+ */
1328
+ async existsById(id, tx) {
1329
+ const sql = `SELECT 1 as exist_flag FROM ${table} WHERE ${idColumn} = ? LIMIT 1`;
1330
+ const result = await resolveOps(tx).get(sql, [id]);
1331
+ if (!result.success) {
1332
+ return result;
1333
+ }
1334
+ return ok(Boolean(result.data));
1335
+ }
1336
+ };
1337
+ }
1338
+ function createTxHandle(baseDmlOps, callbacks) {
1339
+ let active = true;
1340
+ const ensureActive = () => {
1341
+ if (!active) {
1342
+ return err(HaiReldbError.TRANSACTION_FAILED, callbacks.errorMessage("transaction finished"));
1343
+ }
1344
+ return ok(void 0);
1345
+ };
1346
+ const guardedOps = {
1347
+ async query(sql, params) {
1348
+ const check = ensureActive();
1349
+ if (!check.success)
1350
+ return check;
1351
+ return baseDmlOps.query(sql, params);
1352
+ },
1353
+ async get(sql, params) {
1354
+ const check = ensureActive();
1355
+ if (!check.success)
1356
+ return check;
1357
+ return baseDmlOps.get(sql, params);
1358
+ },
1359
+ async execute(sql, params) {
1360
+ const check = ensureActive();
1361
+ if (!check.success)
1362
+ return check;
1363
+ return baseDmlOps.execute(sql, params);
1364
+ },
1365
+ async batch(statements) {
1366
+ const check = ensureActive();
1367
+ if (!check.success)
1368
+ return check;
1369
+ return baseDmlOps.batch(statements);
1370
+ },
1371
+ async queryPage(options) {
1372
+ const check = ensureActive();
1373
+ if (!check.success)
1374
+ return check;
1375
+ return baseDmlOps.queryPage(options);
1376
+ }
1377
+ };
1378
+ const crudManager = {
1379
+ table: (config) => createCrud(guardedOps, config)
1380
+ };
1381
+ return {
1382
+ ...guardedOps,
1383
+ crud: crudManager,
1384
+ async commit() {
1385
+ const check = ensureActive();
1386
+ if (!check.success)
1387
+ return check;
1388
+ try {
1389
+ await callbacks.commit();
1390
+ active = false;
1391
+ return ok(void 0);
1392
+ } catch (error) {
1393
+ active = false;
1394
+ return err(HaiReldbError.TRANSACTION_FAILED, callbacks.errorMessage(String(error)), error);
1395
+ } finally {
1396
+ callbacks.release();
1397
+ }
1398
+ },
1399
+ async rollback() {
1400
+ const check = ensureActive();
1401
+ if (!check.success)
1402
+ return check;
1403
+ try {
1404
+ await callbacks.rollback();
1405
+ active = false;
1406
+ return ok(void 0);
1407
+ } catch (error) {
1408
+ active = false;
1409
+ return err(HaiReldbError.TRANSACTION_FAILED, callbacks.errorMessage(String(error)), error);
1410
+ } finally {
1411
+ callbacks.release();
1412
+ }
1413
+ }
1414
+ };
1415
+ }
1416
+ function createTxWrap(beginTx) {
1417
+ return async (fn) => {
1418
+ const txResult = await beginTx();
1419
+ if (!txResult.success)
1420
+ return txResult;
1421
+ try {
1422
+ const result = await fn(txResult.data);
1423
+ const commitResult = await txResult.data.commit();
1424
+ if (!commitResult.success) {
1425
+ return commitResult;
1426
+ }
1427
+ return ok(result);
1428
+ } catch (error) {
1429
+ await txResult.data.rollback();
1430
+ return err(HaiReldbError.TRANSACTION_FAILED, reldbM("reldb_txFailed", { params: { error: String(error) } }), error);
1431
+ }
1432
+ };
1433
+ }
1434
+
1435
+ // src/providers/reldb-provider-base.ts
1436
+ async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
1437
+ if (!ctx.isConnected()) {
1438
+ return err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
1439
+ }
1440
+ try {
1441
+ return await fn();
1442
+ } catch (error) {
1443
+ ctx.logger.error(errorLabel, { ...errorMeta, error });
1444
+ return err(errorDef, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
1445
+ }
1446
+ }
1447
+ function createBaseDdlOps(ctx, raw) {
1448
+ return {
1449
+ createTable(tableName, columns, ifNotExists = true) {
1450
+ const v1 = validateIdentifier(tableName);
1451
+ if (!v1.success)
1452
+ return Promise.resolve(v1);
1453
+ const v2 = validateIdentifiers(Object.keys(columns));
1454
+ if (!v2.success)
1455
+ return Promise.resolve(v2);
1456
+ return wrapOp(ctx, () => raw.createTable(tableName, columns, ifNotExists), HaiReldbError.DDL_FAILED, "DDL: createTable failed", { tableName });
1457
+ },
1458
+ dropTable(tableName, ifExists = true) {
1459
+ const v = validateIdentifier(tableName);
1460
+ if (!v.success)
1461
+ return Promise.resolve(v);
1462
+ return wrapOp(ctx, () => raw.dropTable(tableName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropTable failed", { tableName });
1463
+ },
1464
+ addColumn(tableName, columnName, columnDef) {
1465
+ const v1 = validateIdentifier(tableName);
1466
+ if (!v1.success)
1467
+ return Promise.resolve(v1);
1468
+ const v2 = validateIdentifier(columnName);
1469
+ if (!v2.success)
1470
+ return Promise.resolve(v2);
1471
+ return wrapOp(ctx, () => raw.addColumn(tableName, columnName, columnDef), HaiReldbError.DDL_FAILED, "DDL: addColumn failed", { tableName, columnName });
1472
+ },
1473
+ dropColumn(tableName, columnName) {
1474
+ const v1 = validateIdentifier(tableName);
1475
+ if (!v1.success)
1476
+ return Promise.resolve(v1);
1477
+ const v2 = validateIdentifier(columnName);
1478
+ if (!v2.success)
1479
+ return Promise.resolve(v2);
1480
+ return wrapOp(ctx, () => raw.dropColumn(tableName, columnName), HaiReldbError.DDL_FAILED, "DDL: dropColumn failed", { tableName, columnName });
1481
+ },
1482
+ renameTable(oldName, newName) {
1483
+ const v1 = validateIdentifier(oldName);
1484
+ if (!v1.success)
1485
+ return Promise.resolve(v1);
1486
+ const v2 = validateIdentifier(newName);
1487
+ if (!v2.success)
1488
+ return Promise.resolve(v2);
1489
+ return wrapOp(ctx, () => raw.renameTable(oldName, newName), HaiReldbError.DDL_FAILED, "DDL: renameTable failed", { oldName, newName });
1490
+ },
1491
+ createIndex(tableName, indexName, indexDef) {
1492
+ const v1 = validateIdentifier(tableName);
1493
+ if (!v1.success)
1494
+ return Promise.resolve(v1);
1495
+ const v2 = validateIdentifier(indexName);
1496
+ if (!v2.success)
1497
+ return Promise.resolve(v2);
1498
+ const v3 = validateIdentifiers(indexDef.columns);
1499
+ if (!v3.success)
1500
+ return Promise.resolve(v3);
1501
+ return wrapOp(ctx, () => raw.createIndex(tableName, indexName, indexDef), HaiReldbError.DDL_FAILED, "DDL: createIndex failed", { tableName, indexName });
1502
+ },
1503
+ dropIndex(indexName, ifExists = true) {
1504
+ const v = validateIdentifier(indexName);
1505
+ if (!v.success)
1506
+ return Promise.resolve(v);
1507
+ return wrapOp(ctx, () => raw.dropIndex(indexName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropIndex failed", { indexName });
1508
+ },
1509
+ raw(sql) {
1510
+ return wrapOp(ctx, () => raw.raw(sql), HaiReldbError.DDL_FAILED, "DDL: raw failed");
1511
+ }
1512
+ };
1513
+ }
1514
+ function createBaseDmlOps(ctx, raw) {
1515
+ return {
1516
+ query: (sql, params) => wrapOp(ctx, () => raw.query(sql, params), HaiReldbError.QUERY_FAILED, "DML: query failed"),
1517
+ get: (sql, params) => wrapOp(ctx, () => raw.get(sql, params), HaiReldbError.QUERY_FAILED, "DML: get failed"),
1518
+ execute: (sql, params) => wrapOp(ctx, () => raw.execute(sql, params), HaiReldbError.QUERY_FAILED, "DML: execute failed"),
1519
+ batch: (stmts) => wrapOp(ctx, () => raw.batch(stmts), HaiReldbError.QUERY_FAILED, "DML: batch failed"),
1520
+ queryPage: (options) => wrapOp(ctx, () => raw.queryPage(options), HaiReldbError.QUERY_FAILED, "DML: queryPage failed")
1521
+ };
1522
+ }
1523
+ function createBaseTxManager(ctx, beginTx) {
1524
+ const begin = () => wrapOp(ctx, beginTx, HaiReldbError.TRANSACTION_FAILED, "TX: begin failed");
1525
+ return {
1526
+ begin,
1527
+ wrap: createTxWrap(begin)
1528
+ };
1529
+ }
1530
+ function createBaseCrudManager(ops) {
1531
+ return {
1532
+ table: (config) => createCrud(ops, config)
1533
+ };
1534
+ }
1535
+ async function queryPageAsync(queryRows, options) {
1536
+ const pagination2 = normalizePagination(options.pagination, options.overrides);
1537
+ const countSql = `SELECT COUNT(*) as cnt FROM (${options.sql}) AS t`;
1538
+ const countRows = await queryRows(countSql, options.params);
1539
+ const total = countRows.length > 0 ? parseCount(countRows[0]) : 0;
1540
+ const dataSql = `${options.sql} LIMIT ? OFFSET ?`;
1541
+ const dataParams = [...options.params ?? [], pagination2.limit, pagination2.offset];
1542
+ const rows = await queryRows(dataSql, dataParams);
1543
+ return buildPaginatedResult(rows, total, pagination2);
1544
+ }
1545
+
1546
+ // src/providers/reldb-ddl-builder.ts
1547
+ function buildColumnSqlBase(name, def, options) {
1548
+ const parts = [options.quoteId(name)];
1549
+ parts.push(options.mapType(def));
1550
+ if (options.inlinePrimaryKey && def.primaryKey) {
1551
+ parts.push("PRIMARY KEY");
1552
+ }
1553
+ if (options.extraConstraints) {
1554
+ parts.push(...options.extraConstraints(def));
1555
+ }
1556
+ if (def.notNull && !def.primaryKey) {
1557
+ parts.push("NOT NULL");
1558
+ }
1559
+ if (def.unique && !def.primaryKey) {
1560
+ parts.push("UNIQUE");
1561
+ }
1562
+ if (def.defaultValue !== void 0) {
1563
+ const custom = options.formatDefault?.(def);
1564
+ if (custom === null) ; else if (custom !== void 0) {
1565
+ parts.push(custom);
1566
+ } else if (def.defaultValue === null) {
1567
+ parts.push("DEFAULT NULL");
1568
+ } else if (typeof def.defaultValue === "string") {
1569
+ if (def.defaultValue.startsWith("(") && def.defaultValue.endsWith(")")) {
1570
+ parts.push(`DEFAULT ${def.defaultValue}`);
1571
+ } else {
1572
+ parts.push(`DEFAULT '${escapeSqlString(def.defaultValue)}'`);
1573
+ }
1574
+ } else {
1575
+ parts.push(`DEFAULT ${def.defaultValue}`);
1576
+ }
1577
+ }
1578
+ if (options.inlinePrimaryKey && def.references) {
1579
+ parts.push(`REFERENCES ${quoteIdentifier(def.references.table)}(${quoteIdentifier(def.references.column)})`);
1580
+ if (def.references.onDelete) {
1581
+ parts.push(`ON DELETE ${def.references.onDelete}`);
1582
+ }
1583
+ if (def.references.onUpdate) {
1584
+ parts.push(`ON UPDATE ${def.references.onUpdate}`);
1585
+ }
1586
+ }
1587
+ return parts.join(" ");
1588
+ }
1589
+ function buildDefaultCreateTableSql(buildColumnSql, quotedTable, columns, ifNotExists) {
1590
+ const columnDefs = Object.entries(columns).map(([name, def]) => buildColumnSql(name, def)).join(", ");
1591
+ const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
1592
+ return `CREATE TABLE ${ifNotExistsClause}${quotedTable} (${columnDefs})`;
1593
+ }
1594
+ function buildDefaultCreateIndexSql(quoteId, quotedTable, quotedIndex, indexDef) {
1595
+ const uniqueClause = indexDef.unique ? "UNIQUE " : "";
1596
+ const columns = indexDef.columns.map((c) => quoteId(c)).join(", ");
1597
+ const whereClause = indexDef.where ? ` WHERE ${indexDef.where}` : "";
1598
+ return `CREATE ${uniqueClause}INDEX IF NOT EXISTS ${quotedIndex} ON ${quotedTable} (${columns})${whereClause}`;
1599
+ }
1600
+ function buildDefaultRenameTableSql(quotedOld, quotedNew) {
1601
+ return `ALTER TABLE ${quotedOld} RENAME TO ${quotedNew}`;
1602
+ }
1603
+ function buildDefaultDropIndexSql(quoteId, indexName, ifExists) {
1604
+ const ifExistsClause = ifExists ? "IF EXISTS " : "";
1605
+ return `DROP INDEX ${ifExistsClause}${quoteId(indexName)}`;
1606
+ }
1607
+
1608
+ // src/providers/reldb-provider-mysql.ts
1609
+ var logger2 = core.logger.child({ module: "reldb", scope: "mysql" });
1610
+ function createMysqlProvider() {
1611
+ let pool = null;
1612
+ const ctx = {
1613
+ isConnected: () => pool !== null,
1614
+ logger: logger2
1615
+ };
1616
+ function mysqlQuoteId(name) {
1617
+ return `\`${name}\``;
1618
+ }
1619
+ function mysqlTxErrorMessage(detail) {
1620
+ return reldbM("reldb_mysqlTxFailed", { params: { error: detail } });
1621
+ }
1622
+ async function findIndexTableName(queryFn, indexName) {
1623
+ try {
1624
+ const [rows] = await queryFn(
1625
+ "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME = ? LIMIT 1",
1626
+ [indexName]
1627
+ );
1628
+ const data = rows;
1629
+ return ok(data[0]?.name ?? null);
1630
+ } catch (error) {
1631
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
1632
+ }
1633
+ }
1634
+ function mapMysqlType(def) {
1635
+ switch (def.type) {
1636
+ case "TEXT":
1637
+ return "VARCHAR(255)";
1638
+ case "INTEGER":
1639
+ return def.autoIncrement ? "BIGINT" : "INT";
1640
+ case "REAL":
1641
+ return "DOUBLE";
1642
+ case "BLOB":
1643
+ return "BLOB";
1644
+ case "BOOLEAN":
1645
+ return "TINYINT(1)";
1646
+ case "TIMESTAMP":
1647
+ return "DATETIME";
1648
+ case "JSON":
1649
+ return "JSON";
1650
+ default:
1651
+ return "TEXT";
1652
+ }
1653
+ }
1654
+ function mysqlBuildColumnSql(name, def) {
1655
+ return buildColumnSqlBase(name, def, {
1656
+ quoteId: mysqlQuoteId,
1657
+ mapType: mapMysqlType,
1658
+ inlinePrimaryKey: false,
1659
+ extraConstraints: (d) => {
1660
+ const extras = [];
1661
+ if (d.primaryKey)
1662
+ extras.push("NOT NULL");
1663
+ if (d.autoIncrement)
1664
+ extras.push("AUTO_INCREMENT");
1665
+ return extras;
1666
+ },
1667
+ formatDefault: (d) => {
1668
+ if (d.defaultValue === void 0)
1669
+ return void 0;
1670
+ if (d.autoIncrement)
1671
+ return null;
1672
+ if (typeof d.defaultValue === "string" && (d.defaultValue === "NOW()" || d.defaultValue === "CURRENT_TIMESTAMP")) {
1673
+ return `DEFAULT ${d.defaultValue}`;
1674
+ }
1675
+ if (typeof d.defaultValue === "boolean") {
1676
+ return `DEFAULT ${d.defaultValue ? 1 : 0}`;
1677
+ }
1678
+ return void 0;
1679
+ }
1680
+ });
1681
+ }
1682
+ function mysqlBuildCreateTableSql(quotedTable, columns, ifNotExists) {
1683
+ const columnDefs = [];
1684
+ let primaryKeyCol = null;
1685
+ for (const [name, def] of Object.entries(columns)) {
1686
+ columnDefs.push(mysqlBuildColumnSql(name, def));
1687
+ if (def.primaryKey) {
1688
+ primaryKeyCol = name;
1689
+ }
1690
+ }
1691
+ if (primaryKeyCol) {
1692
+ columnDefs.push(`PRIMARY KEY (${mysqlQuoteId(primaryKeyCol)})`);
1693
+ }
1694
+ for (const [name, def] of Object.entries(columns)) {
1695
+ if (def.references) {
1696
+ let fkSql = `FOREIGN KEY (${mysqlQuoteId(name)}) REFERENCES ${mysqlQuoteId(def.references.table)}(${mysqlQuoteId(def.references.column)})`;
1697
+ if (def.references.onDelete) {
1698
+ fkSql += ` ON DELETE ${def.references.onDelete}`;
1699
+ }
1700
+ if (def.references.onUpdate) {
1701
+ fkSql += ` ON UPDATE ${def.references.onUpdate}`;
1702
+ }
1703
+ columnDefs.push(fkSql);
1704
+ }
1705
+ }
1706
+ const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
1707
+ return `CREATE TABLE ${ifNotExistsClause}${quotedTable} (${columnDefs.join(", ")}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
1708
+ }
1709
+ const rawDdl = {
1710
+ async createTable(name, columns, ifNotExists = true) {
1711
+ const quotedTable = mysqlQuoteId(name);
1712
+ const sql = mysqlBuildCreateTableSql(quotedTable, columns, ifNotExists);
1713
+ await pool.query(sql);
1714
+ return ok(void 0);
1715
+ },
1716
+ async dropTable(name, ifExists) {
1717
+ const ifExistsClause = ifExists ? "IF EXISTS " : "";
1718
+ await pool.query(`DROP TABLE ${ifExistsClause}${mysqlQuoteId(name)}`);
1719
+ return ok(void 0);
1720
+ },
1721
+ async addColumn(table, column, def) {
1722
+ const colSql = mysqlBuildColumnSql(column, def);
1723
+ await pool.query(`ALTER TABLE ${mysqlQuoteId(table)} ADD COLUMN ${colSql}`);
1724
+ return ok(void 0);
1725
+ },
1726
+ async dropColumn(table, column) {
1727
+ await pool.query(`ALTER TABLE ${mysqlQuoteId(table)} DROP COLUMN ${mysqlQuoteId(column)}`);
1728
+ return ok(void 0);
1729
+ },
1730
+ async renameTable(oldName, newName) {
1731
+ await pool.query(`RENAME TABLE ${mysqlQuoteId(oldName)} TO ${mysqlQuoteId(newName)}`);
1732
+ return ok(void 0);
1733
+ },
1734
+ async createIndex(table, index, def) {
1735
+ const uniqueClause = def.unique ? "UNIQUE " : "";
1736
+ const columns = def.columns.map((c) => mysqlQuoteId(c)).join(", ");
1737
+ await pool.query(`CREATE ${uniqueClause}INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(table)} (${columns})`);
1738
+ return ok(void 0);
1739
+ },
1740
+ async dropIndex(index, ifExists) {
1741
+ const tableResult = await findIndexTableName((s, v) => pool.query(s, v), index);
1742
+ if (!tableResult.success)
1743
+ return tableResult;
1744
+ if (!tableResult.data) {
1745
+ if (ifExists)
1746
+ return ok(void 0);
1747
+ return err(HaiReldbError.DDL_FAILED, reldbM("reldb_ddlFailed", { params: { error: `index not found: ${index}` } }));
1748
+ }
1749
+ await pool.query(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
1750
+ return ok(void 0);
1751
+ },
1752
+ async raw(sql) {
1753
+ await pool.query(sql);
1754
+ return ok(void 0);
1755
+ }
1756
+ };
1757
+ const rawDml = {
1758
+ async query(sql, params) {
1759
+ const [rows] = await pool.query(sql, params);
1760
+ return ok(rows);
1761
+ },
1762
+ async get(sql, params) {
1763
+ const [rows] = await pool.query(sql, params);
1764
+ return ok(rows[0] ?? null);
1765
+ },
1766
+ async execute(sql, params) {
1767
+ const [result] = await pool.execute(sql, params);
1768
+ return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
1769
+ },
1770
+ async batch(statements) {
1771
+ let connection = null;
1772
+ try {
1773
+ connection = await pool.getConnection();
1774
+ await connection.beginTransaction();
1775
+ for (const { sql: s, params } of statements) {
1776
+ await connection.execute(s, params);
1777
+ }
1778
+ await connection.commit();
1779
+ return ok(void 0);
1780
+ } catch (error) {
1781
+ if (connection) {
1782
+ await connection.rollback().catch(() => {
1783
+ });
1784
+ }
1785
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
1786
+ } finally {
1787
+ if (connection) {
1788
+ connection.release();
1789
+ }
1790
+ }
1791
+ },
1792
+ async queryPage(options) {
1793
+ const result = await queryPageAsync(
1794
+ async (sql, params) => {
1795
+ const [rows] = await pool.query(sql, params);
1796
+ return rows;
1797
+ },
1798
+ options
1799
+ );
1800
+ return ok(result);
1801
+ }
1802
+ };
1803
+ function createMysqlTxDmlOps(conn) {
1804
+ return {
1805
+ async query(sql, params) {
1806
+ const [rows] = await conn.query(sql, params);
1807
+ return ok(rows);
1808
+ },
1809
+ async get(sql, params) {
1810
+ const [rows] = await conn.query(sql, params);
1811
+ return ok(rows[0] ?? null);
1812
+ },
1813
+ async execute(sql, params) {
1814
+ const [result] = await conn.execute(sql, params);
1815
+ return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
1816
+ },
1817
+ async batch(statements) {
1818
+ for (const { sql: s, params } of statements) {
1819
+ await conn.execute(s, params);
1820
+ }
1821
+ return ok(void 0);
1822
+ },
1823
+ async queryPage(options) {
1824
+ const result = await queryPageAsync(
1825
+ async (sql, params) => {
1826
+ const [rows] = await conn.query(sql, params);
1827
+ return rows;
1828
+ },
1829
+ options
1830
+ );
1831
+ return ok(result);
1832
+ }
1833
+ };
1834
+ }
1835
+ async function beginTx() {
1836
+ let connection = null;
1837
+ try {
1838
+ connection = await pool.getConnection();
1839
+ await connection.beginTransaction();
1840
+ } catch (error) {
1841
+ if (connection) {
1842
+ connection.release();
1843
+ }
1844
+ return err(HaiReldbError.TRANSACTION_FAILED, mysqlTxErrorMessage(String(error)), error);
1845
+ }
1846
+ const txDmlOps = createMysqlTxDmlOps(connection);
1847
+ return ok(createTxHandle(txDmlOps, {
1848
+ commit: async () => {
1849
+ await connection.commit();
1850
+ },
1851
+ rollback: async () => {
1852
+ await connection.rollback();
1853
+ },
1854
+ release: () => connection.release(),
1855
+ errorMessage: mysqlTxErrorMessage
1856
+ }));
1857
+ }
1858
+ const dmlOps = createBaseDmlOps(ctx, rawDml);
1859
+ return {
1860
+ async connect(config) {
1861
+ if (config.type !== "mysql") {
1862
+ return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_mysqlOnlyMysql"));
1863
+ }
1864
+ try {
1865
+ const mysql = __require("mysql2/promise");
1866
+ pool = mysql.createPool({
1867
+ uri: config.url,
1868
+ host: config.host,
1869
+ port: config.port,
1870
+ database: config.database,
1871
+ user: config.user,
1872
+ password: config.password,
1873
+ ssl: config.ssl,
1874
+ connectionLimit: config.pool?.max ?? 10,
1875
+ waitForConnections: true,
1876
+ queueLimit: 0,
1877
+ charset: config.mysql?.charset ?? "utf8mb4"
1878
+ });
1879
+ await pool.query("SELECT 1");
1880
+ logger2.info("Connected to MySQL", { host: config.host, port: config.port, database: config.database });
1881
+ return ok(void 0);
1882
+ } catch (error) {
1883
+ pool = null;
1884
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
1885
+ }
1886
+ },
1887
+ async close() {
1888
+ if (pool) {
1889
+ try {
1890
+ await pool.end();
1891
+ } catch (error) {
1892
+ pool = null;
1893
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
1894
+ }
1895
+ pool = null;
1896
+ logger2.info("Disconnected from MySQL");
1897
+ }
1898
+ return ok(void 0);
1899
+ },
1900
+ isConnected: () => ctx.isConnected(),
1901
+ ddl: createBaseDdlOps(ctx, rawDdl),
1902
+ sql: dmlOps,
1903
+ crud: createBaseCrudManager(dmlOps),
1904
+ tx: createBaseTxManager(ctx, beginTx)
1905
+ };
1906
+ }
1907
+ var logger3 = core.logger.child({ module: "reldb", scope: "postgres" });
1908
+ function createPostgresProvider() {
1909
+ let pool = null;
1910
+ const ctx = {
1911
+ isConnected: () => pool !== null,
1912
+ logger: logger3
1913
+ };
1914
+ function convertPlaceholders(sql) {
1915
+ let index = 0;
1916
+ return sql.replace(/\?/g, () => `$${++index}`);
1917
+ }
1918
+ function pgTxErrorMessage(detail) {
1919
+ return reldbM("reldb_postgresTxFailed", { params: { error: detail } });
1920
+ }
1921
+ function mapPgType(def) {
1922
+ switch (def.type) {
1923
+ case "TEXT":
1924
+ return "TEXT";
1925
+ case "INTEGER":
1926
+ return def.autoIncrement ? "BIGSERIAL" : "INTEGER";
1927
+ case "REAL":
1928
+ return "DOUBLE PRECISION";
1929
+ case "BLOB":
1930
+ return "BYTEA";
1931
+ case "BOOLEAN":
1932
+ return "BOOLEAN";
1933
+ case "TIMESTAMP":
1934
+ return "TIMESTAMP";
1935
+ case "JSON":
1936
+ return "JSONB";
1937
+ default:
1938
+ return "TEXT";
1939
+ }
1940
+ }
1941
+ function pgBuildColumnSql(name, def) {
1942
+ return buildColumnSqlBase(name, def, {
1943
+ quoteId: quoteIdentifier,
1944
+ mapType: mapPgType,
1945
+ inlinePrimaryKey: true,
1946
+ formatDefault: (d) => {
1947
+ if (d.defaultValue === void 0)
1948
+ return void 0;
1949
+ if (typeof d.defaultValue === "string" && (d.defaultValue === "NOW()" || d.defaultValue === "CURRENT_TIMESTAMP")) {
1950
+ return `DEFAULT ${d.defaultValue}`;
1951
+ }
1952
+ return void 0;
1953
+ }
1954
+ });
1955
+ }
1956
+ async function pgQueryRows(queryFn, sql, params) {
1957
+ const result = await queryFn(convertPlaceholders(sql), params);
1958
+ return result.rows;
1959
+ }
1960
+ const rawDdl = {
1961
+ async createTable(name, columns, ifNotExists = true) {
1962
+ const quotedTable = quoteIdentifier(name);
1963
+ const sql = buildDefaultCreateTableSql(pgBuildColumnSql, quotedTable, columns, ifNotExists);
1964
+ await pool.query(sql);
1965
+ return ok(void 0);
1966
+ },
1967
+ async dropTable(name, ifExists) {
1968
+ const ifExistsClause = ifExists ? "IF EXISTS " : "";
1969
+ await pool.query(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
1970
+ return ok(void 0);
1971
+ },
1972
+ async addColumn(table, column, def) {
1973
+ const colSql = pgBuildColumnSql(column, def);
1974
+ await pool.query(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
1975
+ return ok(void 0);
1976
+ },
1977
+ async dropColumn(table, column) {
1978
+ await pool.query(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
1979
+ return ok(void 0);
1980
+ },
1981
+ async renameTable(oldName, newName) {
1982
+ await pool.query(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
1983
+ return ok(void 0);
1984
+ },
1985
+ async createIndex(table, index, def) {
1986
+ const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
1987
+ await pool.query(sql);
1988
+ return ok(void 0);
1989
+ },
1990
+ async dropIndex(index, ifExists = true) {
1991
+ const sql = buildDefaultDropIndexSql(quoteIdentifier, index, ifExists);
1992
+ await pool.query(sql);
1993
+ return ok(void 0);
1994
+ },
1995
+ async raw(sql) {
1996
+ await pool.query(sql);
1997
+ return ok(void 0);
1998
+ }
1999
+ };
2000
+ const rawDml = {
2001
+ async query(sql, params) {
2002
+ const rows = await pgQueryRows((t, v) => pool.query(t, v), sql, params);
2003
+ return ok(rows);
2004
+ },
2005
+ async get(sql, params) {
2006
+ const rows = await pgQueryRows((t, v) => pool.query(t, v), sql, params);
2007
+ return ok(rows[0] ?? null);
2008
+ },
2009
+ async execute(sql, params) {
2010
+ const result = await pool.query(convertPlaceholders(sql), params);
2011
+ return ok({ changes: result.rowCount ?? 0 });
2012
+ },
2013
+ async batch(statements) {
2014
+ let client = null;
2015
+ try {
2016
+ client = await pool.connect();
2017
+ await client.query("BEGIN");
2018
+ for (const { sql: s, params } of statements) {
2019
+ await client.query(convertPlaceholders(s), params);
2020
+ }
2021
+ await client.query("COMMIT");
2022
+ return ok(void 0);
2023
+ } catch (error) {
2024
+ if (client) {
2025
+ await client.query("ROLLBACK").catch(() => {
2026
+ });
2027
+ }
2028
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
2029
+ } finally {
2030
+ if (client) {
2031
+ client.release();
2032
+ }
2033
+ }
2034
+ },
2035
+ async queryPage(options) {
2036
+ const result = await queryPageAsync(
2037
+ async (sql, params) => {
2038
+ const r = await pool.query(convertPlaceholders(sql), params);
2039
+ return r.rows;
2040
+ },
2041
+ options
2042
+ );
2043
+ return ok(result);
2044
+ }
2045
+ };
2046
+ function createPgTxDmlOps(client) {
2047
+ const queryFn = (text, values) => client.query(text, values);
2048
+ return {
2049
+ async query(sql, params) {
2050
+ const rows = await pgQueryRows(queryFn, sql, params);
2051
+ return ok(rows);
2052
+ },
2053
+ async get(sql, params) {
2054
+ const rows = await pgQueryRows(queryFn, sql, params);
2055
+ return ok(rows[0] ?? null);
2056
+ },
2057
+ async execute(sql, params) {
2058
+ const result = await queryFn(convertPlaceholders(sql), params);
2059
+ return ok({ changes: result.rowCount ?? 0 });
2060
+ },
2061
+ async batch(statements) {
2062
+ for (const { sql: s, params } of statements) {
2063
+ await queryFn(convertPlaceholders(s), params);
2064
+ }
2065
+ return ok(void 0);
2066
+ },
2067
+ async queryPage(options) {
2068
+ const result = await queryPageAsync(
2069
+ async (sql, params) => {
2070
+ const r = await queryFn(convertPlaceholders(sql), params);
2071
+ return r.rows;
2072
+ },
2073
+ options
2074
+ );
2075
+ return ok(result);
2076
+ }
2077
+ };
2078
+ }
2079
+ async function beginTx() {
2080
+ let client = null;
2081
+ try {
2082
+ client = await pool.connect();
2083
+ await client.query("BEGIN");
2084
+ } catch (error) {
2085
+ if (client) {
2086
+ client.release();
2087
+ }
2088
+ return err(HaiReldbError.TRANSACTION_FAILED, pgTxErrorMessage(String(error)), error);
2089
+ }
2090
+ const txDmlOps = createPgTxDmlOps(client);
2091
+ return ok(createTxHandle(txDmlOps, {
2092
+ commit: async () => {
2093
+ await client.query("COMMIT");
2094
+ },
2095
+ rollback: async () => {
2096
+ await client.query("ROLLBACK");
2097
+ },
2098
+ release: () => client.release(),
2099
+ errorMessage: pgTxErrorMessage
2100
+ }));
2101
+ }
2102
+ const dmlOps = createBaseDmlOps(ctx, rawDml);
2103
+ return {
2104
+ async connect(config) {
2105
+ if (config.type !== "postgresql") {
2106
+ return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_postgresOnlyPostgresql"));
2107
+ }
2108
+ try {
2109
+ const { Pool } = __require("pg");
2110
+ pool = new Pool({
2111
+ connectionString: config.url,
2112
+ host: config.host,
2113
+ port: config.port,
2114
+ database: config.database,
2115
+ user: config.user,
2116
+ password: config.password,
2117
+ ssl: config.ssl,
2118
+ min: config.pool?.min,
2119
+ max: config.pool?.max ?? 10,
2120
+ idleTimeoutMillis: config.pool?.idleTimeout,
2121
+ connectionTimeoutMillis: config.pool?.acquireTimeout
2122
+ });
2123
+ await pool.query("SELECT 1");
2124
+ logger3.info("Connected to PostgreSQL", { host: config.host, port: config.port, database: config.database });
2125
+ return ok(void 0);
2126
+ } catch (error) {
2127
+ pool = null;
2128
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2129
+ }
2130
+ },
2131
+ async close() {
2132
+ if (pool) {
2133
+ try {
2134
+ await pool.end();
2135
+ } catch (error) {
2136
+ pool = null;
2137
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2138
+ }
2139
+ pool = null;
2140
+ logger3.info("Disconnected from PostgreSQL");
2141
+ }
2142
+ return ok(void 0);
2143
+ },
2144
+ isConnected: () => ctx.isConnected(),
2145
+ ddl: createBaseDdlOps(ctx, rawDdl),
2146
+ sql: dmlOps,
2147
+ crud: createBaseCrudManager(dmlOps),
2148
+ tx: createBaseTxManager(ctx, beginTx)
2149
+ };
2150
+ }
2151
+ var require2 = createRequire(import.meta.url);
2152
+ var logger4 = core.logger.child({ module: "reldb", scope: "sqlite" });
2153
+ function createSqliteProvider() {
2154
+ let database = null;
2155
+ let txChain = Promise.resolve();
2156
+ const TX_LOCK_TIMEOUT_MS = 3e4;
2157
+ async function acquireTxLock() {
2158
+ let release;
2159
+ const current = new Promise((resolve) => {
2160
+ release = resolve;
2161
+ });
2162
+ const prev = txChain;
2163
+ txChain = txChain.then(() => current);
2164
+ let timer;
2165
+ const timeoutPromise = new Promise((_, reject) => {
2166
+ timer = setTimeout(() => {
2167
+ release();
2168
+ reject(new Error(`SQLite transaction lock acquisition timed out after ${TX_LOCK_TIMEOUT_MS}ms`));
2169
+ }, TX_LOCK_TIMEOUT_MS);
2170
+ });
2171
+ try {
2172
+ await Promise.race([prev, timeoutPromise]);
2173
+ } finally {
2174
+ if (timer !== void 0) {
2175
+ clearTimeout(timer);
2176
+ }
2177
+ }
2178
+ return release;
2179
+ }
2180
+ const ctx = {
2181
+ isConnected: () => database !== null && database.open,
2182
+ logger: logger4
2183
+ };
2184
+ function mapSqliteType(def) {
2185
+ switch (def.type) {
2186
+ case "TEXT":
2187
+ case "JSON":
2188
+ return "TEXT";
2189
+ case "INTEGER":
2190
+ case "BOOLEAN":
2191
+ return "INTEGER";
2192
+ case "REAL":
2193
+ return "REAL";
2194
+ case "BLOB":
2195
+ return "BLOB";
2196
+ case "TIMESTAMP":
2197
+ return "INTEGER";
2198
+ // Unix timestamp
2199
+ default:
2200
+ return "TEXT";
2201
+ }
2202
+ }
2203
+ function sqliteBuildColumnSql(name, def) {
2204
+ return buildColumnSqlBase(name, def, {
2205
+ quoteId: quoteIdentifier,
2206
+ mapType: mapSqliteType,
2207
+ inlinePrimaryKey: true,
2208
+ extraConstraints: (d) => {
2209
+ const extras = [];
2210
+ if (d.primaryKey && d.autoIncrement)
2211
+ extras.push("AUTOINCREMENT");
2212
+ return extras;
2213
+ }
2214
+ });
2215
+ }
2216
+ function sqliteTxErrorMessage(detail) {
2217
+ return reldbM("reldb_sqliteTxFailed", { params: { error: detail } });
2218
+ }
2219
+ const rawDdl = {
2220
+ async createTable(name, columns, ifNotExists = true) {
2221
+ const quotedTable = quoteIdentifier(name);
2222
+ const sql = buildDefaultCreateTableSql(sqliteBuildColumnSql, quotedTable, columns, ifNotExists);
2223
+ database.exec(sql);
2224
+ return ok(void 0);
2225
+ },
2226
+ async dropTable(name, ifExists) {
2227
+ const ifExistsClause = ifExists ? "IF EXISTS " : "";
2228
+ database.exec(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
2229
+ return ok(void 0);
2230
+ },
2231
+ async addColumn(table, column, def) {
2232
+ const colSql = sqliteBuildColumnSql(column, def);
2233
+ database.exec(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
2234
+ return ok(void 0);
2235
+ },
2236
+ async dropColumn(table, column) {
2237
+ database.exec(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
2238
+ return ok(void 0);
2239
+ },
2240
+ async renameTable(oldName, newName) {
2241
+ database.exec(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
2242
+ return ok(void 0);
2243
+ },
2244
+ async createIndex(table, index, def) {
2245
+ const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
2246
+ database.exec(sql);
2247
+ return ok(void 0);
2248
+ },
2249
+ async dropIndex(index, ifExists = true) {
2250
+ const sql = buildDefaultDropIndexSql(quoteIdentifier, index, ifExists);
2251
+ database.exec(sql);
2252
+ return ok(void 0);
2253
+ },
2254
+ async raw(sql) {
2255
+ database.exec(sql);
2256
+ return ok(void 0);
2257
+ }
2258
+ };
2259
+ const rawDml = {
2260
+ async query(sql, params) {
2261
+ const stmt = database.prepare(sql);
2262
+ const rows = params ? stmt.all(...params) : stmt.all();
2263
+ return ok(rows);
2264
+ },
2265
+ async get(sql, params) {
2266
+ const stmt = database.prepare(sql);
2267
+ const row = params ? stmt.get(...params) : stmt.get();
2268
+ return ok(row ?? null);
2269
+ },
2270
+ async execute(sql, params) {
2271
+ const stmt = database.prepare(sql);
2272
+ const result = params ? stmt.run(...params) : stmt.run();
2273
+ return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
2274
+ },
2275
+ async batch(statements) {
2276
+ const releaseTxLock = await acquireTxLock();
2277
+ try {
2278
+ const db = database;
2279
+ const transaction = db.transaction(() => {
2280
+ for (const { sql: s, params } of statements) {
2281
+ const stmt = db.prepare(s);
2282
+ if (params)
2283
+ stmt.run(...params);
2284
+ else stmt.run();
2285
+ }
2286
+ });
2287
+ transaction();
2288
+ return ok(void 0);
2289
+ } catch (error) {
2290
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_sqliteBatchFailed", { params: { error: String(error) } }), error);
2291
+ } finally {
2292
+ releaseTxLock();
2293
+ }
2294
+ },
2295
+ async queryPage(options) {
2296
+ const result = await queryPageAsync(
2297
+ async (sqlStr, params) => {
2298
+ const stmt = database.prepare(sqlStr);
2299
+ return params ? stmt.all(...params) : stmt.all();
2300
+ },
2301
+ options
2302
+ );
2303
+ return ok(result);
2304
+ }
2305
+ };
2306
+ function createSqliteTxDmlOps(db) {
2307
+ return {
2308
+ async query(sql, params) {
2309
+ const stmt = db.prepare(sql);
2310
+ return ok(params ? stmt.all(...params) : stmt.all());
2311
+ },
2312
+ async get(sql, params) {
2313
+ const stmt = db.prepare(sql);
2314
+ return ok((params ? stmt.get(...params) : stmt.get()) ?? null);
2315
+ },
2316
+ async execute(sql, params) {
2317
+ const stmt = db.prepare(sql);
2318
+ const result = params ? stmt.run(...params) : stmt.run();
2319
+ return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
2320
+ },
2321
+ async batch(statements) {
2322
+ for (const { sql: s, params } of statements) {
2323
+ const stmt = db.prepare(s);
2324
+ if (params)
2325
+ stmt.run(...params);
2326
+ else stmt.run();
2327
+ }
2328
+ return ok(void 0);
2329
+ },
2330
+ async queryPage(options) {
2331
+ const result = await queryPageAsync(
2332
+ async (sqlStr, params) => {
2333
+ const stmt = db.prepare(sqlStr);
2334
+ return params ? stmt.all(...params) : stmt.all();
2335
+ },
2336
+ options
2337
+ );
2338
+ return ok(result);
2339
+ }
2340
+ };
2341
+ }
2342
+ async function beginTx() {
2343
+ const db = database;
2344
+ let released = false;
2345
+ const releaseTxLock = await acquireTxLock();
2346
+ const finishTransaction = () => {
2347
+ if (!released) {
2348
+ released = true;
2349
+ releaseTxLock();
2350
+ }
2351
+ };
2352
+ try {
2353
+ db.exec("BEGIN TRANSACTION");
2354
+ } catch (error) {
2355
+ finishTransaction();
2356
+ return err(HaiReldbError.TRANSACTION_FAILED, sqliteTxErrorMessage(String(error)), error);
2357
+ }
2358
+ const txDmlOps = createSqliteTxDmlOps(db);
2359
+ return ok(createTxHandle(txDmlOps, {
2360
+ commit: async () => {
2361
+ db.exec("COMMIT");
2362
+ },
2363
+ rollback: async () => {
2364
+ db.exec("ROLLBACK");
2365
+ },
2366
+ release: () => finishTransaction(),
2367
+ errorMessage: sqliteTxErrorMessage
2368
+ }));
2369
+ }
2370
+ const dmlOps = createBaseDmlOps(ctx, rawDml);
2371
+ return {
2372
+ async connect(config) {
2373
+ if (config.type !== "sqlite") {
2374
+ return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_sqliteOnlySqlite"));
2375
+ }
2376
+ if (!config.database) {
2377
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sqliteNeedPath"));
2378
+ }
2379
+ try {
2380
+ const Database = require2("better-sqlite3");
2381
+ const sqliteOptions = {
2382
+ walMode: true,
2383
+ readonly: false,
2384
+ ...config.sqlite ?? {}
2385
+ };
2386
+ database = new Database(config.database, {
2387
+ readonly: sqliteOptions.readonly ?? false
2388
+ });
2389
+ if (sqliteOptions.walMode !== false) {
2390
+ database.pragma("journal_mode = WAL");
2391
+ }
2392
+ logger4.info("Connected to SQLite", { database: config.database });
2393
+ return ok(void 0);
2394
+ } catch (error) {
2395
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2396
+ }
2397
+ },
2398
+ async close() {
2399
+ if (database) {
2400
+ try {
2401
+ database.close();
2402
+ } catch (error) {
2403
+ database = null;
2404
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
2405
+ }
2406
+ database = null;
2407
+ logger4.info("Disconnected from SQLite");
2408
+ }
2409
+ return ok(void 0);
2410
+ },
2411
+ isConnected: () => ctx.isConnected(),
2412
+ ddl: createBaseDdlOps(ctx, rawDdl),
2413
+ sql: dmlOps,
2414
+ crud: createBaseCrudManager(dmlOps),
2415
+ tx: createBaseTxManager(ctx, beginTx)
2416
+ };
2417
+ }
2418
+
2419
+ // src/reldb-main.ts
2420
+ var logger5 = core.logger.child({ module: "reldb", scope: "main" });
2421
+ var currentProvider = null;
2422
+ var currentConfig = null;
2423
+ var currentJsonOps = null;
2424
+ var initInProgress = false;
2425
+ function createProvider(config) {
2426
+ switch (config.type) {
2427
+ case "sqlite":
2428
+ return createSqliteProvider();
2429
+ case "postgresql":
2430
+ return createPostgresProvider();
2431
+ case "mysql":
2432
+ return createMysqlProvider();
2433
+ }
2434
+ }
2435
+ var notInitialized = core.module.createNotInitializedKit(
2436
+ HaiReldbError.NOT_INITIALIZED,
2437
+ () => reldbM("reldb_notInitialized")
2438
+ );
2439
+ var notInitializedDdl = notInitialized.proxy();
2440
+ var notInitializedSql = notInitialized.proxy();
2441
+ var notInitializedCrud = createBaseCrudManager(notInitializedSql);
2442
+ var notInitializedJson = createJsonOps("sqlite");
2443
+ var notInitializedTx = {
2444
+ begin: async () => notInitialized.result(),
2445
+ wrap: async () => notInitialized.result()
2446
+ };
2447
+ var reldb = {
2448
+ /**
2449
+ * 初始化数据库连接
2450
+ *
2451
+ * @param config - 数据库配置(允许部分字段,内部会补齐默认值)
2452
+ * @returns 初始化结果,失败时包含错误信息
2453
+ */
2454
+ async init(config) {
2455
+ if (initInProgress) {
2456
+ logger5.warn("RelDB module init is already in progress, skipping concurrent call");
2457
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: "init already in progress" } }));
2458
+ }
2459
+ initInProgress = true;
2460
+ try {
2461
+ if (currentProvider) {
2462
+ logger5.warn("RelDB module is already initialized, reinitializing");
2463
+ await reldb.close();
2464
+ }
2465
+ logger5.info("Initializing RelDB module");
2466
+ const parseResult = ReldbConfigSchema.safeParse(config);
2467
+ if (!parseResult.success) {
2468
+ logger5.error("RelDB config validation failed", { error: parseResult.error.message });
2469
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
2470
+ }
2471
+ const parsed = parseResult.data;
2472
+ try {
2473
+ const provider = createProvider(parsed);
2474
+ const connectResult = await provider.connect(parsed);
2475
+ if (!connectResult.success) {
2476
+ logger5.error("RelDB module initialization failed", {
2477
+ code: connectResult.error.code,
2478
+ message: connectResult.error.message
2479
+ });
2480
+ return connectResult;
2481
+ }
2482
+ currentProvider = provider;
2483
+ currentConfig = parsed;
2484
+ currentJsonOps = createJsonOps(parsed.type);
2485
+ logger5.info("RelDB module initialized", { type: parsed.type });
2486
+ return ok(void 0);
2487
+ } catch (error) {
2488
+ logger5.error("RelDB module initialization failed", { error });
2489
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
2490
+ }
2491
+ } finally {
2492
+ initInProgress = false;
2493
+ }
2494
+ },
2495
+ /**
2496
+ * 获取 DDL 操作接口
2497
+ *
2498
+ * 未初始化时返回占位对象(所有调用返回 NOT_INITIALIZED)。
2499
+ */
2500
+ get ddl() {
2501
+ return currentProvider?.ddl ?? notInitializedDdl;
2502
+ },
2503
+ /**
2504
+ * 获取 SQL 操作接口
2505
+ *
2506
+ * 未初始化时返回占位对象(所有调用返回 NOT_INITIALIZED)。
2507
+ */
2508
+ get sql() {
2509
+ return currentProvider?.sql ?? notInitializedSql;
2510
+ },
2511
+ /**
2512
+ * 获取 CRUD 管理器
2513
+ *
2514
+ * 通过 `reldb.crud.table(config)` 获取单表 CRUD 仓库。
2515
+ * 未初始化时返回占位对象(所有调用返回 NOT_INITIALIZED)。
2516
+ */
2517
+ get crud() {
2518
+ return currentProvider?.crud ?? notInitializedCrud;
2519
+ },
2520
+ /** 事务管理器 */
2521
+ get tx() {
2522
+ return currentProvider?.tx ?? notInitializedTx;
2523
+ },
2524
+ /** 获取当前配置(未初始化时为 null) */
2525
+ get config() {
2526
+ return currentConfig;
2527
+ },
2528
+ /** 检查是否已初始化 */
2529
+ get isInitialized() {
2530
+ return currentProvider !== null && currentProvider.isConnected();
2531
+ },
2532
+ /**
2533
+ * 获取分页工具
2534
+ *
2535
+ * 提供 `reldb.pagination.normalize()` 和 `reldb.pagination.build()` 两个纯函数,
2536
+ * 用于自定义分页场景(无需数据库连接)。
2537
+ */
2538
+ get pagination() {
2539
+ return pagination;
2540
+ },
2541
+ /**
2542
+ * 获取 JSON 路径操作 SQL 构建器
2543
+ *
2544
+ * 根据当前数据库类型返回对应的 JSON 操作实现。
2545
+ * 未初始化时返回 SQLite 格式的构建器作为默认值。
2546
+ *
2547
+ * 构建器方法均为纯函数,返回 `{ sql, params }` 可直接嵌入 SQL 查询。
2548
+ *
2549
+ * @example
2550
+ * ```ts
2551
+ * // 提取 JSON 字段值
2552
+ * const { sql, params } = reldb.json.extract('settings', '$.theme')
2553
+ * const rows = await reldb.sql.query(
2554
+ * `SELECT * FROM users WHERE ${sql} = ?`,
2555
+ * [...params, '"dark"'],
2556
+ * )
2557
+ *
2558
+ * // 设置 JSON 字段路径
2559
+ * const { sql, params } = reldb.json.set('settings', '$.theme', 'dark')
2560
+ * await reldb.sql.execute(
2561
+ * `UPDATE users SET settings = ${sql} WHERE id = ?`,
2562
+ * [...params, userId],
2563
+ * )
2564
+ * ```
2565
+ */
2566
+ get json() {
2567
+ return currentJsonOps ?? notInitializedJson;
2568
+ },
2569
+ /**
2570
+ * 关闭数据库连接
2571
+ *
2572
+ * 多次调用安全,未初始化时直接返回。
2573
+ */
2574
+ async close() {
2575
+ if (!currentProvider) {
2576
+ currentConfig = null;
2577
+ currentJsonOps = null;
2578
+ logger5.info("RelDB module already closed, skipping");
2579
+ return ok(void 0);
2580
+ }
2581
+ logger5.info("Closing RelDB module");
2582
+ try {
2583
+ const closeResult = await currentProvider.close();
2584
+ if (!closeResult.success) {
2585
+ logger5.error("RelDB module close failed", { code: closeResult.error.code, message: closeResult.error.message });
2586
+ return closeResult;
2587
+ }
2588
+ logger5.info("RelDB module closed");
2589
+ return ok(void 0);
2590
+ } catch (error) {
2591
+ logger5.error("RelDB module close failed", { error });
2592
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_closeFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
2593
+ } finally {
2594
+ currentProvider = null;
2595
+ currentConfig = null;
2596
+ currentJsonOps = null;
2597
+ }
2598
+ }
2599
+ };
2600
+
2601
+ export { BaseReldbCrudRepository, HaiReldbError, MysqlConfigSchema, MysqlOptionsSchema, PoolConfigSchema, PostgresqlConfigSchema, ReldbConfigSchema, ReldbTypeSchema, SqliteConfigSchema, SqliteOptionsSchema, SslConfigSchema, createJsonOps, escapeSqlString, parseJsonPath, quoteIdentifier, reldb, validateIdentifier, validateIdentifiers };
2602
+ //# sourceMappingURL=index.js.map
2603
+ //# sourceMappingURL=index.js.map