@sfmc-bds/db-server 0.2.0-beta.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/domain/bridge.d.ts +1 -5
  3. package/dist/domain/bridge.d.ts.map +1 -1
  4. package/dist/domain/bridge.js +15 -14
  5. package/dist/domain/bridge.js.map +2 -2
  6. package/dist/domain/installed-content.d.ts +84 -0
  7. package/dist/domain/installed-content.d.ts.map +1 -0
  8. package/dist/domain/installed-content.js +149 -0
  9. package/dist/domain/installed-content.js.map +7 -0
  10. package/dist/domain/player-session.d.ts +8 -0
  11. package/dist/domain/player-session.d.ts.map +1 -0
  12. package/dist/domain/player-session.js +16 -0
  13. package/dist/domain/player-session.js.map +7 -0
  14. package/dist/domain/qq-account-profile.d.ts +51 -0
  15. package/dist/domain/qq-account-profile.d.ts.map +1 -0
  16. package/dist/domain/qq-account-profile.js +129 -0
  17. package/dist/domain/qq-account-profile.js.map +7 -0
  18. package/dist/domain/qq-events.d.ts +2 -2
  19. package/dist/domain/qq-events.d.ts.map +1 -1
  20. package/dist/domain/qq-events.js +6 -3
  21. package/dist/domain/qq-events.js.map +2 -2
  22. package/dist/domain/schema.d.ts.map +1 -1
  23. package/dist/domain/schema.js +48 -0
  24. package/dist/domain/schema.js.map +2 -2
  25. package/dist/domain/system-status.d.ts +3 -0
  26. package/dist/domain/system-status.d.ts.map +1 -1
  27. package/dist/domain/system-status.js +27 -12
  28. package/dist/domain/system-status.js.map +2 -2
  29. package/dist/env.d.ts +0 -2
  30. package/dist/env.d.ts.map +1 -1
  31. package/dist/env.js +12 -24
  32. package/dist/env.js.map +2 -2
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +108 -52
  36. package/dist/index.js.map +3 -3
  37. package/dist/routes/config.d.ts +1 -1
  38. package/dist/routes/config.d.ts.map +1 -1
  39. package/dist/routes/config.js +0 -4
  40. package/dist/routes/config.js.map +2 -2
  41. package/dist/routes/content.d.ts +23 -0
  42. package/dist/routes/content.d.ts.map +1 -0
  43. package/dist/routes/content.js +19 -0
  44. package/dist/routes/content.js.map +7 -0
  45. package/dist/routes/messages.d.ts +14 -6
  46. package/dist/routes/messages.d.ts.map +1 -1
  47. package/dist/routes/messages.js +22 -8
  48. package/dist/routes/messages.js.map +2 -2
  49. package/dist/routes/qq-bind.d.ts.map +1 -1
  50. package/dist/routes/qq-bind.js +29 -1
  51. package/dist/routes/qq-bind.js.map +2 -2
  52. package/dist/routes/qq-events.d.ts +6 -2
  53. package/dist/routes/qq-events.d.ts.map +1 -1
  54. package/dist/routes/qq-events.js +35 -1
  55. package/dist/routes/qq-events.js.map +3 -3
  56. package/dist/routes/qq-join.js +1 -1
  57. package/dist/routes/qq-join.js.map +2 -2
  58. package/dist/routes/status.d.ts +3 -11
  59. package/dist/routes/status.d.ts.map +1 -1
  60. package/dist/routes/status.js +39 -49
  61. package/dist/routes/status.js.map +2 -2
  62. package/dist/tx-runner.d.ts +11 -2
  63. package/dist/tx-runner.d.ts.map +1 -1
  64. package/dist/tx-runner.js +42 -7
  65. package/dist/tx-runner.js.map +2 -2
  66. package/package.json +4 -5
  67. package/dist/domain/bridge.test.d.ts +0 -2
  68. package/dist/domain/bridge.test.d.ts.map +0 -1
  69. package/dist/domain/bridge.test.js +0 -87
  70. package/dist/domain/bridge.test.js.map +0 -7
  71. package/dist/domain/messages-bridge-filter.test.d.ts +0 -5
  72. package/dist/domain/messages-bridge-filter.test.d.ts.map +0 -1
  73. package/dist/domain/messages-bridge-filter.test.js +0 -20
  74. package/dist/domain/messages-bridge-filter.test.js.map +0 -7
  75. package/dist/domain/qq-bind-status.test.d.ts +0 -2
  76. package/dist/domain/qq-bind-status.test.d.ts.map +0 -1
  77. package/dist/domain/qq-bind-status.test.js +0 -211
  78. package/dist/domain/qq-bind-status.test.js.map +0 -7
  79. package/dist/domain/qq-events.test.d.ts +0 -5
  80. package/dist/domain/qq-events.test.d.ts.map +0 -1
  81. package/dist/domain/qq-events.test.js +0 -170
  82. package/dist/domain/qq-events.test.js.map +0 -7
  83. package/dist/domain/qq-join.test.d.ts +0 -2
  84. package/dist/domain/qq-join.test.d.ts.map +0 -1
  85. package/dist/domain/qq-join.test.js +0 -353
  86. package/dist/domain/qq-join.test.js.map +0 -7
  87. package/dist/domain/system-status.test.d.ts +0 -5
  88. package/dist/domain/system-status.test.d.ts.map +0 -1
  89. package/dist/domain/system-status.test.js +0 -19
  90. package/dist/domain/system-status.test.js.map +0 -7
  91. package/dist/runtime.test.d.ts +0 -2
  92. package/dist/runtime.test.d.ts.map +0 -1
  93. package/dist/runtime.test.js +0 -402
  94. package/dist/runtime.test.js.map +0 -7
package/dist/tx-runner.js CHANGED
@@ -9,20 +9,26 @@ function identOk(t) {
9
9
  if (!IDENT.test(t)) throw new Error(`[tx] invalid table name "${t}"`);
10
10
  }
11
11
  class TxRunner {
12
- constructor(deps) {
12
+ constructor(deps, options = {}) {
13
13
  this.deps = deps;
14
+ this.slotWaitTimeoutMs = options.slotWaitTimeoutMs ?? 2e3;
15
+ this.sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? 15e3;
14
16
  }
15
17
  deps;
16
18
  sessions = /* @__PURE__ */ new Map();
17
19
  transactionBusy = false;
18
20
  transactionWaiters = [];
21
+ slotWaitTimeoutMs;
22
+ sessionIdleTimeoutMs;
19
23
  async run(req) {
20
24
  const { moduleId, steps } = req;
21
25
  const manifest = this.deps.enabled.get(moduleId);
22
26
  if (!manifest) return { ok: false, step: -1, error: "\u6A21\u5757\u672A enabled", code: "forbidden" };
23
27
  const traceId = randomUUID().slice(0, 8);
24
28
  const results = [];
25
- await this.acquireTransactionSlot();
29
+ if (!await this.acquireTransactionSlot()) {
30
+ return { ok: false, step: -1, error: "\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", code: "transaction_busy" };
31
+ }
26
32
  try {
27
33
  this.deps.db.exec("BEGIN IMMEDIATE");
28
34
  } catch (e) {
@@ -64,7 +70,9 @@ class TxRunner {
64
70
  async beginSession(moduleId) {
65
71
  const manifest = this.deps.enabled.get(moduleId);
66
72
  if (!manifest) return { ok: false, step: -1, error: "\u6A21\u5757\u672A enabled", code: "forbidden" };
67
- await this.acquireTransactionSlot();
73
+ if (!await this.acquireTransactionSlot()) {
74
+ return { ok: false, step: -1, error: "\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", code: "transaction_busy" };
75
+ }
68
76
  try {
69
77
  this.deps.db.exec("BEGIN IMMEDIATE");
70
78
  } catch (e) {
@@ -74,7 +82,8 @@ class TxRunner {
74
82
  const txId = randomUUID();
75
83
  this.sessions.set(txId, {
76
84
  moduleId,
77
- results: []
85
+ results: [],
86
+ idleTimer: this.createSessionIdleTimer(txId, moduleId)
78
87
  });
79
88
  log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);
80
89
  return { ok: true, txId };
@@ -95,6 +104,7 @@ class TxRunner {
95
104
  if (session.moduleId !== moduleId) {
96
105
  return { ok: false, step: session.results.length, error: "moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26", code: "forbidden" };
97
106
  }
107
+ clearTimeout(session.idleTimer);
98
108
  const manifest = this.deps.enabled.get(moduleId);
99
109
  if (!manifest) {
100
110
  this.abortSession(txId);
@@ -104,6 +114,7 @@ class TxRunner {
104
114
  try {
105
115
  const r = await this.runOne(moduleId, manifest, step);
106
116
  session.results.push(r);
117
+ session.idleTimer = this.createSessionIdleTimer(txId, moduleId);
107
118
  return { ok: true, result: r };
108
119
  } catch (err) {
109
120
  this.abortSession(txId);
@@ -127,6 +138,7 @@ class TxRunner {
127
138
  if (session.moduleId !== moduleId) {
128
139
  return { ok: false, step: -1, error: "moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26", code: "forbidden" };
129
140
  }
141
+ clearTimeout(session.idleTimer);
130
142
  try {
131
143
  this.deps.db.exec("COMMIT");
132
144
  } catch (e) {
@@ -159,6 +171,9 @@ class TxRunner {
159
171
  return { ok: true };
160
172
  }
161
173
  abortSession(txId) {
174
+ const session = this.sessions.get(txId);
175
+ if (!session) return;
176
+ clearTimeout(session.idleTimer);
162
177
  this.sessions.delete(txId);
163
178
  this.rollback();
164
179
  this.releaseTransactionSlot();
@@ -167,18 +182,38 @@ class TxRunner {
167
182
  async acquireTransactionSlot() {
168
183
  if (!this.transactionBusy) {
169
184
  this.transactionBusy = true;
170
- return;
185
+ return true;
171
186
  }
172
- await new Promise((resolve) => this.transactionWaiters.push(resolve));
187
+ return new Promise((resolve) => {
188
+ const waiter = {
189
+ resolve,
190
+ timer: setTimeout(() => {
191
+ const index = this.transactionWaiters.indexOf(waiter);
192
+ if (index >= 0) this.transactionWaiters.splice(index, 1);
193
+ resolve(false);
194
+ }, this.slotWaitTimeoutMs)
195
+ };
196
+ this.transactionWaiters.push(waiter);
197
+ });
173
198
  }
174
199
  releaseTransactionSlot() {
175
200
  const next = this.transactionWaiters.shift();
176
201
  if (next) {
177
- next();
202
+ clearTimeout(next.timer);
203
+ next.resolve(true);
178
204
  return;
179
205
  }
180
206
  this.transactionBusy = false;
181
207
  }
208
+ createSessionIdleTimer(txId, moduleId) {
209
+ const timer = setTimeout(() => {
210
+ if (!this.sessions.has(txId)) return;
211
+ log.warn(`[tx-session ${txId.slice(0, 8)}] idle timeout module=${moduleId} after ${this.sessionIdleTimeoutMs}ms`);
212
+ this.abortSession(txId);
213
+ }, this.sessionIdleTimeoutMs);
214
+ timer.unref?.();
215
+ return timer;
216
+ }
182
217
  mapErrorCode(err) {
183
218
  return err instanceof PermissionDeniedError ? "permission_denied" : err instanceof DispatchError && (err.code === "no_such_service" || err.code === "not_in_requires" || err.code === "forbidden" || err.code === "domain_error") ? err.code : err.code === "no_such_service" ? "no_such_service" : err.code === "not_in_requires" ? "not_in_requires" : err.code === "no_such_table" ? "no_such_table" : "internal";
184
219
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/tx-runner.ts"],
4
- "sourcesContent": ["/**\n * tx-runner.ts \u2014 \u4E8B\u52A1 RPC \u5904\u7406\u5668\n *\n * \u534F\u8BAE(\u4E24\u79CD):\n * A. \u6279\u91CF: POST /api/sfmc/db/tx body={ steps } \u2192 { ok, results? } | { ok:false, step, error, code }\n * B. \u4EA4\u4E92: begin \u2192 step* \u2192 commit|rollback(SDK db.tx \u7528;\u56DE\u8C03\u5185\u53EF\u8BFB\u56DE query/get/call)\n *\n * \u6D41\u7A0B(\u6279\u91CF):\n * 1. \u6821\u9A8C moduleId in enabled\n * 2. BEGIN IMMEDIATE\n * 3. \u987A\u5E8F\u8DD1 steps \u2014 \u4EFB\u4E00\u629B\u9519 \u2192 ROLLBACK + reply { ok:false, step:<i>, ... }\n * 4. \u5168\u8FC7 \u2192 COMMIT + reply { ok:true, results }\n *\n * step.op:\n * - query { table, opts? }\n * - get { table, id }\n * - insert { table, row }\n * - update { table, id, patch }\n * - delete { table, id, hard }\n * - audit { table, rowId, action, data? } \u2192 \u5E73\u53F0 sfmc__audit \u8868\n * - service { name, input } \u2192 \u6D3E\u53D1;\u5931\u8D25\u629B\u9519\u56DE\u9000\u6574\u4E8B\u52A1\n *\n * \u4E8B\u52A1\u5185 service call:\u4E0D\u6301\u6709 db handle \u7ED9 handler(\u907F\u514D\u5E76\u53D1\u5199) \u2014\n * \u5B9E\u73B0:handler \u8FD4\u56DE result \u5199\u56DE step \u8F93\u51FA;\u82E5 handler \u8FD8\u8981\u5199 db,\u5E94\u8D70\u5176\u5B83\u63A5\u53E3\u5728\u4E8B\u52A1\u5916\u505A\u3002\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\nimport { normalizeOrderBy } from \"./lib/order-by.js\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport { Perm, PermissionDeniedError, assertModulePermission } from \"./permission-gate.js\";\nimport type { SchemaRegistry } from \"./schema-registry.js\";\nimport { DispatchError, type ServiceRegistry } from \"./service-registry.js\";\nimport type { WhereExpr } from \"./where.js\";\nimport { compile } from \"./where.js\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction identOk(t: string): void {\n if (!IDENT.test(t)) throw new Error(`[tx] invalid table name \"${t}\"`);\n}\n\nexport interface TxStepQuery {\n op: \"query\";\n table: string;\n opts?: {\n where?: WhereExpr;\n /** SDK \u7528 field;\u5386\u53F2 col \u4ECD\u63A5\u53D7(normalizeOrderBy) */\n orderBy?:\n | { field?: string; col?: string; dir?: \"asc\" | \"desc\" }\n | Array<{ field?: string; col?: string; dir?: \"asc\" | \"desc\" }>;\n limit?: number;\n offset?: number;\n };\n}\n\nexport interface TxStepGet {\n op: \"get\";\n table: string;\n id: string;\n}\n\nexport interface TxStepInsert {\n op: \"insert\";\n table: string;\n row: Record<string, unknown>;\n}\n\nexport interface TxStepUpdate {\n op: \"update\";\n table: string;\n id: string;\n patch: Record<string, unknown>;\n}\n\nexport interface TxStepDelete {\n op: \"delete\";\n table: string;\n id: string;\n hard: boolean;\n}\n\nexport interface TxStepAudit {\n op: \"audit\";\n table: string;\n rowId: string;\n action: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TxStepService {\n op: \"service\";\n name: string;\n input: Record<string, unknown>;\n}\n\nexport type TxStep = TxStepQuery | TxStepGet | TxStepInsert | TxStepUpdate | TxStepDelete | TxStepAudit | TxStepService;\n\nexport type TxStepResult =\n | { op: \"query\"; rows: Record<string, unknown>[] }\n | { op: \"get\"; row: Record<string, unknown> | null }\n | { op: \"insert\"; row: Record<string, unknown> }\n | { op: \"update\"; row: Record<string, unknown> }\n | { op: \"delete\"; changes: number }\n | { op: \"audit\"; changes: number }\n | { op: \"service\"; result: unknown };\n\nexport interface TxResponse {\n ok: true;\n results: TxStepResult[];\n}\n\nexport type TxError = {\n ok: false;\n step: number;\n error: string;\n code:\n | \"permission_denied\"\n | \"forbidden\"\n | \"no_such_service\"\n | \"not_in_requires\"\n | \"domain_error\"\n | \"internal\"\n | \"no_such_table\";\n};\n\nexport interface TxRequest {\n moduleId: string;\n steps: TxStep[];\n}\n\ninterface TxSession {\n moduleId: string;\n results: TxStepResult[];\n}\n\nexport interface TxRunnerDeps {\n db: DatabaseSync;\n query: import(\"./lib/sqlite.js\").QueryFn;\n schema: SchemaRegistry;\n serviceRegistry: ServiceRegistry;\n enabled: Map<string, ModuleManifestV2>;\n}\n\nexport class TxRunner {\n private readonly sessions = new Map<string, TxSession>();\n private transactionBusy = false;\n private readonly transactionWaiters: Array<() => void> = [];\n\n constructor(private readonly deps: TxRunnerDeps) {}\n\n async run(req: TxRequest): Promise<TxResponse | TxError> {\n const { moduleId, steps } = req;\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n // \u6CE8\u610F:\u4E0D\u5728\u6B64\u5904\u505A\u300C\u6574\u4F53 db:write:*\u300D\u65AD\u8A00\u3002\n // \u901A\u914D `db:write:*` \u65E0\u6CD5\u901A\u8FC7 permission-gate \u7684 validPermissionKey \u6821\u9A8C(\u6A21\u5757\u58F0\u660E\u5B83\u4F1A\u542F\u52A8\u5931\u8D25),\n // \u56E0\u6B64\u8BE5\u65AD\u8A00\u4F1A\u8BA9\u6240\u6709\u4E8B\u52A1(\u542B\u53EA\u8BFB\u4E8B\u52A1)\u6052\u88AB\u62D2\u3002\u771F\u6B63\u7684\u8BFB\u5199\u6743\u9650\u7531\u6BCF\u4E2A step \u7684\n // requireTableRead / requireTableWrite \u6309\u5177\u4F53\u8868\u7CBE\u786E gate\u3002\n\n const traceId = randomUUID().slice(0, 8);\n const results: TxStepResult[] = [];\n\n await this.acquireTransactionSlot();\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n for (let i = 0; i < steps.length; i++) {\n const step: TxStep = steps[i]!;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n results.push(r);\n } catch (err) {\n this.rollback();\n this.releaseTransactionSlot();\n const code = this.mapErrorCode(err);\n log.warn(`[tx ${traceId}] step=${i} failed: ${(err as Error).message}`);\n return { ok: false, step: i, error: (err as Error).message, code };\n }\n }\n\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.rollback();\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n this.releaseTransactionSlot();\n log.info(`[tx ${traceId}] module=${moduleId} ${steps.length} steps OK`);\n return { ok: true, results };\n }\n\n /**\n * \u5F00\u542F\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF08\u534F\u8BAE\u6D41\u7A0B\uFF1Abegin \u2192 step* \u2192 commit | rollback\uFF09\u3002\n * \u4F9B\u5BA2\u6237\u7AEF `db.tx` \u5728\u56DE\u8C03\u5185\u90E8\u5F02\u6B65\u6267\u884C\u5E76\u5B9E\u65F6\u8BFB\u56DE query / get / call \u7684\u670D\u52A1\u7AEF\u6267\u884C\u7ED3\u679C\u3002\n * \u5355\u8FDE\u63A5\u4E92\u65A5\u4FDD\u62A4\uFF1A\u4E0E\u6279\u91CF run() \u5171\u7528\u4E8B\u52A1\u961F\u5217\uFF0C\u5E76\u5728\u63D0\u4EA4/\u56DE\u6EDA\u65F6\u5524\u9192\u4E0B\u4E00\u4E2A\u7B49\u5F85\u8005\u3002\n *\n * @param moduleId \u8BF7\u6C42\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @returns \u6210\u529F\u8FD4\u56DE\u5206\u914D\u7684\u4F1A\u8BDD txId\uFF0C\u5931\u8D25\u8FD4\u56DE\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async beginSession(moduleId: string): Promise<{ ok: true; txId: string } | TxError> {\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n\n // run() \u4E0E\u4EA4\u4E92\u5F0F\u4F1A\u8BDD\u5171\u7528\u540C\u4E00 SQLite \u8FDE\u63A5\uFF0C\u5FC5\u987B\u5171\u7528\u540C\u4E00\u4E2A\u4E8B\u52A1\u69FD\u3002\n await this.acquireTransactionSlot();\n\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const txId = randomUUID();\n this.sessions.set(txId, {\n moduleId,\n results: [],\n });\n log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);\n return { ok: true, txId };\n }\n\n /**\n * \u5728\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\u4E2D\u987A\u5E8F\u6267\u884C\u5355\u4E2A\u6B65\u9AA4\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @param step \u5F85\u6267\u884C\u7684\u64CD\u4F5C\u6B65\u9AA4\u5BF9\u8C61\u3002\n * @returns \u6B65\u9AA4\u6267\u884C\u7ED3\u679C\u6216\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async stepSession(\n txId: string,\n moduleId: string,\n step: TxStep\n ): Promise<{ ok: true; result: TxStepResult } | TxError> {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: session.results.length, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) {\n this.abortSession(txId);\n return { ok: false, step: session.results.length, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n }\n const stepIndex = session.results.length;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n session.results.push(r);\n return { ok: true, result: r };\n } catch (err) {\n this.abortSession(txId);\n const code = this.mapErrorCode(err);\n log.warn(`[tx-session ${txId.slice(0, 8)}] step=${stepIndex} failed: ${(err as Error).message}`);\n return { ok: false, step: stepIndex, error: (err as Error).message, code };\n }\n }\n\n /**\n * \u63D0\u4EA4\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u5C06\u6240\u6709\u6B65\u9AA4\u7684\u4FEE\u6539\u6301\u4E45\u5316\u5E76\u91CA\u653E\u8FDE\u63A5\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u63D0\u4EA4\u7ED3\u679C\u54CD\u5E94\u3002\n */\n commitSession(txId: string, moduleId: string): TxResponse | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.abortSession(txId);\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const results = session.results;\n this.sessions.delete(txId);\n this.releaseTransactionSlot();\n log.info(`[tx-session ${txId.slice(0, 8)}] commit ${results.length} steps OK`);\n return { ok: true, results };\n }\n\n /**\n * \u56DE\u6EDA\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u64A4\u9500\u6240\u6709\u672A\u63D0\u4EA4\u7684\u6570\u636E\u5E93\u53D8\u66F4\u3002\n * \u5177\u6709\u5E42\u7B49\u6027\uFF1A\u4F1A\u8BDD\u5DF2\u7ED3\u675F\u6216\u5DF2\u56DE\u6EDA\u65F6\u540C\u6837\u8FD4\u56DE\u6210\u529F\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u56DE\u6EDA\u786E\u8BA4\u7ED3\u679C\u3002\n */\n rollbackSession(txId: string, moduleId: string): { ok: true } | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n // \u5E42\u7B49\uFF1A\u5DF2\u7ED3\u675F\u89C6\u4E3A\u6210\u529F\u56DE\u6EDA\n return { ok: true };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n this.abortSession(txId);\n return { ok: true };\n }\n\n private abortSession(txId: string): void {\n this.sessions.delete(txId);\n this.rollback();\n this.releaseTransactionSlot();\n log.info(`[tx-session ${txId.slice(0, 8)}] aborted`);\n }\n\n private async acquireTransactionSlot(): Promise<void> {\n if (!this.transactionBusy) {\n this.transactionBusy = true;\n return;\n }\n await new Promise<void>((resolve) => this.transactionWaiters.push(resolve));\n }\n\n private releaseTransactionSlot(): void {\n const next = this.transactionWaiters.shift();\n if (next) {\n next();\n return;\n }\n this.transactionBusy = false;\n }\n\n private mapErrorCode(err: unknown): TxError[\"code\"] {\n return err instanceof PermissionDeniedError\n ? \"permission_denied\"\n : err instanceof DispatchError &&\n (err.code === \"no_such_service\" ||\n err.code === \"not_in_requires\" ||\n err.code === \"forbidden\" ||\n err.code === \"domain_error\")\n ? err.code\n : (err as { code?: string }).code === \"no_such_service\"\n ? \"no_such_service\"\n : (err as { code?: string }).code === \"not_in_requires\"\n ? \"not_in_requires\"\n : (err as { code?: string }).code === \"no_such_table\"\n ? \"no_such_table\"\n : \"internal\";\n }\n\n private rollback(): void {\n try {\n this.deps.db.exec(\"ROLLBACK\");\n } catch {\n // best-effort; fall through\n }\n }\n\n /** \u5355 step \u7531\u5BF9\u5E94\u6743\u9650\u68C0\u67E5 + SQL \u8DEF\u5F84\u6267\u884C */\n private async runOne(moduleId: string, manifest: ModuleManifestV2, step: TxStep): Promise<TxStepResult> {\n switch (step.op) {\n case \"query\":\n return this.doQuery(moduleId, manifest, step);\n case \"get\":\n return this.doGet(moduleId, manifest, step);\n case \"insert\":\n return this.doInsert(moduleId, manifest, step);\n case \"update\":\n return this.doUpdate(moduleId, manifest, step);\n case \"delete\":\n return this.doDelete(moduleId, manifest, step);\n case \"audit\":\n return this.doAudit(moduleId, manifest, step);\n case \"service\":\n return this.doService(moduleId, manifest, step);\n }\n }\n\n private requireTableRead(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbRead(table));\n }\n private requireTableWrite(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbWrite(table));\n }\n private assertTableRegistered(table: string): void {\n if (table.startsWith(\"sfmc_\")) return; // \u5E73\u53F0\u8868 \u2014 \u59CB\u7EC8\u53EF\u8BFB\u5199(\u5E73\u53F0\u5C42)\n if (!this.deps.schema.isRegistered(table)) {\n const err = new Error(`\u8868 \"${table}\" \u672A\u6CE8\u518C\u5230 schema-registry`);\n (err as { code?: string }).code = \"no_such_table\";\n throw err;\n }\n }\n\n private doQuery(_mid: string, mod: ModuleManifestV2, step: TxStepQuery): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = compile(step.opts?.where);\n // LSP:\u4E0E SDK QueryOptions.orderBy({field}|[]) \u5BF9\u9F50,\u517C\u6536\u9057\u7559 {col}\n const orders = normalizeOrderBy(step.opts?.orderBy);\n const limit = typeof step.opts?.limit === \"number\" ? step.opts?.limit : undefined;\n const offset = typeof step.opts?.offset === \"number\" ? step.opts?.offset : undefined;\n\n const whereClause = where.sql;\n const params = [...where.values];\n let extra = \"\";\n if (orders.length > 0) {\n const parts: string[] = [];\n for (const o of orders) {\n if (!IDENT.test(o.col)) throw new Error(`[tx] orderBy bad column \"${o.col}\"`);\n parts.push(`\"${o.col}\" ${o.dir === \"desc\" ? \"DESC\" : \"ASC\"}`);\n }\n extra += ` ORDER BY ${parts.join(\", \")}`;\n }\n if (typeof limit === \"number\") {\n extra += \" LIMIT \" + Math.max(0, Math.floor(limit));\n }\n if (typeof offset === \"number\") {\n extra += \" OFFSET \" + Math.max(0, Math.floor(offset));\n }\n\n const sql = `SELECT * FROM \"${step.table}\" WHERE ${whereClause}${extra}`;\n const rows = this.deps.db.prepare(sql).all(...(params as never[])) as Record<string, unknown>[];\n return { op: \"query\", rows };\n }\n\n /** \u89E3\u6790\u4E3B\u952E WHERE\u3002\u5355\u5217\u76F4\u63A5\u7B49\u503C;\u8054\u5408\u4E3B\u952E\u652F\u6301 `a|b|c`(\u4E0E data-backup \u7EA6\u5B9A\u4E00\u81F4)\u3002 */\n private pkWhere(table: string, id: string | number): { clause: string; values: unknown[] } {\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${table}\")`).all() as Array<{\n name: string;\n pk: number;\n }>;\n const pkCols = cols\n .filter((c) => c.pk > 0)\n .sort((a, b) => a.pk - b.pk)\n .map((c) => c.name);\n if (pkCols.length === 0) {\n throw new Error(`[tx] ${table} \u6CA1\u6709 primary key`);\n }\n if (pkCols.length === 1) {\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n const parts = String(id).split(\"|\");\n if (parts.length === pkCols.length) {\n return {\n clause: pkCols.map((c) => `\"${c}\" = ?`).join(\" AND \"),\n values: parts,\n };\n }\n // \u8054\u5408\u4E3B\u952E\u4F46\u53EA\u4F20\u4E86\u9996\u5217(\u9891\u9053 id \u7B49):\u6309\u7B2C\u4E00\u4E3B\u952E\u5217\u5339\u914D\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n\n private doGet(_mid: string, mod: ModuleManifestV2, step: TxStepGet): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n const row = this.deps.db\n .prepare(`SELECT * FROM \"${step.table}\" WHERE ${where.clause}`)\n .get(...(where.values as never[])) as Record<string, unknown> | undefined;\n return { op: \"get\", row: row ?? null };\n }\n\n private doInsert(_mid: string, mod: ModuleManifestV2, step: TxStepInsert): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.row);\n if (cols.length === 0) throw new Error(\"[tx] insert row \u5FC5\u987B\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const placeholders = cols.map(() => \"?\").join(\",\");\n const quoted = cols.map((c) => `\"${c}\"`).join(\",\");\n const values = cols.map((c) => step.row[c]);\n const sql = `INSERT INTO \"${step.table}\" (${quoted}) VALUES (${placeholders})`;\n try {\n this.deps.db.prepare(sql).run(...(values as never[]));\n } catch (e) {\n throw new Error(`INSERT ${step.table} \u5931\u8D25: ${(e as Error).message}`);\n }\n return { op: \"insert\", row: { ...step.row } };\n }\n\n private doUpdate(_mid: string, mod: ModuleManifestV2, step: TxStepUpdate): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.patch);\n if (cols.length === 0) throw new Error(\"[tx] update patch \u4E0D\u80FD\u4E3A\u7A7A\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const where = this.pkWhere(step.table, step.id);\n const sets = cols.map((c) => `\"${c}\" = ?`).join(\",\");\n const params = [...cols.map((c) => step.patch[c]), ...where.values];\n const sql = `UPDATE \"${step.table}\" SET ${sets} WHERE ${where.clause}`;\n const result = this.deps.db.prepare(sql).run(...(params as never[]));\n if (result.changes === 0) {\n throw new Error(`UPDATE ${step.table} id=${step.id} \u5F71\u54CD 0 \u884C(\u627E\u4E0D\u5230 / \u5DF2\u5220?)`);\n }\n return { op: \"update\", row: { ...step.patch, id: step.id } as unknown as Record<string, unknown> };\n }\n\n private doDelete(_mid: string, mod: ModuleManifestV2, step: TxStepDelete): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n if (step.hard) {\n const sql = `DELETE FROM \"${step.table}\" WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${step.table}\")`).all() as Array<{\n name: string;\n }>;\n const hasDeletedAt = cols.find((c) => c.name === \"_deleted_at\");\n if (!hasDeletedAt) {\n throw new Error(`[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`);\n }\n const sql = `UPDATE \"${step.table}\" SET \"_deleted_at\" = ?, \"_version\" = COALESCE(\"_version\",0)+1 WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(Date.now() as never, ...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n\n private doAudit(_mid: string, mod: ModuleManifestV2, step: TxStepAudit): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const dataJson = step.data ? JSON.stringify(step.data) : null;\n const r = this.deps.db\n .prepare(\n `INSERT INTO sfmc__audit (module_id, table_name, row_id, action, data, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`\n )\n .run(mod.id, step.table, step.rowId, step.action, dataJson, new Date().toISOString());\n return { op: \"audit\", changes: Number(r.changes) };\n }\n\n private async doService(_mid: string, mod: ModuleManifestV2, step: TxStepService): Promise<TxStepResult> {\n // requires / \u81EA\u8C03\u7528\u8C41\u514D\u7684\u552F\u4E00\u6743\u5A01\u5728 ServiceRegistry.dispatch(DRY/LSP);\n // \u6B64\u5904\u53EA\u8865 dispatch \u4E0D\u505A\u7684 service:<name> \u6743\u9650\u95E8(\u4E0E HTTP service-routes \u5BF9\u9F50)\u3002\n assertModulePermission(mod.id, mod.permissions, Perm.service(step.name));\n const result = await this.deps.serviceRegistry.dispatch(this.deps.enabled, mod.id, step.name, step.input, {\n query: this.deps.query,\n db: this.deps.db,\n });\n return { op: \"service\", result: result.result };\n }\n}\n"],
5
- "mappings": "AA0BA,SAAS,kBAAkB;AAE3B,SAAS,WAAW;AACpB,SAAS,wBAAwB;AAEjC,SAAS,MAAM,uBAAuB,8BAA8B;AAEpE,SAAS,qBAA2C;AAEpD,SAAS,eAAe;AAExB,MAAM,QAAQ;AAEd,SAAS,QAAQ,GAAiB;AAChC,MAAI,CAAC,MAAM,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,4BAA4B,CAAC,GAAG;AACtE;AAwGO,MAAM,SAAS;AAAA,EAKpB,YAA6B,MAAoB;AAApB;AAAA,EAAqB;AAAA,EAArB;AAAA,EAJZ,WAAW,oBAAI,IAAuB;AAAA,EAC/C,kBAAkB;AAAA,EACT,qBAAwC,CAAC;AAAA,EAI1D,MAAM,IAAI,KAA+C;AACvD,UAAM,EAAE,UAAU,MAAM,IAAI;AAC5B,UAAM,WAAW,KAAK,KAAK,QAAQ,IAAI,QAAQ;AAC/C,QAAI,CAAC,SAAU,QAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,8BAAe,MAAM,YAAY;AAMrF,UAAM,UAAU,WAAW,EAAE,MAAM,GAAG,CAAC;AACvC,UAAM,UAA0B,CAAC;AAEjC,UAAM,KAAK,uBAAuB;AAClC,QAAI;AACF,WAAK,KAAK,GAAG,KAAK,iBAAiB;AAAA,IACrC,SAAS,GAAG;AACV,WAAK,uBAAuB;AAC5B,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,uBAAc,EAAY,OAAO,IAAI,MAAM,WAAW;AAAA,IAC7F;AAEA,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAe,MAAM,CAAC;AAC5B,UAAI;AACF,cAAM,IAAI,MAAM,KAAK,OAAO,UAAU,UAAU,IAAI;AACpD,gBAAQ,KAAK,CAAC;AAAA,MAChB,SAAS,KAAK;AACZ,aAAK,SAAS;AACd,aAAK,uBAAuB;AAC5B,cAAM,OAAO,KAAK,aAAa,GAAG;AAClC,YAAI,KAAK,OAAO,OAAO,UAAU,CAAC,YAAa,IAAc,OAAO,EAAE;AACtE,eAAO,EAAE,IAAI,OAAO,MAAM,GAAG,OAAQ,IAAc,SAAS,KAAK;AAAA,MACnE;AAAA,IACF;AAEA,QAAI;AACF,WAAK,KAAK,GAAG,KAAK,QAAQ;AAAA,IAC5B,SAAS,GAAG;AACV,WAAK,SAAS;AACd,WAAK,uBAAuB;AAC5B,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,wBAAe,EAAY,OAAO,IAAI,MAAM,WAAW;AAAA,IAC9F;AAEA,SAAK,uBAAuB;AAC5B,QAAI,KAAK,OAAO,OAAO,YAAY,QAAQ,IAAI,MAAM,MAAM,WAAW;AACtE,WAAO,EAAE,IAAI,MAAM,QAAQ;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,aAAa,UAAiE;AAClF,UAAM,WAAW,KAAK,KAAK,QAAQ,IAAI,QAAQ;AAC/C,QAAI,CAAC,SAAU,QAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,8BAAe,MAAM,YAAY;AAGrF,UAAM,KAAK,uBAAuB;AAElC,QAAI;AACF,WAAK,KAAK,GAAG,KAAK,iBAAiB;AAAA,IACrC,SAAS,GAAG;AACV,WAAK,uBAAuB;AAC5B,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,uBAAc,EAAY,OAAO,IAAI,MAAM,WAAW;AAAA,IAC7F;AACA,UAAM,OAAO,WAAW;AACxB,SAAK,SAAS,IAAI,MAAM;AAAA,MACtB;AAAA,MACA,SAAS,CAAC;AAAA,IACZ,CAAC;AACD,QAAI,KAAK,eAAe,KAAK,MAAM,GAAG,CAAC,CAAC,kBAAkB,QAAQ,EAAE;AACpE,WAAO,EAAE,IAAI,MAAM,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,YACJ,MACA,UACA,MACuD;AACvD,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,sEAAe,MAAM,WAAW;AAAA,IACvE;AACA,QAAI,QAAQ,aAAa,UAAU;AACjC,aAAO,EAAE,IAAI,OAAO,MAAM,QAAQ,QAAQ,QAAQ,OAAO,2CAAkB,MAAM,YAAY;AAAA,IAC/F;AACA,UAAM,WAAW,KAAK,KAAK,QAAQ,IAAI,QAAQ;AAC/C,QAAI,CAAC,UAAU;AACb,WAAK,aAAa,IAAI;AACtB,aAAO,EAAE,IAAI,OAAO,MAAM,QAAQ,QAAQ,QAAQ,OAAO,8BAAe,MAAM,YAAY;AAAA,IAC5F;AACA,UAAM,YAAY,QAAQ,QAAQ;AAClC,QAAI;AACF,YAAM,IAAI,MAAM,KAAK,OAAO,UAAU,UAAU,IAAI;AACpD,cAAQ,QAAQ,KAAK,CAAC;AACtB,aAAO,EAAE,IAAI,MAAM,QAAQ,EAAE;AAAA,IAC/B,SAAS,KAAK;AACZ,WAAK,aAAa,IAAI;AACtB,YAAM,OAAO,KAAK,aAAa,GAAG;AAClC,UAAI,KAAK,eAAe,KAAK,MAAM,GAAG,CAAC,CAAC,UAAU,SAAS,YAAa,IAAc,OAAO,EAAE;AAC/F,aAAO,EAAE,IAAI,OAAO,MAAM,WAAW,OAAQ,IAAc,SAAS,KAAK;AAAA,IAC3E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAc,MAAc,UAAwC;AAClE,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,sEAAe,MAAM,WAAW;AAAA,IACvE;AACA,QAAI,QAAQ,aAAa,UAAU;AACjC,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,2CAAkB,MAAM,YAAY;AAAA,IAC3E;AACA,QAAI;AACF,WAAK,KAAK,GAAG,KAAK,QAAQ;AAAA,IAC5B,SAAS,GAAG;AACV,WAAK,aAAa,IAAI;AACtB,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,wBAAe,EAAY,OAAO,IAAI,MAAM,WAAW;AAAA,IAC9F;AACA,UAAM,UAAU,QAAQ;AACxB,SAAK,SAAS,OAAO,IAAI;AACzB,SAAK,uBAAuB;AAC5B,QAAI,KAAK,eAAe,KAAK,MAAM,GAAG,CAAC,CAAC,YAAY,QAAQ,MAAM,WAAW;AAC7E,WAAO,EAAE,IAAI,MAAM,QAAQ;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,gBAAgB,MAAc,UAA0C;AACtE,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AAEZ,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AACA,QAAI,QAAQ,aAAa,UAAU;AACjC,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,OAAO,2CAAkB,MAAM,YAAY;AAAA,IAC3E;AACA,SAAK,aAAa,IAAI;AACtB,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AAAA,EAEQ,aAAa,MAAoB;AACvC,SAAK,SAAS,OAAO,IAAI;AACzB,SAAK,SAAS;AACd,SAAK,uBAAuB;AAC5B,QAAI,KAAK,eAAe,KAAK,MAAM,GAAG,CAAC,CAAC,WAAW;AAAA,EACrD;AAAA,EAEA,MAAc,yBAAwC;AACpD,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,kBAAkB;AACvB;AAAA,IACF;AACA,UAAM,IAAI,QAAc,CAAC,YAAY,KAAK,mBAAmB,KAAK,OAAO,CAAC;AAAA,EAC5E;AAAA,EAEQ,yBAA+B;AACrC,UAAM,OAAO,KAAK,mBAAmB,MAAM;AAC3C,QAAI,MAAM;AACR,WAAK;AACL;AAAA,IACF;AACA,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEQ,aAAa,KAA+B;AAClD,WAAO,eAAe,wBAClB,sBACA,eAAe,kBACZ,IAAI,SAAS,qBACZ,IAAI,SAAS,qBACb,IAAI,SAAS,eACb,IAAI,SAAS,kBACf,IAAI,OACH,IAA0B,SAAS,oBAClC,oBACC,IAA0B,SAAS,oBAClC,oBACC,IAA0B,SAAS,kBAClC,kBACA;AAAA,EACd;AAAA,EAEQ,WAAiB;AACvB,QAAI;AACF,WAAK,KAAK,GAAG,KAAK,UAAU;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,OAAO,UAAkB,UAA4B,MAAqC;AACtG,YAAQ,KAAK,IAAI;AAAA,MACf,KAAK;AACH,eAAO,KAAK,QAAQ,UAAU,UAAU,IAAI;AAAA,MAC9C,KAAK;AACH,eAAO,KAAK,MAAM,UAAU,UAAU,IAAI;AAAA,MAC5C,KAAK;AACH,eAAO,KAAK,SAAS,UAAU,UAAU,IAAI;AAAA,MAC/C,KAAK;AACH,eAAO,KAAK,SAAS,UAAU,UAAU,IAAI;AAAA,MAC/C,KAAK;AACH,eAAO,KAAK,SAAS,UAAU,UAAU,IAAI;AAAA,MAC/C,KAAK;AACH,eAAO,KAAK,QAAQ,UAAU,UAAU,IAAI;AAAA,MAC9C,KAAK;AACH,eAAO,KAAK,UAAU,UAAU,UAAU,IAAI;AAAA,IAClD;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAAuB,OAAqB;AACnE,YAAQ,KAAK;AACb,SAAK,sBAAsB,KAAK;AAChC,2BAAuB,IAAI,IAAI,IAAI,aAAa,KAAK,OAAO,KAAK,CAAC;AAAA,EACpE;AAAA,EACQ,kBAAkB,KAAuB,OAAqB;AACpE,YAAQ,KAAK;AACb,SAAK,sBAAsB,KAAK;AAChC,2BAAuB,IAAI,IAAI,IAAI,aAAa,KAAK,QAAQ,KAAK,CAAC;AAAA,EACrE;AAAA,EACQ,sBAAsB,OAAqB;AACjD,QAAI,MAAM,WAAW,OAAO,EAAG;AAC/B,QAAI,CAAC,KAAK,KAAK,OAAO,aAAa,KAAK,GAAG;AACzC,YAAM,MAAM,IAAI,MAAM,WAAM,KAAK,4CAAwB;AACzD,MAAC,IAA0B,OAAO;AAClC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,QAAQ,MAAc,KAAuB,MAAiC;AACpF,SAAK,iBAAiB,KAAK,KAAK,KAAK;AACrC,UAAM,QAAQ,QAAQ,KAAK,MAAM,KAAK;AAEtC,UAAM,SAAS,iBAAiB,KAAK,MAAM,OAAO;AAClD,UAAM,QAAQ,OAAO,KAAK,MAAM,UAAU,WAAW,KAAK,MAAM,QAAQ;AACxE,UAAM,SAAS,OAAO,KAAK,MAAM,WAAW,WAAW,KAAK,MAAM,SAAS;AAE3E,UAAM,cAAc,MAAM;AAC1B,UAAM,SAAS,CAAC,GAAG,MAAM,MAAM;AAC/B,QAAI,QAAQ;AACZ,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,QAAkB,CAAC;AACzB,iBAAW,KAAK,QAAQ;AACtB,YAAI,CAAC,MAAM,KAAK,EAAE,GAAG,EAAG,OAAM,IAAI,MAAM,4BAA4B,EAAE,GAAG,GAAG;AAC5E,cAAM,KAAK,IAAI,EAAE,GAAG,KAAK,EAAE,QAAQ,SAAS,SAAS,KAAK,EAAE;AAAA,MAC9D;AACA,eAAS,aAAa,MAAM,KAAK,IAAI,CAAC;AAAA,IACxC;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,eAAS,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;AAAA,IACpD;AACA,QAAI,OAAO,WAAW,UAAU;AAC9B,eAAS,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC;AAAA,IACtD;AAEA,UAAM,MAAM,kBAAkB,KAAK,KAAK,WAAW,WAAW,GAAG,KAAK;AACtE,UAAM,OAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAI,MAAkB;AACjE,WAAO,EAAE,IAAI,SAAS,KAAK;AAAA,EAC7B;AAAA;AAAA,EAGQ,QAAQ,OAAe,IAA4D;AACzF,UAAM,OAAO,KAAK,KAAK,GAAG,QAAQ,sBAAsB,KAAK,IAAI,EAAE,IAAI;AAIvE,UAAM,SAAS,KACZ,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,EACtB,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,EAC1B,IAAI,CAAC,MAAM,EAAE,IAAI;AACpB,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,IAAI,MAAM,QAAQ,KAAK,2BAAiB;AAAA,IAChD;AACA,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,QAAQ,CAAC,EAAE,EAAE;AAAA,IACtD;AACA,UAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG;AAClC,QAAI,MAAM,WAAW,OAAO,QAAQ;AAClC,aAAO;AAAA,QACL,QAAQ,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,KAAK,OAAO;AAAA,QACpD,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,WAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,QAAQ,CAAC,EAAE,EAAE;AAAA,EACtD;AAAA,EAEQ,MAAM,MAAc,KAAuB,MAA+B;AAChF,SAAK,iBAAiB,KAAK,KAAK,KAAK;AACrC,UAAM,QAAQ,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AAC9C,UAAM,MAAM,KAAK,KAAK,GACnB,QAAQ,kBAAkB,KAAK,KAAK,WAAW,MAAM,MAAM,EAAE,EAC7D,IAAI,GAAI,MAAM,MAAkB;AACnC,WAAO,EAAE,IAAI,OAAO,KAAK,OAAO,KAAK;AAAA,EACvC;AAAA,EAEQ,SAAS,MAAc,KAAuB,MAAkC;AACtF,SAAK,kBAAkB,KAAK,KAAK,KAAK;AACtC,UAAM,OAAO,OAAO,KAAK,KAAK,GAAG;AACjC,QAAI,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,kEAA0B;AACjE,eAAW,KAAK,MAAM;AACpB,UAAI,CAAC,MAAM,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,6BAA6B,CAAC,GAAG;AAAA,IACvE;AACA,UAAM,eAAe,KAAK,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AACjD,UAAM,SAAS,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG;AACjD,UAAM,SAAS,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;AAC1C,UAAM,MAAM,gBAAgB,KAAK,KAAK,MAAM,MAAM,aAAa,YAAY;AAC3E,QAAI;AACF,WAAK,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAI,MAAkB;AAAA,IACtD,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,UAAU,KAAK,KAAK,kBAAS,EAAY,OAAO,EAAE;AAAA,IACpE;AACA,WAAO,EAAE,IAAI,UAAU,KAAK,EAAE,GAAG,KAAK,IAAI,EAAE;AAAA,EAC9C;AAAA,EAEQ,SAAS,MAAc,KAAuB,MAAkC;AACtF,SAAK,kBAAkB,KAAK,KAAK,KAAK;AACtC,UAAM,OAAO,OAAO,KAAK,KAAK,KAAK;AACnC,QAAI,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,4CAAwB;AAC/D,eAAW,KAAK,MAAM;AACpB,UAAI,CAAC,MAAM,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,6BAA6B,CAAC,GAAG;AAAA,IACvE;AACA,UAAM,QAAQ,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AAC9C,UAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG;AACnD,UAAM,SAAS,CAAC,GAAG,KAAK,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG,GAAG,MAAM,MAAM;AAClE,UAAM,MAAM,WAAW,KAAK,KAAK,SAAS,IAAI,UAAU,MAAM,MAAM;AACpE,UAAM,SAAS,KAAK,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAI,MAAkB;AACnE,QAAI,OAAO,YAAY,GAAG;AACxB,YAAM,IAAI,MAAM,UAAU,KAAK,KAAK,OAAO,KAAK,EAAE,4DAAoB;AAAA,IACxE;AACA,WAAO,EAAE,IAAI,UAAU,KAAK,EAAE,GAAG,KAAK,OAAO,IAAI,KAAK,GAAG,EAAwC;AAAA,EACnG;AAAA,EAEQ,SAAS,MAAc,KAAuB,MAAkC;AACtF,SAAK,kBAAkB,KAAK,KAAK,KAAK;AACtC,UAAM,QAAQ,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AAC9C,QAAI,KAAK,MAAM;AACb,YAAMA,OAAM,gBAAgB,KAAK,KAAK,WAAW,MAAM,MAAM;AAC7D,YAAMC,KAAI,KAAK,KAAK,GAAG,QAAQD,IAAG,EAAE,IAAI,GAAI,MAAM,MAAkB;AACpE,aAAO,EAAE,IAAI,UAAU,SAAS,OAAOC,GAAE,OAAO,EAAE;AAAA,IACpD;AACA,UAAM,OAAO,KAAK,KAAK,GAAG,QAAQ,sBAAsB,KAAK,KAAK,IAAI,EAAE,IAAI;AAG5E,UAAM,eAAe,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,aAAa;AAC9D,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,MAAM,QAAQ,KAAK,KAAK,yGAA4D;AAAA,IAChG;AACA,UAAM,MAAM,WAAW,KAAK,KAAK,wEAAwE,MAAM,MAAM;AACrH,UAAM,IAAI,KAAK,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,KAAK,IAAI,GAAY,GAAI,MAAM,MAAkB;AACzF,WAAO,EAAE,IAAI,UAAU,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,EACpD;AAAA,EAEQ,QAAQ,MAAc,KAAuB,MAAiC;AACpF,SAAK,kBAAkB,KAAK,KAAK,KAAK;AACtC,UAAM,WAAW,KAAK,OAAO,KAAK,UAAU,KAAK,IAAI,IAAI;AACzD,UAAM,IAAI,KAAK,KAAK,GACjB;AAAA,MACC;AAAA;AAAA,IAEF,EACC,IAAI,IAAI,IAAI,KAAK,OAAO,KAAK,OAAO,KAAK,QAAQ,WAAU,oBAAI,KAAK,GAAE,YAAY,CAAC;AACtF,WAAO,EAAE,IAAI,SAAS,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,EACnD;AAAA,EAEA,MAAc,UAAU,MAAc,KAAuB,MAA4C;AAGvG,2BAAuB,IAAI,IAAI,IAAI,aAAa,KAAK,QAAQ,KAAK,IAAI,CAAC;AACvE,UAAM,SAAS,MAAM,KAAK,KAAK,gBAAgB,SAAS,KAAK,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,KAAK,OAAO;AAAA,MACxG,OAAO,KAAK,KAAK;AAAA,MACjB,IAAI,KAAK,KAAK;AAAA,IAChB,CAAC;AACD,WAAO,EAAE,IAAI,WAAW,QAAQ,OAAO,OAAO;AAAA,EAChD;AACF;",
4
+ "sourcesContent": ["/**\n * tx-runner.ts \u2014 \u4E8B\u52A1 RPC \u5904\u7406\u5668\n *\n * \u534F\u8BAE(\u4E24\u79CD):\n * A. \u6279\u91CF: POST /api/sfmc/db/tx body={ steps } \u2192 { ok, results? } | { ok:false, step, error, code }\n * B. \u4EA4\u4E92: begin \u2192 step* \u2192 commit|rollback(SDK db.tx \u7528;\u56DE\u8C03\u5185\u53EF\u8BFB\u56DE query/get/call)\n *\n * \u6D41\u7A0B(\u6279\u91CF):\n * 1. \u6821\u9A8C moduleId in enabled\n * 2. BEGIN IMMEDIATE\n * 3. \u987A\u5E8F\u8DD1 steps \u2014 \u4EFB\u4E00\u629B\u9519 \u2192 ROLLBACK + reply { ok:false, step:<i>, ... }\n * 4. \u5168\u8FC7 \u2192 COMMIT + reply { ok:true, results }\n *\n * step.op:\n * - query { table, opts? }\n * - get { table, id }\n * - insert { table, row }\n * - update { table, id, patch }\n * - delete { table, id, hard }\n * - audit { table, rowId, action, data? } \u2192 \u5E73\u53F0 sfmc__audit \u8868\n * - service { name, input } \u2192 \u6D3E\u53D1;\u5931\u8D25\u629B\u9519\u56DE\u9000\u6574\u4E8B\u52A1\n *\n * \u4E8B\u52A1\u5185 service call:\u4E0D\u6301\u6709 db handle \u7ED9 handler(\u907F\u514D\u5E76\u53D1\u5199) \u2014\n * \u5B9E\u73B0:handler \u8FD4\u56DE result \u5199\u56DE step \u8F93\u51FA;\u82E5 handler \u8FD8\u8981\u5199 db,\u5E94\u8D70\u5176\u5B83\u63A5\u53E3\u5728\u4E8B\u52A1\u5916\u505A\u3002\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\nimport { normalizeOrderBy } from \"./lib/order-by.js\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport { Perm, PermissionDeniedError, assertModulePermission } from \"./permission-gate.js\";\nimport type { SchemaRegistry } from \"./schema-registry.js\";\nimport { DispatchError, type ServiceRegistry } from \"./service-registry.js\";\nimport type { WhereExpr } from \"./where.js\";\nimport { compile } from \"./where.js\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction identOk(t: string): void {\n if (!IDENT.test(t)) throw new Error(`[tx] invalid table name \"${t}\"`);\n}\n\nexport interface TxStepQuery {\n op: \"query\";\n table: string;\n opts?: {\n where?: WhereExpr;\n /** SDK \u7528 field;\u5386\u53F2 col \u4ECD\u63A5\u53D7(normalizeOrderBy) */\n orderBy?:\n | { field?: string; col?: string; dir?: \"asc\" | \"desc\" }\n | Array<{ field?: string; col?: string; dir?: \"asc\" | \"desc\" }>;\n limit?: number;\n offset?: number;\n };\n}\n\nexport interface TxStepGet {\n op: \"get\";\n table: string;\n id: string;\n}\n\nexport interface TxStepInsert {\n op: \"insert\";\n table: string;\n row: Record<string, unknown>;\n}\n\nexport interface TxStepUpdate {\n op: \"update\";\n table: string;\n id: string;\n patch: Record<string, unknown>;\n}\n\nexport interface TxStepDelete {\n op: \"delete\";\n table: string;\n id: string;\n hard: boolean;\n}\n\nexport interface TxStepAudit {\n op: \"audit\";\n table: string;\n rowId: string;\n action: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TxStepService {\n op: \"service\";\n name: string;\n input: Record<string, unknown>;\n}\n\nexport type TxStep = TxStepQuery | TxStepGet | TxStepInsert | TxStepUpdate | TxStepDelete | TxStepAudit | TxStepService;\n\nexport type TxStepResult =\n | { op: \"query\"; rows: Record<string, unknown>[] }\n | { op: \"get\"; row: Record<string, unknown> | null }\n | { op: \"insert\"; row: Record<string, unknown> }\n | { op: \"update\"; row: Record<string, unknown> }\n | { op: \"delete\"; changes: number }\n | { op: \"audit\"; changes: number }\n | { op: \"service\"; result: unknown };\n\nexport interface TxResponse {\n ok: true;\n results: TxStepResult[];\n}\n\nexport type TxError = {\n ok: false;\n step: number;\n error: string;\n code:\n | \"permission_denied\"\n | \"forbidden\"\n | \"no_such_service\"\n | \"not_in_requires\"\n | \"domain_error\"\n | \"transaction_busy\"\n | \"internal\"\n | \"no_such_table\";\n};\n\nexport interface TxRequest {\n moduleId: string;\n steps: TxStep[];\n}\n\ninterface TxSession {\n moduleId: string;\n results: TxStepResult[];\n idleTimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TransactionWaiter {\n resolve: (acquired: boolean) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport interface TxRunnerOptions {\n /** \u7B49\u5F85\u5168\u5C40 SQLite \u4E8B\u52A1\u69FD\u7684\u6700\u957F\u65F6\u95F4\u3002 */\n slotWaitTimeoutMs?: number;\n /** \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4E24\u6B21\u8BF7\u6C42\u4E4B\u95F4\u5141\u8BB8\u7A7A\u95F2\u7684\u6700\u957F\u65F6\u95F4\u3002 */\n sessionIdleTimeoutMs?: number;\n}\n\nexport interface TxRunnerDeps {\n db: DatabaseSync;\n query: import(\"./lib/sqlite.js\").QueryFn;\n schema: SchemaRegistry;\n serviceRegistry: ServiceRegistry;\n enabled: Map<string, ModuleManifestV2>;\n}\n\nexport class TxRunner {\n private readonly sessions = new Map<string, TxSession>();\n private transactionBusy = false;\n private readonly transactionWaiters: TransactionWaiter[] = [];\n private readonly slotWaitTimeoutMs: number;\n private readonly sessionIdleTimeoutMs: number;\n\n constructor(\n private readonly deps: TxRunnerDeps,\n options: TxRunnerOptions = {}\n ) {\n this.slotWaitTimeoutMs = options.slotWaitTimeoutMs ?? 2_000;\n this.sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? 15_000;\n }\n\n async run(req: TxRequest): Promise<TxResponse | TxError> {\n const { moduleId, steps } = req;\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n // \u6CE8\u610F:\u4E0D\u5728\u6B64\u5904\u505A\u300C\u6574\u4F53 db:write:*\u300D\u65AD\u8A00\u3002\n // \u901A\u914D `db:write:*` \u65E0\u6CD5\u901A\u8FC7 permission-gate \u7684 validPermissionKey \u6821\u9A8C(\u6A21\u5757\u58F0\u660E\u5B83\u4F1A\u542F\u52A8\u5931\u8D25),\n // \u56E0\u6B64\u8BE5\u65AD\u8A00\u4F1A\u8BA9\u6240\u6709\u4E8B\u52A1(\u542B\u53EA\u8BFB\u4E8B\u52A1)\u6052\u88AB\u62D2\u3002\u771F\u6B63\u7684\u8BFB\u5199\u6743\u9650\u7531\u6BCF\u4E2A step \u7684\n // requireTableRead / requireTableWrite \u6309\u5177\u4F53\u8868\u7CBE\u786E gate\u3002\n\n const traceId = randomUUID().slice(0, 8);\n const results: TxStepResult[] = [];\n\n if (!(await this.acquireTransactionSlot())) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\", code: \"transaction_busy\" };\n }\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n for (let i = 0; i < steps.length; i++) {\n const step: TxStep = steps[i]!;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n results.push(r);\n } catch (err) {\n this.rollback();\n this.releaseTransactionSlot();\n const code = this.mapErrorCode(err);\n log.warn(`[tx ${traceId}] step=${i} failed: ${(err as Error).message}`);\n return { ok: false, step: i, error: (err as Error).message, code };\n }\n }\n\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.rollback();\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n this.releaseTransactionSlot();\n log.info(`[tx ${traceId}] module=${moduleId} ${steps.length} steps OK`);\n return { ok: true, results };\n }\n\n /**\n * \u5F00\u542F\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF08\u534F\u8BAE\u6D41\u7A0B\uFF1Abegin \u2192 step* \u2192 commit | rollback\uFF09\u3002\n * \u4F9B\u5BA2\u6237\u7AEF `db.tx` \u5728\u56DE\u8C03\u5185\u90E8\u5F02\u6B65\u6267\u884C\u5E76\u5B9E\u65F6\u8BFB\u56DE query / get / call \u7684\u670D\u52A1\u7AEF\u6267\u884C\u7ED3\u679C\u3002\n * \u5355\u8FDE\u63A5\u4E92\u65A5\u4FDD\u62A4\uFF1A\u4E0E\u6279\u91CF run() \u5171\u7528\u4E8B\u52A1\u961F\u5217\uFF0C\u5E76\u5728\u63D0\u4EA4/\u56DE\u6EDA\u65F6\u5524\u9192\u4E0B\u4E00\u4E2A\u7B49\u5F85\u8005\u3002\n *\n * @param moduleId \u8BF7\u6C42\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @returns \u6210\u529F\u8FD4\u56DE\u5206\u914D\u7684\u4F1A\u8BDD txId\uFF0C\u5931\u8D25\u8FD4\u56DE\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async beginSession(moduleId: string): Promise<{ ok: true; txId: string } | TxError> {\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n\n // run() \u4E0E\u4EA4\u4E92\u5F0F\u4F1A\u8BDD\u5171\u7528\u540C\u4E00 SQLite \u8FDE\u63A5\uFF0C\u5FC5\u987B\u5171\u7528\u540C\u4E00\u4E2A\u4E8B\u52A1\u69FD\u3002\n if (!(await this.acquireTransactionSlot())) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\", code: \"transaction_busy\" };\n }\n\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const txId = randomUUID();\n this.sessions.set(txId, {\n moduleId,\n results: [],\n idleTimer: this.createSessionIdleTimer(txId, moduleId),\n });\n log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);\n return { ok: true, txId };\n }\n\n /**\n * \u5728\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\u4E2D\u987A\u5E8F\u6267\u884C\u5355\u4E2A\u6B65\u9AA4\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @param step \u5F85\u6267\u884C\u7684\u64CD\u4F5C\u6B65\u9AA4\u5BF9\u8C61\u3002\n * @returns \u6B65\u9AA4\u6267\u884C\u7ED3\u679C\u6216\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async stepSession(\n txId: string,\n moduleId: string,\n step: TxStep\n ): Promise<{ ok: true; result: TxStepResult } | TxError> {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: session.results.length, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n clearTimeout(session.idleTimer);\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) {\n this.abortSession(txId);\n return { ok: false, step: session.results.length, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n }\n const stepIndex = session.results.length;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n session.results.push(r);\n session.idleTimer = this.createSessionIdleTimer(txId, moduleId);\n return { ok: true, result: r };\n } catch (err) {\n this.abortSession(txId);\n const code = this.mapErrorCode(err);\n log.warn(`[tx-session ${txId.slice(0, 8)}] step=${stepIndex} failed: ${(err as Error).message}`);\n return { ok: false, step: stepIndex, error: (err as Error).message, code };\n }\n }\n\n /**\n * \u63D0\u4EA4\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u5C06\u6240\u6709\u6B65\u9AA4\u7684\u4FEE\u6539\u6301\u4E45\u5316\u5E76\u91CA\u653E\u8FDE\u63A5\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u63D0\u4EA4\u7ED3\u679C\u54CD\u5E94\u3002\n */\n commitSession(txId: string, moduleId: string): TxResponse | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n clearTimeout(session.idleTimer);\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.abortSession(txId);\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const results = session.results;\n this.sessions.delete(txId);\n this.releaseTransactionSlot();\n log.info(`[tx-session ${txId.slice(0, 8)}] commit ${results.length} steps OK`);\n return { ok: true, results };\n }\n\n /**\n * \u56DE\u6EDA\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u64A4\u9500\u6240\u6709\u672A\u63D0\u4EA4\u7684\u6570\u636E\u5E93\u53D8\u66F4\u3002\n * \u5177\u6709\u5E42\u7B49\u6027\uFF1A\u4F1A\u8BDD\u5DF2\u7ED3\u675F\u6216\u5DF2\u56DE\u6EDA\u65F6\u540C\u6837\u8FD4\u56DE\u6210\u529F\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u56DE\u6EDA\u786E\u8BA4\u7ED3\u679C\u3002\n */\n rollbackSession(txId: string, moduleId: string): { ok: true } | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n // \u5E42\u7B49\uFF1A\u5DF2\u7ED3\u675F\u89C6\u4E3A\u6210\u529F\u56DE\u6EDA\n return { ok: true };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n this.abortSession(txId);\n return { ok: true };\n }\n\n private abortSession(txId: string): void {\n const session = this.sessions.get(txId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n this.sessions.delete(txId);\n this.rollback();\n this.releaseTransactionSlot();\n log.info(`[tx-session ${txId.slice(0, 8)}] aborted`);\n }\n\n private async acquireTransactionSlot(): Promise<boolean> {\n if (!this.transactionBusy) {\n this.transactionBusy = true;\n return true;\n }\n return new Promise<boolean>((resolve) => {\n const waiter: TransactionWaiter = {\n resolve,\n timer: setTimeout(() => {\n const index = this.transactionWaiters.indexOf(waiter);\n if (index >= 0) this.transactionWaiters.splice(index, 1);\n resolve(false);\n }, this.slotWaitTimeoutMs),\n };\n this.transactionWaiters.push(waiter);\n });\n }\n\n private releaseTransactionSlot(): void {\n const next = this.transactionWaiters.shift();\n if (next) {\n clearTimeout(next.timer);\n next.resolve(true);\n return;\n }\n this.transactionBusy = false;\n }\n\n private createSessionIdleTimer(txId: string, moduleId: string): ReturnType<typeof setTimeout> {\n const timer = setTimeout(() => {\n if (!this.sessions.has(txId)) return;\n log.warn(`[tx-session ${txId.slice(0, 8)}] idle timeout module=${moduleId} after ${this.sessionIdleTimeoutMs}ms`);\n this.abortSession(txId);\n }, this.sessionIdleTimeoutMs);\n timer.unref?.();\n return timer;\n }\n\n private mapErrorCode(err: unknown): TxError[\"code\"] {\n return err instanceof PermissionDeniedError\n ? \"permission_denied\"\n : err instanceof DispatchError &&\n (err.code === \"no_such_service\" ||\n err.code === \"not_in_requires\" ||\n err.code === \"forbidden\" ||\n err.code === \"domain_error\")\n ? err.code\n : (err as { code?: string }).code === \"no_such_service\"\n ? \"no_such_service\"\n : (err as { code?: string }).code === \"not_in_requires\"\n ? \"not_in_requires\"\n : (err as { code?: string }).code === \"no_such_table\"\n ? \"no_such_table\"\n : \"internal\";\n }\n\n private rollback(): void {\n try {\n this.deps.db.exec(\"ROLLBACK\");\n } catch {\n // best-effort; fall through\n }\n }\n\n /** \u5355 step \u7531\u5BF9\u5E94\u6743\u9650\u68C0\u67E5 + SQL \u8DEF\u5F84\u6267\u884C */\n private async runOne(moduleId: string, manifest: ModuleManifestV2, step: TxStep): Promise<TxStepResult> {\n switch (step.op) {\n case \"query\":\n return this.doQuery(moduleId, manifest, step);\n case \"get\":\n return this.doGet(moduleId, manifest, step);\n case \"insert\":\n return this.doInsert(moduleId, manifest, step);\n case \"update\":\n return this.doUpdate(moduleId, manifest, step);\n case \"delete\":\n return this.doDelete(moduleId, manifest, step);\n case \"audit\":\n return this.doAudit(moduleId, manifest, step);\n case \"service\":\n return this.doService(moduleId, manifest, step);\n }\n }\n\n private requireTableRead(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbRead(table));\n }\n private requireTableWrite(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbWrite(table));\n }\n private assertTableRegistered(table: string): void {\n if (table.startsWith(\"sfmc_\")) return; // \u5E73\u53F0\u8868 \u2014 \u59CB\u7EC8\u53EF\u8BFB\u5199(\u5E73\u53F0\u5C42)\n if (!this.deps.schema.isRegistered(table)) {\n const err = new Error(`\u8868 \"${table}\" \u672A\u6CE8\u518C\u5230 schema-registry`);\n (err as { code?: string }).code = \"no_such_table\";\n throw err;\n }\n }\n\n private doQuery(_mid: string, mod: ModuleManifestV2, step: TxStepQuery): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = compile(step.opts?.where);\n // LSP:\u4E0E SDK QueryOptions.orderBy({field}|[]) \u5BF9\u9F50,\u517C\u6536\u9057\u7559 {col}\n const orders = normalizeOrderBy(step.opts?.orderBy);\n const limit = typeof step.opts?.limit === \"number\" ? step.opts?.limit : undefined;\n const offset = typeof step.opts?.offset === \"number\" ? step.opts?.offset : undefined;\n\n const whereClause = where.sql;\n const params = [...where.values];\n let extra = \"\";\n if (orders.length > 0) {\n const parts: string[] = [];\n for (const o of orders) {\n if (!IDENT.test(o.col)) throw new Error(`[tx] orderBy bad column \"${o.col}\"`);\n parts.push(`\"${o.col}\" ${o.dir === \"desc\" ? \"DESC\" : \"ASC\"}`);\n }\n extra += ` ORDER BY ${parts.join(\", \")}`;\n }\n if (typeof limit === \"number\") {\n extra += \" LIMIT \" + Math.max(0, Math.floor(limit));\n }\n if (typeof offset === \"number\") {\n extra += \" OFFSET \" + Math.max(0, Math.floor(offset));\n }\n\n const sql = `SELECT * FROM \"${step.table}\" WHERE ${whereClause}${extra}`;\n const rows = this.deps.db.prepare(sql).all(...(params as never[])) as Record<string, unknown>[];\n return { op: \"query\", rows };\n }\n\n /** \u89E3\u6790\u4E3B\u952E WHERE\u3002\u5355\u5217\u76F4\u63A5\u7B49\u503C;\u8054\u5408\u4E3B\u952E\u652F\u6301 `a|b|c`(\u4E0E data-backup \u7EA6\u5B9A\u4E00\u81F4)\u3002 */\n private pkWhere(table: string, id: string | number): { clause: string; values: unknown[] } {\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${table}\")`).all() as Array<{\n name: string;\n pk: number;\n }>;\n const pkCols = cols\n .filter((c) => c.pk > 0)\n .sort((a, b) => a.pk - b.pk)\n .map((c) => c.name);\n if (pkCols.length === 0) {\n throw new Error(`[tx] ${table} \u6CA1\u6709 primary key`);\n }\n if (pkCols.length === 1) {\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n const parts = String(id).split(\"|\");\n if (parts.length === pkCols.length) {\n return {\n clause: pkCols.map((c) => `\"${c}\" = ?`).join(\" AND \"),\n values: parts,\n };\n }\n // \u8054\u5408\u4E3B\u952E\u4F46\u53EA\u4F20\u4E86\u9996\u5217(\u9891\u9053 id \u7B49):\u6309\u7B2C\u4E00\u4E3B\u952E\u5217\u5339\u914D\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n\n private doGet(_mid: string, mod: ModuleManifestV2, step: TxStepGet): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n const row = this.deps.db\n .prepare(`SELECT * FROM \"${step.table}\" WHERE ${where.clause}`)\n .get(...(where.values as never[])) as Record<string, unknown> | undefined;\n return { op: \"get\", row: row ?? null };\n }\n\n private doInsert(_mid: string, mod: ModuleManifestV2, step: TxStepInsert): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.row);\n if (cols.length === 0) throw new Error(\"[tx] insert row \u5FC5\u987B\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const placeholders = cols.map(() => \"?\").join(\",\");\n const quoted = cols.map((c) => `\"${c}\"`).join(\",\");\n const values = cols.map((c) => step.row[c]);\n const sql = `INSERT INTO \"${step.table}\" (${quoted}) VALUES (${placeholders})`;\n try {\n this.deps.db.prepare(sql).run(...(values as never[]));\n } catch (e) {\n throw new Error(`INSERT ${step.table} \u5931\u8D25: ${(e as Error).message}`);\n }\n return { op: \"insert\", row: { ...step.row } };\n }\n\n private doUpdate(_mid: string, mod: ModuleManifestV2, step: TxStepUpdate): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.patch);\n if (cols.length === 0) throw new Error(\"[tx] update patch \u4E0D\u80FD\u4E3A\u7A7A\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const where = this.pkWhere(step.table, step.id);\n const sets = cols.map((c) => `\"${c}\" = ?`).join(\",\");\n const params = [...cols.map((c) => step.patch[c]), ...where.values];\n const sql = `UPDATE \"${step.table}\" SET ${sets} WHERE ${where.clause}`;\n const result = this.deps.db.prepare(sql).run(...(params as never[]));\n if (result.changes === 0) {\n throw new Error(`UPDATE ${step.table} id=${step.id} \u5F71\u54CD 0 \u884C(\u627E\u4E0D\u5230 / \u5DF2\u5220?)`);\n }\n return { op: \"update\", row: { ...step.patch, id: step.id } as unknown as Record<string, unknown> };\n }\n\n private doDelete(_mid: string, mod: ModuleManifestV2, step: TxStepDelete): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n if (step.hard) {\n const sql = `DELETE FROM \"${step.table}\" WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${step.table}\")`).all() as Array<{\n name: string;\n }>;\n const hasDeletedAt = cols.find((c) => c.name === \"_deleted_at\");\n if (!hasDeletedAt) {\n throw new Error(`[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`);\n }\n const sql = `UPDATE \"${step.table}\" SET \"_deleted_at\" = ?, \"_version\" = COALESCE(\"_version\",0)+1 WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(Date.now() as never, ...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n\n private doAudit(_mid: string, mod: ModuleManifestV2, step: TxStepAudit): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const dataJson = step.data ? JSON.stringify(step.data) : null;\n const r = this.deps.db\n .prepare(\n `INSERT INTO sfmc__audit (module_id, table_name, row_id, action, data, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`\n )\n .run(mod.id, step.table, step.rowId, step.action, dataJson, new Date().toISOString());\n return { op: \"audit\", changes: Number(r.changes) };\n }\n\n private async doService(_mid: string, mod: ModuleManifestV2, step: TxStepService): Promise<TxStepResult> {\n // requires / \u81EA\u8C03\u7528\u8C41\u514D\u7684\u552F\u4E00\u6743\u5A01\u5728 ServiceRegistry.dispatch(DRY/LSP);\n // \u6B64\u5904\u53EA\u8865 dispatch \u4E0D\u505A\u7684 service:<name> \u6743\u9650\u95E8(\u4E0E HTTP service-routes \u5BF9\u9F50)\u3002\n assertModulePermission(mod.id, mod.permissions, Perm.service(step.name));\n const result = await this.deps.serviceRegistry.dispatch(this.deps.enabled, mod.id, step.name, step.input, {\n query: this.deps.query,\n db: this.deps.db,\n });\n return { op: \"service\", result: result.result };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["sql", "r"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfmc-bds/db-server",
3
- "version": "0.2.0-beta.9",
3
+ "version": "0.2.1",
4
4
  "description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
5
5
  "author": "Shiroha7z",
6
6
  "license": "AGPL-3.0-only",
@@ -32,10 +32,11 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "sql-template-strings": "^2.2.2",
35
- "@sfmc-bds/sdk": "0.2.0-beta.13"
35
+ "@sfmc-bds/bds-tools": "0.2.1",
36
+ "@sfmc-bds/sdk": "0.2.1"
36
37
  },
37
38
  "devDependencies": {
38
- "@sfmc-bds/tools": "0.2.0-beta.14"
39
+ "@sfmc-bds/tools": "0.2.1"
39
40
  },
40
41
  "publishConfig": {
41
42
  "access": "public",
@@ -47,8 +48,6 @@
47
48
  "dev": "tsx src/index.ts",
48
49
  "format": "prettier --write \"src/**/*.{ts,tsx,js,json}\"",
49
50
  "start": "node dist/index.js",
50
- "test": "node --test dist/**/*.test.js dist/*.test.js",
51
- "test:api": "pnpm run build && node scripts/test-db-api.mjs",
52
51
  "typecheck": "tsc7 --noEmit"
53
52
  }
54
53
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=bridge.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bridge.test.d.ts","sourceRoot":"","sources":["../../src/domain/bridge.test.ts"],"names":[],"mappings":""}
@@ -1,87 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { makeLLBotConfig, makeOutboundConfig, forwardToQQBridge } from "./bridge.js";
4
- test("makeOutboundConfig\uFF1Aofficial / llbot \u5206\u652F", () => {
5
- const official = makeOutboundConfig({
6
- QQ_BACKEND: "official",
7
- LLBOT_HOST: "127.0.0.1",
8
- LLBOT_PORT: 3004,
9
- LLBOT_TOKEN: "",
10
- QQ_GROUP_ID: "123",
11
- QQ_APP_ID: "app",
12
- QQ_APP_SECRET: "sec",
13
- QQ_SANDBOX: true,
14
- QQ_GROUP_OPENID: "GOPEN",
15
- MCTOQQ_PREFIX: "[MC]"
16
- });
17
- assert.equal(official.backend, "official");
18
- if (official.backend === "official") {
19
- assert.equal(official.groupOpenid, "GOPEN");
20
- assert.equal(official.creds.sandbox, true);
21
- assert.equal(official.prefix, "[MC]");
22
- }
23
- const llbot = makeOutboundConfig({
24
- QQ_BACKEND: "llbot",
25
- LLBOT_HOST: "127.0.0.1",
26
- LLBOT_PORT: 3004,
27
- LLBOT_TOKEN: "t",
28
- QQ_GROUP_ID: "99",
29
- QQ_APP_ID: "",
30
- QQ_APP_SECRET: "",
31
- QQ_SANDBOX: false,
32
- QQ_GROUP_OPENID: "",
33
- MCTOQQ_PREFIX: "[X]"
34
- });
35
- assert.equal(llbot.backend, "llbot");
36
- if (llbot.backend === "llbot") {
37
- assert.equal(llbot.llbot.groupId, "99");
38
- assert.equal(llbot.llbot.prefix, "[X]");
39
- }
40
- });
41
- test("makeLLBotConfig \u4F7F\u7528 mctoqq_prefix", () => {
42
- const cfg = makeLLBotConfig({
43
- LLBOT_HOST: "h",
44
- LLBOT_PORT: 1,
45
- LLBOT_TOKEN: "",
46
- QQ_GROUP_ID: "1",
47
- MCTOQQ_PREFIX: "[P]"
48
- });
49
- assert.equal(cfg.prefix, "[P]");
50
- });
51
- test("forwardToQQBridge official\uFF1A\u7F3A group_openid \u4E0D\u629B\u9519", () => {
52
- assert.doesNotThrow(() => {
53
- forwardToQQBridge(
54
- {
55
- backend: "official",
56
- creds: { appId: "a", appSecret: "s" },
57
- groupOpenid: "",
58
- prefix: "[MC]"
59
- },
60
- "ch",
61
- "Steve",
62
- "hi",
63
- "xuid"
64
- );
65
- });
66
- });
67
- test("forwardToQQBridge llbot\uFF1A\u7F3A\u7FA4\u53F7\u4E0D\u629B\u9519", () => {
68
- assert.doesNotThrow(() => {
69
- forwardToQQBridge(
70
- {
71
- backend: "llbot",
72
- llbot: {
73
- host: "127.0.0.1",
74
- port: 3004,
75
- token: "",
76
- groupId: "0",
77
- prefix: "[MC]"
78
- }
79
- },
80
- "ch",
81
- "Steve",
82
- "hi",
83
- "xuid"
84
- );
85
- });
86
- });
87
- //# sourceMappingURL=bridge.test.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/domain/bridge.test.ts"],
4
- "sourcesContent": ["import assert from \"node:assert/strict\";\r\nimport test from \"node:test\";\r\nimport { makeLLBotConfig, makeOutboundConfig, forwardToQQBridge } from \"./bridge.js\";\r\n\r\ntest(\"makeOutboundConfig\uFF1Aofficial / llbot \u5206\u652F\", () => {\r\n const official = makeOutboundConfig({\r\n QQ_BACKEND: \"official\",\r\n LLBOT_HOST: \"127.0.0.1\",\r\n LLBOT_PORT: 3004,\r\n LLBOT_TOKEN: \"\",\r\n QQ_GROUP_ID: \"123\",\r\n QQ_APP_ID: \"app\",\r\n QQ_APP_SECRET: \"sec\",\r\n QQ_SANDBOX: true,\r\n QQ_GROUP_OPENID: \"GOPEN\",\r\n MCTOQQ_PREFIX: \"[MC]\",\r\n });\r\n assert.equal(official.backend, \"official\");\r\n if (official.backend === \"official\") {\r\n assert.equal(official.groupOpenid, \"GOPEN\");\r\n assert.equal(official.creds.sandbox, true);\r\n assert.equal(official.prefix, \"[MC]\");\r\n }\r\n\r\n const llbot = makeOutboundConfig({\r\n QQ_BACKEND: \"llbot\",\r\n LLBOT_HOST: \"127.0.0.1\",\r\n LLBOT_PORT: 3004,\r\n LLBOT_TOKEN: \"t\",\r\n QQ_GROUP_ID: \"99\",\r\n QQ_APP_ID: \"\",\r\n QQ_APP_SECRET: \"\",\r\n QQ_SANDBOX: false,\r\n QQ_GROUP_OPENID: \"\",\r\n MCTOQQ_PREFIX: \"[X]\",\r\n });\r\n assert.equal(llbot.backend, \"llbot\");\r\n if (llbot.backend === \"llbot\") {\r\n assert.equal(llbot.llbot.groupId, \"99\");\r\n assert.equal(llbot.llbot.prefix, \"[X]\");\r\n }\r\n});\r\n\r\ntest(\"makeLLBotConfig \u4F7F\u7528 mctoqq_prefix\", () => {\r\n const cfg = makeLLBotConfig({\r\n LLBOT_HOST: \"h\",\r\n LLBOT_PORT: 1,\r\n LLBOT_TOKEN: \"\",\r\n QQ_GROUP_ID: \"1\",\r\n MCTOQQ_PREFIX: \"[P]\",\r\n });\r\n assert.equal(cfg.prefix, \"[P]\");\r\n});\r\n\r\ntest(\"forwardToQQBridge official\uFF1A\u7F3A group_openid \u4E0D\u629B\u9519\", () => {\r\n assert.doesNotThrow(() => {\r\n forwardToQQBridge(\r\n {\r\n backend: \"official\",\r\n creds: { appId: \"a\", appSecret: \"s\" },\r\n groupOpenid: \"\",\r\n prefix: \"[MC]\",\r\n },\r\n \"ch\",\r\n \"Steve\",\r\n \"hi\",\r\n \"xuid\"\r\n );\r\n });\r\n});\r\n\r\ntest(\"forwardToQQBridge llbot\uFF1A\u7F3A\u7FA4\u53F7\u4E0D\u629B\u9519\", () => {\r\n assert.doesNotThrow(() => {\r\n forwardToQQBridge(\r\n {\r\n backend: \"llbot\",\r\n llbot: {\r\n host: \"127.0.0.1\",\r\n port: 3004,\r\n token: \"\",\r\n groupId: \"0\",\r\n prefix: \"[MC]\",\r\n },\r\n },\r\n \"ch\",\r\n \"Steve\",\r\n \"hi\",\r\n \"xuid\"\r\n );\r\n });\r\n});\r\n\r"],
5
- "mappings": "AAAA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,iBAAiB,oBAAoB,yBAAyB;AAEvE,KAAK,yDAA0C,MAAM;AACnD,QAAM,WAAW,mBAAmB;AAAA,IAClC,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,WAAW;AAAA,IACX,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,EACjB,CAAC;AACD,SAAO,MAAM,SAAS,SAAS,UAAU;AACzC,MAAI,SAAS,YAAY,YAAY;AACnC,WAAO,MAAM,SAAS,aAAa,OAAO;AAC1C,WAAO,MAAM,SAAS,MAAM,SAAS,IAAI;AACzC,WAAO,MAAM,SAAS,QAAQ,MAAM;AAAA,EACtC;AAEA,QAAM,QAAQ,mBAAmB;AAAA,IAC/B,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,WAAW;AAAA,IACX,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,EACjB,CAAC;AACD,SAAO,MAAM,MAAM,SAAS,OAAO;AACnC,MAAI,MAAM,YAAY,SAAS;AAC7B,WAAO,MAAM,MAAM,MAAM,SAAS,IAAI;AACtC,WAAO,MAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,EACxC;AACF,CAAC;AAED,KAAK,8CAAoC,MAAM;AAC7C,QAAM,MAAM,gBAAgB;AAAA,IAC1B,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,eAAe;AAAA,EACjB,CAAC;AACD,SAAO,MAAM,IAAI,QAAQ,KAAK;AAChC,CAAC;AAED,KAAK,0EAAiD,MAAM;AAC1D,SAAO,aAAa,MAAM;AACxB;AAAA,MACE;AAAA,QACE,SAAS;AAAA,QACT,OAAO,EAAE,OAAO,KAAK,WAAW,IAAI;AAAA,QACpC,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;AAED,KAAK,qEAAkC,MAAM;AAC3C,SAAO,aAAa,MAAM;AACxB;AAAA,MACE;AAAA,QACE,SAAS;AAAA,QACT,OAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;",
6
- "names": []
7
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * messages 出站过滤单测
3
- */
4
- export {};
5
- //# sourceMappingURL=messages-bridge-filter.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"messages-bridge-filter.test.d.ts","sourceRoot":"","sources":["../../src/domain/messages-bridge-filter.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -1,20 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { shouldForwardChatToQQ } from "../routes/messages.js";
4
- test("shouldForwardChatToQQ\uFF1A\u672A\u914D bridge \u4E0D\u51FA\u7AD9", () => {
5
- assert.equal(shouldForwardChatToQQ("", "bridge", "xuid"), false);
6
- });
7
- test("shouldForwardChatToQQ\uFF1A\u9891\u9053\u4E0D\u5339\u914D\u4E0D\u51FA\u7AD9", () => {
8
- assert.equal(shouldForwardChatToQQ("bridge", "other", "xuid"), false);
9
- });
10
- test("shouldForwardChatToQQ\uFF1Aqq_ \u6765\u6E90\u4E0D\u51FA\u7AD9\uFF08\u9632\u56DE\u73AF\uFF09", () => {
11
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", "qq_123"), false);
12
- });
13
- test("shouldForwardChatToQQ\uFF1A\u7A7A fromId \u4E0D\u51FA\u7AD9", () => {
14
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", ""), false);
15
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", " "), false);
16
- });
17
- test("shouldForwardChatToQQ\uFF1A\u5339\u914D\u9891\u9053\u7684\u73A9\u5BB6\u6D88\u606F\u51FA\u7AD9", () => {
18
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", "player-1"), true);
19
- });
20
- //# sourceMappingURL=messages-bridge-filter.test.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/domain/messages-bridge-filter.test.ts"],
4
- "sourcesContent": ["/**\n * messages \u51FA\u7AD9\u8FC7\u6EE4\u5355\u6D4B\n */\n\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { shouldForwardChatToQQ } from \"../routes/messages.js\";\n\ntest(\"shouldForwardChatToQQ\uFF1A\u672A\u914D bridge \u4E0D\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"\", \"bridge\", \"xuid\"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1A\u9891\u9053\u4E0D\u5339\u914D\u4E0D\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"other\", \"xuid\"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1Aqq_ \u6765\u6E90\u4E0D\u51FA\u7AD9\uFF08\u9632\u56DE\u73AF\uFF09\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \"qq_123\"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1A\u7A7A fromId \u4E0D\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \"\"), false);\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \" \"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1A\u5339\u914D\u9891\u9053\u7684\u73A9\u5BB6\u6D88\u606F\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \"player-1\"), true);\n});\n"],
5
- "mappings": "AAIA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,6BAA6B;AAEtC,KAAK,qEAAuC,MAAM;AAChD,SAAO,MAAM,sBAAsB,IAAI,UAAU,MAAM,GAAG,KAAK;AACjE,CAAC;AAED,KAAK,+EAAkC,MAAM;AAC3C,SAAO,MAAM,sBAAsB,UAAU,SAAS,MAAM,GAAG,KAAK;AACtE,CAAC;AAED,KAAK,+FAAwC,MAAM;AACjD,SAAO,MAAM,sBAAsB,UAAU,UAAU,QAAQ,GAAG,KAAK;AACzE,CAAC;AAED,KAAK,+DAAsC,MAAM;AAC/C,SAAO,MAAM,sBAAsB,UAAU,UAAU,EAAE,GAAG,KAAK;AACjE,SAAO,MAAM,sBAAsB,UAAU,UAAU,KAAK,GAAG,KAAK;AACtE,CAAC;AAED,KAAK,iGAAqC,MAAM;AAC9C,SAAO,MAAM,sBAAsB,UAAU,UAAU,UAAU,GAAG,IAAI;AAC1E,CAAC;",
6
- "names": []
7
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=qq-bind-status.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"qq-bind-status.test.d.ts","sourceRoot":"","sources":["../../src/domain/qq-bind-status.test.ts"],"names":[],"mappings":""}