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

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.
@@ -13,10 +13,10 @@
13
13
  */
14
14
  import type { IncomingMessage, ServerResponse } from "node:http";
15
15
  import { json as defaultJson, type Method } from "../lib/http.js";
16
- import { type ModuleAuth } from "./_shared.js";
16
+ import type { IdempotencyStore } from "../lib/idempotency-store.js";
17
17
  import type { SchemaRegistry } from "../schema-registry.js";
18
18
  import type { TxRunner } from "../tx-runner.js";
19
- import type { IdempotencyStore } from "../lib/idempotency-store.js";
19
+ import { type ModuleAuth } from "./_shared.js";
20
20
  export interface DbRoutesDeps {
21
21
  schemaRegistry: SchemaRegistry;
22
22
  txRunner: TxRunner;
@@ -1 +1 @@
1
- {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAEV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CA6KrB"}
1
+ {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAa,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAoKrB"}
@@ -1,7 +1,7 @@
1
1
  import { json as defaultJson } from "../lib/http.js";
2
2
  import { log } from "../lib/log.js";
3
- import { jsonV2Fail, jsonV2Ok } from "./_shared.js";
4
3
  import { PermissionDeniedError } from "../permission-gate.js";
4
+ import { jsonV2Fail, jsonV2Ok } from "./_shared.js";
5
5
  function createDbRoutes(depsIn) {
6
6
  const deps = depsIn;
7
7
  if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {
@@ -138,19 +138,10 @@ function createDbRoutes(depsIn) {
138
138
  if (m) {
139
139
  try {
140
140
  if (m[1] === "probe") {
141
- const r = await deps.idempotent.probe(
142
- moduleId,
143
- String(body.action),
144
- String(body.key)
145
- );
141
+ const r = await deps.idempotent.probe(moduleId, String(body.action), String(body.key));
146
142
  json(res, r);
147
143
  } else {
148
- const r = await deps.idempotent.commit(
149
- moduleId,
150
- String(body.action),
151
- String(body.key),
152
- body.value
153
- );
144
+ const r = await deps.idempotent.commit(moduleId, String(body.action), String(body.key), body.value);
154
145
  jsonV2Ok(res, { committed: r.ok });
155
146
  }
156
147
  } catch (e) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/db-routes.ts"],
4
- "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { log } from \"../lib/log.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\nimport type {\n DefineTableRequest,\n SchemaRegistry,\n} from \"../schema-registry.js\";\nimport type {\n TxRequest,\n TxRunner,\n TxStep,\n} from \"../tx-runner.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(\n moduleId,\n String(body.action),\n String(body.key)\n );\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(\n moduleId,\n String(body.action),\n String(body.key),\n body.value\n );\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\nimport { log } from \"../lib/log.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { DefineTableRequest, SchemaRegistry } from \"../schema-registry.js\";\nimport type { TxRequest, TxRunner, TxStep } from \"../tx-runner.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(moduleId, String(body.action), String(body.key));\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(moduleId, String(body.action), String(body.key), body.value);\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -107,9 +107,7 @@ test("jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)", async () =>
107
107
  test("normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)", async () => {
108
108
  const { normalizeOrderBy } = await import("./lib/order-by.js");
109
109
  deepEqual(normalizeOrderBy(void 0), []);
110
- deepEqual(normalizeOrderBy({ field: "created_at", dir: "desc" }), [
111
- { col: "created_at", dir: "desc" }
112
- ]);
110
+ deepEqual(normalizeOrderBy({ field: "created_at", dir: "desc" }), [{ col: "created_at", dir: "desc" }]);
113
111
  deepEqual(normalizeOrderBy({ col: "id" }), [{ col: "id", dir: "asc" }]);
114
112
  deepEqual(normalizeOrderBy([{ field: "a" }, { col: "b", dir: "desc" }]), [
115
113
  { col: "a", dir: "asc" },
@@ -236,7 +234,7 @@ test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/ge
236
234
  serviceRegistry: new ServiceRegistry(),
237
235
  enabled
238
236
  });
239
- const begin = runner.beginSession("feature-demo");
237
+ const begin = await runner.beginSession("feature-demo");
240
238
  equal(begin.ok, true);
241
239
  if (!begin.ok) return;
242
240
  const { txId } = begin;
@@ -262,4 +260,143 @@ test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/ge
262
260
  equal(rows[0]?.name, "alpha");
263
261
  db.close();
264
262
  });
263
+ test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: \u5E76\u53D1 beginSession \u81EA\u52A8\u6392\u961F\u4E92\u65A5\u6267\u884C", async () => {
264
+ const { DatabaseSync } = await import("node:sqlite");
265
+ const { createQuery } = await import("./lib/sqlite.js");
266
+ const { SchemaRegistry } = await import("./schema-registry.js");
267
+ const { TxRunner } = await import("./tx-runner.js");
268
+ const db = new DatabaseSync(":memory:");
269
+ const query = createQuery(db);
270
+ const schema = new SchemaRegistry(db);
271
+ schema.define("feature-demo", {
272
+ name: "queue_items",
273
+ columns: {
274
+ id: { type: "text", primary: true },
275
+ val: { type: "text", notNull: true }
276
+ },
277
+ softDelete: false
278
+ });
279
+ const enabled = /* @__PURE__ */ new Map([
280
+ [
281
+ "feature-demo",
282
+ {
283
+ id: "feature-demo",
284
+ version: "1.0.0",
285
+ permissions: ["db:read:queue_items", "db:write:queue_items"],
286
+ services: { provides: [], requires: [] },
287
+ db: { tables: [] },
288
+ config: { key: "demo" }
289
+ }
290
+ ]
291
+ ]);
292
+ const runner = new TxRunner({
293
+ db,
294
+ query,
295
+ schema,
296
+ serviceRegistry: new ServiceRegistry(),
297
+ enabled
298
+ });
299
+ const s1 = await runner.beginSession("feature-demo");
300
+ equal(s1.ok, true);
301
+ if (!s1.ok) return;
302
+ let s2Started = false;
303
+ const s2Promise = runner.beginSession("feature-demo").then((res) => {
304
+ s2Started = true;
305
+ return res;
306
+ });
307
+ equal(s2Started, false);
308
+ const s1Step = await runner.stepSession(s1.txId, "feature-demo", {
309
+ op: "insert",
310
+ table: "queue_items",
311
+ row: { id: "1", val: "one" }
312
+ });
313
+ equal(s1Step.ok, true);
314
+ const s1Commit = runner.commitSession(s1.txId, "feature-demo");
315
+ equal(s1Commit.ok, true);
316
+ const s2 = await s2Promise;
317
+ equal(s2.ok, true);
318
+ equal(s2Started, true);
319
+ if (!s2.ok) return;
320
+ const s2Step = await runner.stepSession(s2.txId, "feature-demo", {
321
+ op: "insert",
322
+ table: "queue_items",
323
+ row: { id: "2", val: "two" }
324
+ });
325
+ equal(s2Step.ok, true);
326
+ const s2Commit = runner.commitSession(s2.txId, "feature-demo");
327
+ equal(s2Commit.ok, true);
328
+ const rows = db.prepare("SELECT val FROM queue_items ORDER BY id ASC").all();
329
+ equal(rows.length, 2);
330
+ equal(rows[0]?.val, "one");
331
+ equal(rows[1]?.val, "two");
332
+ db.close();
333
+ });
334
+ test("TxRunner \u5355\u8FDE\u63A5: run \u4E0E\u4EA4\u4E92\u4F1A\u8BDD\u5171\u7528\u4E8B\u52A1\u961F\u5217", async () => {
335
+ const { DatabaseSync } = await import("node:sqlite");
336
+ const { createQuery } = await import("./lib/sqlite.js");
337
+ const { SchemaRegistry } = await import("./schema-registry.js");
338
+ const { TxRunner } = await import("./tx-runner.js");
339
+ const db = new DatabaseSync(":memory:");
340
+ const query = createQuery(db);
341
+ const schema = new SchemaRegistry(db);
342
+ schema.define("feature-demo", {
343
+ name: "queue_items",
344
+ columns: {
345
+ id: { type: "text", primary: true },
346
+ val: { type: "text", notNull: true }
347
+ },
348
+ softDelete: false
349
+ });
350
+ const enabled = /* @__PURE__ */ new Map([
351
+ [
352
+ "feature-demo",
353
+ {
354
+ id: "feature-demo",
355
+ version: "1.0.0",
356
+ permissions: ["db:read:queue_items", "db:write:queue_items"],
357
+ services: { provides: [], requires: [] },
358
+ db: { tables: [] },
359
+ config: { key: "demo" }
360
+ }
361
+ ]
362
+ ]);
363
+ const runner = new TxRunner({
364
+ db,
365
+ query,
366
+ schema,
367
+ serviceRegistry: new ServiceRegistry(),
368
+ enabled
369
+ });
370
+ const session = await runner.beginSession("feature-demo");
371
+ equal(session.ok, true);
372
+ if (!session.ok) return;
373
+ let runFinished = false;
374
+ const queuedRun = runner.run({
375
+ moduleId: "feature-demo",
376
+ steps: [{ op: "query", table: "queue_items" }]
377
+ }).then((result) => {
378
+ runFinished = true;
379
+ return result;
380
+ });
381
+ equal(runFinished, false);
382
+ equal(runner.commitSession(session.txId, "feature-demo").ok, true);
383
+ equal((await queuedRun).ok, true);
384
+ equal(runFinished, true);
385
+ const running = runner.run({
386
+ moduleId: "feature-demo",
387
+ steps: [{ op: "query", table: "queue_items" }]
388
+ });
389
+ let sessionStarted = false;
390
+ const queuedSession = runner.beginSession("feature-demo").then((result) => {
391
+ sessionStarted = true;
392
+ return result;
393
+ });
394
+ equal(sessionStarted, false);
395
+ equal((await running).ok, true);
396
+ const nextSession = await queuedSession;
397
+ equal(nextSession.ok, true);
398
+ equal(sessionStarted, true);
399
+ if (nextSession.ok) equal(runner.rollbackSession(nextSession.txId, "feature-demo").ok, true);
400
+ db.close();
401
+ });
265
402
  //# sourceMappingURL=runtime.test.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/runtime.test.ts"],
4
- "sourcesContent": ["import { deepEqual, equal, rejects, throws } from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { SQL } from \"sql-template-strings\";\nimport { assertIdentifier, quoteIdentifier } from \"./lib/identifiers.js\";\nimport { parseNodeVersion } from \"./lib/runtime.js\";\nimport { raw, sql } from \"./lib/sql-helpers.js\";\nimport { DispatchError, ServiceRegistry } from \"./service-registry.js\";\n\ntest(\"parses Node versions\", () => {\n deepEqual(parseNodeVersion(\"22.5.1\"), { major: 22, minor: 5, patch: 1 });\n deepEqual(parseNodeVersion(\"v22.5.1\"), { major: 22, minor: 5, patch: 1 });\n});\n\ntest(\"accepts only safe SQL identifiers\", () => {\n equal(quoteIdentifier(\"sfmc_players\", \"table\"), '\"sfmc_players\"');\n throws(() => assertIdentifier(\"users; DROP TABLE x\"), /Invalid SQL/);\n});\n\ntest(\"sql() embeds trusted identifiers and binds values\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const q = sql(`SELECT * FROM ${TABLE} WHERE player_id = ?`, [\"p1\"]);\n equal(q.sql, \"SELECT * FROM sfmc_economy_accounts WHERE player_id = ?\");\n deepEqual(q.values, [\"p1\"]);\n});\n\ntest(\"raw() only works via append \u2014 template interpolation still becomes ?\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const broken = SQL`SELECT * FROM ${raw(TABLE)} WHERE id = ${\"p1\"}`;\n equal(broken.sql, \"SELECT * FROM ? WHERE id = ?\");\n\n const ok = SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${\"p1\"}`);\n equal(ok.sql, \"SELECT * FROM sfmc_economy_accounts WHERE id = ?\");\n deepEqual(ok.values, [\"p1\"]);\n});\n\ntest(\"ServiceRegistry: provider self-call skips requires (LSP with tx.call path)\", async () => {\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.stats.monthly\", async () => ({ ok: true }));\n\n const enabled = new Map([\n [\n \"feature-economy\",\n {\n id: \"feature-economy\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [{ name: \"economy.stats.monthly\" }], requires: [] as Array<{ name: string }> },\n db: { tables: [] as unknown[] },\n config: { key: \"economy\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const out = await reg.dispatch(enabled, \"feature-economy\", \"economy.stats.monthly\", {});\n deepEqual(out, { ok: true, result: { ok: true } });\n});\n\ntest(\"ServiceRegistry: missing service \u2192 no_such_service (not not_in_requires)\", async () => {\n const reg = new ServiceRegistry();\n const enabled = new Map([\n [\n \"feature-chat\",\n {\n id: \"feature-chat\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"chat\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n await rejects(\n () => reg.dispatch(enabled, \"feature-chat\", \"economy.account.get\", {}),\n (e: unknown) => e instanceof DispatchError && e.code === \"no_such_service\"\n );\n});\n\ntest(\"jsonV2Fail: ok \u65B9\u8A00 + extra(step) \u5408\u5E76(LSP/DRY)\", async () => {\n const { jsonV2Fail } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Fail(res, \"boom\", 400, \"bad_step\", { step: 2 });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 400);\n deepEqual(payload, { ok: false, error: \"boom\", code: \"bad_step\", step: 2 });\n});\n\ntest(\"jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)\", async () => {\n const { jsonV2Ok } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Ok(res, { rows: [1] });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 200);\n deepEqual(payload, { ok: true, rows: [1] });\n});\n\ntest(\"normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)\", async () => {\n const { normalizeOrderBy } = await import(\"./lib/order-by.js\");\n deepEqual(normalizeOrderBy(undefined), []);\n deepEqual(normalizeOrderBy({ field: \"created_at\", dir: \"desc\" }), [\n { col: \"created_at\", dir: \"desc\" },\n ]);\n deepEqual(normalizeOrderBy({ col: \"id\" }), [{ col: \"id\", dir: \"asc\" }]);\n deepEqual(normalizeOrderBy([{ field: \"a\" }, { col: \"b\", dir: \"desc\" }]), [\n { col: \"a\", dir: \"asc\" },\n { col: \"b\", dir: \"desc\" },\n ]);\n throws(() => normalizeOrderBy({ dir: \"asc\" }), /field\\/col/);\n});\n\ntest(\"syncModuleRuntimeState: enable/disable \u70ED\u66F4\u65B0 token+enabledSet(DIP)\", async () => {\n const { mkdtempSync, rmSync, readFileSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n const { tmpdir } = await import(\"node:os\");\n const { syncModuleRuntimeState } = await import(\"./module-runtime-sync.js\");\n const { deriveToken } = await import(\"./module-auth.js\");\n const { unregisterBuiltinPluginForModule } = await import(\"./services/builtin-handlers.js\");\n\n const root = mkdtempSync(join(tmpdir(), \"sfmc-runtime-sync-\"));\n const dbPath = join(root, \"data\", \"sfmc_data.db\");\n try {\n const enabledSet = new Set<string>([\"feature-a\"]);\n const enabledManifests = new Map();\n const moduleAuth = { tokens: { \"feature-a\": \"old\" } as Record<string, string>, secret: \"test-secret\" };\n const registry = new ServiceRegistry();\n const fakeManifest = {\n id: \"feature-b\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"b\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2;\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: true,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), true);\n equal(enabledManifests.has(\"feature-b\"), true);\n equal(moduleAuth.tokens[\"feature-b\"], deriveToken(\"feature-b\", \"test-secret\"));\n const store = JSON.parse(readFileSync(join(root, \"data\", \"module-tokens.json\"), \"utf8\"));\n equal(store.tokens[\"feature-b\"], moduleAuth.tokens[\"feature-b\"]);\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: false,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), false);\n equal(enabledManifests.has(\"feature-b\"), false);\n equal(moduleAuth.tokens[\"feature-b\"], undefined);\n equal(unregisterBuiltinPluginForModule(registry, \"feature-b\"), 0);\n } finally {\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"module-auth: ensureModuleToken / revoke \u590D\u7528 secret(DRY)\", async () => {\n const { deriveToken, ensureModuleToken, revokeModuleToken } = await import(\"./module-auth.js\");\n const secret = \"test-secret\";\n const auth = { secret, tokens: {} as Record<string, string> };\n equal(ensureModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], deriveToken(\"feature-afk\", secret));\n equal(ensureModuleToken(auth, \"feature-afk\"), false);\n equal(revokeModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], undefined);\n equal(revokeModuleToken(auth, \"feature-afk\"), false);\n});\n\ntest(\"builtin-handlers: \u70ED\u7981\u7528\u6309 moduleId \u5378\u8F7D(DRY/OCP)\", async () => {\n const { unregisterBuiltinPluginForModule, registerBuiltinPluginForModule } = await import(\n \"./services/builtin-handlers.js\"\n );\n\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.account.get\", async () => ({}));\n reg.registerHandler(\"feature-economy\", \"economy.account.debit\", async () => ({}));\n reg.registerHandler(\"other-mod\", \"other.ping\", async () => ({}));\n equal(unregisterBuiltinPluginForModule(reg, \"feature-economy\"), 2);\n equal(reg.list().length, 1);\n equal(reg.list()[0]?.moduleId, \"other-mod\");\n equal(unregisterBuiltinPluginForModule(reg, \"feature-unknown\"), 0);\n\n // \u5DF2\u6709\u540C moduleId handler \u65F6\u70ED\u542F\u7528\u8DF3\u8FC7\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), true);\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), false);\n});\n\ntest(\"TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/get(PR #31 leftover)\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"demo_items\",\n columns: {\n id: { type: \"text\", primary: true },\n name: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:demo_items\", \"db:write:demo_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n const begin = runner.beginSession(\"feature-demo\");\n equal(begin.ok, true);\n if (!begin.ok) return;\n const { txId } = begin;\n\n const ins = await runner.stepSession(txId, \"feature-demo\", {\n op: \"insert\",\n table: \"demo_items\",\n row: { id: \"a1\", name: \"alpha\" },\n });\n equal(ins.ok, true);\n if (!ins.ok) return;\n equal((ins.result as { op: string; row: { id: string } }).row.id, \"a1\");\n\n const got = await runner.stepSession(txId, \"feature-demo\", {\n op: \"get\",\n table: \"demo_items\",\n id: \"a1\",\n });\n equal(got.ok, true);\n if (!got.ok) return;\n equal((got.result as { row: { name: string } | null }).row?.name, \"alpha\");\n\n const committed = runner.commitSession(txId, \"feature-demo\");\n equal(committed.ok, true);\n\n const rows = db.prepare(\"SELECT name FROM demo_items WHERE id = ?\").all(\"a1\") as Array<{ name: string }>;\n equal(rows[0]?.name, \"alpha\");\n db.close();\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { deepEqual, equal, rejects, throws } from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { SQL } from \"sql-template-strings\";\nimport { assertIdentifier, quoteIdentifier } from \"./lib/identifiers.js\";\nimport { parseNodeVersion } from \"./lib/runtime.js\";\nimport { raw, sql } from \"./lib/sql-helpers.js\";\nimport { DispatchError, ServiceRegistry } from \"./service-registry.js\";\n\ntest(\"parses Node versions\", () => {\n deepEqual(parseNodeVersion(\"22.5.1\"), { major: 22, minor: 5, patch: 1 });\n deepEqual(parseNodeVersion(\"v22.5.1\"), { major: 22, minor: 5, patch: 1 });\n});\n\ntest(\"accepts only safe SQL identifiers\", () => {\n equal(quoteIdentifier(\"sfmc_players\", \"table\"), '\"sfmc_players\"');\n throws(() => assertIdentifier(\"users; DROP TABLE x\"), /Invalid SQL/);\n});\n\ntest(\"sql() embeds trusted identifiers and binds values\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const q = sql(`SELECT * FROM ${TABLE} WHERE player_id = ?`, [\"p1\"]);\n equal(q.sql, \"SELECT * FROM sfmc_economy_accounts WHERE player_id = ?\");\n deepEqual(q.values, [\"p1\"]);\n});\n\ntest(\"raw() only works via append \u2014 template interpolation still becomes ?\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const broken = SQL`SELECT * FROM ${raw(TABLE)} WHERE id = ${\"p1\"}`;\n equal(broken.sql, \"SELECT * FROM ? WHERE id = ?\");\n\n const ok = SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${\"p1\"}`);\n equal(ok.sql, \"SELECT * FROM sfmc_economy_accounts WHERE id = ?\");\n deepEqual(ok.values, [\"p1\"]);\n});\n\ntest(\"ServiceRegistry: provider self-call skips requires (LSP with tx.call path)\", async () => {\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.stats.monthly\", async () => ({ ok: true }));\n\n const enabled = new Map([\n [\n \"feature-economy\",\n {\n id: \"feature-economy\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [{ name: \"economy.stats.monthly\" }], requires: [] as Array<{ name: string }> },\n db: { tables: [] as unknown[] },\n config: { key: \"economy\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const out = await reg.dispatch(enabled, \"feature-economy\", \"economy.stats.monthly\", {});\n deepEqual(out, { ok: true, result: { ok: true } });\n});\n\ntest(\"ServiceRegistry: missing service \u2192 no_such_service (not not_in_requires)\", async () => {\n const reg = new ServiceRegistry();\n const enabled = new Map([\n [\n \"feature-chat\",\n {\n id: \"feature-chat\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"chat\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n await rejects(\n () => reg.dispatch(enabled, \"feature-chat\", \"economy.account.get\", {}),\n (e: unknown) => e instanceof DispatchError && e.code === \"no_such_service\"\n );\n});\n\ntest(\"jsonV2Fail: ok \u65B9\u8A00 + extra(step) \u5408\u5E76(LSP/DRY)\", async () => {\n const { jsonV2Fail } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Fail(res, \"boom\", 400, \"bad_step\", { step: 2 });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 400);\n deepEqual(payload, { ok: false, error: \"boom\", code: \"bad_step\", step: 2 });\n});\n\ntest(\"jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)\", async () => {\n const { jsonV2Ok } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Ok(res, { rows: [1] });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 200);\n deepEqual(payload, { ok: true, rows: [1] });\n});\n\ntest(\"normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)\", async () => {\n const { normalizeOrderBy } = await import(\"./lib/order-by.js\");\n deepEqual(normalizeOrderBy(undefined), []);\n deepEqual(normalizeOrderBy({ field: \"created_at\", dir: \"desc\" }), [{ col: \"created_at\", dir: \"desc\" }]);\n deepEqual(normalizeOrderBy({ col: \"id\" }), [{ col: \"id\", dir: \"asc\" }]);\n deepEqual(normalizeOrderBy([{ field: \"a\" }, { col: \"b\", dir: \"desc\" }]), [\n { col: \"a\", dir: \"asc\" },\n { col: \"b\", dir: \"desc\" },\n ]);\n throws(() => normalizeOrderBy({ dir: \"asc\" }), /field\\/col/);\n});\n\ntest(\"syncModuleRuntimeState: enable/disable \u70ED\u66F4\u65B0 token+enabledSet(DIP)\", async () => {\n const { mkdtempSync, rmSync, readFileSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n const { tmpdir } = await import(\"node:os\");\n const { syncModuleRuntimeState } = await import(\"./module-runtime-sync.js\");\n const { deriveToken } = await import(\"./module-auth.js\");\n const { unregisterBuiltinPluginForModule } = await import(\"./services/builtin-handlers.js\");\n\n const root = mkdtempSync(join(tmpdir(), \"sfmc-runtime-sync-\"));\n const dbPath = join(root, \"data\", \"sfmc_data.db\");\n try {\n const enabledSet = new Set<string>([\"feature-a\"]);\n const enabledManifests = new Map();\n const moduleAuth = { tokens: { \"feature-a\": \"old\" } as Record<string, string>, secret: \"test-secret\" };\n const registry = new ServiceRegistry();\n const fakeManifest = {\n id: \"feature-b\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"b\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2;\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: true,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), true);\n equal(enabledManifests.has(\"feature-b\"), true);\n equal(moduleAuth.tokens[\"feature-b\"], deriveToken(\"feature-b\", \"test-secret\"));\n const store = JSON.parse(readFileSync(join(root, \"data\", \"module-tokens.json\"), \"utf8\"));\n equal(store.tokens[\"feature-b\"], moduleAuth.tokens[\"feature-b\"]);\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: false,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), false);\n equal(enabledManifests.has(\"feature-b\"), false);\n equal(moduleAuth.tokens[\"feature-b\"], undefined);\n equal(unregisterBuiltinPluginForModule(registry, \"feature-b\"), 0);\n } finally {\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"module-auth: ensureModuleToken / revoke \u590D\u7528 secret(DRY)\", async () => {\n const { deriveToken, ensureModuleToken, revokeModuleToken } = await import(\"./module-auth.js\");\n const secret = \"test-secret\";\n const auth = { secret, tokens: {} as Record<string, string> };\n equal(ensureModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], deriveToken(\"feature-afk\", secret));\n equal(ensureModuleToken(auth, \"feature-afk\"), false);\n equal(revokeModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], undefined);\n equal(revokeModuleToken(auth, \"feature-afk\"), false);\n});\n\ntest(\"builtin-handlers: \u70ED\u7981\u7528\u6309 moduleId \u5378\u8F7D(DRY/OCP)\", async () => {\n const { unregisterBuiltinPluginForModule, registerBuiltinPluginForModule } =\n await import(\"./services/builtin-handlers.js\");\n\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.account.get\", async () => ({}));\n reg.registerHandler(\"feature-economy\", \"economy.account.debit\", async () => ({}));\n reg.registerHandler(\"other-mod\", \"other.ping\", async () => ({}));\n equal(unregisterBuiltinPluginForModule(reg, \"feature-economy\"), 2);\n equal(reg.list().length, 1);\n equal(reg.list()[0]?.moduleId, \"other-mod\");\n equal(unregisterBuiltinPluginForModule(reg, \"feature-unknown\"), 0);\n\n // \u5DF2\u6709\u540C moduleId handler \u65F6\u70ED\u542F\u7528\u8DF3\u8FC7\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), true);\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), false);\n});\n\ntest(\"TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/get(PR #31 leftover)\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"demo_items\",\n columns: {\n id: { type: \"text\", primary: true },\n name: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:demo_items\", \"db:write:demo_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n const begin = await runner.beginSession(\"feature-demo\");\n equal(begin.ok, true);\n if (!begin.ok) return;\n const { txId } = begin;\n\n const ins = await runner.stepSession(txId, \"feature-demo\", {\n op: \"insert\",\n table: \"demo_items\",\n row: { id: \"a1\", name: \"alpha\" },\n });\n equal(ins.ok, true);\n if (!ins.ok) return;\n equal((ins.result as { op: string; row: { id: string } }).row.id, \"a1\");\n\n const got = await runner.stepSession(txId, \"feature-demo\", {\n op: \"get\",\n table: \"demo_items\",\n id: \"a1\",\n });\n equal(got.ok, true);\n if (!got.ok) return;\n equal((got.result as { row: { name: string } | null }).row?.name, \"alpha\");\n\n const committed = runner.commitSession(txId, \"feature-demo\");\n equal(committed.ok, true);\n\n const rows = db.prepare(\"SELECT name FROM demo_items WHERE id = ?\").all(\"a1\") as Array<{ name: string }>;\n equal(rows[0]?.name, \"alpha\");\n db.close();\n});\n\ntest(\"TxRunner \u4EA4\u4E92\u4F1A\u8BDD: \u5E76\u53D1 beginSession \u81EA\u52A8\u6392\u961F\u4E92\u65A5\u6267\u884C\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"queue_items\",\n columns: {\n id: { type: \"text\", primary: true },\n val: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:queue_items\", \"db:write:queue_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n // Session 1 \u542F\u52A8\n const s1 = await runner.beginSession(\"feature-demo\");\n equal(s1.ok, true);\n if (!s1.ok) return;\n\n // Session 2 \u5E76\u53D1\u542F\u52A8\uFF08\u4F1A\u5F02\u6B65\u6392\u961F\u7B49\u5F85 Session 1 \u5B8C\u6210\uFF09\n let s2Started = false;\n const s2Promise = runner.beginSession(\"feature-demo\").then((res) => {\n s2Started = true;\n return res;\n });\n\n // \u6B64\u65F6 Session 2 \u5E94\u5904\u4E8E\u7B49\u5F85\u4E2D\uFF0C\u672A\u8FDB\u5165\n equal(s2Started, false);\n\n // Session 1 \u63D2\u5165\u6B65\u9AA4\n const s1Step = await runner.stepSession(s1.txId, \"feature-demo\", {\n op: \"insert\",\n table: \"queue_items\",\n row: { id: \"1\", val: \"one\" },\n });\n equal(s1Step.ok, true);\n\n // Session 1 \u63D0\u4EA4\n const s1Commit = runner.commitSession(s1.txId, \"feature-demo\");\n equal(s1Commit.ok, true);\n\n // Session 1 \u63D0\u4EA4\u540E\uFF0CSession 2 \u5E94\u8BE5\u88AB\u5524\u9192\u5E76\u6210\u529F\u83B7\u5F97\u4E8B\u52A1\n const s2 = await s2Promise;\n equal(s2.ok, true);\n equal(s2Started, true);\n if (!s2.ok) return;\n\n // Session 2 \u63D2\u5165\u6B65\u9AA4\u5E76\u63D0\u4EA4\n const s2Step = await runner.stepSession(s2.txId, \"feature-demo\", {\n op: \"insert\",\n table: \"queue_items\",\n row: { id: \"2\", val: \"two\" },\n });\n equal(s2Step.ok, true);\n\n const s2Commit = runner.commitSession(s2.txId, \"feature-demo\");\n equal(s2Commit.ok, true);\n\n const rows = db.prepare(\"SELECT val FROM queue_items ORDER BY id ASC\").all() as Array<{ val: string }>;\n equal(rows.length, 2);\n equal(rows[0]?.val, \"one\");\n equal(rows[1]?.val, \"two\");\n db.close();\n});\n\ntest(\"TxRunner \u5355\u8FDE\u63A5: run \u4E0E\u4EA4\u4E92\u4F1A\u8BDD\u5171\u7528\u4E8B\u52A1\u961F\u5217\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"queue_items\",\n columns: {\n id: { type: \"text\", primary: true },\n val: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:queue_items\", \"db:write:queue_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n const session = await runner.beginSession(\"feature-demo\");\n equal(session.ok, true);\n if (!session.ok) return;\n\n let runFinished = false;\n const queuedRun = runner\n .run({\n moduleId: \"feature-demo\",\n steps: [{ op: \"query\", table: \"queue_items\" }],\n })\n .then((result) => {\n runFinished = true;\n return result;\n });\n equal(runFinished, false);\n\n equal(runner.commitSession(session.txId, \"feature-demo\").ok, true);\n equal((await queuedRun).ok, true);\n equal(runFinished, true);\n\n const running = runner.run({\n moduleId: \"feature-demo\",\n steps: [{ op: \"query\", table: \"queue_items\" }],\n });\n let sessionStarted = false;\n const queuedSession = runner.beginSession(\"feature-demo\").then((result) => {\n sessionStarted = true;\n return result;\n });\n equal(sessionStarted, false);\n equal((await running).ok, true);\n\n const nextSession = await queuedSession;\n equal(nextSession.ok, true);\n equal(sessionStarted, true);\n if (nextSession.ok) equal(runner.rollbackSession(nextSession.txId, \"feature-demo\").ok, true);\n\n db.close();\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -24,10 +24,10 @@
24
24
  * 实现:handler 返回 result 写回 step 输出;若 handler 还要写 db,应走其它接口在事务外做。
25
25
  */
26
26
  import type { DatabaseSync } from "node:sqlite";
27
+ import type { ModuleManifestV2 } from "./manifest-loader.js";
27
28
  import type { SchemaRegistry } from "./schema-registry.js";
28
- import type { WhereExpr } from "./where.js";
29
29
  import { type ServiceRegistry } from "./service-registry.js";
30
- import type { ModuleManifestV2 } from "./manifest-loader.js";
30
+ import type { WhereExpr } from "./where.js";
31
31
  export interface TxStepQuery {
32
32
  op: "query";
33
33
  table: string;
@@ -128,20 +128,22 @@ export interface TxRunnerDeps {
128
128
  export declare class TxRunner {
129
129
  private readonly deps;
130
130
  private readonly sessions;
131
+ private transactionBusy;
132
+ private readonly transactionWaiters;
131
133
  constructor(deps: TxRunnerDeps);
132
134
  run(req: TxRequest): Promise<TxResponse | TxError>;
133
135
  /**
134
136
  * 开启交互式事务会话(协议流程:begin → step* → commit | rollback)。
135
137
  * 供客户端 `db.tx` 在回调内部异步执行并实时读回 query / get / call 的服务端执行结果。
136
- * 单连接保护:新会话开启时自动清理历史残留会话,避免事务锁冲突。
138
+ * 单连接互斥保护:与批量 run() 共用事务队列,并在提交/回滚时唤醒下一个等待者。
137
139
  *
138
140
  * @param moduleId 请求模块唯一标识符。
139
141
  * @returns 成功返回分配的会话 txId,失败返回错误响应。
140
142
  */
141
- beginSession(moduleId: string): {
143
+ beginSession(moduleId: string): Promise<{
142
144
  ok: true;
143
145
  txId: string;
144
- } | TxError;
146
+ } | TxError>;
145
147
  /**
146
148
  * 在当前交互式事务会话中顺序执行单个步骤。
147
149
  *
@@ -174,6 +176,8 @@ export declare class TxRunner {
174
176
  ok: true;
175
177
  } | TxError;
176
178
  private abortSession;
179
+ private acquireTransactionSlot;
180
+ private releaseTransactionSlot;
177
181
  private mapErrorCode;
178
182
  private rollback;
179
183
  /** 单 step 由对应权限检查 + SQL 路径执行 */
@@ -1 +1 @@
1
- {"version":3,"file":"tx-runner.d.ts","sourceRoot":"","sources":["../src/tx-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tx-runner.d.ts","sourceRoot":"","sources":["../src/tx-runner.ts"],"names":[],"mappings":"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"}
package/dist/tx-runner.js CHANGED
@@ -1,13 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { compile } from "./where.js";
3
2
  import { log } from "./lib/log.js";
4
- import { DispatchError } from "./service-registry.js";
5
- import {
6
- PermissionDeniedError,
7
- Perm,
8
- assertModulePermission
9
- } from "./permission-gate.js";
10
3
  import { normalizeOrderBy } from "./lib/order-by.js";
4
+ import { Perm, PermissionDeniedError, assertModulePermission } from "./permission-gate.js";
5
+ import { DispatchError } from "./service-registry.js";
6
+ import { compile } from "./where.js";
11
7
  const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
12
8
  function identOk(t) {
13
9
  if (!IDENT.test(t)) throw new Error(`[tx] invalid table name "${t}"`);
@@ -18,15 +14,19 @@ class TxRunner {
18
14
  }
19
15
  deps;
20
16
  sessions = /* @__PURE__ */ new Map();
17
+ transactionBusy = false;
18
+ transactionWaiters = [];
21
19
  async run(req) {
22
20
  const { moduleId, steps } = req;
23
21
  const manifest = this.deps.enabled.get(moduleId);
24
22
  if (!manifest) return { ok: false, step: -1, error: "\u6A21\u5757\u672A enabled", code: "forbidden" };
25
23
  const traceId = randomUUID().slice(0, 8);
26
24
  const results = [];
25
+ await this.acquireTransactionSlot();
27
26
  try {
28
27
  this.deps.db.exec("BEGIN IMMEDIATE");
29
28
  } catch (e) {
29
+ this.releaseTransactionSlot();
30
30
  return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${e.message}`, code: "internal" };
31
31
  }
32
32
  for (let i = 0; i < steps.length; i++) {
@@ -36,6 +36,7 @@ class TxRunner {
36
36
  results.push(r);
37
37
  } catch (err) {
38
38
  this.rollback();
39
+ this.releaseTransactionSlot();
39
40
  const code = this.mapErrorCode(err);
40
41
  log.warn(`[tx ${traceId}] step=${i} failed: ${err.message}`);
41
42
  return { ok: false, step: i, error: err.message, code };
@@ -45,37 +46,35 @@ class TxRunner {
45
46
  this.deps.db.exec("COMMIT");
46
47
  } catch (e) {
47
48
  this.rollback();
49
+ this.releaseTransactionSlot();
48
50
  return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${e.message}`, code: "internal" };
49
51
  }
52
+ this.releaseTransactionSlot();
50
53
  log.info(`[tx ${traceId}] module=${moduleId} ${steps.length} steps OK`);
51
54
  return { ok: true, results };
52
55
  }
53
56
  /**
54
57
  * 开启交互式事务会话(协议流程:begin → step* → commit | rollback)。
55
58
  * 供客户端 `db.tx` 在回调内部异步执行并实时读回 query / get / call 的服务端执行结果。
56
- * 单连接保护:新会话开启时自动清理历史残留会话,避免事务锁冲突。
59
+ * 单连接互斥保护:与批量 run() 共用事务队列,并在提交/回滚时唤醒下一个等待者。
57
60
  *
58
61
  * @param moduleId 请求模块唯一标识符。
59
62
  * @returns 成功返回分配的会话 txId,失败返回错误响应。
60
63
  */
61
- beginSession(moduleId) {
64
+ async beginSession(moduleId) {
62
65
  const manifest = this.deps.enabled.get(moduleId);
63
66
  if (!manifest) return { ok: false, step: -1, error: "\u6A21\u5757\u672A enabled", code: "forbidden" };
64
- if (this.sessions.size > 0) {
65
- for (const id of [...this.sessions.keys()]) {
66
- this.abortSession(id);
67
- }
68
- }
67
+ await this.acquireTransactionSlot();
69
68
  try {
70
69
  this.deps.db.exec("BEGIN IMMEDIATE");
71
70
  } catch (e) {
71
+ this.releaseTransactionSlot();
72
72
  return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${e.message}`, code: "internal" };
73
73
  }
74
74
  const txId = randomUUID();
75
75
  this.sessions.set(txId, {
76
76
  moduleId,
77
- results: [],
78
- openedAt: Date.now()
77
+ results: []
79
78
  });
80
79
  log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);
81
80
  return { ok: true, txId };
@@ -136,6 +135,7 @@ class TxRunner {
136
135
  }
137
136
  const results = session.results;
138
137
  this.sessions.delete(txId);
138
+ this.releaseTransactionSlot();
139
139
  log.info(`[tx-session ${txId.slice(0, 8)}] commit ${results.length} steps OK`);
140
140
  return { ok: true, results };
141
141
  }
@@ -161,8 +161,24 @@ class TxRunner {
161
161
  abortSession(txId) {
162
162
  this.sessions.delete(txId);
163
163
  this.rollback();
164
+ this.releaseTransactionSlot();
164
165
  log.info(`[tx-session ${txId.slice(0, 8)}] aborted`);
165
166
  }
167
+ async acquireTransactionSlot() {
168
+ if (!this.transactionBusy) {
169
+ this.transactionBusy = true;
170
+ return;
171
+ }
172
+ await new Promise((resolve) => this.transactionWaiters.push(resolve));
173
+ }
174
+ releaseTransactionSlot() {
175
+ const next = this.transactionWaiters.shift();
176
+ if (next) {
177
+ next();
178
+ return;
179
+ }
180
+ this.transactionBusy = false;
181
+ }
166
182
  mapErrorCode(err) {
167
183
  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";
168
184
  }
@@ -307,9 +323,7 @@ class TxRunner {
307
323
  const cols = this.deps.db.prepare(`PRAGMA table_info("${step.table}")`).all();
308
324
  const hasDeletedAt = cols.find((c) => c.name === "_deleted_at");
309
325
  if (!hasDeletedAt) {
310
- throw new Error(
311
- `[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`
312
- );
326
+ throw new Error(`[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`);
313
327
  }
314
328
  const sql = `UPDATE "${step.table}" SET "_deleted_at" = ?, "_version" = COALESCE("_version",0)+1 WHERE ${where.clause}`;
315
329
  const r = this.deps.db.prepare(sql).run(Date.now(), ...where.values);
@@ -326,13 +340,10 @@ class TxRunner {
326
340
  }
327
341
  async doService(_mid, mod, step) {
328
342
  assertModulePermission(mod.id, mod.permissions, Perm.service(step.name));
329
- const result = await this.deps.serviceRegistry.dispatch(
330
- this.deps.enabled,
331
- mod.id,
332
- step.name,
333
- step.input,
334
- { query: this.deps.query, db: this.deps.db }
335
- );
343
+ const result = await this.deps.serviceRegistry.dispatch(this.deps.enabled, mod.id, step.name, step.input, {
344
+ query: this.deps.query,
345
+ db: this.deps.db
346
+ });
336
347
  return { op: "service", result: result.result };
337
348
  }
338
349
  }
@@ -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 type { SchemaRegistry } from \"./schema-registry.js\";\nimport type { WhereExpr } from \"./where.js\";\nimport { compile } from \"./where.js\";\nimport { log } from \"./lib/log.js\";\nimport { DispatchError, type ServiceRegistry } from \"./service-registry.js\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport {\n PermissionDeniedError,\n Perm,\n assertModulePermission,\n} from \"./permission-gate.js\";\nimport { normalizeOrderBy } from \"./lib/order-by.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 =\n | TxStepQuery\n | TxStepGet\n | TxStepInsert\n | TxStepUpdate\n | TxStepDelete\n | TxStepAudit\n | 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 openedAt: 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\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 try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\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 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 return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\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\u4FDD\u62A4\uFF1A\u65B0\u4F1A\u8BDD\u5F00\u542F\u65F6\u81EA\u52A8\u6E05\u7406\u5386\u53F2\u6B8B\u7559\u4F1A\u8BDD\uFF0C\u907F\u514D\u4E8B\u52A1\u9501\u51B2\u7A81\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 beginSession(moduleId: string): { 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 // \u6E05\u6B8B\u7559\u4F1A\u8BDD(\u5D29\u6E83/\u672A commit),\u518D\u5F00\u65B0\u4E8B\u52A1\n if (this.sessions.size > 0) {\n for (const id of [...this.sessions.keys()]) {\n this.abortSession(id);\n }\n }\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\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 openedAt: Date.now(),\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 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\n private abortSession(txId: string): void {\n this.sessions.delete(txId);\n this.rollback();\n log.info(`[tx-session ${txId.slice(0, 8)}] aborted`);\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(\n moduleId: string,\n manifest: ModuleManifestV2,\n step: TxStep\n ): 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(\n `[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`\n );\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(\n _mid: string,\n mod: ModuleManifestV2,\n step: TxStepService\n ): 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(\n this.deps.enabled,\n mod.id,\n step.name,\n step.input,\n { query: this.deps.query, db: this.deps.db }\n );\n return { op: \"service\", result: result.result };\n }\n}\n"],
5
- "mappings": "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",
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;",
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.8",
3
+ "version": "0.2.0-beta.9",
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,10 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "sql-template-strings": "^2.2.2",
35
- "@sfmc-bds/sdk": "0.2.0-beta.12"
35
+ "@sfmc-bds/sdk": "0.2.0-beta.13"
36
36
  },
37
37
  "devDependencies": {
38
- "@sfmc-bds/tools": "0.2.0-beta.13"
38
+ "@sfmc-bds/tools": "0.2.0-beta.14"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public",