@sfmc-bds/db-server 0.2.0-beta.4 → 0.2.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +663 -0
  2. package/dist/domain/bridge.d.ts +57 -23
  3. package/dist/domain/bridge.d.ts.map +1 -1
  4. package/dist/domain/bridge.js +77 -6
  5. package/dist/domain/bridge.js.map +2 -2
  6. package/dist/domain/bridge.test.d.ts +2 -0
  7. package/dist/domain/bridge.test.d.ts.map +1 -0
  8. package/dist/domain/bridge.test.js +87 -0
  9. package/dist/domain/bridge.test.js.map +7 -0
  10. package/dist/domain/economy.d.ts +38 -7
  11. package/dist/domain/economy.d.ts.map +1 -1
  12. package/dist/domain/economy.js.map +2 -2
  13. package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
  14. package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
  15. package/dist/domain/messages-bridge-filter.test.js +20 -0
  16. package/dist/domain/messages-bridge-filter.test.js.map +7 -0
  17. package/dist/domain/qq-bind-status.test.d.ts +2 -0
  18. package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
  19. package/dist/domain/qq-bind-status.test.js +211 -0
  20. package/dist/domain/qq-bind-status.test.js.map +7 -0
  21. package/dist/domain/qq-events.d.ts +84 -0
  22. package/dist/domain/qq-events.d.ts.map +1 -0
  23. package/dist/domain/qq-events.js +193 -0
  24. package/dist/domain/qq-events.js.map +7 -0
  25. package/dist/domain/qq-events.test.d.ts +5 -0
  26. package/dist/domain/qq-events.test.d.ts.map +1 -0
  27. package/dist/domain/qq-events.test.js +170 -0
  28. package/dist/domain/qq-events.test.js.map +7 -0
  29. package/dist/domain/qq-join.test.d.ts +2 -0
  30. package/dist/domain/qq-join.test.d.ts.map +1 -0
  31. package/dist/domain/qq-join.test.js +353 -0
  32. package/dist/domain/qq-join.test.js.map +7 -0
  33. package/dist/domain/schema.d.ts +17 -19
  34. package/dist/domain/schema.d.ts.map +1 -1
  35. package/dist/domain/schema.js +69 -0
  36. package/dist/domain/schema.js.map +2 -2
  37. package/dist/domain/system-status.d.ts +75 -0
  38. package/dist/domain/system-status.d.ts.map +1 -0
  39. package/dist/domain/system-status.js +181 -0
  40. package/dist/domain/system-status.js.map +7 -0
  41. package/dist/domain/system-status.test.d.ts +5 -0
  42. package/dist/domain/system-status.test.d.ts.map +1 -0
  43. package/dist/domain/system-status.test.js +19 -0
  44. package/dist/domain/system-status.test.js.map +7 -0
  45. package/dist/domain/transaction.d.ts +5 -4
  46. package/dist/domain/transaction.d.ts.map +1 -1
  47. package/dist/domain/transaction.js.map +2 -2
  48. package/dist/env.d.ts +7 -0
  49. package/dist/env.d.ts.map +1 -1
  50. package/dist/env.js +55 -3
  51. package/dist/env.js.map +2 -2
  52. package/dist/index.d.ts +0 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +105 -19
  55. package/dist/index.js.map +2 -2
  56. package/dist/lib/idempotency-store.d.ts +17 -11
  57. package/dist/lib/idempotency-store.d.ts.map +1 -1
  58. package/dist/lib/idempotency-store.js.map +2 -2
  59. package/dist/lib/idempotency.d.ts +3 -4
  60. package/dist/lib/idempotency.d.ts.map +1 -1
  61. package/dist/lib/idempotency.js.map +2 -2
  62. package/dist/lib/module-state.d.ts +38 -8
  63. package/dist/lib/module-state.d.ts.map +1 -1
  64. package/dist/lib/module-state.js.map +2 -2
  65. package/dist/lib/order-by.d.ts +8 -6
  66. package/dist/lib/order-by.d.ts.map +1 -1
  67. package/dist/lib/order-by.js.map +2 -2
  68. package/dist/lib/sql-helpers.d.ts +25 -13
  69. package/dist/lib/sql-helpers.d.ts.map +1 -1
  70. package/dist/lib/sql-helpers.js.map +2 -2
  71. package/dist/lib/sqlite.d.ts +14 -5
  72. package/dist/lib/sqlite.d.ts.map +1 -1
  73. package/dist/lib/sqlite.js.map +2 -2
  74. package/dist/manifest-loader.d.ts +5 -2
  75. package/dist/manifest-loader.d.ts.map +1 -1
  76. package/dist/manifest-loader.js +16 -6
  77. package/dist/manifest-loader.js.map +2 -2
  78. package/dist/manifest.d.ts +1 -2
  79. package/dist/manifest.d.ts.map +1 -1
  80. package/dist/manifest.js +13 -8
  81. package/dist/manifest.js.map +2 -2
  82. package/dist/project-root.d.ts +1 -1
  83. package/dist/project-root.d.ts.map +1 -1
  84. package/dist/project-root.js +2 -2
  85. package/dist/project-root.js.map +2 -2
  86. package/dist/routes/_shared.d.ts +14 -5
  87. package/dist/routes/_shared.d.ts.map +1 -1
  88. package/dist/routes/_shared.js.map +2 -2
  89. package/dist/routes/config.d.ts +8 -11
  90. package/dist/routes/config.d.ts.map +1 -1
  91. package/dist/routes/config.js +17 -23
  92. package/dist/routes/config.js.map +2 -2
  93. package/dist/routes/db-routes.d.ts +6 -0
  94. package/dist/routes/db-routes.d.ts.map +1 -1
  95. package/dist/routes/db-routes.js.map +2 -2
  96. package/dist/routes/messages.d.ts +6 -2
  97. package/dist/routes/messages.d.ts.map +1 -1
  98. package/dist/routes/messages.js +17 -3
  99. package/dist/routes/messages.js.map +2 -2
  100. package/dist/routes/module-config-routes.d.ts +13 -9
  101. package/dist/routes/module-config-routes.d.ts.map +1 -1
  102. package/dist/routes/module-config-routes.js.map +2 -2
  103. package/dist/routes/modules.d.ts +6 -0
  104. package/dist/routes/modules.d.ts.map +1 -1
  105. package/dist/routes/modules.js.map +2 -2
  106. package/dist/routes/qq-bind.d.ts +24 -0
  107. package/dist/routes/qq-bind.d.ts.map +1 -0
  108. package/dist/routes/qq-bind.js +179 -0
  109. package/dist/routes/qq-bind.js.map +7 -0
  110. package/dist/routes/qq-events.d.ts +20 -0
  111. package/dist/routes/qq-events.d.ts.map +1 -0
  112. package/dist/routes/qq-events.js +39 -0
  113. package/dist/routes/qq-events.js.map +7 -0
  114. package/dist/routes/qq-join.d.ts +46 -0
  115. package/dist/routes/qq-join.d.ts.map +1 -0
  116. package/dist/routes/qq-join.js +277 -0
  117. package/dist/routes/qq-join.js.map +7 -0
  118. package/dist/routes/service-routes.d.ts +6 -0
  119. package/dist/routes/service-routes.d.ts.map +1 -1
  120. package/dist/routes/service-routes.js.map +2 -2
  121. package/dist/routes/status.d.ts +19 -0
  122. package/dist/routes/status.d.ts.map +1 -0
  123. package/dist/routes/status.js +74 -0
  124. package/dist/routes/status.js.map +7 -0
  125. package/dist/schema-registry.d.ts +16 -15
  126. package/dist/schema-registry.d.ts.map +1 -1
  127. package/dist/schema-registry.js +7 -1
  128. package/dist/schema-registry.js.map +2 -2
  129. package/dist/server.d.ts +19 -0
  130. package/dist/server.d.ts.map +1 -1
  131. package/dist/server.js.map +2 -2
  132. package/dist/service-registry.d.ts +39 -9
  133. package/dist/service-registry.d.ts.map +1 -1
  134. package/dist/service-registry.js +30 -2
  135. package/dist/service-registry.js.map +2 -2
  136. package/dist/services/builtin-handlers.d.ts +27 -7
  137. package/dist/services/builtin-handlers.d.ts.map +1 -1
  138. package/dist/services/builtin-handlers.js.map +2 -2
  139. package/dist/tx-runner.d.ts +29 -3
  140. package/dist/tx-runner.d.ts.map +1 -1
  141. package/dist/tx-runner.js +29 -3
  142. package/dist/tx-runner.js.map +2 -2
  143. package/package.json +17 -15
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/schema-registry.ts"],
4
- "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u6536\u96C6\u6A21\u5757 defineTable,\u7EDF\u4E00\u5EFA\u8868\n *\n * \u534F\u8BAE:\n * - \u6A21\u5757 SAPI \u542F\u52A8\u65F6\u8C03 db.defineTable(name, columns, {softDelete})\n * \u2192 POST /api/sfmc/db/define-table {moduleId, name, columns, softDelete}\n * - schema-registry \u6536\u96C6\u6240\u6709\u8C03\u7528,CAS \u9501\n * - \u7B49\u6240\u6709\"\u5DF2\u6CE8\u518C\u6A21\u5757\"\u90FD\u8C03\u8FC7(\u8D85\u65F6 fallback \u6216 explicit `finalize()`),\n * \u4E00\u6B21\u6027 CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS\n * - \u540C\u4E00 module \u91CD\u590D define \u540C\u540D\u8868 = \u5E42\u7B49(\u540C\u540D columns \u5141\u8BB8)\n * - \u4E0D\u540C module \u62A2\u540C\u540D\u8868 = \u542F\u52A8\u5931\u8D25\n * - \u5E73\u53F0\u8868(moduleId = \"__platform__\")\u7531 db-tables.ts \u5728 initSchema \u76F4\u63A5 bootstrap\n *\n * \u5217\u7C7B\u578B\u767D\u540D\u5355:\n * text / integer / real / blob\n * \u4FEE\u9970: primary / notNull / unique / default / index\n * softDelete = \u81EA\u52A8\u52A0 _deleted_at INTEGER(\u5217\u540D\u5E26\u4E0B\u5212\u7EBF) \u548C _version INTEGER\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 * \u4E00\u6B21\u6027\u628A columns \u7FFB\u8BD1\u6210 CREATE TABLE\u3002\n * \u4F8B\u5982 {id:{type:\"text\",primary:true},name:{type:\"text\",notNull:true}}\n * \u2192 \"id\" TEXT PRIMARY KEY, \"name\" TEXT NOT NULL\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 * \u628A\u4E00\u5F20\u8868\u7684\u6240\u6709\u5217(\u542B softDelete \u9690\u5F0F\u5217)\u62FC\u6210 CREATE TABLE \u7684\u5217\u5B9A\u4E49\u6570\u7EC4\u3002\n * \u9690\u5F0F _deleted_at / _version \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u8FFD\u52A0\u300C\u4E00\u6B21\u300D(\u6B64\u524D\u8BEF\u5728\u6BCF\u4E2A\u5217\n * \u5B50\u53E5\u91CC\u5404\u8FFD\u52A0\u4E00\u6B21,\u4E14\u65E0\u9017\u53F7\u5206\u9694,\u5BFC\u81F4 softDelete \u8868\u5EFA\u8868 SQL \u8BED\u6CD5\u9519\u8BEF)\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 /** \u6A21\u5757 SAPI \u542F\u52A8\u65F6\u8C03\u3002 */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\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 = \"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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",
6
6
  "names": []
7
7
  }
package/dist/server.d.ts CHANGED
@@ -9,6 +9,12 @@ interface CheckResult {
9
9
  port: number;
10
10
  error?: string;
11
11
  }
12
+ /**
13
+ * 检测指定端口是否冲突可用。
14
+ *
15
+ * @param port 待检测的端口号。
16
+ * @returns 包含端口可用性状态的检测结果对象。
17
+ */
12
18
  export declare function checkPortConflict(port: number): Promise<CheckResult>;
13
19
  export type ServerEnv = {
14
20
  PORT: number;
@@ -21,7 +27,20 @@ export interface ServerOptions {
21
27
  handle: Handler;
22
28
  onListening?: (server: http.Server) => void;
23
29
  }
30
+ /**
31
+ * 创建并配置 db-server 的原生 HTTP 服务实例(集成请求耗时监控与状态码日志)。
32
+ *
33
+ * @param options 服务端启动配置选项。
34
+ * @returns 原生 http.Server 实例。
35
+ */
24
36
  export declare function createServer({ handle, env, onListening }: ServerOptions): http.Server;
37
+ /**
38
+ * 启动交互式控制台 REPL 界面,支持在终端输入 `help`、`status`、`stop` 运维管理指令。
39
+ *
40
+ * @param server HTTP 服务实例。
41
+ * @param db 数据库连接实例。
42
+ * @returns 控制台 Readline 接口对象。
43
+ */
25
44
  export declare function startConsole(server: http.Server, db: DatabaseSync | null): readline.Interface;
26
45
  export {};
27
46
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,UAAU,WAAW;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAgBpE;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpG,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM,CA2BrF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,GAAG,QAAQ,CAAC,SAAS,CAyB7F"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,UAAU,WAAW;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAgBpE;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpG,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM,CA2BrF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,GAAG,QAAQ,CAAC,SAAS,CA0B7F"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/server.ts"],
4
- "sourcesContent": ["/**\n * server.ts \u2014 HTTP \u670D\u52A1 + \u63A7\u5236\u53F0\n */\n\nimport http from \"node:http\";\nimport net from \"node:net\";\nimport readline from \"node:readline\";\nimport type { DatabaseSync } from \"node:sqlite\";\n\nimport { log } from \"./lib/log.js\";\n\ninterface CheckResult {\n ok: boolean;\n port: number;\n error?: string;\n}\n\nexport function checkPortConflict(port: number): Promise<CheckResult> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n const result: { ok: false; port: number; error?: string } = { ok: false, port };\n if (err.code) {\n result.error = err.code;\n }\n resolve(result);\n });\n server.once(\"listening\", () => {\n server.close();\n resolve({ ok: true, port });\n });\n server.listen(port, \"127.0.0.1\");\n });\n}\n\nexport type ServerEnv = {\n PORT: number;\n HOST: string;\n AUTH_TOKEN?: string;\n};\n\nexport type Handler = (req: http.IncomingMessage, res: http.ServerResponse) => void | Promise<void>;\n\nexport interface ServerOptions {\n env: ServerEnv;\n handle: Handler;\n onListening?: (server: http.Server) => void;\n}\n\nexport function createServer({ handle, env, onListening }: ServerOptions): http.Server {\n const server = http.createServer((req, res) => {\n const startedAt = Date.now();\n let bodyStr = \"\";\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n bodyStr = Buffer.concat(chunks).toString(\"utf-8\").slice(0, 300);\n (req as http.IncomingMessage & { bodyBuffer: string }).bodyBuffer = bodyStr;\n });\n res.once(\"finish\", () => {\n const quietPoll = (req.url ?? \"\").startsWith(\"/api/sfmc/messages?\") && res.statusCode < 400;\n const bodySnippet = req.method === \"GET\" ? \"\" : ` ${bodyStr.slice(0, 200)}`;\n if (!quietPoll)\n log.info(`[HTTP] ${req.method} ${req.url} ${res.statusCode} ${Date.now() - startedAt}ms${bodySnippet}`);\n });\n void handle(req, res);\n });\n\n server.listen(env.PORT, env.HOST, () => {\n log.success(`HTTP \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${env.PORT} (loopback only)`);\n log.info(`API \u5065\u5EB7\u68C0\u67E5: http://${env.HOST}:${env.PORT}/api/health`);\n log.info(`\u9274\u6743: ${env.AUTH_TOKEN ? \"\u5DF2\u542F\u7528 token\" : \"\u672A\u542F\u7528\"}`);\n if (onListening) onListening(server);\n });\n\n return server;\n}\n\nexport function startConsole(server: http.Server, db: DatabaseSync | null): readline.Interface {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n rl.on(\"line\", (line: string) => {\n const cmd = line.trim().toLowerCase();\n if (cmd === \"help\") {\n console.log(\"DB Server \u53EF\u7528\u547D\u4EE4:\");\n console.log(\" help \u2014 \u663E\u793A\u5E2E\u52A9\");\n console.log(\" status \u2014 \u663E\u793A\u670D\u52A1\u72B6\u6001\");\n console.log(\" stop \u2014 \u505C\u6B62\u670D\u52A1\");\n } else if (cmd === \"status\") {\n console.log(`\u72B6\u6001: \u8FD0\u884C\u4E2D`);\n console.log(` HTTP \u7AEF\u53E3: ${(server.address() as { port: number }).port}`);\n console.log(` \u6570\u636E\u5E93: ${db ? \"(\u5DF2\u8FDE\u63A5)\" : \"(\u672A\u8FDE\u63A5)\"}`);\n } else if (cmd === \"stop\") {\n log.info(\"\u6B63\u5728\u505C\u6B62\u670D\u52A1...\");\n rl.close();\n server.close();\n if (db) db.close();\n process.exit(0);\n } else {\n log.warn(`\u672A\u77E5\u547D\u4EE4: ${cmd}\uFF0C\u8F93\u5165 help \u67E5\u770B\u5E2E\u52A9`);\n }\n });\n rl.on(\"SIGINT\", () => process.exit());\n return rl;\n}\n\n"],
5
- "mappings": "AAIA,OAAO,UAAU;AACjB,OAAO,SAAS;AAChB,OAAO,cAAc;AAGrB,SAAS,WAAW;AAQb,SAAS,kBAAkB,MAAoC;AACpE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,SAAS,IAAI,aAAa;AAChC,WAAO,KAAK,SAAS,CAAC,QAA+B;AACnD,YAAM,SAAsD,EAAE,IAAI,OAAO,KAAK;AAC9E,UAAI,IAAI,MAAM;AACZ,eAAO,QAAQ,IAAI;AAAA,MACrB;AACA,cAAQ,MAAM;AAAA,IAChB,CAAC;AACD,WAAO,KAAK,aAAa,MAAM;AAC7B,aAAO,MAAM;AACb,cAAQ,EAAE,IAAI,MAAM,KAAK,CAAC;AAAA,IAC5B,CAAC;AACD,WAAO,OAAO,MAAM,WAAW;AAAA,EACjC,CAAC;AACH;AAgBO,SAAS,aAAa,EAAE,QAAQ,KAAK,YAAY,GAA+B;AACrF,QAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,UAAU;AACd,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,MAAc,OAAO,KAAK,CAAC,CAAC;AAC5C,QAAI,GAAG,OAAO,MAAM;AAClB,gBAAU,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,EAAE,MAAM,GAAG,GAAG;AAC9D,MAAC,IAAsD,aAAa;AAAA,IACtE,CAAC;AACD,QAAI,KAAK,UAAU,MAAM;AACvB,YAAM,aAAa,IAAI,OAAO,IAAI,WAAW,qBAAqB,KAAK,IAAI,aAAa;AACxF,YAAM,cAAc,IAAI,WAAW,QAAQ,KAAK,IAAI,QAAQ,MAAM,GAAG,GAAG,CAAC;AACzE,UAAI,CAAC;AACH,YAAI,KAAK,UAAU,IAAI,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,IAAI,KAAK,IAAI,IAAI,SAAS,KAAK,WAAW,EAAE;AAAA,IAC1G,CAAC;AACD,SAAK,OAAO,KAAK,GAAG;AAAA,EACtB,CAAC;AAED,SAAO,OAAO,IAAI,MAAM,IAAI,MAAM,MAAM;AACtC,QAAI,QAAQ,yDAAiB,IAAI,IAAI,kBAAkB;AACvD,QAAI,KAAK,wCAAoB,IAAI,IAAI,IAAI,IAAI,IAAI,aAAa;AAC9D,QAAI,KAAK,iBAAO,IAAI,aAAa,6BAAc,oBAAK,EAAE;AACtD,QAAI,YAAa,aAAY,MAAM;AAAA,EACrC,CAAC;AAED,SAAO;AACT;AAEO,SAAS,aAAa,QAAqB,IAA6C;AAC7F,QAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,KAAG,GAAG,QAAQ,CAAC,SAAiB;AAC9B,UAAM,MAAM,KAAK,KAAK,EAAE,YAAY;AACpC,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAI,qCAAiB;AAC7B,cAAQ,IAAI,2CAAkB;AAC9B,cAAQ,IAAI,uDAAoB;AAChC,cAAQ,IAAI,2CAAkB;AAAA,IAChC,WAAW,QAAQ,UAAU;AAC3B,cAAQ,IAAI,kCAAS;AACrB,cAAQ,IAAI,wBAAe,OAAO,QAAQ,EAAuB,IAAI,EAAE;AACvE,cAAQ,IAAI,yBAAU,KAAK,yBAAU,sBAAO,EAAE;AAAA,IAChD,WAAW,QAAQ,QAAQ;AACzB,UAAI,KAAK,yCAAW;AACpB,SAAG,MAAM;AACT,aAAO,MAAM;AACb,UAAI,GAAI,IAAG,MAAM;AACjB,cAAQ,KAAK,CAAC;AAAA,IAChB,OAAO;AACL,UAAI,KAAK,6BAAS,GAAG,kDAAe;AAAA,IACtC;AAAA,EACF,CAAC;AACD,KAAG,GAAG,UAAU,MAAM,QAAQ,KAAK,CAAC;AACpC,SAAO;AACT;",
4
+ "sourcesContent": ["/**\n * server.ts \u2014 HTTP \u670D\u52A1 + \u63A7\u5236\u53F0\n */\n\nimport http from \"node:http\";\nimport net from \"node:net\";\nimport readline from \"node:readline\";\nimport type { DatabaseSync } from \"node:sqlite\";\n\nimport { log } from \"./lib/log.js\";\n\ninterface CheckResult {\n ok: boolean;\n port: number;\n error?: string;\n}\n\n/**\n * \u68C0\u6D4B\u6307\u5B9A\u7AEF\u53E3\u662F\u5426\u51B2\u7A81\u53EF\u7528\u3002\n *\n * @param port \u5F85\u68C0\u6D4B\u7684\u7AEF\u53E3\u53F7\u3002\n * @returns \u5305\u542B\u7AEF\u53E3\u53EF\u7528\u6027\u72B6\u6001\u7684\u68C0\u6D4B\u7ED3\u679C\u5BF9\u8C61\u3002\n */\nexport function checkPortConflict(port: number): Promise<CheckResult> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n const result: { ok: false; port: number; error?: string } = { ok: false, port };\n if (err.code) {\n result.error = err.code;\n }\n resolve(result);\n });\n server.once(\"listening\", () => {\n server.close();\n resolve({ ok: true, port });\n });\n server.listen(port, \"127.0.0.1\");\n });\n}\n\nexport type ServerEnv = {\n PORT: number;\n HOST: string;\n AUTH_TOKEN?: string;\n};\n\nexport type Handler = (req: http.IncomingMessage, res: http.ServerResponse) => void | Promise<void>;\n\nexport interface ServerOptions {\n env: ServerEnv;\n handle: Handler;\n onListening?: (server: http.Server) => void;\n}\n\n/**\n * \u521B\u5EFA\u5E76\u914D\u7F6E db-server \u7684\u539F\u751F HTTP \u670D\u52A1\u5B9E\u4F8B\uFF08\u96C6\u6210\u8BF7\u6C42\u8017\u65F6\u76D1\u63A7\u4E0E\u72B6\u6001\u7801\u65E5\u5FD7\uFF09\u3002\n *\n * @param options \u670D\u52A1\u7AEF\u542F\u52A8\u914D\u7F6E\u9009\u9879\u3002\n * @returns \u539F\u751F http.Server \u5B9E\u4F8B\u3002\n */\nexport function createServer({ handle, env, onListening }: ServerOptions): http.Server {\n const server = http.createServer((req, res) => {\n const startedAt = Date.now();\n let bodyStr = \"\";\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n bodyStr = Buffer.concat(chunks).toString(\"utf-8\").slice(0, 300);\n (req as http.IncomingMessage & { bodyBuffer: string }).bodyBuffer = bodyStr;\n });\n res.once(\"finish\", () => {\n const quietPoll = (req.url ?? \"\").startsWith(\"/api/sfmc/messages?\") && res.statusCode < 400;\n const bodySnippet = req.method === \"GET\" ? \"\" : ` ${bodyStr.slice(0, 200)}`;\n if (!quietPoll)\n log.info(`[HTTP] ${req.method} ${req.url} ${res.statusCode} ${Date.now() - startedAt}ms${bodySnippet}`);\n });\n void handle(req, res);\n });\n\n server.listen(env.PORT, env.HOST, () => {\n log.success(`HTTP \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${env.PORT} (loopback only)`);\n log.info(`API \u5065\u5EB7\u68C0\u67E5: http://${env.HOST}:${env.PORT}/api/health`);\n log.info(`\u9274\u6743: ${env.AUTH_TOKEN ? \"\u5DF2\u542F\u7528 token\" : \"\u672A\u542F\u7528\"}`);\n if (onListening) onListening(server);\n });\n\n return server;\n}\n\n/**\n * \u542F\u52A8\u4EA4\u4E92\u5F0F\u63A7\u5236\u53F0 REPL \u754C\u9762\uFF0C\u652F\u6301\u5728\u7EC8\u7AEF\u8F93\u5165 `help`\u3001`status`\u3001`stop` \u8FD0\u7EF4\u7BA1\u7406\u6307\u4EE4\u3002\n *\n * @param server HTTP \u670D\u52A1\u5B9E\u4F8B\u3002\n * @param db \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n * @returns \u63A7\u5236\u53F0 Readline \u63A5\u53E3\u5BF9\u8C61\u3002\n */\nexport function startConsole(server: http.Server, db: DatabaseSync | null): readline.Interface {\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n rl.on(\"line\", (line: string) => {\n const cmd = line.trim().toLowerCase();\n if (cmd === \"help\") {\n console.log(\"DB Server \u53EF\u7528\u547D\u4EE4:\");\n console.log(\" help \u2014 \u663E\u793A\u5E2E\u52A9\");\n console.log(\" status \u2014 \u663E\u793A\u670D\u52A1\u72B6\u6001\");\n console.log(\" stop \u2014 \u505C\u6B62\u670D\u52A1\");\n } else if (cmd === \"status\") {\n console.log(`\u72B6\u6001: \u8FD0\u884C\u4E2D`);\n console.log(` HTTP \u7AEF\u53E3: ${(server.address() as { port: number }).port}`);\n console.log(` \u6570\u636E\u5E93: ${db ? \"(\u5DF2\u8FDE\u63A5)\" : \"(\u672A\u8FDE\u63A5)\"}`);\n } else if (cmd === \"stop\") {\n log.info(\"\u6B63\u5728\u505C\u6B62\u670D\u52A1...\");\n rl.close();\n server.close();\n if (db) db.close();\n process.exit(0);\n } else {\n log.warn(`\u672A\u77E5\u547D\u4EE4: ${cmd}\uFF0C\u8F93\u5165 help \u67E5\u770B\u5E2E\u52A9`);\n }\n });\n rl.on(\"SIGINT\", () => process.exit());\n return rl;\n}\n\n"],
5
+ "mappings": "AAIA,OAAO,UAAU;AACjB,OAAO,SAAS;AAChB,OAAO,cAAc;AAGrB,SAAS,WAAW;AAcb,SAAS,kBAAkB,MAAoC;AACpE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,SAAS,IAAI,aAAa;AAChC,WAAO,KAAK,SAAS,CAAC,QAA+B;AACnD,YAAM,SAAsD,EAAE,IAAI,OAAO,KAAK;AAC9E,UAAI,IAAI,MAAM;AACZ,eAAO,QAAQ,IAAI;AAAA,MACrB;AACA,cAAQ,MAAM;AAAA,IAChB,CAAC;AACD,WAAO,KAAK,aAAa,MAAM;AAC7B,aAAO,MAAM;AACb,cAAQ,EAAE,IAAI,MAAM,KAAK,CAAC;AAAA,IAC5B,CAAC;AACD,WAAO,OAAO,MAAM,WAAW;AAAA,EACjC,CAAC;AACH;AAsBO,SAAS,aAAa,EAAE,QAAQ,KAAK,YAAY,GAA+B;AACrF,QAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,UAAU;AACd,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,MAAc,OAAO,KAAK,CAAC,CAAC;AAC5C,QAAI,GAAG,OAAO,MAAM;AAClB,gBAAU,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,EAAE,MAAM,GAAG,GAAG;AAC9D,MAAC,IAAsD,aAAa;AAAA,IACtE,CAAC;AACD,QAAI,KAAK,UAAU,MAAM;AACvB,YAAM,aAAa,IAAI,OAAO,IAAI,WAAW,qBAAqB,KAAK,IAAI,aAAa;AACxF,YAAM,cAAc,IAAI,WAAW,QAAQ,KAAK,IAAI,QAAQ,MAAM,GAAG,GAAG,CAAC;AACzE,UAAI,CAAC;AACH,YAAI,KAAK,UAAU,IAAI,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,IAAI,KAAK,IAAI,IAAI,SAAS,KAAK,WAAW,EAAE;AAAA,IAC1G,CAAC;AACD,SAAK,OAAO,KAAK,GAAG;AAAA,EACtB,CAAC;AAED,SAAO,OAAO,IAAI,MAAM,IAAI,MAAM,MAAM;AACtC,QAAI,QAAQ,yDAAiB,IAAI,IAAI,kBAAkB;AACvD,QAAI,KAAK,wCAAoB,IAAI,IAAI,IAAI,IAAI,IAAI,aAAa;AAC9D,QAAI,KAAK,iBAAO,IAAI,aAAa,6BAAc,oBAAK,EAAE;AACtD,QAAI,YAAa,aAAY,MAAM;AAAA,EACrC,CAAC;AAED,SAAO;AACT;AASO,SAAS,aAAa,QAAqB,IAA6C;AAE7F,QAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,KAAG,GAAG,QAAQ,CAAC,SAAiB;AAC9B,UAAM,MAAM,KAAK,KAAK,EAAE,YAAY;AACpC,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAI,qCAAiB;AAC7B,cAAQ,IAAI,2CAAkB;AAC9B,cAAQ,IAAI,uDAAoB;AAChC,cAAQ,IAAI,2CAAkB;AAAA,IAChC,WAAW,QAAQ,UAAU;AAC3B,cAAQ,IAAI,kCAAS;AACrB,cAAQ,IAAI,wBAAe,OAAO,QAAQ,EAAuB,IAAI,EAAE;AACvE,cAAQ,IAAI,yBAAU,KAAK,yBAAU,sBAAO,EAAE;AAAA,IAChD,WAAW,QAAQ,QAAQ;AACzB,UAAI,KAAK,yCAAW;AACpB,SAAG,MAAM;AACT,aAAO,MAAM;AACb,UAAI,GAAI,IAAG,MAAM;AACjB,cAAQ,KAAK,CAAC;AAAA,IAChB,OAAO;AACL,UAAI,KAAK,6BAAS,GAAG,kDAAe;AAAA,IACtC;AAAA,EACF,CAAC;AACD,KAAG,GAAG,UAAU,MAAM,QAAQ,KAAK,CAAC;AACpC,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,13 +1,14 @@
1
1
  /**
2
- * service-registry.ts — 跨模块 service.get 后端
2
+ * service-registry.ts — 跨模块服务注册表与调度中心(Service Registry)
3
3
  *
4
- * 协议:
5
- * HTTP GET /api/sfmc/services/<name>?input=<urlencoded-json>
6
- * 鉴权:Bearer module_token + ?moduleId=<callerId>
4
+ * 协议规范:
5
+ * - HTTP GET `/api/sfmc/services/<name>?input=<urlencoded-json>`
6
+ * - 鉴权机制:Bearer module_token + 查询参数 `?moduleId=<callerId>`
7
7
  *
8
- * 派发:
9
- * handler 注册在 db-server 进程内存。外层 db.tx 内的 tx.call 会注入
10
- * ctx.tx = { query, db },handler 不得再 BEGIN(方案 A)。
8
+ * 调度与事务边界:
9
+ * 服务处理器注册在 db-server 进程内存中。
10
+ * 在外层 `db.tx` 事务会话内调用 `tx.call` 时,会通过 `ctx.tx = { query, db }` 注入现有事务上下文,
11
+ * 处理器复用当前事务连接,严禁重复开启嵌套事务。
11
12
  */
12
13
  import type { DatabaseSync } from "node:sqlite";
13
14
  import type { ModuleManifestV2 } from "./manifest-loader.js";
@@ -23,19 +24,48 @@ export interface ServiceTxContext {
23
24
  export interface ServiceDispatchContext {
24
25
  callerModuleId: string;
25
26
  payload: unknown;
26
- /** 外层 db.tx 已打开时注入;handler 复用连接,禁止嵌套 BEGIN */
27
+ /** 外层 db.tx 已打开时注入;处理器复用此连接,禁止开启嵌套 BEGIN */
27
28
  tx?: ServiceTxContext;
28
29
  }
29
30
  export type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;
31
+ /** 跨模块服务注册表管理器。 */
30
32
  export declare class ServiceRegistry {
31
33
  private readonly handlers;
32
- /** 提供方在 db-server 启动期注册 handler */
34
+ /**
35
+ * 注册指定模块提供的跨模块服务处理器。
36
+ *
37
+ * @param moduleId 服务提供方模块 ID。
38
+ * @param name 服务完整名称。
39
+ * @param handle 业务处理函数。
40
+ */
33
41
  registerHandler(moduleId: string, name: string, handle: ServiceHandler): void;
42
+ /**
43
+ * 注销指定名称的服务处理器。
44
+ *
45
+ * @param name 待注销的服务名称。
46
+ */
34
47
  unregisterHandler(name: string): void;
48
+ /**
49
+ * 获取当前已注册的全部跨模块服务列表。
50
+ *
51
+ * @returns 包含服务名与所属模块 ID 的列表。
52
+ */
35
53
  list(): Array<{
36
54
  name: string;
37
55
  moduleId: string;
38
56
  }>;
57
+ /**
58
+ * 调度并执行指定的跨模块服务。
59
+ * 包含调用方与提供方启停状态校验、声明式契约(`services.requires`)匹配及异常分流。
60
+ *
61
+ * @param enabled 当前所有已启用的模块清单字典。
62
+ * @param callerModuleId 发起调用的模块唯一标识符。
63
+ * @param name 目标服务完整名称。
64
+ * @param payload 传入调用的参数数据。
65
+ * @param tx 可选的当前事务上下文。
66
+ * @returns 服务执行返回结果。
67
+ * @throws {DispatchError} 服务未找到、未声明依赖或鉴权失败时抛出。
68
+ */
39
69
  dispatch(enabled: Map<string, ModuleManifestV2>, callerModuleId: string, name: string, payload: unknown, tx?: ServiceTxContext): Promise<ServiceCallResult>;
40
70
  }
41
71
  export declare class DispatchError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../src/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,YAAY,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,EAAE,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAO/E,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE,mCAAmC;IACnC,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAK5E;IAED,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEpC;IAED,IAAI,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAEhD;IAEK,QAAQ,CACZ,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACtC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,gBAAgB,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAqC5B;CAEF;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IACxF,MAAM,EAAE,MAAM,CAAC;IACf,YACE,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,EACvF,MAAM,EAAE,MAAM,EAKf;CACF"}
1
+ {"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../src/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,YAAY,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,EAAE,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAO/E,mBAAmB;AACnB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAK5E;IAED;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEpC;IAED;;;;OAIG;IACH,IAAI,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAEhD;IAED;;;;;;;;;;;OAWG;IACG,QAAQ,CAEZ,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACtC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,gBAAgB,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAqC5B;CAEF;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IACxF,MAAM,EAAE,MAAM,CAAC;IACf,YACE,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,EACvF,MAAM,EAAE,MAAM,EAKf;CACF"}
@@ -1,18 +1,46 @@
1
1
  class ServiceRegistry {
2
2
  handlers = /* @__PURE__ */ new Map();
3
- /** 提供方在 db-server 启动期注册 handler */
3
+ /**
4
+ * 注册指定模块提供的跨模块服务处理器。
5
+ *
6
+ * @param moduleId 服务提供方模块 ID。
7
+ * @param name 服务完整名称。
8
+ * @param handle 业务处理函数。
9
+ */
4
10
  registerHandler(moduleId, name, handle) {
5
11
  if (this.handlers.has(name)) {
6
- throw new Error(`[service] "${name}" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C,${moduleId} \u62A2\u6CE8`);
12
+ throw new Error(`[service] "${name}" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C, ${moduleId} \u62A2\u6CE8`);
7
13
  }
8
14
  this.handlers.set(name, { moduleId, handle });
9
15
  }
16
+ /**
17
+ * 注销指定名称的服务处理器。
18
+ *
19
+ * @param name 待注销的服务名称。
20
+ */
10
21
  unregisterHandler(name) {
11
22
  this.handlers.delete(name);
12
23
  }
24
+ /**
25
+ * 获取当前已注册的全部跨模块服务列表。
26
+ *
27
+ * @returns 包含服务名与所属模块 ID 的列表。
28
+ */
13
29
  list() {
14
30
  return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));
15
31
  }
32
+ /**
33
+ * 调度并执行指定的跨模块服务。
34
+ * 包含调用方与提供方启停状态校验、声明式契约(`services.requires`)匹配及异常分流。
35
+ *
36
+ * @param enabled 当前所有已启用的模块清单字典。
37
+ * @param callerModuleId 发起调用的模块唯一标识符。
38
+ * @param name 目标服务完整名称。
39
+ * @param payload 传入调用的参数数据。
40
+ * @param tx 可选的当前事务上下文。
41
+ * @returns 服务执行返回结果。
42
+ * @throws {DispatchError} 服务未找到、未声明依赖或鉴权失败时抛出。
43
+ */
16
44
  async dispatch(enabled, callerModuleId, name, payload, tx) {
17
45
  const handler = this.handlers.get(name);
18
46
  if (!handler) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/service-registry.ts"],
4
- "sourcesContent": ["/**\n * service-registry.ts \u2014 \u8DE8\u6A21\u5757 service.get \u540E\u7AEF\n *\n * \u534F\u8BAE:\n * HTTP GET /api/sfmc/services/<name>?input=<urlencoded-json>\n * \u9274\u6743:Bearer module_token + ?moduleId=<callerId>\n *\n * \u6D3E\u53D1:\n * handler \u6CE8\u518C\u5728 db-server \u8FDB\u7A0B\u5185\u5B58\u3002\u5916\u5C42 db.tx \u5185\u7684 tx.call \u4F1A\u6CE8\u5165\n * ctx.tx = { query, db },handler \u4E0D\u5F97\u518D BEGIN(\u65B9\u6848 A)\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport type { QueryFn } from \"./lib/sqlite.js\";\n\nexport interface ServiceCallResult {\n ok: true;\n result: unknown;\n}\n\nexport interface ServiceTxContext {\n query: QueryFn;\n db: DatabaseSync;\n}\n\nexport interface ServiceDispatchContext {\n callerModuleId: string;\n payload: unknown;\n /** \u5916\u5C42 db.tx \u5DF2\u6253\u5F00\u65F6\u6CE8\u5165;handler \u590D\u7528\u8FDE\u63A5,\u7981\u6B62\u5D4C\u5957 BEGIN */\n tx?: ServiceTxContext;\n}\n\nexport type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;\n\ninterface RegisteredHandler {\n moduleId: string;\n handle: ServiceHandler;\n}\n\nexport class ServiceRegistry {\n private readonly handlers = new Map<string, RegisteredHandler>();\n\n /** \u63D0\u4F9B\u65B9\u5728 db-server \u542F\u52A8\u671F\u6CE8\u518C handler */\n registerHandler(moduleId: string, name: string, handle: ServiceHandler): void {\n if (this.handlers.has(name)) {\n throw new Error(`[service] \"${name}\" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C,${moduleId} \u62A2\u6CE8`);\n }\n this.handlers.set(name, { moduleId, handle });\n }\n\n unregisterHandler(name: string): void {\n this.handlers.delete(name);\n }\n\n list(): Array<{ name: string; moduleId: string }> {\n return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));\n }\n\n async dispatch(\n enabled: Map<string, ModuleManifestV2>,\n callerModuleId: string,\n name: string,\n payload: unknown,\n tx?: ServiceTxContext\n ): Promise<ServiceCallResult> {\n const handler = this.handlers.get(name);\n if (!handler) {\n throw new DispatchError(`service \"${name}\" \u672A\u6CE8\u518C`, \"no_such_service\", 404);\n }\n if (!enabled.has(handler.moduleId)) {\n throw new DispatchError(`service \"${name}\" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, \"forbidden\", 403);\n }\n const caller = enabled.get(callerModuleId);\n if (!caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n const declared = caller.services.requires.find((r) => r.name === name);\n // \u63D0\u4F9B\u65B9\u8C03\u81EA\u5DF1\u7684 service \u514D requires(\u4F8B\u5982 economy \u767D\u76AE\u4E66\u8C03 stats.monthly)\n if (!declared && callerModuleId !== handler.moduleId) {\n throw new DispatchError(\n `${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E \"${name}\"`,\n \"not_in_requires\",\n 403\n );\n }\n try {\n const result = await handler.handle({\n callerModuleId,\n payload,\n ...(tx ? { tx } : {}),\n });\n return { ok: true, result };\n } catch (e) {\n // \u4FDD\u7559 handler \u629B\u51FA\u7684\u9886\u57DF/\u9274\u6743\u9519\u8BEF\u5951\u7EA6,\u52FF\u538B\u6210 500 internal(LSP)\n if (e instanceof DispatchError) throw e;\n const err = e as Error & { code?: string; status?: number };\n if (typeof err.status === \"number\" && err.code) {\n throw new DispatchError(err.message, err.code as DispatchError[\"code\"], err.status);\n }\n throw new DispatchError(`handler \u629B\u9519: ${err.message}`, \"internal\", 500);\n }\n }\n\n}\n\nexport class DispatchError extends Error {\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\";\n status: number;\n constructor(\n message: string,\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\",\n status: number\n ) {\n super(message);\n this.code = code;\n this.status = status;\n }\n}\n"],
5
- "mappings": "AAwCO,MAAM,gBAAgB;AAAA,EACV,WAAW,oBAAI,IAA+B;AAAA;AAAA,EAG/D,gBAAgB,UAAkB,MAAc,QAA8B;AAC5E,QAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI,MAAM,cAAc,IAAI,kBAAQ,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,iBAAO,QAAQ,eAAK;AAAA,IACjG;AACA,SAAK,SAAS,IAAI,MAAM,EAAE,UAAU,OAAO,CAAC;AAAA,EAC9C;AAAA,EAEA,kBAAkB,MAAoB;AACpC,SAAK,SAAS,OAAO,IAAI;AAAA,EAC3B;AAAA,EAEA,OAAkD;AAChD,WAAO,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EACzF;AAAA,EAEA,MAAM,SACJ,SACA,gBACA,MACA,SACA,IAC4B;AAC5B,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,mBAAmB,GAAG;AAAA,IACzE;AACA,QAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AAClC,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,QAAQ,QAAQ,mBAAc,aAAa,GAAG;AAAA,IACjG;AACA,UAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,cAAc,sBAAO,cAAc,mBAAc,aAAa,GAAG;AAAA,IAC7E;AACA,UAAM,WAAW,OAAO,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAErE,QAAI,CAAC,YAAY,mBAAmB,QAAQ,UAAU;AACpD,YAAM,IAAI;AAAA,QACR,GAAG,cAAc,0DAAsC,IAAI;AAAA,QAC3D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,OAAO;AAAA,QAClC;AAAA,QACA;AAAA,QACA,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,MACrB,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,OAAO;AAAA,IAC5B,SAAS,GAAG;AAEV,UAAI,aAAa,cAAe,OAAM;AACtC,YAAM,MAAM;AACZ,UAAI,OAAO,IAAI,WAAW,YAAY,IAAI,MAAM;AAC9C,cAAM,IAAI,cAAc,IAAI,SAAS,IAAI,MAA+B,IAAI,MAAM;AAAA,MACpF;AACA,YAAM,IAAI,cAAc,yBAAe,IAAI,OAAO,IAAI,YAAY,GAAG;AAAA,IACvE;AAAA,EACF;AAEF;AAEO,MAAM,sBAAsB,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EACA,YACE,SACA,MACA,QACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;",
4
+ "sourcesContent": ["/**\n * service-registry.ts \u2014 \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\uFF08Service Registry\uFF09\n *\n * \u534F\u8BAE\u89C4\u8303\uFF1A\n * - HTTP GET `/api/sfmc/services/<name>?input=<urlencoded-json>`\n * - \u9274\u6743\u673A\u5236\uFF1ABearer module_token + \u67E5\u8BE2\u53C2\u6570 `?moduleId=<callerId>`\n *\n * \u8C03\u5EA6\u4E0E\u4E8B\u52A1\u8FB9\u754C\uFF1A\n * \u670D\u52A1\u5904\u7406\u5668\u6CE8\u518C\u5728 db-server \u8FDB\u7A0B\u5185\u5B58\u4E2D\u3002\n * \u5728\u5916\u5C42 `db.tx` \u4E8B\u52A1\u4F1A\u8BDD\u5185\u8C03\u7528 `tx.call` \u65F6\uFF0C\u4F1A\u901A\u8FC7 `ctx.tx = { query, db }` \u6CE8\u5165\u73B0\u6709\u4E8B\u52A1\u4E0A\u4E0B\u6587\uFF0C\n * \u5904\u7406\u5668\u590D\u7528\u5F53\u524D\u4E8B\u52A1\u8FDE\u63A5\uFF0C\u4E25\u7981\u91CD\u590D\u5F00\u542F\u5D4C\u5957\u4E8B\u52A1\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport type { QueryFn } from \"./lib/sqlite.js\";\n\nexport interface ServiceCallResult {\n ok: true;\n result: unknown;\n}\n\nexport interface ServiceTxContext {\n query: QueryFn;\n db: DatabaseSync;\n}\n\nexport interface ServiceDispatchContext {\n callerModuleId: string;\n payload: unknown;\n /** \u5916\u5C42 db.tx \u5DF2\u6253\u5F00\u65F6\u6CE8\u5165\uFF1B\u5904\u7406\u5668\u590D\u7528\u6B64\u8FDE\u63A5\uFF0C\u7981\u6B62\u5F00\u542F\u5D4C\u5957 BEGIN\u3002 */\n tx?: ServiceTxContext;\n}\n\nexport type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;\n\ninterface RegisteredHandler {\n moduleId: string;\n handle: ServiceHandler;\n}\n\n/** \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u7BA1\u7406\u5668\u3002 */\nexport class ServiceRegistry {\n private readonly handlers = new Map<string, RegisteredHandler>();\n\n /**\n * \u6CE8\u518C\u6307\u5B9A\u6A21\u5757\u63D0\u4F9B\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param moduleId \u670D\u52A1\u63D0\u4F9B\u65B9\u6A21\u5757 ID\u3002\n * @param name \u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param handle \u4E1A\u52A1\u5904\u7406\u51FD\u6570\u3002\n */\n registerHandler(moduleId: string, name: string, handle: ServiceHandler): void {\n if (this.handlers.has(name)) {\n throw new Error(`[service] \"${name}\" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C, ${moduleId} \u62A2\u6CE8`);\n }\n this.handlers.set(name, { moduleId, handle });\n }\n\n /**\n * \u6CE8\u9500\u6307\u5B9A\u540D\u79F0\u7684\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param name \u5F85\u6CE8\u9500\u7684\u670D\u52A1\u540D\u79F0\u3002\n */\n unregisterHandler(name: string): void {\n this.handlers.delete(name);\n }\n\n /**\n * \u83B7\u53D6\u5F53\u524D\u5DF2\u6CE8\u518C\u7684\u5168\u90E8\u8DE8\u6A21\u5757\u670D\u52A1\u5217\u8868\u3002\n *\n * @returns \u5305\u542B\u670D\u52A1\u540D\u4E0E\u6240\u5C5E\u6A21\u5757 ID \u7684\u5217\u8868\u3002\n */\n list(): Array<{ name: string; moduleId: string }> {\n return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));\n }\n\n /**\n * \u8C03\u5EA6\u5E76\u6267\u884C\u6307\u5B9A\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u3002\n * \u5305\u542B\u8C03\u7528\u65B9\u4E0E\u63D0\u4F9B\u65B9\u542F\u505C\u72B6\u6001\u6821\u9A8C\u3001\u58F0\u660E\u5F0F\u5951\u7EA6\uFF08`services.requires`\uFF09\u5339\u914D\u53CA\u5F02\u5E38\u5206\u6D41\u3002\n *\n * @param enabled \u5F53\u524D\u6240\u6709\u5DF2\u542F\u7528\u7684\u6A21\u5757\u6E05\u5355\u5B57\u5178\u3002\n * @param callerModuleId \u53D1\u8D77\u8C03\u7528\u7684\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param name \u76EE\u6807\u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param payload \u4F20\u5165\u8C03\u7528\u7684\u53C2\u6570\u6570\u636E\u3002\n * @param tx \u53EF\u9009\u7684\u5F53\u524D\u4E8B\u52A1\u4E0A\u4E0B\u6587\u3002\n * @returns \u670D\u52A1\u6267\u884C\u8FD4\u56DE\u7ED3\u679C\u3002\n * @throws {DispatchError} \u670D\u52A1\u672A\u627E\u5230\u3001\u672A\u58F0\u660E\u4F9D\u8D56\u6216\u9274\u6743\u5931\u8D25\u65F6\u629B\u51FA\u3002\n */\n async dispatch(\n\n enabled: Map<string, ModuleManifestV2>,\n callerModuleId: string,\n name: string,\n payload: unknown,\n tx?: ServiceTxContext\n ): Promise<ServiceCallResult> {\n const handler = this.handlers.get(name);\n if (!handler) {\n throw new DispatchError(`service \"${name}\" \u672A\u6CE8\u518C`, \"no_such_service\", 404);\n }\n if (!enabled.has(handler.moduleId)) {\n throw new DispatchError(`service \"${name}\" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, \"forbidden\", 403);\n }\n const caller = enabled.get(callerModuleId);\n if (!caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n const declared = caller.services.requires.find((r) => r.name === name);\n // \u63D0\u4F9B\u65B9\u8C03\u81EA\u5DF1\u7684 service \u514D requires(\u4F8B\u5982 economy \u767D\u76AE\u4E66\u8C03 stats.monthly)\n if (!declared && callerModuleId !== handler.moduleId) {\n throw new DispatchError(\n `${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E \"${name}\"`,\n \"not_in_requires\",\n 403\n );\n }\n try {\n const result = await handler.handle({\n callerModuleId,\n payload,\n ...(tx ? { tx } : {}),\n });\n return { ok: true, result };\n } catch (e) {\n // \u4FDD\u7559 handler \u629B\u51FA\u7684\u9886\u57DF/\u9274\u6743\u9519\u8BEF\u5951\u7EA6,\u52FF\u538B\u6210 500 internal(LSP)\n if (e instanceof DispatchError) throw e;\n const err = e as Error & { code?: string; status?: number };\n if (typeof err.status === \"number\" && err.code) {\n throw new DispatchError(err.message, err.code as DispatchError[\"code\"], err.status);\n }\n throw new DispatchError(`handler \u629B\u9519: ${err.message}`, \"internal\", 500);\n }\n }\n\n}\n\nexport class DispatchError extends Error {\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\";\n status: number;\n constructor(\n message: string,\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\",\n status: number\n ) {\n super(message);\n this.code = code;\n this.status = status;\n }\n}\n"],
5
+ "mappings": "AA0CO,MAAM,gBAAgB;AAAA,EACV,WAAW,oBAAI,IAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/D,gBAAgB,UAAkB,MAAc,QAA8B;AAC5E,QAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI,MAAM,cAAc,IAAI,kBAAQ,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,kBAAQ,QAAQ,eAAK;AAAA,IAClG;AACA,SAAK,SAAS,IAAI,MAAM,EAAE,UAAU,OAAO,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,MAAoB;AACpC,SAAK,SAAS,OAAO,IAAI;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAkD;AAChD,WAAO,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,SAEJ,SACA,gBACA,MACA,SACA,IAC4B;AAC5B,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,mBAAmB,GAAG;AAAA,IACzE;AACA,QAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AAClC,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,QAAQ,QAAQ,mBAAc,aAAa,GAAG;AAAA,IACjG;AACA,UAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,cAAc,sBAAO,cAAc,mBAAc,aAAa,GAAG;AAAA,IAC7E;AACA,UAAM,WAAW,OAAO,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAErE,QAAI,CAAC,YAAY,mBAAmB,QAAQ,UAAU;AACpD,YAAM,IAAI;AAAA,QACR,GAAG,cAAc,0DAAsC,IAAI;AAAA,QAC3D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,OAAO;AAAA,QAClC;AAAA,QACA;AAAA,QACA,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,MACrB,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,OAAO;AAAA,IAC5B,SAAS,GAAG;AAEV,UAAI,aAAa,cAAe,OAAM;AACtC,YAAM,MAAM;AACZ,UAAI,OAAO,IAAI,WAAW,YAAY,IAAI,MAAM;AAC9C,cAAM,IAAI,cAAc,IAAI,SAAS,IAAI,MAA+B,IAAI,MAAM;AAAA,MACpF;AACA,YAAM,IAAI,cAAc,yBAAe,IAAI,OAAO,IAAI,YAAY,GAAG;AAAA,IACvE;AAAA,EACF;AAEF;AAEO,MAAM,sBAAsB,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EACA,YACE,SACA,MACA,QACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * services/builtin-handlers.ts — 进程内置 service 插件注册表
2
+ * services/builtin-handlers.ts — 进程内置跨模块服务插件注册表
3
3
  *
4
- * 新增内置 handler 只追加 BUILTIN_SERVICE_PLUGINS(OCP),
5
- * 勿在 index.ts 再写 if (enabledSet.has(...)) registerXxx 链。
4
+ * 遵循开闭原则(OCP):新增内置服务仅需向 `BUILTIN_SERVICE_PLUGINS` 追加配置,
5
+ * 避免在主入口硬编码 `if (enabledSet.has(...)) registerXxx` 注册链。
6
6
  */
7
7
  import type { DatabaseSync } from "node:sqlite";
8
8
  import type { QueryFn } from "../lib/sqlite.js";
@@ -15,12 +15,32 @@ export type BuiltinServicePlugin = {
15
15
  moduleId: string;
16
16
  register: (registry: ServiceRegistry, deps: BuiltinServiceDeps) => void;
17
17
  };
18
- /** 内置 service 插件清单 — 唯一扩展点 */
18
+ /** 内置 service 插件清单 — 扩展注册列表。 */
19
19
  export declare const BUILTIN_SERVICE_PLUGINS: BuiltinServicePlugin[];
20
- /** 按 enabledSet 注册内置插件,返回已注册插件数 */
20
+ /**
21
+ * 根据已启用模块集合批量注册内置服务插件。
22
+ *
23
+ * @param registry 服务注册表实例。
24
+ * @param deps 数据库查询与连接依赖。
25
+ * @param enabledSet 当前处于启用状态的模块 ID 集合。
26
+ * @returns 成功注册的插件数量。
27
+ */
21
28
  export declare function registerEnabledBuiltinServices(registry: ServiceRegistry, deps: BuiltinServiceDeps, enabledSet: Set<string>): number;
22
- /** 热启用:只注册单个内置插件(若尚未注册) — 以 registry.moduleId 为权威,勿硬编码 service 名(DRY) */
29
+ /**
30
+ * 热启用指定模块的内置服务插件(若尚未注册)。
31
+ *
32
+ * @param registry 服务注册表实例。
33
+ * @param deps 数据库依赖。
34
+ * @param moduleId 目标模块唯一标识符。
35
+ * @returns 若成功注册返回 `true`,若插件不存在或已注册则返回 `false`。
36
+ */
23
37
  export declare function registerBuiltinPluginForModule(registry: ServiceRegistry, deps: BuiltinServiceDeps, moduleId: string): boolean;
24
- /** 热禁用:按 moduleId 卸掉该模块全部 handler(勿维护 serviceNames 副本 — DRY/OCP) */
38
+ /**
39
+ * 热禁用指定模块的全部服务处理器。
40
+ *
41
+ * @param registry 服务注册表实例。
42
+ * @param moduleId 待卸载服务的模块 ID。
43
+ * @returns 成功卸载的服务数量。
44
+ */
25
45
  export declare function unregisterBuiltinPluginForModule(registry: ServiceRegistry, moduleId: string): number;
26
46
  //# sourceMappingURL=builtin-handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builtin-handlers.d.ts","sourceRoot":"","sources":["../../src/services/builtin-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,MAAM,MAAM,kBAAkB,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,YAAY,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,8BAA8B;AAC9B,eAAO,MAAM,uBAAuB,EAAE,oBAAoB,EAEzD,CAAC;AAEF,mCAAmC;AACnC,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,kBAAkB,EACxB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CAQR;AAED,yEAAyE;AACzE,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAOT;AAED,oEAAoE;AACpE,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQpG"}
1
+ {"version":3,"file":"builtin-handlers.d.ts","sourceRoot":"","sources":["../../src/services/builtin-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,MAAM,MAAM,kBAAkB,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,YAAY,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,EAAE,oBAAoB,EAEzD,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,kBAAkB,EACxB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CAQR;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAOT;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASpG"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/services/builtin-handlers.ts"],
4
- "sourcesContent": ["/**\n * services/builtin-handlers.ts \u2014 \u8FDB\u7A0B\u5185\u7F6E service \u63D2\u4EF6\u6CE8\u518C\u8868\n *\n * \u65B0\u589E\u5185\u7F6E handler \u53EA\u8FFD\u52A0 BUILTIN_SERVICE_PLUGINS(OCP),\n * \u52FF\u5728 index.ts \u518D\u5199 if (enabledSet.has(...)) registerXxx \u94FE\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\nimport type { ServiceRegistry } from \"../service-registry.js\";\nimport { registerEconomyHandlers } from \"./economy-handlers.js\";\n\nexport type BuiltinServiceDeps = { query: QueryFn; db: DatabaseSync };\n\nexport type BuiltinServicePlugin = {\n moduleId: string;\n register: (registry: ServiceRegistry, deps: BuiltinServiceDeps) => void;\n};\n\n/** \u5185\u7F6E service \u63D2\u4EF6\u6E05\u5355 \u2014 \u552F\u4E00\u6269\u5C55\u70B9 */\nexport const BUILTIN_SERVICE_PLUGINS: BuiltinServicePlugin[] = [\n { moduleId: \"feature-economy\", register: registerEconomyHandlers },\n];\n\n/** \u6309 enabledSet \u6CE8\u518C\u5185\u7F6E\u63D2\u4EF6,\u8FD4\u56DE\u5DF2\u6CE8\u518C\u63D2\u4EF6\u6570 */\nexport function registerEnabledBuiltinServices(\n registry: ServiceRegistry,\n deps: BuiltinServiceDeps,\n enabledSet: Set<string>\n): number {\n let n = 0;\n for (const plugin of BUILTIN_SERVICE_PLUGINS) {\n if (!enabledSet.has(plugin.moduleId)) continue;\n plugin.register(registry, deps);\n n += 1;\n }\n return n;\n}\n\n/** \u70ED\u542F\u7528:\u53EA\u6CE8\u518C\u5355\u4E2A\u5185\u7F6E\u63D2\u4EF6(\u82E5\u5C1A\u672A\u6CE8\u518C) \u2014 \u4EE5 registry.moduleId \u4E3A\u6743\u5A01,\u52FF\u786C\u7F16\u7801 service \u540D(DRY) */\nexport function registerBuiltinPluginForModule(\n registry: ServiceRegistry,\n deps: BuiltinServiceDeps,\n moduleId: string\n): boolean {\n const plugin = BUILTIN_SERVICE_PLUGINS.find((p) => p.moduleId === moduleId);\n if (!plugin) return false;\n const already = registry.list().some((h) => h.moduleId === moduleId);\n if (already) return false;\n plugin.register(registry, deps);\n return true;\n}\n\n/** \u70ED\u7981\u7528:\u6309 moduleId \u5378\u6389\u8BE5\u6A21\u5757\u5168\u90E8 handler(\u52FF\u7EF4\u62A4 serviceNames \u526F\u672C \u2014 DRY/OCP) */\nexport function unregisterBuiltinPluginForModule(registry: ServiceRegistry, moduleId: string): number {\n let n = 0;\n for (const h of registry.list()) {\n if (h.moduleId !== moduleId) continue;\n registry.unregisterHandler(h.name);\n n += 1;\n }\n return n;\n}\n"],
5
- "mappings": "AAUA,SAAS,+BAA+B;AAUjC,MAAM,0BAAkD;AAAA,EAC7D,EAAE,UAAU,mBAAmB,UAAU,wBAAwB;AACnE;AAGO,SAAS,+BACd,UACA,MACA,YACQ;AACR,MAAI,IAAI;AACR,aAAW,UAAU,yBAAyB;AAC5C,QAAI,CAAC,WAAW,IAAI,OAAO,QAAQ,EAAG;AACtC,WAAO,SAAS,UAAU,IAAI;AAC9B,SAAK;AAAA,EACP;AACA,SAAO;AACT;AAGO,SAAS,+BACd,UACA,MACA,UACS;AACT,QAAM,SAAS,wBAAwB,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC1E,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,SAAS,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AACnE,MAAI,QAAS,QAAO;AACpB,SAAO,SAAS,UAAU,IAAI;AAC9B,SAAO;AACT;AAGO,SAAS,iCAAiC,UAA2B,UAA0B;AACpG,MAAI,IAAI;AACR,aAAW,KAAK,SAAS,KAAK,GAAG;AAC/B,QAAI,EAAE,aAAa,SAAU;AAC7B,aAAS,kBAAkB,EAAE,IAAI;AACjC,SAAK;AAAA,EACP;AACA,SAAO;AACT;",
4
+ "sourcesContent": ["/**\n * services/builtin-handlers.ts \u2014 \u8FDB\u7A0B\u5185\u7F6E\u8DE8\u6A21\u5757\u670D\u52A1\u63D2\u4EF6\u6CE8\u518C\u8868\n *\n * \u9075\u5FAA\u5F00\u95ED\u539F\u5219\uFF08OCP\uFF09\uFF1A\u65B0\u589E\u5185\u7F6E\u670D\u52A1\u4EC5\u9700\u5411 `BUILTIN_SERVICE_PLUGINS` \u8FFD\u52A0\u914D\u7F6E\uFF0C\n * \u907F\u514D\u5728\u4E3B\u5165\u53E3\u786C\u7F16\u7801 `if (enabledSet.has(...)) registerXxx` \u6CE8\u518C\u94FE\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\nimport type { ServiceRegistry } from \"../service-registry.js\";\nimport { registerEconomyHandlers } from \"./economy-handlers.js\";\n\nexport type BuiltinServiceDeps = { query: QueryFn; db: DatabaseSync };\n\nexport type BuiltinServicePlugin = {\n moduleId: string;\n register: (registry: ServiceRegistry, deps: BuiltinServiceDeps) => void;\n};\n\n/** \u5185\u7F6E service \u63D2\u4EF6\u6E05\u5355 \u2014 \u6269\u5C55\u6CE8\u518C\u5217\u8868\u3002 */\nexport const BUILTIN_SERVICE_PLUGINS: BuiltinServicePlugin[] = [\n { moduleId: \"feature-economy\", register: registerEconomyHandlers },\n];\n\n/**\n * \u6839\u636E\u5DF2\u542F\u7528\u6A21\u5757\u96C6\u5408\u6279\u91CF\u6CE8\u518C\u5185\u7F6E\u670D\u52A1\u63D2\u4EF6\u3002\n *\n * @param registry \u670D\u52A1\u6CE8\u518C\u8868\u5B9E\u4F8B\u3002\n * @param deps \u6570\u636E\u5E93\u67E5\u8BE2\u4E0E\u8FDE\u63A5\u4F9D\u8D56\u3002\n * @param enabledSet \u5F53\u524D\u5904\u4E8E\u542F\u7528\u72B6\u6001\u7684\u6A21\u5757 ID \u96C6\u5408\u3002\n * @returns \u6210\u529F\u6CE8\u518C\u7684\u63D2\u4EF6\u6570\u91CF\u3002\n */\nexport function registerEnabledBuiltinServices(\n registry: ServiceRegistry,\n deps: BuiltinServiceDeps,\n enabledSet: Set<string>\n): number {\n let n = 0;\n for (const plugin of BUILTIN_SERVICE_PLUGINS) {\n if (!enabledSet.has(plugin.moduleId)) continue;\n plugin.register(registry, deps);\n n += 1;\n }\n return n;\n}\n\n/**\n * \u70ED\u542F\u7528\u6307\u5B9A\u6A21\u5757\u7684\u5185\u7F6E\u670D\u52A1\u63D2\u4EF6\uFF08\u82E5\u5C1A\u672A\u6CE8\u518C\uFF09\u3002\n *\n * @param registry \u670D\u52A1\u6CE8\u518C\u8868\u5B9E\u4F8B\u3002\n * @param deps \u6570\u636E\u5E93\u4F9D\u8D56\u3002\n * @param moduleId \u76EE\u6807\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @returns \u82E5\u6210\u529F\u6CE8\u518C\u8FD4\u56DE `true`\uFF0C\u82E5\u63D2\u4EF6\u4E0D\u5B58\u5728\u6216\u5DF2\u6CE8\u518C\u5219\u8FD4\u56DE `false`\u3002\n */\nexport function registerBuiltinPluginForModule(\n registry: ServiceRegistry,\n deps: BuiltinServiceDeps,\n moduleId: string\n): boolean {\n const plugin = BUILTIN_SERVICE_PLUGINS.find((p) => p.moduleId === moduleId);\n if (!plugin) return false;\n const already = registry.list().some((h) => h.moduleId === moduleId);\n if (already) return false;\n plugin.register(registry, deps);\n return true;\n}\n\n/**\n * \u70ED\u7981\u7528\u6307\u5B9A\u6A21\u5757\u7684\u5168\u90E8\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param registry \u670D\u52A1\u6CE8\u518C\u8868\u5B9E\u4F8B\u3002\n * @param moduleId \u5F85\u5378\u8F7D\u670D\u52A1\u7684\u6A21\u5757 ID\u3002\n * @returns \u6210\u529F\u5378\u8F7D\u7684\u670D\u52A1\u6570\u91CF\u3002\n */\nexport function unregisterBuiltinPluginForModule(registry: ServiceRegistry, moduleId: string): number {\n\n let n = 0;\n for (const h of registry.list()) {\n if (h.moduleId !== moduleId) continue;\n registry.unregisterHandler(h.name);\n n += 1;\n }\n return n;\n}\n"],
5
+ "mappings": "AAUA,SAAS,+BAA+B;AAUjC,MAAM,0BAAkD;AAAA,EAC7D,EAAE,UAAU,mBAAmB,UAAU,wBAAwB;AACnE;AAUO,SAAS,+BACd,UACA,MACA,YACQ;AACR,MAAI,IAAI;AACR,aAAW,UAAU,yBAAyB;AAC5C,QAAI,CAAC,WAAW,IAAI,OAAO,QAAQ,EAAG;AACtC,WAAO,SAAS,UAAU,IAAI;AAC9B,SAAK;AAAA,EACP;AACA,SAAO;AACT;AAUO,SAAS,+BACd,UACA,MACA,UACS;AACT,QAAM,SAAS,wBAAwB,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC1E,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,SAAS,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AACnE,MAAI,QAAS,QAAO;AACpB,SAAO,SAAS,UAAU,IAAI;AAC9B,SAAO;AACT;AASO,SAAS,iCAAiC,UAA2B,UAA0B;AAEpG,MAAI,IAAI;AACR,aAAW,KAAK,SAAS,KAAK,GAAG;AAC/B,QAAI,EAAE,aAAa,SAAU;AAC7B,aAAS,kBAAkB,EAAE,IAAI;AACjC,SAAK;AAAA,EACP;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -131,19 +131,45 @@ export declare class TxRunner {
131
131
  constructor(deps: TxRunnerDeps);
132
132
  run(req: TxRequest): Promise<TxResponse | TxError>;
133
133
  /**
134
- * 交互式事务:begin → step* → commit|rollback
135
- * SDK db.tx 在回调内 await query/get/call 真实结果(PR #31 未完成项)。
136
- * 单连接模型:新会话会先清掉残留会话(避免 BEGIN 锁死)。
134
+ * 开启交互式事务会话(协议流程:begin → step* → commit | rollback)。
135
+ * 供客户端 `db.tx` 在回调内部异步执行并实时读回 query / get / call 的服务端执行结果。
136
+ * 单连接保护:新会话开启时自动清理历史残留会话,避免事务锁冲突。
137
+ *
138
+ * @param moduleId 请求模块唯一标识符。
139
+ * @returns 成功返回分配的会话 txId,失败返回错误响应。
137
140
  */
138
141
  beginSession(moduleId: string): {
139
142
  ok: true;
140
143
  txId: string;
141
144
  } | TxError;
145
+ /**
146
+ * 在当前交互式事务会话中顺序执行单个步骤。
147
+ *
148
+ * @param txId 会话唯一标识符。
149
+ * @param moduleId 调用模块标识。
150
+ * @param step 待执行的操作步骤对象。
151
+ * @returns 步骤执行结果或错误响应。
152
+ */
142
153
  stepSession(txId: string, moduleId: string, step: TxStep): Promise<{
143
154
  ok: true;
144
155
  result: TxStepResult;
145
156
  } | TxError>;
157
+ /**
158
+ * 提交当前交互式事务会话,将所有步骤的修改持久化并释放连接。
159
+ *
160
+ * @param txId 会话唯一标识符。
161
+ * @param moduleId 调用模块标识。
162
+ * @returns 提交结果响应。
163
+ */
146
164
  commitSession(txId: string, moduleId: string): TxResponse | TxError;
165
+ /**
166
+ * 回滚当前交互式事务会话,撤销所有未提交的数据库变更。
167
+ * 具有幂等性:会话已结束或已回滚时同样返回成功。
168
+ *
169
+ * @param txId 会话唯一标识符。
170
+ * @param moduleId 调用模块标识。
171
+ * @returns 回滚确认结果。
172
+ */
147
173
  rollbackSession(txId: string, moduleId: string): {
148
174
  ok: true;
149
175
  } | TxError;
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAc7D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,SAAS,CAAC;QAClB,+CAA+C;QAC/C,OAAO,CAAC,EACJ;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;SAAE,GACtD,KAAK,CAAC;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;SAAE,CAAC,CAAC;QAClE,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,MAAM,MAAM,GACd,WAAW,GACX,SAAS,GACT,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,WAAW,GACX,aAAa,CAAC;AAElB,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAAE,GAChD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,GAClD;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC9C;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC9C;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EACA,mBAAmB,GACnB,WAAW,GACX,iBAAiB,GACjB,iBAAiB,GACjB,cAAc,GACd,UAAU,GACV,eAAe,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAQD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,YAAY,CAAC;IACjB,KAAK,EAAE,OAAO,iBAAiB,EAAE,OAAO,CAAC;IACzC,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACxC;AAED,qBAAa,QAAQ;IAGP,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IAEzD,YAA6B,IAAI,EAAE,YAAY,EAAI;IAE7C,GAAG,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,CAwCvD;IAED;;;;;;;OAOG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAsBnE;IAED;;;;;;;OAOG;IACG,WAAW,CACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,GAAG,OAAO,CAAC,CAwBvD;IAED;;;;;;OAMG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAkBlE;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAWtE;IAGD,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,QAAQ;IAQhB,gCAAgC;YAClB,MAAM;IAuBpB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,OAAO;IA+Bf,4DAA4D;IAC5D,OAAO,CAAC,OAAO;IA0Bf,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,QAAQ;IAkBhB,OAAO,CAAC,QAAQ;IAsBhB,OAAO,CAAC,OAAO;YAYD,SAAS;CAiBxB"}
package/dist/tx-runner.js CHANGED
@@ -51,9 +51,12 @@ class TxRunner {
51
51
  return { ok: true, results };
52
52
  }
53
53
  /**
54
- * 交互式事务:begin → step* → commit|rollback
55
- * SDK db.tx 在回调内 await query/get/call 真实结果(PR #31 未完成项)。
56
- * 单连接模型:新会话会先清掉残留会话(避免 BEGIN 锁死)。
54
+ * 开启交互式事务会话(协议流程:begin → step* → commit | rollback)。
55
+ * 供客户端 `db.tx` 在回调内部异步执行并实时读回 query / get / call 的服务端执行结果。
56
+ * 单连接保护:新会话开启时自动清理历史残留会话,避免事务锁冲突。
57
+ *
58
+ * @param moduleId 请求模块唯一标识符。
59
+ * @returns 成功返回分配的会话 txId,失败返回错误响应。
57
60
  */
58
61
  beginSession(moduleId) {
59
62
  const manifest = this.deps.enabled.get(moduleId);
@@ -77,6 +80,14 @@ class TxRunner {
77
80
  log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);
78
81
  return { ok: true, txId };
79
82
  }
83
+ /**
84
+ * 在当前交互式事务会话中顺序执行单个步骤。
85
+ *
86
+ * @param txId 会话唯一标识符。
87
+ * @param moduleId 调用模块标识。
88
+ * @param step 待执行的操作步骤对象。
89
+ * @returns 步骤执行结果或错误响应。
90
+ */
80
91
  async stepSession(txId, moduleId, step) {
81
92
  const session = this.sessions.get(txId);
82
93
  if (!session) {
@@ -102,6 +113,13 @@ class TxRunner {
102
113
  return { ok: false, step: stepIndex, error: err.message, code };
103
114
  }
104
115
  }
116
+ /**
117
+ * 提交当前交互式事务会话,将所有步骤的修改持久化并释放连接。
118
+ *
119
+ * @param txId 会话唯一标识符。
120
+ * @param moduleId 调用模块标识。
121
+ * @returns 提交结果响应。
122
+ */
105
123
  commitSession(txId, moduleId) {
106
124
  const session = this.sessions.get(txId);
107
125
  if (!session) {
@@ -121,6 +139,14 @@ class TxRunner {
121
139
  log.info(`[tx-session ${txId.slice(0, 8)}] commit ${results.length} steps OK`);
122
140
  return { ok: true, results };
123
141
  }
142
+ /**
143
+ * 回滚当前交互式事务会话,撤销所有未提交的数据库变更。
144
+ * 具有幂等性:会话已结束或已回滚时同样返回成功。
145
+ *
146
+ * @param txId 会话唯一标识符。
147
+ * @param moduleId 调用模块标识。
148
+ * @returns 回滚确认结果。
149
+ */
124
150
  rollbackSession(txId, moduleId) {
125
151
  const session = this.sessions.get(txId);
126
152
  if (!session) {