@h-ai/reldb 1.0.0-beta.2 → 1.0.0-beta.20

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,5 +1,5 @@
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';
1
+ import { ReldbConfigSchema, MysqlPoolConfigSchema } from './chunk-7OE27ZH3.js';
2
+ export { MysqlConfigSchema, MysqlOptionsSchema, MysqlPoolConfigSchema, MysqlSslConfigSchema, PoolConfigSchema, PostgresqlConfigSchema, ReldbConfigSchema, ReldbOperationLogConfigSchema, ReldbTypeSchema, SqliteConfigSchema, SqliteOptionsSchema, SslConfigSchema } from './chunk-7OE27ZH3.js';
3
3
  import { core, ok, err, HaiCommonError } from '@h-ai/core';
4
4
  import process from 'process';
5
5
  import { createRequire } from 'module';
@@ -253,7 +253,10 @@ var BaseReldbCrudRepository = class {
253
253
  * @returns 表结构定义
254
254
  */
255
255
  buildReldbTableDef(fields) {
256
- const entries = fields.map((field) => [field.columnName, this.normalizeReldbColumnDef(field.def)]);
256
+ const entries = fields.map((field) => [
257
+ field.columnName,
258
+ this.normalizeReldbColumnDef(field.def)
259
+ ]);
257
260
  return Object.fromEntries(entries);
258
261
  }
259
262
  /**
@@ -599,7 +602,10 @@ var BaseReldbCrudRepository = class {
599
602
  } else {
600
603
  sql = `INSERT INTO ${this.table} (${insertColumns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${this.idColumn}) DO UPDATE SET ${updateSet}`;
601
604
  }
602
- return this.sql(tx).execute(sql, [...insertValues, ...updateValues]);
605
+ return this.sql(tx).execute(sql, [
606
+ ...insertValues,
607
+ ...updateValues
608
+ ]);
603
609
  }
604
610
  /**
605
611
  * 根据主键查找单条记录
@@ -879,13 +885,19 @@ function createSqliteJsonOps() {
879
885
  set(column, path, value) {
880
886
  return {
881
887
  sql: `json_set(${column}, ?, json(?))`,
882
- params: [path, JSON.stringify(value)]
888
+ params: [
889
+ path,
890
+ JSON.stringify(value)
891
+ ]
883
892
  };
884
893
  },
885
894
  insert(column, path, value) {
886
895
  return {
887
896
  sql: `json_insert(${column}, ?, json(?))`,
888
- params: [path, JSON.stringify(value)]
897
+ params: [
898
+ path,
899
+ JSON.stringify(value)
900
+ ]
889
901
  };
890
902
  },
891
903
  remove(column, path) {
@@ -897,7 +909,9 @@ function createSqliteJsonOps() {
897
909
  merge(column, patch) {
898
910
  return {
899
911
  sql: `json_patch(${column}, ?)`,
900
- params: [JSON.stringify(patch)]
912
+ params: [
913
+ JSON.stringify(patch)
914
+ ]
901
915
  };
902
916
  }
903
917
  };
@@ -907,14 +921,25 @@ function buildPostgresMergePatch(column, patch) {
907
921
  if (!normalized || typeof normalized !== "object" || Array.isArray(normalized))
908
922
  throw new TypeError(reldbM("reldb_jsonPatchObjectRequired"));
909
923
  const steps = [];
910
- const pending = Object.entries(normalized).reverse().map(([key, value]) => ({ path: [key], value }));
924
+ const pending = Object.entries(normalized).reverse().map(([key, value]) => ({
925
+ path: [key],
926
+ value
927
+ }));
911
928
  while (pending.length > 0) {
912
929
  const entry = pending.pop();
913
930
  const object = entry.value !== null && typeof entry.value === "object" && !Array.isArray(entry.value);
914
- steps.push({ path: entry.path, value: object ? {} : entry.value, object });
931
+ steps.push({
932
+ path: entry.path,
933
+ value: object ? {} : entry.value,
934
+ object
935
+ });
915
936
  if (object) {
916
- for (const [key, value] of Object.entries(entry.value).reverse())
917
- pending.push({ path: [...entry.path, key], value });
937
+ for (const [key, value] of Object.entries(entry.value).reverse()) {
938
+ pending.push({
939
+ path: [...entry.path, key],
940
+ value
941
+ });
942
+ }
918
943
  }
919
944
  }
920
945
  return {
@@ -939,7 +964,9 @@ function buildPostgresMergePatch(column, patch) {
939
964
  FROM merged JOIN patch_steps ON patch_steps.step = merged.step + 1
940
965
  )
941
966
  SELECT document FROM merged ORDER BY step DESC LIMIT 1)`,
942
- params: [JSON.stringify(steps)]
967
+ params: [
968
+ JSON.stringify(steps)
969
+ ]
943
970
  };
944
971
  }
945
972
  function createPostgresJsonOps() {
@@ -955,14 +982,20 @@ function createPostgresJsonOps() {
955
982
  const segments = parseJsonPath(path);
956
983
  return {
957
984
  sql: `jsonb_set(${column}, ?::text[], ?::jsonb)`,
958
- params: [segments, JSON.stringify(value)]
985
+ params: [
986
+ segments,
987
+ JSON.stringify(value)
988
+ ]
959
989
  };
960
990
  },
961
991
  insert(column, path, value) {
962
992
  const segments = parseJsonPath(path);
963
993
  return {
964
994
  sql: `jsonb_insert(${column}, ?::text[], ?::jsonb)`,
965
- params: [segments, JSON.stringify(value)]
995
+ params: [
996
+ segments,
997
+ JSON.stringify(value)
998
+ ]
966
999
  };
967
1000
  },
968
1001
  remove(column, path) {
@@ -988,13 +1021,19 @@ function createMysqlJsonOps() {
988
1021
  set(column, path, value) {
989
1022
  return {
990
1023
  sql: `JSON_SET(${column}, ?, CAST(? AS JSON))`,
991
- params: [path, JSON.stringify(value)]
1024
+ params: [
1025
+ path,
1026
+ JSON.stringify(value)
1027
+ ]
992
1028
  };
993
1029
  },
994
1030
  insert(column, path, value) {
995
1031
  return {
996
1032
  sql: `JSON_INSERT(${column}, ?, CAST(? AS JSON))`,
997
- params: [path, JSON.stringify(value)]
1033
+ params: [
1034
+ path,
1035
+ JSON.stringify(value)
1036
+ ]
998
1037
  };
999
1038
  },
1000
1039
  remove(column, path) {
@@ -1006,7 +1045,9 @@ function createMysqlJsonOps() {
1006
1045
  merge(column, patch) {
1007
1046
  return {
1008
1047
  sql: `JSON_MERGE_PATCH(${column}, CAST(? AS JSON))`,
1009
- params: [JSON.stringify(patch)]
1048
+ params: [
1049
+ JSON.stringify(patch)
1050
+ ]
1010
1051
  };
1011
1052
  }
1012
1053
  };
@@ -1088,7 +1129,9 @@ function validateIdentifier(name) {
1088
1129
  if (VALID_IDENTIFIER_RE.test(name)) {
1089
1130
  return ok(name);
1090
1131
  }
1091
- return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_invalidIdentifier", { params: { name } }));
1132
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_invalidIdentifier", {
1133
+ params: { name }
1134
+ }));
1092
1135
  }
1093
1136
  function validateIdentifiers(names) {
1094
1137
  for (const name of names) {
@@ -1331,7 +1374,11 @@ function createCrud(ops, config) {
1331
1374
  return findResult;
1332
1375
  }
1333
1376
  if (findResult.data === null) {
1334
- return err(HaiReldbError.RECORD_NOT_FOUND, reldbM("reldb_crudRecordNotFound", { params: { id: String(id) } }));
1377
+ return err(HaiReldbError.RECORD_NOT_FOUND, reldbM("reldb_crudRecordNotFound", {
1378
+ params: {
1379
+ id: String(id)
1380
+ }
1381
+ }));
1335
1382
  }
1336
1383
  return ok(findResult.data);
1337
1384
  },
@@ -1404,7 +1451,10 @@ function createCrud(ops, config) {
1404
1451
  }
1405
1452
  const setClause = filtered.map((column) => `${column} = ?`).join(", ");
1406
1453
  const sql = `UPDATE ${table} SET ${setClause} WHERE ${idColumn} = ?`;
1407
- const params = [...filtered.map((column) => data[column]), id];
1454
+ const params = [
1455
+ ...filtered.map((column) => data[column]),
1456
+ id
1457
+ ];
1408
1458
  return resolveOps(tx).execute(sql, params);
1409
1459
  },
1410
1460
  /**
@@ -1517,12 +1567,11 @@ function createTxHandle(baseDmlOps, callbacks) {
1517
1567
  const check = ensureActive();
1518
1568
  if (!check.success)
1519
1569
  return check;
1570
+ active = false;
1520
1571
  try {
1521
1572
  await callbacks.commit();
1522
- active = false;
1523
1573
  return ok(void 0);
1524
1574
  } catch (error) {
1525
- active = false;
1526
1575
  return err(HaiReldbError.TRANSACTION_FAILED, callbacks.errorMessage(String(error)), error);
1527
1576
  } finally {
1528
1577
  callbacks.release();
@@ -1532,12 +1581,11 @@ function createTxHandle(baseDmlOps, callbacks) {
1532
1581
  const check = ensureActive();
1533
1582
  if (!check.success)
1534
1583
  return check;
1584
+ active = false;
1535
1585
  try {
1536
1586
  await callbacks.rollback();
1537
- active = false;
1538
1587
  return ok(void 0);
1539
1588
  } catch (error) {
1540
- active = false;
1541
1589
  return err(HaiReldbError.TRANSACTION_FAILED, callbacks.errorMessage(String(error)), error);
1542
1590
  } finally {
1543
1591
  callbacks.release();
@@ -1559,7 +1607,11 @@ function createTxWrap(beginTx) {
1559
1607
  return ok(result);
1560
1608
  } catch (error) {
1561
1609
  await txResult.data.rollback();
1562
- return err(HaiReldbError.TRANSACTION_FAILED, reldbM("reldb_txFailed", { params: { error: String(error) } }), error);
1610
+ return err(HaiReldbError.TRANSACTION_FAILED, reldbM("reldb_txFailed", {
1611
+ params: {
1612
+ error: String(error)
1613
+ }
1614
+ }), error);
1563
1615
  }
1564
1616
  };
1565
1617
  }
@@ -1599,8 +1651,14 @@ async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
1599
1651
  let definition = original ?? errorDef;
1600
1652
  const traceId = core.context.get().traceId ?? core.context.get().requestId ?? original?.traceId ?? core.id.uuid();
1601
1653
  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))
1654
+ if (definition.code === HaiReldbError.QUERY_FAILED.code && ([
1655
+ "23505",
1656
+ "ER_DUP_ENTRY",
1657
+ "SQLITE_CONSTRAINT_UNIQUE",
1658
+ "SQLITE_CONSTRAINT_PRIMARYKEY"
1659
+ ].includes(String(fields.code)) || fields.errno === 1062)) {
1603
1660
  definition = HaiReldbError.DUPLICATE_ENTRY;
1661
+ }
1604
1662
  const secrets = /* @__PURE__ */ new Set();
1605
1663
  const seen = /* @__PURE__ */ new WeakSet();
1606
1664
  const collect = (value) => {
@@ -1614,17 +1672,44 @@ async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
1614
1672
  }
1615
1673
  };
1616
1674
  collect(errorMeta?.params);
1617
- const payloads = [errorMeta, ...Array.isArray(errorMeta?.statements) ? errorMeta.statements : []];
1675
+ const payloads = [
1676
+ errorMeta,
1677
+ ...Array.isArray(errorMeta?.statements) ? errorMeta.statements : []
1678
+ ];
1618
1679
  for (const payload of payloads) {
1619
1680
  collect(payload?.params);
1620
1681
  for (const match of (typeof payload?.sql === "string" ? payload.sql : "").matchAll(/'((?:''|[^'])*)'/g))
1621
1682
  secrets.add(match[1].replaceAll("''", "'"));
1622
1683
  }
1623
1684
  const diagnostic = {};
1624
- for (const key2 of ["name", "message", "stack", "code", "errno", "sqlState", "detail", "hint", "constraint", "schema", "routine", "severity", "table", "column"]) {
1685
+ for (const key2 of [
1686
+ "name",
1687
+ "message",
1688
+ "stack",
1689
+ "code",
1690
+ "errno",
1691
+ "sqlState",
1692
+ "detail",
1693
+ "hint",
1694
+ "constraint",
1695
+ "schema",
1696
+ "routine",
1697
+ "severity",
1698
+ "table",
1699
+ "column"
1700
+ ]) {
1625
1701
  const value = error instanceof Error && key2 === "message" ? error.message : error instanceof Error && key2 === "stack" ? error.stack : fields[key2];
1626
1702
  if (typeof value === "string") {
1627
- if (["code", "sqlState", "schema", "routine", "severity", "constraint", "table", "column"].includes(key2)) {
1703
+ if ([
1704
+ "code",
1705
+ "sqlState",
1706
+ "schema",
1707
+ "routine",
1708
+ "severity",
1709
+ "constraint",
1710
+ "table",
1711
+ "column"
1712
+ ].includes(key2)) {
1628
1713
  diagnostic[key2] = core.sanitize.sanitizeText(value);
1629
1714
  } else {
1630
1715
  const frames = key2 === "stack" ? value.search(/^\s+at\s/m) : -1;
@@ -1637,9 +1722,21 @@ async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
1637
1722
  }
1638
1723
  if (!Object.keys(diagnostic).length)
1639
1724
  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 });
1725
+ ctx.logger.error(errorLabel, {
1726
+ ...summarizeOperationPayload(errorMeta),
1727
+ table: errorMeta?.tableName,
1728
+ column: errorMeta?.columnName,
1729
+ errorCode: definition.code,
1730
+ traceId,
1731
+ error: diagnostic
1732
+ });
1641
1733
  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 });
1734
+ return err({
1735
+ ...definition,
1736
+ traceId,
1737
+ message: definition.code === HaiReldbError.DUPLICATE_ENTRY.code ? reldbM("reldb_duplicateEntry") : original ? core.sanitize.sanitizeText(original.message, [...secrets]) : reldbM(key),
1738
+ cause: diagnostic
1739
+ });
1643
1740
  };
1644
1741
  try {
1645
1742
  const result = await fn();
@@ -1783,14 +1880,20 @@ async function queryPageAsync(queryRows, options) {
1783
1880
  const countRows = await queryRows(countSql, options.params);
1784
1881
  const total = countRows.length > 0 ? parseCount(countRows[0]) : 0;
1785
1882
  const dataSql = `${options.sql} LIMIT ? OFFSET ?`;
1786
- const dataParams = [...options.params ?? [], pagination2.limit, pagination2.offset];
1883
+ const dataParams = [
1884
+ ...options.params ?? [],
1885
+ pagination2.limit,
1886
+ pagination2.offset
1887
+ ];
1787
1888
  const rows = await queryRows(dataSql, dataParams);
1788
1889
  return buildPaginatedResult(rows, total, pagination2);
1789
1890
  }
1790
1891
 
1791
1892
  // src/providers/reldb-ddl-builder.ts
1792
1893
  function buildColumnSqlBase(name, def, options) {
1793
- const parts = [options.quoteId(name)];
1894
+ const parts = [
1895
+ options.quoteId(name)
1896
+ ];
1794
1897
  parts.push(options.mapType(def));
1795
1898
  if (options.inlinePrimaryKey && def.primaryKey) {
1796
1899
  parts.push("PRIMARY KEY");
@@ -1934,10 +2037,18 @@ function createMysqlProvider() {
1934
2037
  }));
1935
2038
  }, (error) => {
1936
2039
  releaseSlot();
1937
- finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error));
2040
+ finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", {
2041
+ params: {
2042
+ error: String(error)
2043
+ }
2044
+ }), error));
1938
2045
  }).catch((error) => {
1939
2046
  ready = false;
1940
- finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: reldbM("reldb_mysqlPoolReleaseFailed") } }), error));
2047
+ finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", {
2048
+ params: {
2049
+ error: reldbM("reldb_mysqlPoolReleaseFailed")
2050
+ }
2051
+ }), error));
1941
2052
  logger2.warn("MySQL connection release failed; pool requires cleanup");
1942
2053
  });
1943
2054
  }
@@ -1986,7 +2097,11 @@ function createMysqlProvider() {
1986
2097
  poolConfig = null;
1987
2098
  return ok(void 0);
1988
2099
  } catch (error) {
1989
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
2100
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", {
2101
+ params: {
2102
+ error: String(error)
2103
+ }
2104
+ }), error);
1990
2105
  }
1991
2106
  })().finally(() => {
1992
2107
  closing = null;
@@ -2002,7 +2117,9 @@ function createMysqlProvider() {
2002
2117
  return `\`${name}\``;
2003
2118
  }
2004
2119
  function mysqlTxErrorMessage(detail) {
2005
- return reldbM("reldb_mysqlTxFailed", { params: { error: detail } });
2120
+ return reldbM("reldb_mysqlTxFailed", {
2121
+ params: { error: detail }
2122
+ });
2006
2123
  }
2007
2124
  async function findIndexTableName(indexName) {
2008
2125
  return withConnection(async (connection) => {
@@ -2129,7 +2246,9 @@ function createMysqlProvider() {
2129
2246
  if (!tableResult.data) {
2130
2247
  if (ifExists)
2131
2248
  return ok(void 0);
2132
- return err(HaiReldbError.DDL_FAILED, reldbM("reldb_ddlFailed", { params: { error: `index not found: ${index}` } }));
2249
+ return err(HaiReldbError.DDL_FAILED, reldbM("reldb_ddlFailed", {
2250
+ params: { error: `index not found: ${index}` }
2251
+ }));
2133
2252
  }
2134
2253
  return executeDdl(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
2135
2254
  },
@@ -2168,7 +2287,11 @@ function createMysqlProvider() {
2168
2287
  } catch (error) {
2169
2288
  await connection.rollback().catch(() => {
2170
2289
  });
2171
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
2290
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", {
2291
+ params: {
2292
+ error: String(error)
2293
+ }
2294
+ }), error);
2172
2295
  }
2173
2296
  });
2174
2297
  },
@@ -2233,7 +2356,10 @@ function createMysqlProvider() {
2233
2356
  return err(HaiReldbError.TRANSACTION_FAILED, mysqlTxErrorMessage(String(error)), error);
2234
2357
  }
2235
2358
  const txDmlOps = createMysqlTxDmlOps(connection);
2236
- const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => connection !== null }, txDmlOps);
2359
+ const guardedTxDmlOps = createBaseDmlOps({
2360
+ ...ctx,
2361
+ isConnected: () => connection !== null
2362
+ }, txDmlOps);
2237
2363
  return ok(createTxHandle(guardedTxDmlOps, {
2238
2364
  commit: async () => {
2239
2365
  await connection.commit();
@@ -2275,7 +2401,13 @@ function createMysqlProvider() {
2275
2401
  poolConfig = config.pool ?? MysqlPoolConfigSchema.parse({});
2276
2402
  pool = mysql.createPool({
2277
2403
  // 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 },
2404
+ ...config.url ? { uri: config.url } : {
2405
+ host: config.host,
2406
+ port: config.port,
2407
+ database: config.database,
2408
+ user: config.user,
2409
+ password: config.password
2410
+ },
2279
2411
  ssl: config.ssl,
2280
2412
  connectionLimit: poolConfig.max,
2281
2413
  // 排队由本 Provider 管理,超时项可直接移除;驱动绝不保留取消后的等待者。
@@ -2286,12 +2418,21 @@ function createMysqlProvider() {
2286
2418
  await pool.query("SELECT 1");
2287
2419
  ready = true;
2288
2420
  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 });
2421
+ logger2.info("Connected to MySQL", config.url ? {
2422
+ target: core.sanitize.sanitizeUrl(config.url)
2423
+ } : { host: config.host, port: config.port, database: config.database });
2290
2424
  return ok(void 0);
2291
2425
  } catch (error) {
2292
2426
  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 } });
2427
+ const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", {
2428
+ params: {
2429
+ error: String(error)
2430
+ }
2431
+ }), error);
2432
+ return cleanup.success || result.success ? result : err({
2433
+ ...result.error,
2434
+ ext: { cleanupPending: true, cleanupError: cleanup.error }
2435
+ });
2295
2436
  }
2296
2437
  },
2297
2438
  close: closeResource,
@@ -2339,7 +2480,11 @@ function createPostgresProvider() {
2339
2480
  pool = null;
2340
2481
  return ok(void 0);
2341
2482
  } catch (error) {
2342
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
2483
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", {
2484
+ params: {
2485
+ error: String(error)
2486
+ }
2487
+ }), error);
2343
2488
  }
2344
2489
  })().finally(() => {
2345
2490
  closing = null;
@@ -2442,7 +2587,9 @@ function createPostgresProvider() {
2442
2587
  return out;
2443
2588
  }
2444
2589
  function pgTxErrorMessage(detail) {
2445
- return reldbM("reldb_postgresTxFailed", { params: { error: detail } });
2590
+ return reldbM("reldb_postgresTxFailed", {
2591
+ params: { error: detail }
2592
+ });
2446
2593
  }
2447
2594
  function mapPgType(def) {
2448
2595
  switch (def.type) {
@@ -2565,7 +2712,11 @@ function createPostgresProvider() {
2565
2712
  await client.query("ROLLBACK").catch(() => {
2566
2713
  });
2567
2714
  }
2568
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
2715
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", {
2716
+ params: {
2717
+ error: String(error)
2718
+ }
2719
+ }), error);
2569
2720
  } finally {
2570
2721
  if (client) {
2571
2722
  client.release();
@@ -2631,7 +2782,10 @@ function createPostgresProvider() {
2631
2782
  return err(HaiReldbError.TRANSACTION_FAILED, pgTxErrorMessage(String(error)), error);
2632
2783
  }
2633
2784
  const txDmlOps = createPgTxDmlOps(client);
2634
- const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => client !== null }, txDmlOps);
2785
+ const guardedTxDmlOps = createBaseDmlOps({
2786
+ ...ctx,
2787
+ isConnected: () => client !== null
2788
+ }, txDmlOps);
2635
2789
  return ok(createTxHandle(guardedTxDmlOps, {
2636
2790
  commit: async () => {
2637
2791
  await client.query("COMMIT");
@@ -2697,8 +2851,15 @@ function createPostgresProvider() {
2697
2851
  return ok(void 0);
2698
2852
  } catch (error) {
2699
2853
  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 } });
2854
+ const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", {
2855
+ params: {
2856
+ error: String(error)
2857
+ }
2858
+ }), error);
2859
+ return cleanup.success || result.success ? result : err({
2860
+ ...result.error,
2861
+ ext: { cleanupPending: true, cleanupError: cleanup.error }
2862
+ });
2702
2863
  }
2703
2864
  },
2704
2865
  close: closeResource,
@@ -2931,7 +3092,11 @@ function createSqliteProvider() {
2931
3092
  engine = null;
2932
3093
  return ok(void 0);
2933
3094
  } catch (error) {
2934
- return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
3095
+ return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", {
3096
+ params: {
3097
+ error: String(error)
3098
+ }
3099
+ }), error);
2935
3100
  }
2936
3101
  })().finally(() => {
2937
3102
  closing = null;
@@ -3001,7 +3166,9 @@ function createSqliteProvider() {
3001
3166
  });
3002
3167
  }
3003
3168
  function sqliteTxErrorMessage(detail) {
3004
- return reldbM("reldb_sqliteTxFailed", { params: { error: detail } });
3169
+ return reldbM("reldb_sqliteTxFailed", {
3170
+ params: { error: detail }
3171
+ });
3005
3172
  }
3006
3173
  const rawDdl = {
3007
3174
  async createTable(name, columns, ifNotExists = true) {
@@ -3062,7 +3229,11 @@ function createSqliteProvider() {
3062
3229
  await engine.batch(statements);
3063
3230
  return ok(void 0);
3064
3231
  } catch (error) {
3065
- return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_sqliteBatchFailed", { params: { error: String(error) } }), error);
3232
+ return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_sqliteBatchFailed", {
3233
+ params: {
3234
+ error: String(error)
3235
+ }
3236
+ }), error);
3066
3237
  } finally {
3067
3238
  releaseTxLock();
3068
3239
  }
@@ -3118,7 +3289,10 @@ function createSqliteProvider() {
3118
3289
  return err(HaiReldbError.TRANSACTION_FAILED, sqliteTxErrorMessage(String(error)), error);
3119
3290
  }
3120
3291
  const txDmlOps = createSqliteTxDmlOps(txEngine);
3121
- const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => txEngine.isOpen() }, txDmlOps);
3292
+ const guardedTxDmlOps = createBaseDmlOps({
3293
+ ...ctx,
3294
+ isConnected: () => txEngine.isOpen()
3295
+ }, txDmlOps);
3122
3296
  return ok(createTxHandle(guardedTxDmlOps, {
3123
3297
  commit: async () => {
3124
3298
  await txEngine.exec("COMMIT");
@@ -3163,8 +3337,15 @@ function createSqliteProvider() {
3163
3337
  return ok(void 0);
3164
3338
  } catch (error) {
3165
3339
  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 } });
3340
+ const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", {
3341
+ params: {
3342
+ error: String(error)
3343
+ }
3344
+ }), error);
3345
+ return cleanup.success || result.success ? result : err({
3346
+ ...result.error,
3347
+ ext: { cleanupPending: true, cleanupError: cleanup.error }
3348
+ });
3168
3349
  }
3169
3350
  },
3170
3351
  close: closeResource,
@@ -3175,6 +3356,113 @@ function createSqliteProvider() {
3175
3356
  tx: createBaseTxManager(ctx, beginTx)
3176
3357
  };
3177
3358
  }
3359
+ async function schemaColumns(db, table, sql = db.sql) {
3360
+ if (!/^[a-z][a-z0-9_]{0,62}$/.test(table))
3361
+ return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
3362
+ const type = db.config?.type;
3363
+ const found = type === "sqlite" ? await sql.query(`PRAGMA table_info(${table})`) : await sql.query(`SELECT column_name AS name FROM information_schema.columns WHERE table_schema=${type === "mysql" ? "DATABASE()" : "current_schema()"} AND table_name=?`, [table]);
3364
+ return found.success ? ok(found.data.map((row) => row.name)) : found;
3365
+ }
3366
+ async function migrateSchema(db, module, steps, options = {}) {
3367
+ if (!db.isInitialized || !/^[a-z][a-z0-9-]{0,62}$/.test(module) || steps.some((step, index) => step.version !== index + 1))
3368
+ return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
3369
+ const table = "hai_schema_migrations";
3370
+ const targetVersion = steps.length;
3371
+ const failed = (cause) => err({
3372
+ ...HaiCommonError.INIT_FAILED,
3373
+ message: core.i18n.coreM("core_runtimeStartFailed", {
3374
+ params: { module }
3375
+ }),
3376
+ ext: { module, phase: "migration" },
3377
+ cause
3378
+ });
3379
+ async function inspect(sql) {
3380
+ const columns = await schemaColumns(db, table, sql);
3381
+ if (!columns.success)
3382
+ return failed(columns.error);
3383
+ const row = columns.data.length ? await sql.get(`SELECT version FROM ${table} WHERE module=?`, [module]) : ok(null);
3384
+ if (!row.success)
3385
+ return failed(row.error);
3386
+ const currentVersion = Number(row.data?.version ?? 0);
3387
+ if (currentVersion > targetVersion)
3388
+ return failed();
3389
+ const changes = [];
3390
+ for (const step of steps) {
3391
+ const needed = await step.needed(sql);
3392
+ if (!needed.success)
3393
+ return failed(needed.error);
3394
+ if (needed.data)
3395
+ changes.push(step.name);
3396
+ }
3397
+ return ok({
3398
+ module,
3399
+ currentVersion,
3400
+ targetVersion,
3401
+ changes,
3402
+ requiresExclusiveAccess: changes.length > 0
3403
+ });
3404
+ }
3405
+ if (options.dryRun)
3406
+ return inspect(db.sql);
3407
+ const created = await db.sql.execute(`CREATE TABLE IF NOT EXISTS ${table} (module VARCHAR(64) PRIMARY KEY, version INTEGER NOT NULL)`);
3408
+ if (!created.success)
3409
+ return failed(created.error);
3410
+ const seeded = await db.sql.execute(db.config?.type === "mysql" ? `INSERT IGNORE INTO ${table}(module,version) VALUES (?,0)` : `INSERT INTO ${table}(module,version) VALUES (?,0) ON CONFLICT(module) DO NOTHING`, [module]);
3411
+ if (!seeded.success)
3412
+ return failed(seeded.error);
3413
+ const started = await db.tx.begin();
3414
+ if (!started.success)
3415
+ return failed(started.error);
3416
+ const tx = started.data;
3417
+ let mysqlLock = false;
3418
+ let committed = false;
3419
+ try {
3420
+ if (db.config?.type === "mysql") {
3421
+ const lock = await tx.get("SELECT GET_LOCK(?,30) AS acquired", [`hai-schema:${module}`]);
3422
+ if (!lock.success || Number(lock.data?.acquired) !== 1) {
3423
+ return failed(lock.success ? new Error("Migration lock unavailable") : lock.error);
3424
+ }
3425
+ mysqlLock = true;
3426
+ }
3427
+ const locked = await tx.execute(`UPDATE ${table} SET version=version WHERE module=?`, [module]);
3428
+ if (!locked.success)
3429
+ return failed(locked.error);
3430
+ const plan = await inspect(tx);
3431
+ if (!plan.success)
3432
+ return plan;
3433
+ for (const step of steps) {
3434
+ const needed = await step.needed(tx);
3435
+ if (!needed.success)
3436
+ return failed(needed.error);
3437
+ if (needed.data) {
3438
+ const applied = await step.apply(tx);
3439
+ if (!applied.success)
3440
+ return failed(applied.error);
3441
+ const remaining = await step.needed(tx);
3442
+ if (!remaining.success || remaining.data) {
3443
+ return failed(remaining.success ? new Error("Migration step did not converge") : remaining.error);
3444
+ }
3445
+ }
3446
+ }
3447
+ const recorded = await tx.execute(`UPDATE ${table} SET version=? WHERE module=?`, [targetVersion, module]);
3448
+ if (!recorded.success)
3449
+ return failed(recorded.error);
3450
+ if (mysqlLock) {
3451
+ await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
3452
+ mysqlLock = false;
3453
+ }
3454
+ const result = await tx.commit();
3455
+ committed = result.success;
3456
+ return result.success ? plan : failed(result.error);
3457
+ } catch (error) {
3458
+ return failed(error);
3459
+ } finally {
3460
+ if (mysqlLock)
3461
+ await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
3462
+ if (!committed)
3463
+ await tx.rollback();
3464
+ }
3465
+ }
3178
3466
 
3179
3467
  // src/reldb-main.ts
3180
3468
  var logger5 = core.logger.child({ module: "reldb", scope: "main" });
@@ -3196,15 +3484,26 @@ async function closeOwnedProvider() {
3196
3484
  return ok(void 0);
3197
3485
  try {
3198
3486
  const result = await pendingProvider.close();
3199
- if (!result.success)
3200
- return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true } });
3487
+ if (!result.success) {
3488
+ return err({
3489
+ ...result.error,
3490
+ ext: { ...result.error.ext, cleanupPending: true }
3491
+ });
3492
+ }
3201
3493
  pendingProvider = null;
3202
3494
  return ok(void 0);
3203
3495
  } catch (error) {
3204
3496
  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 } });
3497
+ return err({
3498
+ ...HaiReldbError.CONNECTION_FAILED,
3499
+ message: reldbM("reldb_closeFailed", {
3500
+ params: {
3501
+ error: error instanceof Error ? error.message : String(error)
3502
+ }
3503
+ }),
3504
+ cause: error,
3505
+ ext: { cleanupPending: true }
3506
+ });
3208
3507
  }
3209
3508
  }
3210
3509
  async function failInitialization(result) {
@@ -3215,9 +3514,15 @@ async function failInitialization(result) {
3215
3514
  const ext = { ...result.error.ext };
3216
3515
  delete ext.cleanupPending;
3217
3516
  delete ext.cleanupError;
3218
- return err({ ...result.error, ext: Object.keys(ext).length > 0 ? ext : void 0 });
3517
+ return err({
3518
+ ...result.error,
3519
+ ext: Object.keys(ext).length > 0 ? ext : void 0
3520
+ });
3219
3521
  }
3220
- return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } });
3522
+ return err({
3523
+ ...result.error,
3524
+ ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error }
3525
+ });
3221
3526
  }
3222
3527
  function createProvider(config) {
3223
3528
  switch (config.type) {
@@ -3259,7 +3564,9 @@ var reldb = {
3259
3564
  async init(config) {
3260
3565
  if (initInProgress || closingPromise) {
3261
3566
  logger5.warn("RelDB module init is already in progress, skipping concurrent call");
3262
- return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: "init already in progress" } }));
3567
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", {
3568
+ params: { error: "init already in progress" }
3569
+ }));
3263
3570
  }
3264
3571
  initInProgress = true;
3265
3572
  let resolveInit;
@@ -3274,7 +3581,9 @@ var reldb = {
3274
3581
  const parseResult = ReldbConfigSchema.safeParse(config);
3275
3582
  if (!parseResult.success) {
3276
3583
  logger5.error("RelDB config validation failed", { error: parseResult.error.message });
3277
- return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
3584
+ return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", {
3585
+ params: { error: parseResult.error.message }
3586
+ }), parseResult.error);
3278
3587
  }
3279
3588
  const parsed = parseResult.data;
3280
3589
  if (core.module.requiresSharedState(process.env) && parsed.type === "sqlite")
@@ -3298,7 +3607,11 @@ var reldb = {
3298
3607
  return ok(void 0);
3299
3608
  } catch (error) {
3300
3609
  logger5.error("RelDB module initialization failed", { error });
3301
- return await failInitialization(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error));
3610
+ return await failInitialization(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", {
3611
+ params: {
3612
+ error: error instanceof Error ? error.message : String(error)
3613
+ }
3614
+ }), error));
3302
3615
  }
3303
3616
  } finally {
3304
3617
  initInProgress = false;
@@ -3335,6 +3648,18 @@ var reldb = {
3335
3648
  get tx() {
3336
3649
  return currentProvider?.tx ?? notInitializedTx;
3337
3650
  },
3651
+ /** 迁移操作绑定当前实例,调用方只提供模块自己的步骤。 */
3652
+ migrateSchema(module, steps, options) {
3653
+ if (!currentProvider)
3654
+ return Promise.resolve(notInitialized.result());
3655
+ return migrateSchema(reldb, module, steps, options);
3656
+ },
3657
+ /** 列检查复用当前连接或显式传入的事务。 */
3658
+ schemaColumns(table, sql) {
3659
+ if (!currentProvider)
3660
+ return Promise.resolve(notInitialized.result());
3661
+ return schemaColumns(reldb, table, sql);
3662
+ },
3338
3663
  /** 获取当前脱敏后的配置快照(未初始化时为 null) */
3339
3664
  get config() {
3340
3665
  if (!currentConfig)
@@ -3412,99 +3737,7 @@ var reldb = {
3412
3737
  return await closingPromise;
3413
3738
  }
3414
3739
  };
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
- }
3507
3740
 
3508
- export { BaseReldbCrudRepository, HaiReldbError, createJsonOps, escapeSqlString, migrateSchema, parseJsonPath, quoteIdentifier, reldb, schemaColumns, validateIdentifier, validateIdentifiers };
3741
+ export { BaseReldbCrudRepository, HaiReldbError, createJsonOps, escapeSqlString, parseJsonPath, quoteIdentifier, reldb, validateIdentifier, validateIdentifiers };
3509
3742
  //# sourceMappingURL=index.js.map
3510
3743
  //# sourceMappingURL=index.js.map