@sfmc-bds/db-server 0.2.0-beta.7 → 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.
@@ -1,7 +1,16 @@
1
1
  import { log } from "./lib/log.js";
2
2
  const PLATFORM_MODULE_ID = "__platform__";
3
3
  const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
- const VALID_TYPES = /* @__PURE__ */ new Set(["text", "integer", "real", "blob"]);
4
+ const VALID_TYPES = /* @__PURE__ */ new Set([
5
+ "text",
6
+ "integer",
7
+ "real",
8
+ "blob",
9
+ "TEXT",
10
+ "INTEGER",
11
+ "REAL",
12
+ "BLOB"
13
+ ]);
5
14
  function assertValid(req, moduleId) {
6
15
  if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name "${req.name}"`);
7
16
  if (!req.columns || Object.keys(req.columns).length === 0) {
@@ -10,9 +19,11 @@ function assertValid(req, moduleId) {
10
19
  let pks = 0;
11
20
  for (const [colName, def] of Object.entries(req.columns)) {
12
21
  if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name "${colName}" in ${req.name}`);
13
- if (!VALID_TYPES.has(def.type)) {
22
+ const rawType = String(def.type || "");
23
+ if (!VALID_TYPES.has(rawType)) {
14
24
  throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type "${def.type}"`);
15
25
  }
26
+ def.type = rawType.toLowerCase();
16
27
  if (def.primary) pks++;
17
28
  if (def.ref) {
18
29
  const [t, c] = def.ref.split(".");
@@ -93,12 +104,65 @@ class SchemaRegistry {
93
104
  }
94
105
  /** 幂等地物理建表 + 建索引(供 define / finalize 共用)。 */
95
106
  createPhysical(t) {
96
- const cols = buildColumnList(t.columns, t.softDelete);
97
- this.db.exec(`CREATE TABLE IF NOT EXISTS "${t.name}" (${cols.join(", ")})`);
107
+ const existingTableInfo = this.db.prepare(`PRAGMA table_info("${t.name}")`).all();
108
+ if (existingTableInfo.length > 0) {
109
+ let rowCount = 0;
110
+ try {
111
+ const countRes = this.db.prepare(`SELECT COUNT(*) AS c FROM "${t.name}"`).get();
112
+ rowCount = countRes?.c ?? 0;
113
+ } catch {
114
+ rowCount = 0;
115
+ }
116
+ const existingColNames = new Set(existingTableInfo.map((c) => c.name));
117
+ const requiredCols = Object.keys(t.columns);
118
+ const hasMissingRequiredCol = requiredCols.some((c) => !existingColNames.has(c));
119
+ const pkDef = Object.entries(t.columns).find(([_, c]) => c.primary);
120
+ const pkMatches = !pkDef || existingTableInfo.some((c) => c.pk === 1 && c.name === pkDef[0]);
121
+ if (rowCount === 0 && (hasMissingRequiredCol || !pkMatches)) {
122
+ try {
123
+ this.db.exec(`DROP TABLE "${t.name}"`);
124
+ } catch {
125
+ }
126
+ const cols = buildColumnList(t.columns, t.softDelete);
127
+ this.db.exec(`CREATE TABLE "${t.name}" (${cols.join(", ")})`);
128
+ } else {
129
+ for (const [colName, colDef] of Object.entries(t.columns)) {
130
+ if (!existingColNames.has(colName)) {
131
+ const clause = buildColumnClause(colName, colDef);
132
+ try {
133
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN ${clause};`);
134
+ } catch (e) {
135
+ log.warn(`[schema] ${t.moduleId}: ALTER TABLE "${t.name}" ADD COLUMN "${colName}" \u5F02\u5E38: ${e.message}`);
136
+ }
137
+ }
138
+ }
139
+ if (t.softDelete) {
140
+ if (!existingColNames.has("_deleted_at")) {
141
+ try {
142
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN "_deleted_at" INTEGER;`);
143
+ } catch {
144
+ }
145
+ }
146
+ if (!existingColNames.has("_version")) {
147
+ try {
148
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN "_version" INTEGER DEFAULT 0;`);
149
+ } catch {
150
+ }
151
+ }
152
+ }
153
+ }
154
+ } else {
155
+ const cols = buildColumnList(t.columns, t.softDelete);
156
+ this.db.exec(`CREATE TABLE IF NOT EXISTS "${t.name}" (${cols.join(", ")})`);
157
+ }
98
158
  for (const [n, def] of Object.entries(t.columns)) {
99
159
  if (def.index) {
100
160
  const idxName = `idx_${t.name}_${n}`.slice(0, 60);
101
- this.db.exec(`CREATE INDEX IF NOT EXISTS "${idxName}" ON "${t.name}"("${n}")`);
161
+ try {
162
+ this.db.exec(`CREATE INDEX IF NOT EXISTS "${idxName}" ON "${t.name}"("${n}")`);
163
+ } catch (e) {
164
+ log.warn(`[schema] ${t.moduleId}: \u521B\u5EFA\u7D22\u5F15 "${idxName}" \u5931\u8D25: ${e.message}`);
165
+ }
102
166
  }
103
167
  }
104
168
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/schema-registry.ts"],
4
- "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u52A8\u6001\u6570\u636E\u8868\u7ED3\u6784\u6CE8\u518C\u4E0E\u521B\u5EFA\u7BA1\u7406\u5668\n *\n * \u534F\u8BAE\u4E0E\u6D41\u7A0B\uFF1A\n * 1. \u6A21\u5757\u542F\u52A8\u65F6\u8C03\u7528 `db.defineTable(name, columns, { softDelete })` \u5411\u670D\u52A1\u7AEF\u6CE8\u518C\u8868\u7ED3\u6784\u5B9A\u4E49\u3002\n * 2. `SchemaRegistry` \u6821\u9A8C\u5217\u5B9A\u4E49\u5408\u6CD5\u6027\u3001\u5916\u952E\u5F15\u7528\u4E0E\u4E3B\u952E\u552F\u4E00\u6027\u3002\n * 3. \u6821\u9A8C\u901A\u8FC7\u540E\u81EA\u52A8\u6267\u884C `CREATE TABLE IF NOT EXISTS` \u53CA\u7D22\u5F15\u521B\u5EFA\u8BED\u53E5\u3002\n *\n * \u51B2\u7A81\u4E0E\u9694\u79BB\u63A7\u5236\uFF1A\n * - \u76F8\u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49\u540C\u540D\u8868\uFF1A\u5177\u5907\u5E42\u7B49\u6027\uFF08\u8981\u6C42\u5217\u7ED3\u6784\u5B8C\u5168\u4E00\u81F4\uFF09\n * - \u4E0D\u540C\u6A21\u5757\u5B9A\u4E49\u76F8\u540C\u8868\u540D\uFF1A\u629B\u51FA\u51B2\u7A81\u5F02\u5E38\u62D2\u7EDD\u6CE8\u518C\n * - \u8F6F\u5220\u9664\u652F\u6301\uFF1A\u5F00\u542F `softDelete`\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09\u65F6\u81EA\u52A8\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0 `_deleted_at` \u4E0E\u4E50\u89C2\u9501 `_version`\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\n\nexport type ColumnType = \"text\" | \"integer\" | \"real\" | \"blob\";\n\nexport interface ColumnDef {\n type: ColumnType;\n primary?: boolean;\n notNull?: boolean;\n unique?: boolean;\n default?: Primitive;\n index?: boolean;\n ref?: string;\n}\n\nexport type Primitive = string | number | boolean | null;\n\nexport interface DefineTableRequest {\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete?: boolean;\n}\n\nexport interface DefineTableResult {\n table: string;\n created: boolean;\n indices: string[];\n}\n\ninterface DefinedTable {\n moduleId: string;\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete: boolean;\n}\n\nexport const PLATFORM_MODULE_ID = \"__platform__\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst VALID_TYPES: Set<ColumnType> = new Set([\"text\", \"integer\", \"real\", \"blob\"]);\n\nfunction assertValid(req: DefineTableRequest, moduleId: string): void {\n if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name \"${req.name}\"`);\n if (!req.columns || Object.keys(req.columns).length === 0) {\n throw new Error(`[schema] ${moduleId}: ${req.name} has no columns`);\n }\n let pks = 0;\n for (const [colName, def] of Object.entries(req.columns)) {\n if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name \"${colName}\" in ${req.name}`);\n if (!VALID_TYPES.has(def.type)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type \"${def.type}\"`);\n }\n if (def.primary) pks++;\n if (def.ref) {\n const [t, c] = def.ref.split(\".\");\n if (!t || !c || !IDENT.test(t) || !IDENT.test(c)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} invalid ref \"${def.ref}\"`);\n }\n }\n }\n if (pks > 1) throw new Error(`[schema] ${moduleId}: ${req.name} has ${pks} primary keys (max 1)`);\n}\n\n/**\n * \u5C06\u5355\u5217\u5B9A\u4E49\u7F16\u8BD1\u4E3A SQL \u5217\u5B9A\u4E49\u5B50\u53E5\uFF08\u5982 `\"id\" TEXT PRIMARY KEY`\uFF09\u3002\n *\n * @param name \u5217\u540D\u3002\n * @param def \u5217\u5C5E\u6027\u5B9A\u4E49\u3002\n * @returns \u7F16\u8BD1\u540E\u7684 SQL \u7247\u6BB5\u3002\n */\nfunction buildColumnClause(name: string, def: ColumnDef): string {\n const parts: string[] = [`\"${name}\"`, def.type.toUpperCase()];\n if (def.primary) parts.push(\"PRIMARY KEY\");\n if (def.notNull && !def.primary) parts.push(\"NOT NULL\");\n if (def.unique) parts.push(\"UNIQUE\");\n if (def.default !== undefined && def.default !== null) {\n if (typeof def.default === \"string\") parts.push(`DEFAULT '${def.default.replace(/'/g, \"''\")}'`);\n else parts.push(`DEFAULT ${def.default}`);\n }\n if (def.ref) parts.push(`REFERENCES ${def.ref}`);\n return parts.join(\" \");\n}\n\n/**\n * \u5C06\u6574\u5F20\u8868\u7684\u6240\u6709\u5217\u5B9A\u4E49\uFF08\u5305\u542B softDelete \u9690\u5F0F\u5217\uFF09\u7F16\u8BD1\u4E3A\u5217\u5B9A\u4E49\u5B57\u7B26\u4E32\u6570\u7EC4\u3002\n * \u8F6F\u5220\u9664\u5217 `_deleted_at` \u4E0E\u4E50\u89C2\u9501\u7248\u672C\u53F7 `_version` \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0\u3002\n *\n * @param columns \u5217\u7ED3\u6784\u5B9A\u4E49\u5B57\u5178\u3002\n * @param softDelete \u662F\u5426\u542F\u7528\u8F6F\u5220\u9664\u6269\u5C55\u5217\u3002\n * @returns \u7F16\u8BD1\u540E\u7684\u5217\u5B9A\u4E49\u5B50\u53E5\u6570\u7EC4\u3002\n */\nfunction buildColumnList(columns: Record<string, ColumnDef>, softDelete: boolean): string[] {\n const cols: string[] = [];\n for (const [n, def] of Object.entries(columns)) {\n cols.push(buildColumnClause(n, def));\n }\n if (softDelete) {\n cols.push('\"_deleted_at\" INTEGER', '\"_version\" INTEGER DEFAULT 0');\n }\n return cols;\n}\n\nexport class SchemaRegistry {\n private readonly db: DatabaseSync;\n private readonly tables = new Map<string, DefinedTable>();\n private finalized = false;\n\n constructor(db: DatabaseSync) {\n this.db = db;\n }\n\n /**\n * \u6CE8\u518C\u5E76\u521B\u5EFA\u6A21\u5757\u6570\u636E\u8868\u7ED3\u6784\u3002\n *\n * @param moduleId \u6CE8\u518C\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param req \u8868\u7ED3\u6784\u5B9A\u4E49\u8BF7\u6C42\u5BF9\u8C61\u3002\n * @returns \u5EFA\u8868\u7ED3\u679C\u4E0E\u521B\u5EFA\u7684\u7D22\u5F15\u5217\u8868\u3002\n */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\n\n if (this.finalized) {\n throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);\n }\n assertValid(req, moduleId);\n\n const existing = this.tables.get(req.name);\n if (existing) {\n if (existing.moduleId === moduleId) {\n // \u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49:\u6821\u9A8C columns \u4E00\u81F4\n if (JSON.stringify(existing.columns) !== JSON.stringify(req.columns)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",columns \u4E0D\u4E00\u81F4`);\n }\n if (existing.softDelete !== (req.softDelete ?? true)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",softDelete \u4E0D\u4E00\u81F4`);\n }\n log.info(`[schema] ${moduleId} \u91CD\u590D define \"${req.name}\" \u8DF3\u8FC7`);\n return { table: req.name, created: false, indices: [] };\n }\n throw new Error(\n `[schema] \u8868 \"${req.name}\" \u5DF2\u88AB\u6A21\u5757 ${existing.moduleId} \u5B9A\u4E49,${moduleId} \u4E0D\u80FD\u62A2\u540C\u540D\u8868`\n );\n }\n\n const softDelete = req.softDelete ?? true;\n const defined: DefinedTable = {\n moduleId,\n name: req.name,\n columns: req.columns,\n softDelete,\n };\n this.tables.set(req.name, defined);\n // \u7ACB\u5373\u7269\u7406\u5EFA\u8868(\u5E42\u7B49 CREATE TABLE IF NOT EXISTS)\u3002\n // \u539F\u8BBE\u8BA1\u662F\u300Cdefine \u6536\u96C6 \u2192 finalize \u7EDF\u4E00\u5EFA\u8868\u300D,\u4F46 finalize() \u4ECE\u672A\u88AB\u8C03\u7528,\n // \u5BFC\u81F4\u6A21\u5757\u8868\u6C38\u8FDC\u4E0D\u4F1A\u843D\u5730\u3001\u540E\u7EED insert/query \u5168\u90E8 no such table\u3002\n // \u91C7\u7528 define \u5373\u5EFA\u8868:\u78B0\u649E\u68C0\u6D4B\u5DF2\u5728\u4E0A\u65B9\u6309\u5185\u5B58\u8868\u5B8C\u6210,\u4E0D\u5F71\u54CD\u5355\u4E00 owner \u4FDD\u8BC1\u3002\n this.createPhysical(defined);\n return { table: req.name, created: true, indices: [] };\n }\n\n /** \u5E42\u7B49\u5730\u7269\u7406\u5EFA\u8868 + \u5EFA\u7D22\u5F15(\u4F9B define / finalize \u5171\u7528)\u3002 */\n private createPhysical(t: DefinedTable): void {\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`);\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n }\n\n /**\n * \u771F\u6B63\u5EFA\u8868\u7684\u65F6\u523B\u3002\u6240\u6709 enabled \u6A21\u5757 init \u5B8C\u6210(\u6216\u660E\u786E\u8C03)\u540E\u518D finalize\u3002\n * CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS,\u5E42\u7B49\u3002\n */\n finalize(): { tableNames: string[]; created: string[] } {\n if (this.finalized) return { tableNames: [...this.tables.keys()], created: [] };\n this.finalized = true;\n const created: string[] = [];\n\n for (const t of this.tables.values()) {\n const cols = buildColumnList(t.columns, t.softDelete);\n const ddl = `CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`;\n this.db.exec(ddl);\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n\n const exists =\n (this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(t.name) as { name?: string } | undefined)?.name === t.name;\n if (exists) created.push(t.name);\n log.success(`[schema] ${t.moduleId} \u2192 ${t.name} (softDelete=${t.softDelete})`);\n }\n\n return { tableNames: [...this.tables.keys()], created };\n }\n\n listTables(): string[] {\n return [...this.tables.keys()];\n }\n\n isRegistered(name: string): boolean {\n return this.tables.has(name);\n }\n\n getOwner(name: string): string | undefined {\n return this.tables.get(name)?.moduleId;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u52A8\u6001\u6570\u636E\u8868\u7ED3\u6784\u6CE8\u518C\u4E0E\u521B\u5EFA\u7BA1\u7406\u5668\n *\n * \u534F\u8BAE\u4E0E\u6D41\u7A0B\uFF1A\n * 1. \u6A21\u5757\u542F\u52A8\u65F6\u8C03\u7528 `db.defineTable(name, columns, { softDelete })` \u5411\u670D\u52A1\u7AEF\u6CE8\u518C\u8868\u7ED3\u6784\u5B9A\u4E49\u3002\n * 2. `SchemaRegistry` \u6821\u9A8C\u5217\u5B9A\u4E49\u5408\u6CD5\u6027\u3001\u5916\u952E\u5F15\u7528\u4E0E\u4E3B\u952E\u552F\u4E00\u6027\u3002\n * 3. \u6821\u9A8C\u901A\u8FC7\u540E\u81EA\u52A8\u6267\u884C `CREATE TABLE IF NOT EXISTS` \u53CA\u7D22\u5F15\u521B\u5EFA\u8BED\u53E5\u3002\n *\n * \u51B2\u7A81\u4E0E\u9694\u79BB\u63A7\u5236\uFF1A\n * - \u76F8\u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49\u540C\u540D\u8868\uFF1A\u5177\u5907\u5E42\u7B49\u6027\uFF08\u8981\u6C42\u5217\u7ED3\u6784\u5B8C\u5168\u4E00\u81F4\uFF09\n * - \u4E0D\u540C\u6A21\u5757\u5B9A\u4E49\u76F8\u540C\u8868\u540D\uFF1A\u629B\u51FA\u51B2\u7A81\u5F02\u5E38\u62D2\u7EDD\u6CE8\u518C\n * - \u8F6F\u5220\u9664\u652F\u6301\uFF1A\u5F00\u542F `softDelete`\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09\u65F6\u81EA\u52A8\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0 `_deleted_at` \u4E0E\u4E50\u89C2\u9501 `_version`\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\n\nexport type ColumnType =\n | \"text\"\n | \"integer\"\n | \"real\"\n | \"blob\"\n | \"TEXT\"\n | \"INTEGER\"\n | \"REAL\"\n | \"BLOB\";\n\nexport interface ColumnDef {\n type: ColumnType;\n primary?: boolean;\n notNull?: boolean;\n unique?: boolean;\n default?: Primitive;\n index?: boolean;\n ref?: string;\n}\n\nexport type Primitive = string | number | boolean | null;\n\nexport interface DefineTableRequest {\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete?: boolean;\n}\n\nexport interface DefineTableResult {\n table: string;\n created: boolean;\n indices: string[];\n}\n\ninterface DefinedTable {\n moduleId: string;\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete: boolean;\n}\n\nexport const PLATFORM_MODULE_ID = \"__platform__\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst VALID_TYPES: Set<string> = new Set([\n \"text\",\n \"integer\",\n \"real\",\n \"blob\",\n \"TEXT\",\n \"INTEGER\",\n \"REAL\",\n \"BLOB\",\n]);\n\nfunction assertValid(req: DefineTableRequest, moduleId: string): void {\n if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name \"${req.name}\"`);\n if (!req.columns || Object.keys(req.columns).length === 0) {\n throw new Error(`[schema] ${moduleId}: ${req.name} has no columns`);\n }\n let pks = 0;\n for (const [colName, def] of Object.entries(req.columns)) {\n if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name \"${colName}\" in ${req.name}`);\n const rawType = String(def.type || \"\");\n if (!VALID_TYPES.has(rawType)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type \"${def.type}\"`);\n }\n def.type = rawType.toLowerCase() as ColumnType;\n if (def.primary) pks++;\n if (def.ref) {\n const [t, c] = def.ref.split(\".\");\n if (!t || !c || !IDENT.test(t) || !IDENT.test(c)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} invalid ref \"${def.ref}\"`);\n }\n }\n }\n if (pks > 1) throw new Error(`[schema] ${moduleId}: ${req.name} has ${pks} primary keys (max 1)`);\n}\n\n/**\n * \u5C06\u5355\u5217\u5B9A\u4E49\u7F16\u8BD1\u4E3A SQL \u5217\u5B9A\u4E49\u5B50\u53E5\uFF08\u5982 `\"id\" TEXT PRIMARY KEY`\uFF09\u3002\n *\n * @param name \u5217\u540D\u3002\n * @param def \u5217\u5C5E\u6027\u5B9A\u4E49\u3002\n * @returns \u7F16\u8BD1\u540E\u7684 SQL \u7247\u6BB5\u3002\n */\nfunction buildColumnClause(name: string, def: ColumnDef): string {\n const parts: string[] = [`\"${name}\"`, def.type.toUpperCase()];\n if (def.primary) parts.push(\"PRIMARY KEY\");\n if (def.notNull && !def.primary) parts.push(\"NOT NULL\");\n if (def.unique) parts.push(\"UNIQUE\");\n if (def.default !== undefined && def.default !== null) {\n if (typeof def.default === \"string\") parts.push(`DEFAULT '${def.default.replace(/'/g, \"''\")}'`);\n else parts.push(`DEFAULT ${def.default}`);\n }\n if (def.ref) parts.push(`REFERENCES ${def.ref}`);\n return parts.join(\" \");\n}\n\n/**\n * \u5C06\u6574\u5F20\u8868\u7684\u6240\u6709\u5217\u5B9A\u4E49\uFF08\u5305\u542B softDelete \u9690\u5F0F\u5217\uFF09\u7F16\u8BD1\u4E3A\u5217\u5B9A\u4E49\u5B57\u7B26\u4E32\u6570\u7EC4\u3002\n * \u8F6F\u5220\u9664\u5217 `_deleted_at` \u4E0E\u4E50\u89C2\u9501\u7248\u672C\u53F7 `_version` \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0\u3002\n *\n * @param columns \u5217\u7ED3\u6784\u5B9A\u4E49\u5B57\u5178\u3002\n * @param softDelete \u662F\u5426\u542F\u7528\u8F6F\u5220\u9664\u6269\u5C55\u5217\u3002\n * @returns \u7F16\u8BD1\u540E\u7684\u5217\u5B9A\u4E49\u5B50\u53E5\u6570\u7EC4\u3002\n */\nfunction buildColumnList(columns: Record<string, ColumnDef>, softDelete: boolean): string[] {\n const cols: string[] = [];\n for (const [n, def] of Object.entries(columns)) {\n cols.push(buildColumnClause(n, def));\n }\n if (softDelete) {\n cols.push('\"_deleted_at\" INTEGER', '\"_version\" INTEGER DEFAULT 0');\n }\n return cols;\n}\n\nexport class SchemaRegistry {\n private readonly db: DatabaseSync;\n private readonly tables = new Map<string, DefinedTable>();\n private finalized = false;\n\n constructor(db: DatabaseSync) {\n this.db = db;\n }\n\n /**\n * \u6CE8\u518C\u5E76\u521B\u5EFA\u6A21\u5757\u6570\u636E\u8868\u7ED3\u6784\u3002\n *\n * @param moduleId \u6CE8\u518C\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param req \u8868\u7ED3\u6784\u5B9A\u4E49\u8BF7\u6C42\u5BF9\u8C61\u3002\n * @returns \u5EFA\u8868\u7ED3\u679C\u4E0E\u521B\u5EFA\u7684\u7D22\u5F15\u5217\u8868\u3002\n */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\n\n if (this.finalized) {\n throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);\n }\n assertValid(req, moduleId);\n\n const existing = this.tables.get(req.name);\n if (existing) {\n if (existing.moduleId === moduleId) {\n // \u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49:\u6821\u9A8C columns \u4E00\u81F4\n if (JSON.stringify(existing.columns) !== JSON.stringify(req.columns)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",columns \u4E0D\u4E00\u81F4`);\n }\n if (existing.softDelete !== (req.softDelete ?? true)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",softDelete \u4E0D\u4E00\u81F4`);\n }\n log.info(`[schema] ${moduleId} \u91CD\u590D define \"${req.name}\" \u8DF3\u8FC7`);\n return { table: req.name, created: false, indices: [] };\n }\n throw new Error(\n `[schema] \u8868 \"${req.name}\" \u5DF2\u88AB\u6A21\u5757 ${existing.moduleId} \u5B9A\u4E49,${moduleId} \u4E0D\u80FD\u62A2\u540C\u540D\u8868`\n );\n }\n\n const softDelete = req.softDelete ?? true;\n const defined: DefinedTable = {\n moduleId,\n name: req.name,\n columns: req.columns,\n softDelete,\n };\n this.tables.set(req.name, defined);\n // \u7ACB\u5373\u7269\u7406\u5EFA\u8868(\u5E42\u7B49 CREATE TABLE IF NOT EXISTS)\u3002\n // \u539F\u8BBE\u8BA1\u662F\u300Cdefine \u6536\u96C6 \u2192 finalize \u7EDF\u4E00\u5EFA\u8868\u300D,\u4F46 finalize() \u4ECE\u672A\u88AB\u8C03\u7528,\n // \u5BFC\u81F4\u6A21\u5757\u8868\u6C38\u8FDC\u4E0D\u4F1A\u843D\u5730\u3001\u540E\u7EED insert/query \u5168\u90E8 no such table\u3002\n // \u91C7\u7528 define \u5373\u5EFA\u8868:\u78B0\u649E\u68C0\u6D4B\u5DF2\u5728\u4E0A\u65B9\u6309\u5185\u5B58\u8868\u5B8C\u6210,\u4E0D\u5F71\u54CD\u5355\u4E00 owner \u4FDD\u8BC1\u3002\n this.createPhysical(defined);\n return { table: req.name, created: true, indices: [] };\n }\n\n /** \u5E42\u7B49\u5730\u7269\u7406\u5EFA\u8868 + \u5EFA\u7D22\u5F15(\u4F9B define / finalize \u5171\u7528)\u3002 */\n private createPhysical(t: DefinedTable): void {\n const existingTableInfo = this.db.prepare(`PRAGMA table_info(\"${t.name}\")`).all() as Array<{\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: unknown;\n pk: number;\n }>;\n\n if (existingTableInfo.length > 0) {\n let rowCount = 0;\n try {\n const countRes = this.db.prepare(`SELECT COUNT(*) AS c FROM \"${t.name}\"`).get() as { c: number } | undefined;\n rowCount = countRes?.c ?? 0;\n } catch {\n rowCount = 0;\n }\n const existingColNames = new Set(existingTableInfo.map((c) => c.name));\n const requiredCols = Object.keys(t.columns);\n const hasMissingRequiredCol = requiredCols.some((c) => !existingColNames.has(c));\n const pkDef = Object.entries(t.columns).find(([_, c]) => c.primary);\n const pkMatches = !pkDef || existingTableInfo.some((c) => c.pk === 1 && c.name === pkDef[0]);\n\n if (rowCount === 0 && (hasMissingRequiredCol || !pkMatches)) {\n // \u7A7A\u8868\u4E14\u7ED3\u6784\u4E0E\u5F53\u524D\u5B9A\u4E49\u4E0D\u517C\u5BB9\uFF08\u5982\u65E7\u7248\u672C\u9057\u7559\u7A7A\u8868\uFF09\uFF1A\u5B89\u5168\u5220\u9664\u5E76\u4EE5\u65B0\u7ED3\u6784\u91CD\u5EFA\n try {\n this.db.exec(`DROP TABLE \"${t.name}\"`);\n } catch {}\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE \"${t.name}\" (${cols.join(\", \")})`);\n } else {\n // \u8868\u4E2D\u6709\u6570\u636E\u6216\u7ED3\u6784\u517C\u5BB9\uFF1A\u901A\u8FC7 ALTER TABLE ADD COLUMN \u589E\u91CF\u8865\u9F50\u7F3A\u5931\u5217\n for (const [colName, colDef] of Object.entries(t.columns)) {\n if (!existingColNames.has(colName)) {\n const clause = buildColumnClause(colName, colDef);\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN ${clause};`);\n } catch (e) {\n log.warn(`[schema] ${t.moduleId}: ALTER TABLE \"${t.name}\" ADD COLUMN \"${colName}\" \u5F02\u5E38: ${(e as Error).message}`);\n }\n }\n }\n if (t.softDelete) {\n if (!existingColNames.has(\"_deleted_at\")) {\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN \"_deleted_at\" INTEGER;`);\n } catch {}\n }\n if (!existingColNames.has(\"_version\")) {\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN \"_version\" INTEGER DEFAULT 0;`);\n } catch {}\n }\n }\n }\n } else {\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`);\n }\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n try {\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n } catch (e) {\n log.warn(`[schema] ${t.moduleId}: \u521B\u5EFA\u7D22\u5F15 \"${idxName}\" \u5931\u8D25: ${(e as Error).message}`);\n }\n }\n }\n }\n\n /**\n * \u771F\u6B63\u5EFA\u8868\u7684\u65F6\u523B\u3002\u6240\u6709 enabled \u6A21\u5757 init \u5B8C\u6210(\u6216\u660E\u786E\u8C03)\u540E\u518D finalize\u3002\n * CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS,\u5E42\u7B49\u3002\n */\n finalize(): { tableNames: string[]; created: string[] } {\n if (this.finalized) return { tableNames: [...this.tables.keys()], created: [] };\n this.finalized = true;\n const created: string[] = [];\n\n for (const t of this.tables.values()) {\n const cols = buildColumnList(t.columns, t.softDelete);\n const ddl = `CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`;\n this.db.exec(ddl);\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n\n const exists =\n (this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(t.name) as { name?: string } | undefined)?.name === t.name;\n if (exists) created.push(t.name);\n log.success(`[schema] ${t.moduleId} \u2192 ${t.name} (softDelete=${t.softDelete})`);\n }\n\n return { tableNames: [...this.tables.keys()], created };\n }\n\n listTables(): string[] {\n return [...this.tables.keys()];\n }\n\n isRegistered(name: string): boolean {\n return this.tables.has(name);\n }\n\n getOwner(name: string): string | undefined {\n return this.tables.get(name)?.moduleId;\n }\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": "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",
6
6
  "names": ["sql", "r"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfmc-bds/db-server",
3
- "version": "0.2.0-beta.7",
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.11"
35
+ "@sfmc-bds/sdk": "0.2.0-beta.13"
36
36
  },
37
37
  "devDependencies": {
38
- "@sfmc-bds/tools": "0.2.0-beta.12"
38
+ "@sfmc-bds/tools": "0.2.0-beta.14"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public",