@sfmc-bds/db-server 0.2.0-beta.1 → 0.2.0-beta.11

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 (163) 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/player-session.d.ts +8 -0
  18. package/dist/domain/player-session.d.ts.map +1 -0
  19. package/dist/domain/player-session.js +16 -0
  20. package/dist/domain/player-session.js.map +7 -0
  21. package/dist/domain/player-session.test.d.ts +2 -0
  22. package/dist/domain/player-session.test.d.ts.map +1 -0
  23. package/dist/domain/player-session.test.js +33 -0
  24. package/dist/domain/player-session.test.js.map +7 -0
  25. package/dist/domain/qq-bind-status.test.d.ts +2 -0
  26. package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
  27. package/dist/domain/qq-bind-status.test.js +211 -0
  28. package/dist/domain/qq-bind-status.test.js.map +7 -0
  29. package/dist/domain/qq-events.d.ts +84 -0
  30. package/dist/domain/qq-events.d.ts.map +1 -0
  31. package/dist/domain/qq-events.js +193 -0
  32. package/dist/domain/qq-events.js.map +7 -0
  33. package/dist/domain/qq-events.test.d.ts +5 -0
  34. package/dist/domain/qq-events.test.d.ts.map +1 -0
  35. package/dist/domain/qq-events.test.js +170 -0
  36. package/dist/domain/qq-events.test.js.map +7 -0
  37. package/dist/domain/qq-join.test.d.ts +2 -0
  38. package/dist/domain/qq-join.test.d.ts.map +1 -0
  39. package/dist/domain/qq-join.test.js +353 -0
  40. package/dist/domain/qq-join.test.js.map +7 -0
  41. package/dist/domain/schema.d.ts +15 -18
  42. package/dist/domain/schema.d.ts.map +1 -1
  43. package/dist/domain/schema.js +104 -453
  44. package/dist/domain/schema.js.map +2 -2
  45. package/dist/domain/system-status.d.ts +75 -0
  46. package/dist/domain/system-status.d.ts.map +1 -0
  47. package/dist/domain/system-status.js +181 -0
  48. package/dist/domain/system-status.js.map +7 -0
  49. package/dist/domain/system-status.test.d.ts +5 -0
  50. package/dist/domain/system-status.test.d.ts.map +1 -0
  51. package/dist/domain/system-status.test.js +19 -0
  52. package/dist/domain/system-status.test.js.map +7 -0
  53. package/dist/domain/transaction.d.ts +5 -4
  54. package/dist/domain/transaction.d.ts.map +1 -1
  55. package/dist/domain/transaction.js.map +2 -2
  56. package/dist/env.d.ts +7 -0
  57. package/dist/env.d.ts.map +1 -1
  58. package/dist/env.js +57 -9
  59. package/dist/env.js.map +2 -2
  60. package/dist/index.d.ts +1 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +128 -38
  63. package/dist/index.js.map +2 -2
  64. package/dist/lib/idempotency-store.d.ts +17 -11
  65. package/dist/lib/idempotency-store.d.ts.map +1 -1
  66. package/dist/lib/idempotency-store.js.map +2 -2
  67. package/dist/lib/idempotency.d.ts +3 -4
  68. package/dist/lib/idempotency.d.ts.map +1 -1
  69. package/dist/lib/idempotency.js.map +2 -2
  70. package/dist/lib/log.d.ts.map +1 -1
  71. package/dist/lib/log.js +3 -6
  72. package/dist/lib/log.js.map +2 -2
  73. package/dist/lib/module-state.d.ts +38 -8
  74. package/dist/lib/module-state.d.ts.map +1 -1
  75. package/dist/lib/module-state.js.map +2 -2
  76. package/dist/lib/order-by.d.ts +8 -6
  77. package/dist/lib/order-by.d.ts.map +1 -1
  78. package/dist/lib/order-by.js.map +2 -2
  79. package/dist/lib/sql-helpers.d.ts +25 -13
  80. package/dist/lib/sql-helpers.d.ts.map +1 -1
  81. package/dist/lib/sql-helpers.js.map +2 -2
  82. package/dist/lib/sqlite.d.ts +14 -5
  83. package/dist/lib/sqlite.d.ts.map +1 -1
  84. package/dist/lib/sqlite.js.map +2 -2
  85. package/dist/manifest-loader.d.ts +5 -2
  86. package/dist/manifest-loader.d.ts.map +1 -1
  87. package/dist/manifest-loader.js +16 -6
  88. package/dist/manifest-loader.js.map +2 -2
  89. package/dist/manifest.d.ts +1 -2
  90. package/dist/manifest.d.ts.map +1 -1
  91. package/dist/manifest.js +13 -8
  92. package/dist/manifest.js.map +2 -2
  93. package/dist/module-auth.d.ts.map +1 -1
  94. package/dist/module-auth.js +2 -6
  95. package/dist/module-auth.js.map +2 -2
  96. package/dist/permission-gate.d.ts.map +1 -1
  97. package/dist/permission-gate.js +8 -2
  98. package/dist/permission-gate.js.map +2 -2
  99. package/dist/project-root.d.ts +6 -0
  100. package/dist/project-root.d.ts.map +1 -0
  101. package/dist/project-root.js +9 -0
  102. package/dist/project-root.js.map +7 -0
  103. package/dist/routes/_shared.d.ts +14 -5
  104. package/dist/routes/_shared.d.ts.map +1 -1
  105. package/dist/routes/_shared.js.map +2 -2
  106. package/dist/routes/config.d.ts +8 -11
  107. package/dist/routes/config.d.ts.map +1 -1
  108. package/dist/routes/config.js +17 -23
  109. package/dist/routes/config.js.map +2 -2
  110. package/dist/routes/db-routes.d.ts +8 -2
  111. package/dist/routes/db-routes.d.ts.map +1 -1
  112. package/dist/routes/db-routes.js +5 -12
  113. package/dist/routes/db-routes.js.map +2 -2
  114. package/dist/routes/messages.d.ts +6 -2
  115. package/dist/routes/messages.d.ts.map +1 -1
  116. package/dist/routes/messages.js +17 -3
  117. package/dist/routes/messages.js.map +2 -2
  118. package/dist/routes/module-config-routes.d.ts +13 -9
  119. package/dist/routes/module-config-routes.d.ts.map +1 -1
  120. package/dist/routes/module-config-routes.js.map +2 -2
  121. package/dist/routes/modules.d.ts +6 -0
  122. package/dist/routes/modules.d.ts.map +1 -1
  123. package/dist/routes/modules.js.map +2 -2
  124. package/dist/routes/qq-bind.d.ts +24 -0
  125. package/dist/routes/qq-bind.d.ts.map +1 -0
  126. package/dist/routes/qq-bind.js +179 -0
  127. package/dist/routes/qq-bind.js.map +7 -0
  128. package/dist/routes/qq-events.d.ts +20 -0
  129. package/dist/routes/qq-events.d.ts.map +1 -0
  130. package/dist/routes/qq-events.js +39 -0
  131. package/dist/routes/qq-events.js.map +7 -0
  132. package/dist/routes/qq-join.d.ts +46 -0
  133. package/dist/routes/qq-join.d.ts.map +1 -0
  134. package/dist/routes/qq-join.js +277 -0
  135. package/dist/routes/qq-join.js.map +7 -0
  136. package/dist/routes/service-routes.d.ts +6 -0
  137. package/dist/routes/service-routes.d.ts.map +1 -1
  138. package/dist/routes/service-routes.js.map +2 -2
  139. package/dist/routes/status.d.ts +19 -0
  140. package/dist/routes/status.d.ts.map +1 -0
  141. package/dist/routes/status.js +74 -0
  142. package/dist/routes/status.js.map +7 -0
  143. package/dist/runtime.test.js +216 -4
  144. package/dist/runtime.test.js.map +2 -2
  145. package/dist/schema-registry.d.ts +17 -16
  146. package/dist/schema-registry.d.ts.map +1 -1
  147. package/dist/schema-registry.js +76 -6
  148. package/dist/schema-registry.js.map +2 -2
  149. package/dist/server.d.ts +19 -0
  150. package/dist/server.d.ts.map +1 -1
  151. package/dist/server.js.map +2 -2
  152. package/dist/service-registry.d.ts +39 -9
  153. package/dist/service-registry.d.ts.map +1 -1
  154. package/dist/service-registry.js +30 -2
  155. package/dist/service-registry.js.map +2 -2
  156. package/dist/services/builtin-handlers.d.ts +27 -7
  157. package/dist/services/builtin-handlers.d.ts.map +1 -1
  158. package/dist/services/builtin-handlers.js.map +2 -2
  159. package/dist/tx-runner.d.ts +48 -9
  160. package/dist/tx-runner.d.ts.map +1 -1
  161. package/dist/tx-runner.js +99 -27
  162. package/dist/tx-runner.js.map +2 -2
  163. package/package.json +26 -24
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/config.ts"],
4
- "sourcesContent": ["/**\n * routes/config.ts \u2014 \u914D\u7F6E\u8DEF\u7531\uFF08\u76F4\u63A5\u8BFB\u53D6 configs/*.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/configs/all \u2014 SAPI \u542F\u52A8\u65F6\u4E00\u6B21\u6027\u62C9\u53D6\u6240\u6709\u914D\u7F6E\n * GET /api/sfmc/settings \u2014 \u5E73\u94FA settings.json\n * GET /api/sfmc/settings/:key \u2014 \u542B land:* / bridge_channel_id \u7B49 fallback \u67E5\u8BE2\n * GET /api/sfmc/areas \u2014 areas.json\n * GET /api/sfmc/permissions \u2014 permissions.json\n * GET /api/sfmc/banned_items \u2014 banned_items.json\n * GET /api/sfmc/clean \u2014 clean.json\n * GET /api/sfmc/grids \u2014 grids.json\n * GET /api/sfmc/peace_filters \u2014 peace_filters.json\n * GET /api/sfmc/qa \u2014 questions.json\n */\n\nimport { configPath, readJson, type ConfigName } from \"@sfmc-bds/sdk/node/config\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n projectRoot: string;\n /**\n * \u6CE8\u5165\u6A21\u5757\u5217\u8868(\u4E0E GET /api/sfmc/modules \u540C\u6E90 \u2014 DRY)\u3002\n * \u52FF\u5728\u672C\u8DEF\u7531\u518D\u8BFB catalog/lock(DIP:\u9AD8\u5C42\u88C5\u914D,\u8DEF\u7531\u53EA\u6D88\u8D39\u62BD\u8C61)\u3002\n */\n listModules?: () => Array<Record<string, unknown>>;\n /**\n * \u6CE8\u5165\u6A21\u5757 HMAC token \u8868(\u4EC5 loopback \u53EF\u8FBE;\u4F9B SAPI host-bootstrap \u6CE8\u5165\n * set*ModuleContext,\u56E0 SAPI \u4E0D\u80FD\u76F4\u63A5\u8BFB\u4E0E DB \u540C\u76EE\u5F55\u7684 module-tokens.json)\u3002\n */\n getModuleTokens?: () => Record<string, string>;\n}\n\nfunction isMetaKey(k: string): boolean {\n return k === \"$schema\" || String(k).startsWith(\"_\");\n}\n\nfunction stripMetaDeep(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripMetaDeep);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (!isMetaKey(k)) out[k] = stripMetaDeep(val);\n }\n return out;\n }\n return v;\n}\n\nfunction stripMeta(obj: Record<string, unknown> | null): Record<string, unknown> {\n if (!obj || typeof obj !== \"object\" || Array.isArray(obj)) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isMetaKey(k)) out[k] = v;\n }\n return out;\n}\n\nfunction jsonValue(v: unknown): string {\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v ?? \"\");\n}\n\nfunction arrayOrEmpty(v: unknown): unknown[] {\n return Array.isArray(v) ? v : [];\n}\n\nfunction createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }: Deps) {\n /** \u4ED3\u9876\u670D\u52A1 config \u8BFB\u53D6\u7EDF\u4E00\u8D70 SDK;\u95ED\u5305\u6355\u83B7 projectRoot\u3002 */\n const readCfg = (name: ConfigName): unknown => readJson(configPath(projectRoot, name));\n\n function getAllConfigs(): Record<string, unknown> {\n const modules = typeof listModules === \"function\" ? listModules() : [];\n const module_tokens = typeof getModuleTokens === \"function\" ? getModuleTokens() : {};\n /**\n * SAPI ConfigManager \u5951\u7EA6:banned_items \u4E3A string[](\u4E0E GET /banned_items \u540C\u6E90)\u3002\n * \u52FF\u518D\u6620\u5C04\u6210 {item_id} \u2014 \u5426\u5219 filter(typeof s === \"string\") \u4F1A\u5F97\u5230\u7A7A\u7F13\u5B58(LSP)\u3002\n * \u5176\u4F59\u8D44\u6E90\u590D\u7528\u5355\u8D44\u6E90 helpers,\u907F\u514D getAllConfigs \u4E0E\u4E13\u7528\u8DEF\u7531\u53CC\u5199(DRY)\u3002\n */\n return {\n // \u4E0E /api/sfmc/modules \u540C\u6E90;ConfigManager.init \u4E00\u6B21\u62C9\u9F50\u542F\u505C\u6001(DRY)\n modules,\n // loopback-only \u4E0B\u53D1;SAPI \u65E0 fs,\u9760\u6B64\u6CE8\u5165\u6A21\u5757\u8EAB\u4EFD(DIP)\n module_tokens,\n settings: stripMeta(readCfg(\"settings.json\") as Record<string, unknown> | null),\n areas: getAreas(),\n permissions: getPermissions(),\n banned_items: getBannedItems(),\n clean: getClean(),\n grids: getGrids(),\n peace_filters: getPeaceFilters(),\n questions: getQA(),\n };\n }\n\n function getSettingsFlat(): Array<{ key: string; value: string }> {\n const obj = stripMeta(readJson(configPath(projectRoot, \"settings.json\")) as Record<string, unknown> | null);\n return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));\n }\n\n function getSettingByKey(key: string): { value: unknown; source?: string } {\n const settings = (readJson(configPath(projectRoot, \"settings.json\")) as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {\n return { value: settings[key], source: \"settings.json\" };\n }\n if (key === \"bridge_channel_id\") {\n const qq = (readCfg(\"qq_config.json\") as Record<string, unknown> | null) ?? {};\n if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: \"qq_config.json\" };\n }\n if (key.startsWith(\"land:\")) {\n const land = (readCfg(\"land.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {\n return { value: land[key], source: \"land.json\" };\n }\n }\n return { value: null };\n }\n\n function getAreas(): unknown[] {\n return (arrayOrEmpty(readCfg(\"areas.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.module && r.dimension != null)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPermissions(): unknown[] {\n return (arrayOrEmpty(readCfg(\"permissions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.player_name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getBannedItems(): string[] {\n return (arrayOrEmpty(readCfg(\"banned_items.json\")) as Array<string>).filter(\n (i) => typeof i === \"string\" && i && !i.startsWith(\"_\")\n );\n }\n\n function getClean(): { item_max: number; poll_interval: number } {\n const c = stripMetaDeep(readCfg(\"clean.json\") ?? {}) as Record<string, unknown>;\n return { item_max: (c.item_max as number) ?? 192, poll_interval: (c.poll_interval as number) ?? 60 };\n }\n\n function getGrids(): unknown[] {\n return (arrayOrEmpty(readCfg(\"grids.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPeaceFilters(): unknown[] {\n return (arrayOrEmpty(readCfg(\"peace_filters.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.family)\n .map((r) => stripMetaDeep(r));\n }\n\n function getQA(): Array<Record<string, unknown>> {\n return (arrayOrEmpty(readCfg(\"questions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.question)\n .map((r, idx) => {\n const clean = stripMetaDeep(r) as Record<string, unknown>;\n return {\n id: idx + 1,\n weight: clean.weight ?? 1,\n question: clean.question,\n answers: clean.answers ?? [],\n msg_right: clean.msg_right ?? \"\",\n msg_wrong: clean.msg_wrong ?? \"\",\n explanation: clean.explanation ?? \"\",\n min_rank: clean.min_rank ?? null,\n max_rank: clean.max_rank ?? null,\n rewards: clean.rewards ?? [],\n punishments: clean.punishments ?? [],\n };\n });\n }\n\n return async function handleConfigRoute({\n path: requestPath,\n method,\n params,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params;\n if (requestPath === \"/api/sfmc/configs/all\") {\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, getAllConfigs());\n return true;\n }\n if (requestPath === \"/api/sfmc/settings\") {\n if (method === \"GET\") {\n json(res, { settings: getSettingsFlat() });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (requestPath.startsWith(\"/api/sfmc/settings/\")) {\n const key = decodeURIComponent(requestPath.slice(\"/api/sfmc/settings/\".length));\n if (method === \"GET\") {\n json(res, getSettingByKey(key));\n return true;\n }\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n if (requestPath === \"/api/sfmc/areas\") {\n if (method === \"GET\") {\n json(res, { areas: getAreas() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/permissions\") {\n if (method === \"GET\") {\n json(res, { permissions: getPermissions() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/banned_items\") {\n if (method === \"GET\") {\n json(res, { items: getBannedItems() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/clean\") {\n if (method === \"GET\") {\n json(res, { clean: getClean() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/grids\") {\n if (method === \"GET\") {\n json(res, { grids: getGrids() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/peace_filters\") {\n if (method === \"GET\") {\n json(res, { filters: getPeaceFilters() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/qa\") {\n if (method === \"GET\") {\n json(res, { questions: getQA() });\n return true;\n }\n }\n return false;\n };\n}\n\nexport { createConfigRoutes };\n"],
5
- "mappings": "AAgBA,SAAS,YAAY,gBAAiC;AAiBtD,SAAS,UAAU,GAAoB;AACrC,SAAO,MAAM,aAAa,OAAO,CAAC,EAAE,WAAW,GAAG;AACpD;AAEA,SAAS,cAAc,GAAqB;AAC1C,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,IAAI,aAAa;AAChD,MAAI,KAAK,OAAO,MAAM,UAAU;AAC9B,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,CAA4B,GAAG;AACnE,UAAI,CAAC,UAAU,CAAC,EAAG,KAAI,CAAC,IAAI,cAAc,GAAG;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAA8D;AAC/E,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,EAAG,QAAO,CAAC;AACnE,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,CAAC,UAAU,CAAC,EAAG,KAAI,CAAC,IAAI;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,SAAS,UAAU,GAAoB;AACrC,SAAO,MAAM,QAAQ,OAAO,MAAM,WAAW,KAAK,UAAU,CAAC,IAAI,OAAO,KAAK,EAAE;AACjF;AAEA,SAAS,aAAa,GAAuB;AAC3C,SAAO,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC;AACjC;AAEA,SAAS,mBAAmB,EAAE,MAAM,aAAa,aAAa,gBAAgB,GAAS;AAErF,QAAM,UAAU,CAAC,SAA8B,SAAS,WAAW,aAAa,IAAI,CAAC;AAErF,WAAS,gBAAyC;AAChD,UAAM,UAAU,OAAO,gBAAgB,aAAa,YAAY,IAAI,CAAC;AACrE,UAAM,gBAAgB,OAAO,oBAAoB,aAAa,gBAAgB,IAAI,CAAC;AAMnF,WAAO;AAAA;AAAA,MAEL;AAAA;AAAA,MAEA;AAAA,MACA,UAAU,UAAU,QAAQ,eAAe,CAAmC;AAAA,MAC9E,OAAO,SAAS;AAAA,MAChB,aAAa,eAAe;AAAA,MAC5B,cAAc,eAAe;AAAA,MAC7B,OAAO,SAAS;AAAA,MAChB,OAAO,SAAS;AAAA,MAChB,eAAe,gBAAgB;AAAA,MAC/B,WAAW,MAAM;AAAA,IACnB;AAAA,EACF;AAEA,WAAS,kBAAyD;AAChE,UAAM,MAAM,UAAU,SAAS,WAAW,aAAa,eAAe,CAAC,CAAmC;AAC1G,WAAO,OAAO,QAAQ,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,OAAO,UAAU,KAAK,EAAE,EAAE;AAAA,EACrF;AAEA,WAAS,gBAAgB,KAAkD;AACzE,UAAM,WAAY,SAAS,WAAW,aAAa,eAAe,CAAC,KAAwC,CAAC;AAC5G,QAAI,OAAO,UAAU,eAAe,KAAK,UAAU,GAAG,KAAK,CAAC,UAAU,GAAG,GAAG;AAC1E,aAAO,EAAE,OAAO,SAAS,GAAG,GAAG,QAAQ,gBAAgB;AAAA,IACzD;AACA,QAAI,QAAQ,qBAAqB;AAC/B,YAAM,KAAM,QAAQ,gBAAgB,KAAwC,CAAC;AAC7E,UAAI,GAAG,kBAAmB,QAAO,EAAE,OAAO,GAAG,mBAAmB,QAAQ,iBAAiB;AAAA,IAC3F;AACA,QAAI,IAAI,WAAW,OAAO,GAAG;AAC3B,YAAM,OAAQ,QAAQ,WAAW,KAAwC,CAAC;AAC1E,UAAI,OAAO,UAAU,eAAe,KAAK,MAAM,GAAG,KAAK,CAAC,UAAU,GAAG,GAAG;AACtE,eAAO,EAAE,OAAO,KAAK,GAAG,GAAG,QAAQ,YAAY;AAAA,MACjD;AAAA,IACF;AACA,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AAEA,WAAS,WAAsB;AAC7B,WAAQ,aAAa,QAAQ,YAAY,CAAC,EACvC,OAAO,CAAC,MAAM,KAAK,EAAE,UAAU,EAAE,aAAa,IAAI,EAClD,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC;AAAA,EAChC;AAEA,WAAS,iBAA4B;AACnC,WAAQ,aAAa,QAAQ,kBAAkB,CAAC,EAC7C,OAAO,CAAC,MAAM,KAAK,EAAE,WAAW,EAChC,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC;AAAA,EAChC;AAEA,WAAS,iBAA2B;AAClC,WAAQ,aAAa,QAAQ,mBAAmB,CAAC,EAAoB;AAAA,MACnE,CAAC,MAAM,OAAO,MAAM,YAAY,KAAK,CAAC,EAAE,WAAW,GAAG;AAAA,IACxD;AAAA,EACF;AAEA,WAAS,WAAwD;AAC/D,UAAM,IAAI,cAAc,QAAQ,YAAY,KAAK,CAAC,CAAC;AACnD,WAAO,EAAE,UAAW,EAAE,YAAuB,KAAK,eAAgB,EAAE,iBAA4B,GAAG;AAAA,EACrG;AAEA,WAAS,WAAsB;AAC7B,WAAQ,aAAa,QAAQ,YAAY,CAAC,EACvC,OAAO,CAAC,MAAM,KAAK,EAAE,IAAI,EACzB,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC;AAAA,EAChC;AAEA,WAAS,kBAA6B;AACpC,WAAQ,aAAa,QAAQ,oBAAoB,CAAC,EAC/C,OAAO,CAAC,MAAM,KAAK,EAAE,MAAM,EAC3B,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC;AAAA,EAChC;AAEA,WAAS,QAAwC;AAC/C,WAAQ,aAAa,QAAQ,gBAAgB,CAAC,EAC3C,OAAO,CAAC,MAAM,KAAK,EAAE,QAAQ,EAC7B,IAAI,CAAC,GAAG,QAAQ;AACf,YAAM,QAAQ,cAAc,CAAC;AAC7B,aAAO;AAAA,QACL,IAAI,MAAM;AAAA,QACV,QAAQ,MAAM,UAAU;AAAA,QACxB,UAAU,MAAM;AAAA,QAChB,SAAS,MAAM,WAAW,CAAC;AAAA,QAC3B,WAAW,MAAM,aAAa;AAAA,QAC9B,WAAW,MAAM,aAAa;AAAA,QAC9B,aAAa,MAAM,eAAe;AAAA,QAClC,UAAU,MAAM,YAAY;AAAA,QAC5B,UAAU,MAAM,YAAY;AAAA,QAC5B,SAAS,MAAM,WAAW,CAAC;AAAA,QAC3B,aAAa,MAAM,eAAe,CAAC;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACL;AAEA,SAAO,eAAe,kBAAkB;AAAA,IACtC,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKqB;AACnB,SAAK;AACL,QAAI,gBAAgB,yBAAyB;AAC3C,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,eAAO;AAAA,MACT;AACA,WAAK,KAAK,cAAc,CAAC;AACzB,aAAO;AAAA,IACT;AACA,QAAI,gBAAgB,sBAAsB;AACxC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,UAAU,gBAAgB,EAAE,CAAC;AACzC,eAAO;AAAA,MACT;AACA,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AACA,QAAI,YAAY,WAAW,qBAAqB,GAAG;AACjD,YAAM,MAAM,mBAAmB,YAAY,MAAM,sBAAsB,MAAM,CAAC;AAC9E,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,gBAAgB,GAAG,CAAC;AAC9B,eAAO;AAAA,MACT;AACA,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,qBAAqB,GAAG,GAAG;AAC9D,aAAO;AAAA,IACT;AACA,QAAI,gBAAgB,mBAAmB;AACrC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,OAAO,SAAS,EAAE,CAAC;AAC/B,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,gBAAgB,yBAAyB;AAC3C,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,aAAa,eAAe,EAAE,CAAC;AAC3C,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,gBAAgB,0BAA0B;AAC5C,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,OAAO,eAAe,EAAE,CAAC;AACrC,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,gBAAgB,mBAAmB;AACrC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,OAAO,SAAS,EAAE,CAAC;AAC/B,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,gBAAgB,mBAAmB;AACrC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,OAAO,SAAS,EAAE,CAAC;AAC/B,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,gBAAgB,2BAA2B;AAC7C,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;AACxC,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,gBAAgB,gBAAgB;AAClC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,WAAW,MAAM,EAAE,CAAC;AAChC,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * routes/config.ts \u2014 \u914D\u7F6E\u8DEF\u7531\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/configs/all \u2014 SAPI ConfigManager \u542F\u52A8\u5FEB\u7167\uFF08\u4EC5\u5E73\u53F0\u57DF\uFF09\n * GET /api/sfmc/settings \u2014 \u5E73\u94FA settings.json\uFF08legacy\uFF09\n * GET /api/sfmc/settings/:key \u2014 \u542B land:* / bridge_channel_id \u7B49 fallback\n * GET /api/sfmc/areas|permissions|\u2026 \u2014 legacy \u53EA\u8BFB JSON\uFF08\u6587\u4EF6\u540D\u5BF9 db-server \u4E3A\u9ED1\u7BB1\uFF09\n *\n * DIP\uFF1A\u6A21\u5757/legacy JSON \u4E0D\u8FDB SDK ConfigName\uFF1B\u672C\u8DEF\u7531\u7528 configDir + \u6587\u4EF6\u540D\u62FC\u63A5\u8DEF\u5F84\u3002\n * \u6A21\u5757\u79C1\u6709\u914D\u7F6E\u7684\u8BFB\u5199\u6743\u5A01\u5165\u53E3\u662F module-config-routes\uFF08configs/:configKey\uFF09\u3002\n */\n\nimport { join } from \"node:path\";\nimport { configDir, configPath, readJson, type ConfigName } from \"@sfmc-bds/sdk/node/config\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n projectRoot: string;\n /**\n * \u6CE8\u5165\u6A21\u5757\u5217\u8868(\u4E0E GET /api/sfmc/modules \u540C\u6E90 \u2014 DRY)\u3002\n * \u52FF\u5728\u672C\u8DEF\u7531\u518D\u8BFB catalog/lock(DIP:\u9AD8\u5C42\u88C5\u914D,\u8DEF\u7531\u53EA\u6D88\u8D39\u62BD\u8C61)\u3002\n */\n listModules?: () => Array<Record<string, unknown>>;\n /**\n * \u6CE8\u5165\u6A21\u5757 HMAC token \u8868(\u4EC5 loopback \u53EF\u8FBE;\u4F9B SAPI host-bootstrap \u6CE8\u5165\n * set*ModuleContext,\u56E0 SAPI \u4E0D\u80FD\u76F4\u63A5\u8BFB\u4E0E DB \u540C\u76EE\u5F55\u7684 module-tokens.json)\u3002\n */\n getModuleTokens?: () => Record<string, string>;\n}\n\nfunction isMetaKey(k: string): boolean {\n return k === \"$schema\" || String(k).startsWith(\"_\");\n}\n\nfunction stripMetaDeep(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripMetaDeep);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (!isMetaKey(k)) out[k] = stripMetaDeep(val);\n }\n return out;\n }\n return v;\n}\n\nfunction stripMeta(obj: Record<string, unknown> | null): Record<string, unknown> {\n if (!obj || typeof obj !== \"object\" || Array.isArray(obj)) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isMetaKey(k)) out[k] = v;\n }\n return out;\n}\n\nfunction jsonValue(v: unknown): string {\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v ?? \"\");\n}\n\nfunction arrayOrEmpty(v: unknown): unknown[] {\n return Array.isArray(v) ? v : [];\n}\n\nfunction createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }: Deps) {\n /** \u5E73\u53F0 ConfigName\uFF08qq_config \u7B49\uFF09 */\n const readPlatform = (name: ConfigName): unknown => readJson(configPath(projectRoot, name));\n /** \u9ED1\u7BB1 JSON\uFF1A\u4EFB\u610F configs/<file>\uFF0C\u4E0D\u8FDB ConfigName */\n const readOpaque = (fileName: string): unknown => readJson(join(configDir(projectRoot), fileName));\n\n /**\n * SAPI ConfigManager \u5951\u7EA6\uFF1A\u53EA\u7F13\u5B58 modules / settings / permissions\u3002\n * \u52FF\u518D\u5F80 all \u585E areas \u7B49 legacy \u57DF\uFF08\u90A3\u4E9B\u8D70\u4E13\u7528 GET \u6216\u6A21\u5757 configKey\uFF09\u3002\n */\n function getAllConfigs(): Record<string, unknown> {\n const modules = typeof listModules === \"function\" ? listModules() : [];\n const module_tokens = typeof getModuleTokens === \"function\" ? getModuleTokens() : {};\n return {\n modules,\n module_tokens,\n settings: stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null),\n permissions: getPermissions(),\n };\n }\n\n function getSettingsFlat(): Array<{ key: string; value: string }> {\n const obj = stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null);\n return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));\n }\n\n function getSettingByKey(key: string): { value: unknown; source?: string } {\n const settings = (readOpaque(\"settings.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {\n return { value: settings[key], source: \"settings.json\" };\n }\n if (key === \"bridge_channel_id\") {\n const qq = (readPlatform(\"qq_config.json\") as Record<string, unknown> | null) ?? {};\n if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: \"qq_config.json\" };\n }\n if (key.startsWith(\"land:\")) {\n const land = (readOpaque(\"land.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {\n return { value: land[key], source: \"land.json\" };\n }\n }\n return { value: null };\n }\n\n function getAreas(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"areas.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.module && r.dimension != null)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPermissions(): unknown[] {\n return (arrayOrEmpty(readPlatform(\"permissions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.player_name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getBannedItems(): string[] {\n return (arrayOrEmpty(readOpaque(\"banned_items.json\")) as Array<string>).filter(\n (i) => typeof i === \"string\" && i && !i.startsWith(\"_\")\n );\n }\n\n function getClean(): { item_max: number; poll_interval: number } {\n const c = stripMetaDeep(readOpaque(\"clean.json\") ?? {}) as Record<string, unknown>;\n return { item_max: (c.item_max as number) ?? 192, poll_interval: (c.poll_interval as number) ?? 60 };\n }\n\n function getGrids(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"grids.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPeaceFilters(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"peace_filters.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.family)\n .map((r) => stripMetaDeep(r));\n }\n\n function getQA(): Array<Record<string, unknown>> {\n return (arrayOrEmpty(readOpaque(\"questions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.question)\n .map((r, idx) => {\n const clean = stripMetaDeep(r) as Record<string, unknown>;\n return {\n id: idx + 1,\n weight: clean.weight ?? 1,\n question: clean.question,\n answers: clean.answers ?? [],\n msg_right: clean.msg_right ?? \"\",\n msg_wrong: clean.msg_wrong ?? \"\",\n explanation: clean.explanation ?? \"\",\n min_rank: clean.min_rank ?? null,\n max_rank: clean.max_rank ?? null,\n rewards: clean.rewards ?? [],\n punishments: clean.punishments ?? [],\n };\n });\n }\n\n return async function handleConfigRoute({\n path: requestPath,\n method,\n params,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params;\n if (requestPath === \"/api/sfmc/configs/all\") {\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, getAllConfigs());\n return true;\n }\n if (requestPath === \"/api/sfmc/settings\") {\n if (method === \"GET\") {\n json(res, { settings: getSettingsFlat() });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (requestPath.startsWith(\"/api/sfmc/settings/\")) {\n const key = decodeURIComponent(requestPath.slice(\"/api/sfmc/settings/\".length));\n if (method === \"GET\") {\n json(res, getSettingByKey(key));\n return true;\n }\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n if (requestPath === \"/api/sfmc/areas\") {\n if (method === \"GET\") {\n json(res, { areas: getAreas() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/permissions\") {\n if (method === \"GET\") {\n json(res, { permissions: getPermissions() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/banned_items\") {\n if (method === \"GET\") {\n json(res, { items: getBannedItems() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/clean\") {\n if (method === \"GET\") {\n json(res, { clean: getClean() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/grids\") {\n if (method === \"GET\") {\n json(res, { grids: getGrids() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/peace_filters\") {\n if (method === \"GET\") {\n json(res, { filters: getPeaceFilters() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/qa\") {\n if (method === \"GET\") {\n json(res, { questions: getQA() });\n return true;\n }\n }\n return false;\n };\n}\n\nexport { createConfigRoutes };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -13,16 +13,22 @@
13
13
  */
14
14
  import type { IncomingMessage, ServerResponse } from "node:http";
15
15
  import { json as defaultJson, type Method } from "../lib/http.js";
16
- import { type ModuleAuth } from "./_shared.js";
16
+ import type { IdempotencyStore } from "../lib/idempotency-store.js";
17
17
  import type { SchemaRegistry } from "../schema-registry.js";
18
18
  import type { TxRunner } from "../tx-runner.js";
19
- import type { IdempotencyStore } from "../lib/idempotency-store.js";
19
+ import { type ModuleAuth } from "./_shared.js";
20
20
  export interface DbRoutesDeps {
21
21
  schemaRegistry: SchemaRegistry;
22
22
  txRunner: TxRunner;
23
23
  idempotent: IdempotencyStore;
24
24
  json?: typeof defaultJson;
25
25
  }
26
+ /**
27
+ * 创建 `/api/sfmc/db/*` 数据库路由请求处理器。
28
+ *
29
+ * @param depsIn 依赖注入项(包含 schemaRegistry、txRunner、idempotent 等)。
30
+ * @returns 异步路由处理函数。
31
+ */
26
32
  export declare function createDbRoutes(depsIn: Partial<DbRoutesDeps>): (ctx: {
27
33
  path: string;
28
34
  method: Method | string;
@@ -1 +1 @@
1
- {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAEV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CA6KrB"}
1
+ {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAa,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAoKrB"}
@@ -1,6 +1,7 @@
1
1
  import { json as defaultJson } from "../lib/http.js";
2
- import { jsonV2Fail, jsonV2Ok } from "./_shared.js";
2
+ import { log } from "../lib/log.js";
3
3
  import { PermissionDeniedError } from "../permission-gate.js";
4
+ import { jsonV2Fail, jsonV2Ok } from "./_shared.js";
4
5
  function createDbRoutes(depsIn) {
5
6
  const deps = depsIn;
6
7
  if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {
@@ -25,6 +26,7 @@ function createDbRoutes(depsIn) {
25
26
  const result = deps.schemaRegistry.define(moduleId, req2);
26
27
  jsonV2Ok(res, { table: result.table, created: result.created });
27
28
  } catch (e) {
29
+ log.warn(`[define-table failed] ${moduleId} table=${body?.name}: ${e.message}`);
28
30
  jsonV2Fail(res, e.message, 400);
29
31
  }
30
32
  return true;
@@ -136,19 +138,10 @@ function createDbRoutes(depsIn) {
136
138
  if (m) {
137
139
  try {
138
140
  if (m[1] === "probe") {
139
- const r = await deps.idempotent.probe(
140
- moduleId,
141
- String(body.action),
142
- String(body.key)
143
- );
141
+ const r = await deps.idempotent.probe(moduleId, String(body.action), String(body.key));
144
142
  json(res, r);
145
143
  } else {
146
- const r = await deps.idempotent.commit(
147
- moduleId,
148
- String(body.action),
149
- String(body.key),
150
- body.value
151
- );
144
+ const r = await deps.idempotent.commit(moduleId, String(body.action), String(body.key), body.value);
152
145
  jsonV2Ok(res, { committed: r.ok });
153
146
  }
154
147
  } catch (e) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/db-routes.ts"],
4
- "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\nimport type {\n DefineTableRequest,\n SchemaRegistry,\n} from \"../schema-registry.js\";\nimport type {\n TxRequest,\n TxRunner,\n TxStep,\n} from \"../tx-runner.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u300C\u9274\u6743\u8EAB\u4EFD\u300D\u6267\u884C\u4E8B\u52A1:\u5FFD\u7565 body \u91CC\u81EA\u5E26\u7684 moduleId,\n // \u65E2\u9632\u6B62\u6301 A \u7684 token \u5192\u7528 B \u7684\u6743\u9650\u8D8A\u6743,\u4E5F\u4FEE\u590D\u5BA2\u6237\u7AEF\u53EA\u53D1 {steps} \u65F6\n // moduleId \u7F3A\u5931\u5BFC\u81F4\u7684\u4E8B\u52A1\u6052\u88AB\u62D2\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse/TxError \u81EA\u8EAB\u5DF2\u5E26 ok \u5B57\u6BB5;\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B SDK \u5728\u56DE\u8C03\u5185\u8BFB\u56DE query/get/call \u7ED3\u679C\n // OCP/DRY:\u65B0\u4F1A\u8BDD\u52A8\u8BCD\u53EA\u5F80\u8868\u91CC\u52A0\u4E00\u9879,\u52FF\u518D\u590D\u5236 try/catch \u56DB\u4EFD\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(\n moduleId,\n String(body.action),\n String(body.key)\n );\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(\n moduleId,\n String(body.action),\n String(body.key),\n body.value\n );\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\nimport { log } from \"../lib/log.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { DefineTableRequest, SchemaRegistry } from \"../schema-registry.js\";\nimport type { TxRequest, TxRunner, TxStep } from \"../tx-runner.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(moduleId, String(body.action), String(body.key));\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(moduleId, String(body.action), String(body.key), body.value);\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 路由列表:
5
5
  * GET /api/sfmc/messages — 模糊/过滤查询消息
6
- * POST /api/sfmc/messages — 批量 INSERT OR REPLACE 消息(同时转发至 QQ Bridge)
6
+ * POST /api/sfmc/messages — 批量 INSERT OR REPLACE 消息(匹配 bridge 频道时转发至 QQ)
7
7
  */
8
8
  import type { QueryFn } from "../lib/sqlite.js";
9
9
  interface Deps {
@@ -11,8 +11,12 @@ interface Deps {
11
11
  body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
12
12
  json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
13
13
  forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;
14
+ /** 当前桥接频道;空则不做 MC→QQ 聊天出站(事件通道不受影响) */
15
+ getBridgeChannelId: () => string;
14
16
  }
15
- declare function createMessagesRoutes({ query, body, json, forwardToQQBridge }: Deps): ({ path, method, params, req, res, }: {
17
+ /** 是否应将本条消息推到 QQ 群 */
18
+ export declare function shouldForwardChatToQQ(bridgeChannelId: string, channelId: string, fromId: string): boolean;
19
+ declare function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }: Deps): ({ path, method, params, req, res, }: {
16
20
  path: string;
17
21
  method: string;
18
22
  params: URLSearchParams;
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/routes/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnG;AAED,iBAAS,oBAAoB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,IAAI,yCAOvE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAqDrB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/routes/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClG,uCAAuC;IACvC,kBAAkB,EAAE,MAAM,MAAM,CAAC;CAClC;AAED,sBAAsB;AACtB,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAUT;AAED,iBAAS,oBAAoB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,EAAE,IAAI,yCAO3F;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAyDrB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
@@ -1,5 +1,14 @@
1
1
  import { SQL } from "sql-template-strings";
2
- function createMessagesRoutes({ query, body, json, forwardToQQBridge }) {
2
+ function shouldForwardChatToQQ(bridgeChannelId, channelId, fromId) {
3
+ const bridge = String(bridgeChannelId ?? "").trim();
4
+ if (!bridge) return false;
5
+ if (String(channelId ?? "").trim() !== bridge) return false;
6
+ const fid = String(fromId ?? "").trim();
7
+ if (!fid) return false;
8
+ if (fid.startsWith("qq_")) return false;
9
+ return true;
10
+ }
11
+ function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }) {
3
12
  return async function handle({
4
13
  path,
5
14
  method,
@@ -47,8 +56,12 @@ function createMessagesRoutes({ query, body, json, forwardToQQBridge }) {
47
56
  )`
48
57
  );
49
58
  }
59
+ const bridgeId = getBridgeChannelId();
50
60
  for (const m of messages) {
51
- forwardToQQBridge(m.channelId, m.fromName, m.content, m.fromid);
61
+ const channelId = String(m.channelId ?? "");
62
+ const fromId = String(m.fromid ?? "");
63
+ if (!shouldForwardChatToQQ(bridgeId, channelId, fromId)) continue;
64
+ forwardToQQBridge(channelId, String(m.fromName ?? ""), String(m.content ?? ""), fromId);
52
65
  }
53
66
  json(res, { success: true });
54
67
  } else {
@@ -60,6 +73,7 @@ function createMessagesRoutes({ query, body, json, forwardToQQBridge }) {
60
73
  };
61
74
  }
62
75
  export {
63
- createMessagesRoutes
76
+ createMessagesRoutes,
77
+ shouldForwardChatToQQ
64
78
  };
65
79
  //# sourceMappingURL=messages.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/messages.ts"],
4
- "sourcesContent": ["/**\n * routes/messages.ts \u2014 \u804A\u5929\u6D88\u606F (sfmc_chat_messages)\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/messages \u2014 \u6A21\u7CCA/\u8FC7\u6EE4\u67E5\u8BE2\u6D88\u606F\n * POST /api/sfmc/messages \u2014 \u6279\u91CF INSERT OR REPLACE \u6D88\u606F\uFF08\u540C\u65F6\u8F6C\u53D1\u81F3 QQ Bridge\uFF09\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;\n}\n\nfunction createMessagesRoutes({ query, body, json, forwardToQQBridge }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path === \"/api/sfmc/messages\") {\n if (method === \"GET\") {\n const stmt = SQL`SELECT * FROM sfmc_chat_messages WHERE 1=1`;\n const search = params.get(\"search\")?.trim();\n if (search) stmt.append(SQL` AND (content LIKE ${`%${search}%`})`);\n const type = params.get(\"type\")?.trim();\n if (type) stmt.append(SQL` AND type = ${type}`);\n const channelId = params.get(\"channelId\")?.trim();\n if (channelId) stmt.append(SQL` AND channel_id = ${channelId}`);\n const from = params.get(\"from\")?.trim();\n if (from) stmt.append(SQL` AND from_id = ${from}`);\n const minCreatedAt = params.get(\"minCreatedAt\")?.trim();\n if (minCreatedAt) stmt.append(SQL` AND created_at >= ${Number(minCreatedAt)}`);\n const minSentAt = params.get(\"minSentAt\")?.trim();\n if (minSentAt) stmt.append(SQL` AND created_at >= ${Number(minSentAt)}`);\n const maxCreatedAt = params.get(\"maxCreatedAt\")?.trim();\n if (maxCreatedAt) stmt.append(SQL` AND created_at <= ${Number(maxCreatedAt)}`);\n stmt.append(SQL` ORDER BY created_at ASC`);\n json(res, { messages: query(stmt) });\n } else if (method === \"POST\") {\n const { messages } = await body(req);\n if (!Array.isArray(messages) || messages.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n if ((messages as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n // \u591A\u884C INSERT \u6539\u6210\u5FAA\u73AF\u5355\u6761 INSERT OR REPLACE \u2014\u2014 \u7B80\u5355\u4F18\u5148\n for (const m of messages as Array<Record<string, unknown>>) {\n query(\n SQL`INSERT OR REPLACE INTO sfmc_chat_messages (\n id, channel_id, from_id, from_name, type, content, attachment, show_timestamp, created_at\n ) VALUES (\n ${m.id}, ${m.channelId}, ${m.fromid}, ${m.fromName},\n ${m.type ?? \"text\"}, ${m.content}, ${m.attachment ?? null},\n ${m.showTimestamp ? 1 : 0}, ${m.timestamp}\n )`\n );\n }\n for (const m of messages as Array<Record<string, unknown>>) {\n forwardToQQBridge(m.channelId as string, m.fromName as string, m.content as string, m.fromid as string);\n }\n json(res, { success: true });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n return false;\n };\n}\n\nexport { createMessagesRoutes };\n"],
5
- "mappings": "AAQA,SAAS,WAAW;AAUpB,SAAS,qBAAqB,EAAE,OAAO,MAAM,MAAM,kBAAkB,GAAS;AAC5E,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,sBAAsB;AACjC,UAAI,WAAW,OAAO;AACpB,cAAM,OAAO;AACb,cAAM,SAAS,OAAO,IAAI,QAAQ,GAAG,KAAK;AAC1C,YAAI,OAAQ,MAAK,OAAO,yBAAyB,IAAI,MAAM,GAAG,GAAG;AACjE,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,kBAAkB,IAAI,EAAE;AAC9C,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,wBAAwB,SAAS,EAAE;AAC9D,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,qBAAqB,IAAI,EAAE;AACjD,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,yBAAyB,OAAO,SAAS,CAAC,EAAE;AACvE,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,aAAK,OAAO,6BAA6B;AACzC,aAAK,KAAK,EAAE,UAAU,MAAM,IAAI,EAAE,CAAC;AAAA,MACrC,WAAW,WAAW,QAAQ;AAC5B,cAAM,EAAE,SAAS,IAAI,MAAM,KAAK,GAAG;AACnC,YAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AACrD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,UAAU,GAAG,GAAG;AACnD,iBAAO;AAAA,QACT;AACA,YAAK,SAAuB,SAAS,KAAK;AACxC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,GAAG,GAAG;AAC7D,iBAAO;AAAA,QACT;AAEA,mBAAW,KAAK,UAA4C;AAC1D;AAAA,YACE;AAAA;AAAA;AAAA,kBAGM,EAAE,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,MAAM,KAAK,EAAE,QAAQ;AAAA,kBAChD,EAAE,QAAQ,MAAM,KAAK,EAAE,OAAO,KAAK,EAAE,cAAc,IAAI;AAAA,kBACvD,EAAE,gBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS;AAAA;AAAA,UAE/C;AAAA,QACF;AACA,mBAAW,KAAK,UAA4C;AAC1D,4BAAkB,EAAE,WAAqB,EAAE,UAAoB,EAAE,SAAmB,EAAE,MAAgB;AAAA,QACxG;AACA,aAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,MAC7B,OAAO;AACL,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * routes/messages.ts \u2014 \u804A\u5929\u6D88\u606F (sfmc_chat_messages)\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/messages \u2014 \u6A21\u7CCA/\u8FC7\u6EE4\u67E5\u8BE2\u6D88\u606F\n * POST /api/sfmc/messages \u2014 \u6279\u91CF INSERT OR REPLACE \u6D88\u606F\uFF08\u5339\u914D bridge \u9891\u9053\u65F6\u8F6C\u53D1\u81F3 QQ\uFF09\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;\n /** \u5F53\u524D\u6865\u63A5\u9891\u9053\uFF1B\u7A7A\u5219\u4E0D\u505A MC\u2192QQ \u804A\u5929\u51FA\u7AD9\uFF08\u4E8B\u4EF6\u901A\u9053\u4E0D\u53D7\u5F71\u54CD\uFF09 */\n getBridgeChannelId: () => string;\n}\n\n/** \u662F\u5426\u5E94\u5C06\u672C\u6761\u6D88\u606F\u63A8\u5230 QQ \u7FA4 */\nexport function shouldForwardChatToQQ(\n bridgeChannelId: string,\n channelId: string,\n fromId: string\n): boolean {\n const bridge = String(bridgeChannelId ?? \"\").trim();\n if (!bridge) return false;\n if (String(channelId ?? \"\").trim() !== bridge) return false;\n const fid = String(fromId ?? \"\").trim();\n // \u65E0\u53D1\u9001\u8005 id \u4E0D\u51FA\u7AD9\uFF08\u907F\u514D\u89E3\u6790\u5931\u8D25\u65F6\u8BEF\u63A8\uFF09\n if (!fid) return false;\n // QQ\u2192MC \u5199\u5165\u7684 fromid \u5F62\u5982 qq_*\uFF0C\u7981\u6B62\u56DE\u73AF\u63A8\u7FA4\n if (fid.startsWith(\"qq_\")) return false;\n return true;\n}\n\nfunction createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path === \"/api/sfmc/messages\") {\n if (method === \"GET\") {\n const stmt = SQL`SELECT * FROM sfmc_chat_messages WHERE 1=1`;\n const search = params.get(\"search\")?.trim();\n if (search) stmt.append(SQL` AND (content LIKE ${`%${search}%`})`);\n const type = params.get(\"type\")?.trim();\n if (type) stmt.append(SQL` AND type = ${type}`);\n const channelId = params.get(\"channelId\")?.trim();\n if (channelId) stmt.append(SQL` AND channel_id = ${channelId}`);\n const from = params.get(\"from\")?.trim();\n if (from) stmt.append(SQL` AND from_id = ${from}`);\n const minCreatedAt = params.get(\"minCreatedAt\")?.trim();\n if (minCreatedAt) stmt.append(SQL` AND created_at >= ${Number(minCreatedAt)}`);\n const minSentAt = params.get(\"minSentAt\")?.trim();\n if (minSentAt) stmt.append(SQL` AND created_at >= ${Number(minSentAt)}`);\n const maxCreatedAt = params.get(\"maxCreatedAt\")?.trim();\n if (maxCreatedAt) stmt.append(SQL` AND created_at <= ${Number(maxCreatedAt)}`);\n stmt.append(SQL` ORDER BY created_at ASC`);\n json(res, { messages: query(stmt) });\n } else if (method === \"POST\") {\n const { messages } = await body(req);\n if (!Array.isArray(messages) || messages.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n if ((messages as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n // \u591A\u884C INSERT \u6539\u6210\u5FAA\u73AF\u5355\u6761 INSERT OR REPLACE \u2014\u2014 \u7B80\u5355\u4F18\u5148\n for (const m of messages as Array<Record<string, unknown>>) {\n query(\n SQL`INSERT OR REPLACE INTO sfmc_chat_messages (\n id, channel_id, from_id, from_name, type, content, attachment, show_timestamp, created_at\n ) VALUES (\n ${m.id}, ${m.channelId}, ${m.fromid}, ${m.fromName},\n ${m.type ?? \"text\"}, ${m.content}, ${m.attachment ?? null},\n ${m.showTimestamp ? 1 : 0}, ${m.timestamp}\n )`\n );\n }\n const bridgeId = getBridgeChannelId();\n for (const m of messages as Array<Record<string, unknown>>) {\n const channelId = String(m.channelId ?? \"\");\n const fromId = String(m.fromid ?? \"\");\n if (!shouldForwardChatToQQ(bridgeId, channelId, fromId)) continue;\n forwardToQQBridge(channelId, String(m.fromName ?? \"\"), String(m.content ?? \"\"), fromId);\n }\n json(res, { success: true });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n return false;\n };\n}\n\nexport { createMessagesRoutes };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,15 +1,13 @@
1
1
  /**
2
- * routes/module-config-routes.ts — 模块配置文件管理
2
+ * routes/module-config-routes.ts — 模块私有配置文件读写路由
3
3
  *
4
- * 端点:
5
- * GET /api/sfmc/configs/:configKey → { config: <configs/<configKey>.json> }
6
- * POST /api/sfmc/configs/:configKey/set → 写一个 key
7
- * GET /api/sfmc/configs/:configKey/notify (SSE:onChange 推送)
4
+ * REST 端点:
5
+ * - GET /api/sfmc/configs/:configKey 读取对应 `configs/<configKey>.json` 配置文件
6
+ * - POST /api/sfmc/configs/:configKey/set 修改并持久化指定配置键值对
7
+ * - GET /api/sfmc/configs/:configKey/notify 通过 SSE 推送配置实时变更通知
8
8
  *
9
- * 鉴权:模块身份来自 ctx.moduleAuth;Permission = config:read:configKey / config:write:configKey。
10
- *
11
- * 设计:文件 = configs/<configKey>.json;整文件 read-modify-write(lock by
12
- * 简单 mutex),不是细粒度 lock — 模块 config 文件本来就小;并发冲突概率极低。
9
+ * 鉴权控制:
10
+ * 模块身份经 `ctx.moduleAuth` 验证;必须具备 `config:read:<configKey>` 或 `config:write:<configKey>` 权限。
13
11
  */
14
12
  import type { IncomingMessage, ServerResponse } from "node:http";
15
13
  import { json as defaultJson, type Method } from "../lib/http.js";
@@ -20,6 +18,12 @@ export interface ModuleConfigRoutesDeps {
20
18
  enabled: Map<string, ModuleManifestV2>;
21
19
  json?: typeof defaultJson;
22
20
  }
21
+ /**
22
+ * 创建模块私有配置路由请求处理器。
23
+ *
24
+ * @param depsIn 依赖注入项(包含工作根目录与已启用模块字典)。
25
+ * @returns 异步路由处理函数。
26
+ */
23
27
  export declare function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>): (ctx: {
24
28
  path: string;
25
29
  method: Method | string;
@@ -1 +1 @@
1
- {"version":3,"file":"module-config-routes.d.ts","sourceRoot":"","sources":["../../src/routes/module-config-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AA+BD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,SAS3D;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAwErB"}
1
+ {"version":3,"file":"module-config-routes.d.ts","sourceRoot":"","sources":["../../src/routes/module-config-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AA+BD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,SAU3D;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAwErB"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/module-config-routes.ts"],
4
- "sourcesContent": ["/**\n * routes/module-config-routes.ts \u2014 \u6A21\u5757\u914D\u7F6E\u6587\u4EF6\u7BA1\u7406\n *\n * \u7AEF\u70B9:\n * GET /api/sfmc/configs/:configKey \u2192 { config: <configs/<configKey>.json> }\n * POST /api/sfmc/configs/:configKey/set \u2192 \u5199\u4E00\u4E2A key\n * GET /api/sfmc/configs/:configKey/notify (SSE:onChange \u63A8\u9001)\n *\n * \u9274\u6743:\u6A21\u5757\u8EAB\u4EFD\u6765\u81EA ctx.moduleAuth;Permission = config:read:configKey / config:write:configKey\u3002\n *\n * \u8BBE\u8BA1:\u6587\u4EF6 = configs/<configKey>.json;\u6574\u6587\u4EF6 read-modify-write(lock by\n * \u7B80\u5355 mutex),\u4E0D\u662F\u7EC6\u7C92\u5EA6 lock \u2014 \u6A21\u5757 config \u6587\u4EF6\u672C\u6765\u5C31\u5C0F;\u5E76\u53D1\u51B2\u7A81\u6982\u7387\u6781\u4F4E\u3002\n */\n\nimport { join } from \"node:path\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { assertModulePermission, Perm } from \"../permission-gate.js\";\nimport type { ModuleManifestV2 } from \"../manifest-loader.js\";\nimport { jsonV2Fail, type ModuleAuth } from \"./_shared.js\";\n\nexport interface ModuleConfigRoutesDeps {\n projectRoot: string;\n enabled: Map<string, ModuleManifestV2>;\n json?: typeof defaultJson;\n}\n\nfunction configPath(projectRoot: string, key: string): string {\n if (!/^[A-Za-z0-9_-]+$/.test(key)) {\n throw new Error(`invalid configKey \"${key}\"`);\n }\n return join(projectRoot, \"configs\", `${key}.json`);\n}\n\nfunction readConfig(file: string): Record<string, unknown> {\n const parsed = readJson<unknown>(file);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n}\n\nconst subscribers = new Map<string, Set<(payload: unknown) => void>>();\n\nfunction subscribe(configKey: string, cb: (payload: unknown) => void): () => void {\n if (!subscribers.has(configKey)) subscribers.set(configKey, new Set());\n subscribers.get(configKey)!.add(cb);\n return () => {\n subscribers.get(configKey)?.delete(cb);\n };\n}\n\nfunction notify(configKey: string, key: string, value: unknown): void {\n const set = subscribers.get(configKey);\n if (!set) return;\n for (const cb of set) cb({ key, value });\n}\n\nexport function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>) {\n const deps = depsIn as Partial<ModuleConfigRoutesDeps>;\n if (!deps.projectRoot || !deps.enabled) {\n throw new Error(\"createModuleConfigRoutes: \u7F3A\u5C11 projectRoot / enabled map\");\n }\n const json = deps.json || defaultJson;\n const projectRoot = deps.projectRoot;\n const enabled = deps.enabled!;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, req, res } = ctx;\n const m = path.match(/^\\/api\\/sfmc\\/configs\\/([A-Za-z0-9_-]+)(?:\\/(set|notify))?$/);\n if (!m) return false;\n const configKey = m[1];\n const tail = m[2];\n const auth = ctx.moduleAuth;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized\", 401, \"unauthorized\");\n return true;\n }\n const manifest = enabled.get(auth.id);\n if (!manifest || manifest.configKey !== configKey) {\n jsonV2Fail(res, `\u6A21\u5757 ${auth.id} \u4E0D\u6301\u6709 configKey \"${configKey}\"`, 403, \"forbidden\");\n return true;\n }\n\n const file = configPath(projectRoot, configKey);\n\n if (!tail && method === \"GET\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configRead(configKey));\n const cfg = readConfig(file);\n json(res, { config: cfg });\n } catch (e) {\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n // LSP: GET \u5931\u8D25\u4E0E /set \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"set\" && method === \"POST\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configWrite(configKey));\n const body = (await (ctx.body as Promise<Record<string, unknown>>)) || {};\n const key = String(body.key);\n const value = body.value;\n const cfg = readConfig(file);\n cfg[key] = value;\n writeJson(file, cfg);\n notify(configKey, key, value);\n json(res, { ok: true });\n } catch (e) {\n // LSP: /set \u6210\u529F\u7528 ok:true,\u5931\u8D25\u7EDF\u4E00 ok:false(\u52FF\u6DF7\u7528 success)\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"notify\" && method === \"GET\") {\n // SSE \u2014 PoC \u628A\u76D1\u542C\u7559\u5728\u5185\u5B58\u91CC,\u4E0D\u6301\u4E45\u5316\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\":ok\\n\\n\");\n const unsub = subscribe(configKey, (payload) => {\n try {\n res.write(`data: ${JSON.stringify(payload)}\\n\\n`);\n } catch {\n unsub();\n }\n });\n req.on(\"close\", unsub);\n return true;\n }\n\n return false;\n };\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/module-config-routes.ts \u2014 \u6A21\u5757\u79C1\u6709\u914D\u7F6E\u6587\u4EF6\u8BFB\u5199\u8DEF\u7531\n *\n * REST \u7AEF\u70B9\uFF1A\n * - GET /api/sfmc/configs/:configKey \u8BFB\u53D6\u5BF9\u5E94 `configs/<configKey>.json` \u914D\u7F6E\u6587\u4EF6\n * - POST /api/sfmc/configs/:configKey/set \u4FEE\u6539\u5E76\u6301\u4E45\u5316\u6307\u5B9A\u914D\u7F6E\u952E\u503C\u5BF9\n * - GET /api/sfmc/configs/:configKey/notify \u901A\u8FC7 SSE \u63A8\u9001\u914D\u7F6E\u5B9E\u65F6\u53D8\u66F4\u901A\u77E5\n *\n * \u9274\u6743\u63A7\u5236\uFF1A\n * \u6A21\u5757\u8EAB\u4EFD\u7ECF `ctx.moduleAuth` \u9A8C\u8BC1\uFF1B\u5FC5\u987B\u5177\u5907 `config:read:<configKey>` \u6216 `config:write:<configKey>` \u6743\u9650\u3002\n */\n\nimport { join } from \"node:path\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { assertModulePermission, Perm } from \"../permission-gate.js\";\nimport type { ModuleManifestV2 } from \"../manifest-loader.js\";\nimport { jsonV2Fail, type ModuleAuth } from \"./_shared.js\";\n\nexport interface ModuleConfigRoutesDeps {\n projectRoot: string;\n enabled: Map<string, ModuleManifestV2>;\n json?: typeof defaultJson;\n}\n\nfunction configPath(projectRoot: string, key: string): string {\n if (!/^[A-Za-z0-9_-]+$/.test(key)) {\n throw new Error(`invalid configKey \"${key}\"`);\n }\n return join(projectRoot, \"configs\", `${key}.json`);\n}\n\nfunction readConfig(file: string): Record<string, unknown> {\n const parsed = readJson<unknown>(file);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n}\n\nconst subscribers = new Map<string, Set<(payload: unknown) => void>>();\n\nfunction subscribe(configKey: string, cb: (payload: unknown) => void): () => void {\n if (!subscribers.has(configKey)) subscribers.set(configKey, new Set());\n subscribers.get(configKey)!.add(cb);\n return () => {\n subscribers.get(configKey)?.delete(cb);\n };\n}\n\nfunction notify(configKey: string, key: string, value: unknown): void {\n const set = subscribers.get(configKey);\n if (!set) return;\n for (const cb of set) cb({ key, value });\n}\n\n/**\n * \u521B\u5EFA\u6A21\u5757\u79C1\u6709\u914D\u7F6E\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B\u5DE5\u4F5C\u6839\u76EE\u5F55\u4E0E\u5DF2\u542F\u7528\u6A21\u5757\u5B57\u5178\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>) {\n\n const deps = depsIn as Partial<ModuleConfigRoutesDeps>;\n if (!deps.projectRoot || !deps.enabled) {\n throw new Error(\"createModuleConfigRoutes: \u7F3A\u5C11 projectRoot / enabled map\");\n }\n const json = deps.json || defaultJson;\n const projectRoot = deps.projectRoot;\n const enabled = deps.enabled!;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, req, res } = ctx;\n const m = path.match(/^\\/api\\/sfmc\\/configs\\/([A-Za-z0-9_-]+)(?:\\/(set|notify))?$/);\n if (!m) return false;\n const configKey = m[1];\n const tail = m[2];\n const auth = ctx.moduleAuth;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized\", 401, \"unauthorized\");\n return true;\n }\n const manifest = enabled.get(auth.id);\n if (!manifest || manifest.configKey !== configKey) {\n jsonV2Fail(res, `\u6A21\u5757 ${auth.id} \u4E0D\u6301\u6709 configKey \"${configKey}\"`, 403, \"forbidden\");\n return true;\n }\n\n const file = configPath(projectRoot, configKey);\n\n if (!tail && method === \"GET\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configRead(configKey));\n const cfg = readConfig(file);\n json(res, { config: cfg });\n } catch (e) {\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n // LSP: GET \u5931\u8D25\u4E0E /set \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"set\" && method === \"POST\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configWrite(configKey));\n const body = (await (ctx.body as Promise<Record<string, unknown>>)) || {};\n const key = String(body.key);\n const value = body.value;\n const cfg = readConfig(file);\n cfg[key] = value;\n writeJson(file, cfg);\n notify(configKey, key, value);\n json(res, { ok: true });\n } catch (e) {\n // LSP: /set \u6210\u529F\u7528 ok:true,\u5931\u8D25\u7EDF\u4E00 ok:false(\u52FF\u6DF7\u7528 success)\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"notify\" && method === \"GET\") {\n // SSE \u2014 PoC \u628A\u76D1\u542C\u7559\u5728\u5185\u5B58\u91CC,\u4E0D\u6301\u4E45\u5316\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\":ok\\n\\n\");\n const unsub = subscribe(configKey, (payload) => {\n try {\n res.write(`data: ${JSON.stringify(payload)}\\n\\n`);\n } catch {\n unsub();\n }\n });\n req.on(\"close\", unsub);\n return true;\n }\n\n return false;\n };\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -23,6 +23,12 @@ interface Deps {
23
23
  body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
24
24
  json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
25
25
  }
26
+ /**
27
+ * 创建模块目录检索与启停管控路由处理器。
28
+ *
29
+ * @param deps 依赖注入字典。
30
+ * @returns 异步路由处理函数。
31
+ */
26
32
  declare function createModuleRoutes({ loadModuleCatalog, buildModuleList, resolveModuleByKey, setModuleEnabled, body, json, }: Deps): ({ path, method, params, req, res, }: {
27
33
  path: string;
28
34
  method: string;
@@ -1 +1 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/routes/modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,UAAU,IAAI;IACZ,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;IACnC,eAAe,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,gBAAgB,EAAE,CAChB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,EAC3C,OAAO,EAAE,OAAO,KACb,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAED,iBAAS,kBAAkB,CAAC,EAC1B,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,IAAI,GACL,EAAE,IAAI,yCAOF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAqErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/routes/modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,UAAU,IAAI;IACZ,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;IACnC,eAAe,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,gBAAgB,EAAE,CAChB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,EAC3C,OAAO,EAAE,OAAO,KACb,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAED;;;;;GAKG;AACH,iBAAS,kBAAkB,CAAC,EAC1B,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,IAAI,GACL,EAAE,IAAI,yCAQF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAqErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/modules.ts"],
4
- "sourcesContent": ["/**\n * routes/modules.ts \u2014 \u6A21\u5757\u76EE\u5F55\u4E0E\u542F\u505C\uFF08\u57FA\u4E8E modules/catalog.json + module-lock.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/modules \u2014 \u6A21\u5757\u5217\u8868\uFF08\u542B enable \u72B6\u6001\uFF09\n * GET /api/sfmc/modules/catalog \u2014 \u539F\u59CB catalog \u5217\u8868\n * GET /api/sfmc/modules/:key \u2014 \u5355\u4E2A\u6A21\u5757\u8BE6\u60C5\n * PATCH/PUT /api/sfmc/modules/:key \u2014 \u8BBE\u7F6E enabled\n * POST /api/sfmc/modules/:key/enable \u2014 \u542F\u7528\u6A21\u5757\n * POST /api/sfmc/modules/:key/disable \u2014 \u7981\u7528\u6A21\u5757\n */\n\ninterface Deps {\n loadModuleCatalog: () => unknown[];\n buildModuleList: () => Array<Record<string, unknown>>;\n resolveModuleByKey: (key: string) => { id: string; canDisable: boolean } | null;\n setModuleEnabled: (\n module: { id: string; canDisable: boolean },\n enabled: boolean\n ) => void;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\nfunction createModuleRoutes({\n loadModuleCatalog,\n buildModuleList,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n}: Deps) {\n return async function handleModuleRoute({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params; // unused\n if (path === \"/api/sfmc/modules/catalog\") {\n if (method === \"GET\") {\n json(res, { modules: loadModuleCatalog() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n if (path === \"/api/sfmc/modules\") {\n if (method === \"GET\") json(res, { modules: buildModuleList() });\n else json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (!path.startsWith(\"/api/sfmc/modules/\")) return false;\n\n const rest = path.slice(\"/api/sfmc/modules/\".length);\n const [rawKey, action] = rest.split(\"/\");\n const module = resolveModuleByKey(decodeURIComponent(rawKey ?? \"\"));\n if (!module) {\n json(res, { success: false, error: \"module_not_found\" }, 404);\n return true;\n }\n\n const current = () => buildModuleList().find((entry) => entry.id === module.id);\n if (!action && method === \"GET\") {\n json(res, { module: current() ?? null });\n } else if (!action && (method === \"PATCH\" || method === \"PUT\")) {\n const { enabled } = await body(req);\n if (!enabled && !module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n try {\n setModuleEnabled(module, !!enabled);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"enable\" && method === \"POST\") {\n try {\n setModuleEnabled(module, true);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"disable\" && method === \"POST\") {\n if (!module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n setModuleEnabled(module, false);\n json(res, { success: true, module: current() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n };\n}\n\nexport { createModuleRoutes };\n"],
5
- "mappings": "AAwBA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAS;AACP,SAAO,eAAe,kBAAkB;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,SAAK;AACL,QAAI,SAAS,6BAA6B;AACxC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;AAAA,MAC5C,OAAO;AACL,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,QAAI,SAAS,qBAAqB;AAChC,UAAI,WAAW,MAAO,MAAK,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;AAAA,UACzD,MAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAC1D,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,WAAW,oBAAoB,EAAG,QAAO;AAEnD,UAAM,OAAO,KAAK,MAAM,qBAAqB,MAAM;AACnD,UAAM,CAAC,QAAQ,MAAM,IAAI,KAAK,MAAM,GAAG;AACvC,UAAM,SAAS,mBAAmB,mBAAmB,UAAU,EAAE,CAAC;AAClE,QAAI,CAAC,QAAQ;AACX,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mBAAmB,GAAG,GAAG;AAC5D,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,MAAM,gBAAgB,EAAE,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO,EAAE;AAC9E,QAAI,CAAC,UAAU,WAAW,OAAO;AAC/B,WAAK,KAAK,EAAE,QAAQ,QAAQ,KAAK,KAAK,CAAC;AAAA,IACzC,WAAW,CAAC,WAAW,WAAW,WAAW,WAAW,QAAQ;AAC9D,YAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,GAAG;AAClC,UAAI,CAAC,WAAW,CAAC,OAAO,YAAY;AAClC,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,wBAAwB,GAAG,GAAG;AACjE,eAAO;AAAA,MACT;AACA,UAAI;AACF,yBAAiB,QAAQ,CAAC,CAAC,OAAO;AAAA,MACpC,SAAS,OAAO;AACd,cAAM,IAAI;AACV,YAAI,EAAE,SAAS,oBAAoB;AACjC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,OAAO,EAAE,MAAM,GAAG,GAAG;AAC5E,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AACA,WAAK,KAAK,EAAE,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAChD,WAAW,WAAW,YAAY,WAAW,QAAQ;AACnD,UAAI;AACF,yBAAiB,QAAQ,IAAI;AAAA,MAC/B,SAAS,OAAO;AACd,cAAM,IAAI;AACV,YAAI,EAAE,SAAS,oBAAoB;AACjC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,OAAO,EAAE,MAAM,GAAG,GAAG;AAC5E,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AACA,WAAK,KAAK,EAAE,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAChD,WAAW,WAAW,aAAa,WAAW,QAAQ;AACpD,UAAI,CAAC,OAAO,YAAY;AACtB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,wBAAwB,GAAG,GAAG;AACjE,eAAO;AAAA,MACT;AACA,uBAAiB,QAAQ,KAAK;AAC9B,WAAK,KAAK,EAAE,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAChD,OAAO;AACL,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * routes/modules.ts \u2014 \u6A21\u5757\u76EE\u5F55\u4E0E\u542F\u505C\uFF08\u57FA\u4E8E modules/catalog.json + module-lock.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/modules \u2014 \u6A21\u5757\u5217\u8868\uFF08\u542B enable \u72B6\u6001\uFF09\n * GET /api/sfmc/modules/catalog \u2014 \u539F\u59CB catalog \u5217\u8868\n * GET /api/sfmc/modules/:key \u2014 \u5355\u4E2A\u6A21\u5757\u8BE6\u60C5\n * PATCH/PUT /api/sfmc/modules/:key \u2014 \u8BBE\u7F6E enabled\n * POST /api/sfmc/modules/:key/enable \u2014 \u542F\u7528\u6A21\u5757\n * POST /api/sfmc/modules/:key/disable \u2014 \u7981\u7528\u6A21\u5757\n */\n\ninterface Deps {\n loadModuleCatalog: () => unknown[];\n buildModuleList: () => Array<Record<string, unknown>>;\n resolveModuleByKey: (key: string) => { id: string; canDisable: boolean } | null;\n setModuleEnabled: (\n module: { id: string; canDisable: boolean },\n enabled: boolean\n ) => void;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\n/**\n * \u521B\u5EFA\u6A21\u5757\u76EE\u5F55\u68C0\u7D22\u4E0E\u542F\u505C\u7BA1\u63A7\u8DEF\u7531\u5904\u7406\u5668\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5B57\u5178\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nfunction createModuleRoutes({\n loadModuleCatalog,\n buildModuleList,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n}: Deps) {\n\n return async function handleModuleRoute({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params; // unused\n if (path === \"/api/sfmc/modules/catalog\") {\n if (method === \"GET\") {\n json(res, { modules: loadModuleCatalog() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n if (path === \"/api/sfmc/modules\") {\n if (method === \"GET\") json(res, { modules: buildModuleList() });\n else json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (!path.startsWith(\"/api/sfmc/modules/\")) return false;\n\n const rest = path.slice(\"/api/sfmc/modules/\".length);\n const [rawKey, action] = rest.split(\"/\");\n const module = resolveModuleByKey(decodeURIComponent(rawKey ?? \"\"));\n if (!module) {\n json(res, { success: false, error: \"module_not_found\" }, 404);\n return true;\n }\n\n const current = () => buildModuleList().find((entry) => entry.id === module.id);\n if (!action && method === \"GET\") {\n json(res, { module: current() ?? null });\n } else if (!action && (method === \"PATCH\" || method === \"PUT\")) {\n const { enabled } = await body(req);\n if (!enabled && !module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n try {\n setModuleEnabled(module, !!enabled);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"enable\" && method === \"POST\") {\n try {\n setModuleEnabled(module, true);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"disable\" && method === \"POST\") {\n if (!module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n setModuleEnabled(module, false);\n json(res, { success: true, module: current() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n };\n}\n\nexport { createModuleRoutes };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * routes/qq-bind.ts — QQ↔MC 身份绑定(平台 HTTP)
3
+ *
4
+ * POST /api/sfmc/qq/bind/request — QQ 侧申请短码
5
+ * POST /api/sfmc/qq/bind/confirm — 游戏侧确认
6
+ * POST /api/sfmc/qq/bind/unbind — 解绑
7
+ * GET /api/sfmc/qq/bind/me — 查询
8
+ */
9
+ import type { QueryFn } from "../lib/sqlite.js";
10
+ declare const CODE_TTL_MS: number;
11
+ interface Deps {
12
+ query: QueryFn;
13
+ body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
14
+ json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
15
+ }
16
+ declare function createQqBindRoutes({ query, body, json }: Deps): ({ path, method, params, req, res, }: {
17
+ path: string;
18
+ method: string;
19
+ params: URLSearchParams;
20
+ req: import("http").IncomingMessage;
21
+ res: import("http").ServerResponse;
22
+ }) => Promise<boolean>;
23
+ export { createQqBindRoutes, CODE_TTL_MS };
24
+ //# sourceMappingURL=qq-bind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-bind.d.ts","sourceRoot":"","sources":["../../src/routes/qq-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,QAAA,MAAM,WAAW,QAAgB,CAAC;AAGlC,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAQD,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,yCAOlD;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAsLrB;AAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC"}