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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +663 -0
  2. package/dist/domain/bridge.d.ts +57 -23
  3. package/dist/domain/bridge.d.ts.map +1 -1
  4. package/dist/domain/bridge.js +77 -6
  5. package/dist/domain/bridge.js.map +2 -2
  6. package/dist/domain/bridge.test.d.ts +2 -0
  7. package/dist/domain/bridge.test.d.ts.map +1 -0
  8. package/dist/domain/bridge.test.js +87 -0
  9. package/dist/domain/bridge.test.js.map +7 -0
  10. package/dist/domain/economy.d.ts +38 -7
  11. package/dist/domain/economy.d.ts.map +1 -1
  12. package/dist/domain/economy.js.map +2 -2
  13. package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
  14. package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
  15. package/dist/domain/messages-bridge-filter.test.js +20 -0
  16. package/dist/domain/messages-bridge-filter.test.js.map +7 -0
  17. package/dist/domain/qq-bind-status.test.d.ts +2 -0
  18. package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
  19. package/dist/domain/qq-bind-status.test.js +211 -0
  20. package/dist/domain/qq-bind-status.test.js.map +7 -0
  21. package/dist/domain/qq-events.d.ts +84 -0
  22. package/dist/domain/qq-events.d.ts.map +1 -0
  23. package/dist/domain/qq-events.js +193 -0
  24. package/dist/domain/qq-events.js.map +7 -0
  25. package/dist/domain/qq-events.test.d.ts +5 -0
  26. package/dist/domain/qq-events.test.d.ts.map +1 -0
  27. package/dist/domain/qq-events.test.js +170 -0
  28. package/dist/domain/qq-events.test.js.map +7 -0
  29. package/dist/domain/qq-join.test.d.ts +2 -0
  30. package/dist/domain/qq-join.test.d.ts.map +1 -0
  31. package/dist/domain/qq-join.test.js +353 -0
  32. package/dist/domain/qq-join.test.js.map +7 -0
  33. package/dist/domain/schema.d.ts +17 -19
  34. package/dist/domain/schema.d.ts.map +1 -1
  35. package/dist/domain/schema.js +69 -0
  36. package/dist/domain/schema.js.map +2 -2
  37. package/dist/domain/system-status.d.ts +75 -0
  38. package/dist/domain/system-status.d.ts.map +1 -0
  39. package/dist/domain/system-status.js +181 -0
  40. package/dist/domain/system-status.js.map +7 -0
  41. package/dist/domain/system-status.test.d.ts +5 -0
  42. package/dist/domain/system-status.test.d.ts.map +1 -0
  43. package/dist/domain/system-status.test.js +19 -0
  44. package/dist/domain/system-status.test.js.map +7 -0
  45. package/dist/domain/transaction.d.ts +5 -4
  46. package/dist/domain/transaction.d.ts.map +1 -1
  47. package/dist/domain/transaction.js.map +2 -2
  48. package/dist/env.d.ts +7 -0
  49. package/dist/env.d.ts.map +1 -1
  50. package/dist/env.js +55 -3
  51. package/dist/env.js.map +2 -2
  52. package/dist/index.d.ts +0 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +105 -19
  55. package/dist/index.js.map +2 -2
  56. package/dist/lib/idempotency-store.d.ts +17 -11
  57. package/dist/lib/idempotency-store.d.ts.map +1 -1
  58. package/dist/lib/idempotency-store.js.map +2 -2
  59. package/dist/lib/idempotency.d.ts +3 -4
  60. package/dist/lib/idempotency.d.ts.map +1 -1
  61. package/dist/lib/idempotency.js.map +2 -2
  62. package/dist/lib/module-state.d.ts +38 -8
  63. package/dist/lib/module-state.d.ts.map +1 -1
  64. package/dist/lib/module-state.js.map +2 -2
  65. package/dist/lib/order-by.d.ts +8 -6
  66. package/dist/lib/order-by.d.ts.map +1 -1
  67. package/dist/lib/order-by.js.map +2 -2
  68. package/dist/lib/sql-helpers.d.ts +25 -13
  69. package/dist/lib/sql-helpers.d.ts.map +1 -1
  70. package/dist/lib/sql-helpers.js.map +2 -2
  71. package/dist/lib/sqlite.d.ts +14 -5
  72. package/dist/lib/sqlite.d.ts.map +1 -1
  73. package/dist/lib/sqlite.js.map +2 -2
  74. package/dist/manifest-loader.d.ts +5 -2
  75. package/dist/manifest-loader.d.ts.map +1 -1
  76. package/dist/manifest-loader.js +16 -6
  77. package/dist/manifest-loader.js.map +2 -2
  78. package/dist/manifest.d.ts +1 -2
  79. package/dist/manifest.d.ts.map +1 -1
  80. package/dist/manifest.js +13 -8
  81. package/dist/manifest.js.map +2 -2
  82. package/dist/project-root.d.ts +1 -1
  83. package/dist/project-root.d.ts.map +1 -1
  84. package/dist/project-root.js +2 -2
  85. package/dist/project-root.js.map +2 -2
  86. package/dist/routes/_shared.d.ts +14 -5
  87. package/dist/routes/_shared.d.ts.map +1 -1
  88. package/dist/routes/_shared.js.map +2 -2
  89. package/dist/routes/config.d.ts +8 -11
  90. package/dist/routes/config.d.ts.map +1 -1
  91. package/dist/routes/config.js +17 -23
  92. package/dist/routes/config.js.map +2 -2
  93. package/dist/routes/db-routes.d.ts +6 -0
  94. package/dist/routes/db-routes.d.ts.map +1 -1
  95. package/dist/routes/db-routes.js.map +2 -2
  96. package/dist/routes/messages.d.ts +6 -2
  97. package/dist/routes/messages.d.ts.map +1 -1
  98. package/dist/routes/messages.js +17 -3
  99. package/dist/routes/messages.js.map +2 -2
  100. package/dist/routes/module-config-routes.d.ts +13 -9
  101. package/dist/routes/module-config-routes.d.ts.map +1 -1
  102. package/dist/routes/module-config-routes.js.map +2 -2
  103. package/dist/routes/modules.d.ts +6 -0
  104. package/dist/routes/modules.d.ts.map +1 -1
  105. package/dist/routes/modules.js.map +2 -2
  106. package/dist/routes/qq-bind.d.ts +24 -0
  107. package/dist/routes/qq-bind.d.ts.map +1 -0
  108. package/dist/routes/qq-bind.js +179 -0
  109. package/dist/routes/qq-bind.js.map +7 -0
  110. package/dist/routes/qq-events.d.ts +20 -0
  111. package/dist/routes/qq-events.d.ts.map +1 -0
  112. package/dist/routes/qq-events.js +39 -0
  113. package/dist/routes/qq-events.js.map +7 -0
  114. package/dist/routes/qq-join.d.ts +46 -0
  115. package/dist/routes/qq-join.d.ts.map +1 -0
  116. package/dist/routes/qq-join.js +277 -0
  117. package/dist/routes/qq-join.js.map +7 -0
  118. package/dist/routes/service-routes.d.ts +6 -0
  119. package/dist/routes/service-routes.d.ts.map +1 -1
  120. package/dist/routes/service-routes.js.map +2 -2
  121. package/dist/routes/status.d.ts +19 -0
  122. package/dist/routes/status.d.ts.map +1 -0
  123. package/dist/routes/status.js +74 -0
  124. package/dist/routes/status.js.map +7 -0
  125. package/dist/schema-registry.d.ts +16 -15
  126. package/dist/schema-registry.d.ts.map +1 -1
  127. package/dist/schema-registry.js +7 -1
  128. package/dist/schema-registry.js.map +2 -2
  129. package/dist/server.d.ts +19 -0
  130. package/dist/server.d.ts.map +1 -1
  131. package/dist/server.js.map +2 -2
  132. package/dist/service-registry.d.ts +39 -9
  133. package/dist/service-registry.d.ts.map +1 -1
  134. package/dist/service-registry.js +30 -2
  135. package/dist/service-registry.js.map +2 -2
  136. package/dist/services/builtin-handlers.d.ts +27 -7
  137. package/dist/services/builtin-handlers.d.ts.map +1 -1
  138. package/dist/services/builtin-handlers.js.map +2 -2
  139. package/dist/tx-runner.d.ts +29 -3
  140. package/dist/tx-runner.d.ts.map +1 -1
  141. package/dist/tx-runner.js +29 -3
  142. package/dist/tx-runner.js.map +2 -2
  143. package/package.json +17 -15
package/dist/env.js CHANGED
@@ -8,6 +8,19 @@ import {
8
8
  import { isAbsolute, join, resolve } from "node:path";
9
9
  import { log } from "./lib/log.js";
10
10
  import { PROJECT_ROOT as RESOLVED_ROOT } from "./project-root.js";
11
+ const SECRET_CONFIG_KEYS = /* @__PURE__ */ new Set([
12
+ "qq_app_secret",
13
+ "llbot_token",
14
+ "http_auth",
15
+ "agent_secret"
16
+ ]);
17
+ function redactConfigValue(key, value) {
18
+ if (SECRET_CONFIG_KEYS.has(key)) {
19
+ const s = String(value ?? "");
20
+ return s.length > 0 ? "***" : "";
21
+ }
22
+ return String(value);
23
+ }
11
24
  function loadEnv() {
12
25
  const PROJECT_ROOT = RESOLVED_ROOT;
13
26
  ensureCoreConfigs(PROJECT_ROOT, ["db_config", "qq_config", "permissions"]);
@@ -27,13 +40,13 @@ function loadEnv() {
27
40
  for (const [k, v] of Object.entries(dbconfig)) {
28
41
  const envKey = k.replace(/([A-Z])/g, "_$1").toUpperCase();
29
42
  process.env[envKey] = String(v);
30
- log.info(`db_config::${k} -> process.env.${envKey} = ${String(v)}`);
43
+ log.info(`db_config::${k} -> process.env.${envKey} = ${redactConfigValue(k, v)}`);
31
44
  }
32
45
  for (const [k, v] of Object.entries(qqconfig)) {
33
46
  const envKey = k.replace(/([A-Z])/g, "_$1").toUpperCase();
34
47
  if (process.env[envKey] === void 0) {
35
48
  process.env[envKey] = String(v);
36
- log.info(`qq_config::${k} -> process.env.${envKey} = ${String(v)}`);
49
+ log.info(`qq_config::${k} -> process.env.${envKey} = ${redactConfigValue(k, v)}`);
37
50
  }
38
51
  }
39
52
  function pick(jsonVal, envKey, fallback, source) {
@@ -42,7 +55,7 @@ function loadEnv() {
42
55
  }
43
56
  const fromEnv = envBaseline[envKey];
44
57
  if (fromEnv !== void 0 && fromEnv !== "") {
45
- log.info(`${source} \u672A\u5728 JSON \u4E2D\u914D\u7F6E,\u4F7F\u7528\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF ${envKey} = ${fromEnv}`);
58
+ log.info(`${source} \u672A\u5728 JSON \u4E2D\u914D\u7F6E,\u4F7F\u7528\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF ${envKey} = ${redactConfigValue(source, fromEnv)}`);
46
59
  return fromEnv;
47
60
  }
48
61
  return fallback;
@@ -63,6 +76,39 @@ function loadEnv() {
63
76
  const QQ_BRIDGE_CHANNEL_ID = String(
64
77
  pick(qqconfig["bridge_channel_id"], "BRIDGE_CHANNEL_ID", "", "bridge_channel_id")
65
78
  );
79
+ const rawBackend = String(
80
+ pick(
81
+ qqconfig["qq_backend"],
82
+ "QQ_BACKEND",
83
+ DEFAULT_QQ_CONFIG.qq_backend ?? "official",
84
+ "qq_backend"
85
+ )
86
+ );
87
+ const QQ_BACKEND = rawBackend === "llbot" ? "llbot" : "official";
88
+ const QQ_APP_ID = String(
89
+ pick(qqconfig["qq_app_id"], "QQ_APP_ID", "", "qq_app_id")
90
+ );
91
+ const QQ_APP_SECRET = String(
92
+ pick(qqconfig["qq_app_secret"], "QQ_APP_SECRET", "", "qq_app_secret")
93
+ );
94
+ const sandboxRaw = pick(
95
+ qqconfig["qq_sandbox"],
96
+ "QQ_SANDBOX",
97
+ false,
98
+ "qq_sandbox"
99
+ );
100
+ const QQ_SANDBOX = sandboxRaw === true || sandboxRaw === "true" || sandboxRaw === "1";
101
+ const QQ_GROUP_OPENID = String(
102
+ pick(qqconfig["qq_group_openid"], "QQ_GROUP_OPENID", "", "qq_group_openid")
103
+ );
104
+ const MCTOQQ_PREFIX = String(
105
+ pick(
106
+ qqconfig["mctoqq_prefix"],
107
+ "MCTOQQ_PREFIX",
108
+ DEFAULT_QQ_CONFIG.mctoqq_prefix ?? "[MC]",
109
+ "mctoqq_prefix"
110
+ )
111
+ );
66
112
  const AUTH_TOKEN = String(pick(dbconfig["http_auth"], "HTTP_AUTH", "", "http_auth"));
67
113
  const MODULES_DIR = dbconfig["modulesDir"] ? resolve(PROJECT_ROOT, String(dbconfig["modulesDir"])) : join(PROJECT_ROOT, "modules");
68
114
  const MODULE_CATALOG_PATH = modulePath(MODULES_DIR, "catalog.json");
@@ -78,6 +124,12 @@ function loadEnv() {
78
124
  LLBOT_TOKEN,
79
125
  QQ_GROUP_ID,
80
126
  QQ_BRIDGE_CHANNEL_ID,
127
+ QQ_BACKEND,
128
+ QQ_APP_ID,
129
+ QQ_APP_SECRET,
130
+ QQ_SANDBOX,
131
+ QQ_GROUP_OPENID,
132
+ MCTOQQ_PREFIX,
81
133
  MODULES_DIR,
82
134
  MODULE_CATALOG_PATH,
83
135
  MODULE_LOCK_PATH,
package/dist/env.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/env.ts"],
4
- "sourcesContent": ["/**\n * env.ts \u2014 \u73AF\u5883\u914D\u7F6E\u52A0\u8F7D\n */\n\nimport {\n ensureCoreConfigs,\n loadEnsuredConfig,\n modulePath,\n DEFAULT_DB_CONFIG,\n DEFAULT_QQ_CONFIG,\n} from \"@sfmc-bds/sdk/node/config\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nimport { log } from \"./lib/log.js\";\nimport { PROJECT_ROOT as RESOLVED_ROOT } from \"./project-root.js\";\n\n\nexport interface EnvConfig {\n PROJECT_ROOT: string;\n PORT: number;\n HOST: string;\n DB_PATH: string;\n AUTH_TOKEN: string;\n // LLBot HTTP\n LLBOT_HOST: string;\n LLBOT_PORT: number;\n LLBOT_TOKEN: string;\n // QQ \u7FA4 / channel(\u4ECE qq_config.json \u900F\u51FA,\u7ED9 bridge / messages \u8DEF\u7531\u7528)\n QQ_GROUP_ID: string;\n QQ_BRIDGE_CHANNEL_ID: string;\n MODULES_DIR: string;\n MODULE_CATALOG_PATH: string;\n MODULE_LOCK_PATH: string;\n dbconfig: Record<string, unknown>;\n qqconfig: Record<string, unknown>;\n}\n\nexport function loadEnv(): EnvConfig {\n const PROJECT_ROOT = RESOLVED_ROOT;\n /* \u542F\u52A8\u65F6\u786E\u8BA4 db/qq/permissions \u5B58\u5728;\u4E0D\u5B58\u5728\u5C31\u5199\u5E26\u9ED8\u8BA4\u503C\u7684\u9AA8\u67B6\u3002\n * \u4E0D\u4F9D\u8D56 wizard:wizard \u53EA\u586B\u5B57\u6BB5,\u9AA8\u67B6\u7531\u670D\u52A1\u81EA\u5DF1 ensure\u3002 */\n ensureCoreConfigs(PROJECT_ROOT, [\"db_config\", \"qq_config\", \"permissions\"]);\n const dbconfig = loadEnsuredConfig(\n PROJECT_ROOT,\n \"db_config.json\",\n \"db_config\",\n { ...DEFAULT_DB_CONFIG } as Record<string, unknown>\n );\n const qqconfig = loadEnsuredConfig(\n PROJECT_ROOT,\n \"qq_config.json\",\n \"qq_config\",\n { ...DEFAULT_QQ_CONFIG } as Record<string, unknown>\n );\n\n // \u2500\u2500 \u4F18\u5148\u7EA7:JSON > \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF > \u9ED8\u8BA4\u503C \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const envBaseline = { ...process.env };\n\n for (const [k, v] of Object.entries(dbconfig)) {\n const envKey = k.replace(/([A-Z])/g, \"_$1\").toUpperCase();\n process.env[envKey] = String(v);\n log.info(`db_config::${k} -> process.env.${envKey} = ${String(v)}`);\n }\n for (const [k, v] of Object.entries(qqconfig)) {\n const envKey = k.replace(/([A-Z])/g, \"_$1\").toUpperCase();\n if (process.env[envKey] === undefined) {\n process.env[envKey] = String(v);\n log.info(`qq_config::${k} -> process.env.${envKey} = ${String(v)}`);\n }\n }\n\n // \u5DE5\u5177:JSON > envBaseline(\u539F process.env) > default\n // \u8BED\u4E49:JSON \u91CC\u6709\u8FD9\u4E2A\u952E(\u975E undefined / null)\u5C31\u7528 JSON;\u7A7A\u4E32\u30010\u3001false \u90FD\u7B97\"\u663E\u5F0F\u914D\u7F6E\"\n // \u53EA\u6709 JSON \u5B8C\u5168\u6CA1\u8FD9\u4E2A\u952E(\u6216\u8005\u663E\u5F0F null)\u624D fall through \u5230 env\n function pick<T>(jsonVal: T | undefined | null, envKey: string, fallback: T, source: string): T {\n if (jsonVal !== undefined && jsonVal !== null) {\n return jsonVal;\n }\n const fromEnv = envBaseline[envKey];\n if (fromEnv !== undefined && fromEnv !== \"\") {\n log.info(`${source} \u672A\u5728 JSON \u4E2D\u914D\u7F6E,\u4F7F\u7528\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF ${envKey} = ${fromEnv}`);\n return fromEnv as unknown as T;\n }\n return fallback;\n }\n\n const PORT = parseInt(String(pick(dbconfig[\"db_port\"] as number | undefined, \"DB_PORT\", 3001, \"db_port\")), 10);\n const HOST = \"127.0.0.1\";\n const DB_PATH_RAW = String(pick(dbconfig[\"dbDir\"] as string | undefined, \"DB_DIR\", \"data/sfmc_data.db\", \"dbDir\"));\n const DB_PATH = isAbsolute(DB_PATH_RAW) ? DB_PATH_RAW : resolve(PROJECT_ROOT, DB_PATH_RAW);\n const LLBOT_HOST = String(\n pick(qqconfig[\"llbot_host\"] as string | undefined, \"LLBOT_HOST\", \"127.0.0.1\", \"llbot_host\")\n );\n const LLBOT_PORT = parseInt(\n String(pick(qqconfig[\"llbot_port\"] as number | undefined, \"LLBOT_PORT\", 3004, \"llbot_port\")),\n 10\n );\n const LLBOT_TOKEN = String(pick(qqconfig[\"llbot_token\"] as string | undefined, \"LLBOT_TOKEN\", \"\", \"llbot_token\"));\n const QQ_GROUP_ID = String(pick(qqconfig[\"qq_group_id\"] as string | undefined, \"QQ_GROUP_ID\", \"\", \"qq_group_id\"));\n const QQ_BRIDGE_CHANNEL_ID = String(\n pick(qqconfig[\"bridge_channel_id\"] as string | undefined, \"BRIDGE_CHANNEL_ID\", \"\", \"bridge_channel_id\")\n );\n const AUTH_TOKEN = String(pick(dbconfig[\"http_auth\"] as string | undefined, \"HTTP_AUTH\", \"\", \"http_auth\"));\n const MODULES_DIR = dbconfig[\"modulesDir\"]\n ? resolve(PROJECT_ROOT, String(dbconfig[\"modulesDir\"]))\n : join(PROJECT_ROOT, \"modules\");\n const MODULE_CATALOG_PATH = modulePath(MODULES_DIR, \"catalog.json\");\n const MODULE_LOCK_PATH = modulePath(MODULES_DIR, \"module-lock.json\");\n\n return {\n PROJECT_ROOT,\n PORT,\n HOST,\n DB_PATH,\n AUTH_TOKEN,\n LLBOT_HOST,\n LLBOT_PORT,\n LLBOT_TOKEN,\n QQ_GROUP_ID,\n QQ_BRIDGE_CHANNEL_ID,\n MODULES_DIR,\n MODULE_CATALOG_PATH,\n MODULE_LOCK_PATH,\n dbconfig,\n qqconfig,\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * env.ts \u2014 \u73AF\u5883\u914D\u7F6E\u52A0\u8F7D\n */\n\nimport {\n ensureCoreConfigs,\n loadEnsuredConfig,\n modulePath,\n DEFAULT_DB_CONFIG,\n DEFAULT_QQ_CONFIG,\n type QQBackend,\n} from \"@sfmc-bds/sdk/node/config\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nimport { log } from \"./lib/log.js\";\nimport { PROJECT_ROOT as RESOLVED_ROOT } from \"./project-root.js\";\n\n/** \u65E5\u5FD7\u8131\u654F\uFF1A\u5BC6\u94A5\u7C7B\u5B57\u6BB5\u4E0D\u6253\u5370\u660E\u6587 */\nconst SECRET_CONFIG_KEYS = new Set([\n \"qq_app_secret\",\n \"llbot_token\",\n \"http_auth\",\n \"agent_secret\",\n]);\n\nfunction redactConfigValue(key: string, value: unknown): string {\n if (SECRET_CONFIG_KEYS.has(key)) {\n const s = String(value ?? \"\");\n return s.length > 0 ? \"***\" : \"\";\n }\n return String(value);\n}\n\nexport interface EnvConfig {\n PROJECT_ROOT: string;\n PORT: number;\n HOST: string;\n DB_PATH: string;\n AUTH_TOKEN: string;\n // LLBot HTTP\n LLBOT_HOST: string;\n LLBOT_PORT: number;\n LLBOT_TOKEN: string;\n // QQ \u7FA4 / channel(\u4ECE qq_config.json \u900F\u51FA,\u7ED9 bridge / messages \u8DEF\u7531\u7528)\n QQ_GROUP_ID: string;\n QQ_BRIDGE_CHANNEL_ID: string;\n // \u5B98\u65B9 Bot\n QQ_BACKEND: QQBackend;\n QQ_APP_ID: string;\n QQ_APP_SECRET: string;\n QQ_SANDBOX: boolean;\n QQ_GROUP_OPENID: string;\n MCTOQQ_PREFIX: string;\n MODULES_DIR: string;\n MODULE_CATALOG_PATH: string;\n MODULE_LOCK_PATH: string;\n dbconfig: Record<string, unknown>;\n qqconfig: Record<string, unknown>;\n}\n\nexport function loadEnv(): EnvConfig {\n const PROJECT_ROOT = RESOLVED_ROOT;\n /* \u542F\u52A8\u65F6\u786E\u8BA4 db/qq/permissions \u5B58\u5728;\u4E0D\u5B58\u5728\u5C31\u5199\u5E26\u9ED8\u8BA4\u503C\u7684\u9AA8\u67B6\u3002\n * \u4E0D\u4F9D\u8D56 wizard:wizard \u53EA\u586B\u5B57\u6BB5,\u9AA8\u67B6\u7531\u670D\u52A1\u81EA\u5DF1 ensure\u3002 */\n ensureCoreConfigs(PROJECT_ROOT, [\"db_config\", \"qq_config\", \"permissions\"]);\n const dbconfig = loadEnsuredConfig(\n PROJECT_ROOT,\n \"db_config.json\",\n \"db_config\",\n { ...DEFAULT_DB_CONFIG } as Record<string, unknown>\n );\n const qqconfig = loadEnsuredConfig(\n PROJECT_ROOT,\n \"qq_config.json\",\n \"qq_config\",\n { ...DEFAULT_QQ_CONFIG } as Record<string, unknown>\n );\n\n // \u2500\u2500 \u4F18\u5148\u7EA7:JSON > \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF > \u9ED8\u8BA4\u503C \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const envBaseline = { ...process.env };\n\n for (const [k, v] of Object.entries(dbconfig)) {\n const envKey = k.replace(/([A-Z])/g, \"_$1\").toUpperCase();\n process.env[envKey] = String(v);\n log.info(`db_config::${k} -> process.env.${envKey} = ${redactConfigValue(k, v)}`);\n }\n for (const [k, v] of Object.entries(qqconfig)) {\n const envKey = k.replace(/([A-Z])/g, \"_$1\").toUpperCase();\n if (process.env[envKey] === undefined) {\n process.env[envKey] = String(v);\n log.info(`qq_config::${k} -> process.env.${envKey} = ${redactConfigValue(k, v)}`);\n }\n }\n\n // \u5DE5\u5177:JSON > envBaseline(\u539F process.env) > default\n // \u8BED\u4E49:JSON \u91CC\u6709\u8FD9\u4E2A\u952E(\u975E undefined / null)\u5C31\u7528 JSON;\u7A7A\u4E32\u30010\u3001false \u90FD\u7B97\"\u663E\u5F0F\u914D\u7F6E\"\n // \u53EA\u6709 JSON \u5B8C\u5168\u6CA1\u8FD9\u4E2A\u952E(\u6216\u8005\u663E\u5F0F null)\u624D fall through \u5230 env\n function pick<T>(jsonVal: T | undefined | null, envKey: string, fallback: T, source: string): T {\n if (jsonVal !== undefined && jsonVal !== null) {\n return jsonVal;\n }\n const fromEnv = envBaseline[envKey];\n if (fromEnv !== undefined && fromEnv !== \"\") {\n log.info(`${source} \u672A\u5728 JSON \u4E2D\u914D\u7F6E,\u4F7F\u7528\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF ${envKey} = ${redactConfigValue(source, fromEnv)}`);\n return fromEnv as unknown as T;\n }\n return fallback;\n }\n\n const PORT = parseInt(String(pick(dbconfig[\"db_port\"] as number | undefined, \"DB_PORT\", 3001, \"db_port\")), 10);\n const HOST = \"127.0.0.1\";\n const DB_PATH_RAW = String(pick(dbconfig[\"dbDir\"] as string | undefined, \"DB_DIR\", \"data/sfmc_data.db\", \"dbDir\"));\n const DB_PATH = isAbsolute(DB_PATH_RAW) ? DB_PATH_RAW : resolve(PROJECT_ROOT, DB_PATH_RAW);\n const LLBOT_HOST = String(\n pick(qqconfig[\"llbot_host\"] as string | undefined, \"LLBOT_HOST\", \"127.0.0.1\", \"llbot_host\")\n );\n const LLBOT_PORT = parseInt(\n String(pick(qqconfig[\"llbot_port\"] as number | undefined, \"LLBOT_PORT\", 3004, \"llbot_port\")),\n 10\n );\n const LLBOT_TOKEN = String(pick(qqconfig[\"llbot_token\"] as string | undefined, \"LLBOT_TOKEN\", \"\", \"llbot_token\"));\n const QQ_GROUP_ID = String(pick(qqconfig[\"qq_group_id\"] as string | undefined, \"QQ_GROUP_ID\", \"\", \"qq_group_id\"));\n const QQ_BRIDGE_CHANNEL_ID = String(\n pick(qqconfig[\"bridge_channel_id\"] as string | undefined, \"BRIDGE_CHANNEL_ID\", \"\", \"bridge_channel_id\")\n );\n const rawBackend = String(\n pick(\n qqconfig[\"qq_backend\"] as string | undefined,\n \"QQ_BACKEND\",\n DEFAULT_QQ_CONFIG.qq_backend ?? \"official\",\n \"qq_backend\"\n )\n );\n const QQ_BACKEND: QQBackend = rawBackend === \"llbot\" ? \"llbot\" : \"official\";\n const QQ_APP_ID = String(\n pick(qqconfig[\"qq_app_id\"] as string | undefined, \"QQ_APP_ID\", \"\", \"qq_app_id\")\n );\n const QQ_APP_SECRET = String(\n pick(qqconfig[\"qq_app_secret\"] as string | undefined, \"QQ_APP_SECRET\", \"\", \"qq_app_secret\")\n );\n const sandboxRaw = pick(\n qqconfig[\"qq_sandbox\"] as boolean | string | undefined,\n \"QQ_SANDBOX\",\n false as boolean | string,\n \"qq_sandbox\"\n );\n const QQ_SANDBOX =\n sandboxRaw === true || sandboxRaw === \"true\" || sandboxRaw === \"1\";\n const QQ_GROUP_OPENID = String(\n pick(qqconfig[\"qq_group_openid\"] as string | undefined, \"QQ_GROUP_OPENID\", \"\", \"qq_group_openid\")\n );\n const MCTOQQ_PREFIX = String(\n pick(\n qqconfig[\"mctoqq_prefix\"] as string | undefined,\n \"MCTOQQ_PREFIX\",\n DEFAULT_QQ_CONFIG.mctoqq_prefix ?? \"[MC]\",\n \"mctoqq_prefix\"\n )\n );\n const AUTH_TOKEN = String(pick(dbconfig[\"http_auth\"] as string | undefined, \"HTTP_AUTH\", \"\", \"http_auth\"));\n const MODULES_DIR = dbconfig[\"modulesDir\"]\n ? resolve(PROJECT_ROOT, String(dbconfig[\"modulesDir\"]))\n : join(PROJECT_ROOT, \"modules\");\n const MODULE_CATALOG_PATH = modulePath(MODULES_DIR, \"catalog.json\");\n const MODULE_LOCK_PATH = modulePath(MODULES_DIR, \"module-lock.json\");\n\n return {\n PROJECT_ROOT,\n PORT,\n HOST,\n DB_PATH,\n AUTH_TOKEN,\n LLBOT_HOST,\n LLBOT_PORT,\n LLBOT_TOKEN,\n QQ_GROUP_ID,\n QQ_BRIDGE_CHANNEL_ID,\n QQ_BACKEND,\n QQ_APP_ID,\n QQ_APP_SECRET,\n QQ_SANDBOX,\n QQ_GROUP_OPENID,\n MCTOQQ_PREFIX,\n MODULES_DIR,\n MODULE_CATALOG_PATH,\n MODULE_LOCK_PATH,\n dbconfig,\n qqconfig,\n };\n}\n\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  /**
2
- * index.ts — 入口(v2 启动顺序)
3
2
  *
4
3
  * 工作流:
5
4
  * 1. 加载 env
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAwB1C,QAAA,MAAM,GAAG,8BAAY,CAAC;AACtB,QAAA,MAAM,EAAE,oCAA4B,CAAC;AAGrC,QAAA,MAAM,KAAK,mCAAkB,CAAC;AA4B9B,QAAA,MAAM,gBAAgB,8DAAwE,CAAC;AAU/F,QAAA,MAAM,UAAU,0CAId,CAAC;AAGH,QAAA,MAAM,cAAc,gBAAyB,CAAC;AAC9C,QAAA,MAAM,eAAe,iBAAwB,CAAC;AAE9C,QAAA,MAAM,QAAQ,UAMZ,CAAC;AA4QH,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAcH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAgC1C,QAAA,MAAM,GAAG,8BAAY,CAAC;AACtB,QAAA,MAAM,EAAE,oCAA4B,CAAC;AAGrC,QAAA,MAAM,KAAK,mCAAkB,CAAC;AA4B9B,QAAA,MAAM,gBAAgB,8DAAwE,CAAC;AAU/F,QAAA,MAAM,UAAU,0CAId,CAAC;AAGH,QAAA,MAAM,cAAc,gBAAyB,CAAC;AAC9C,QAAA,MAAM,eAAe,iBAAwB,CAAC;AAE9C,QAAA,MAAM,QAAQ,UAMZ,CAAC;AAwXH,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -14,16 +14,25 @@ import { ServiceRegistry } from "./service-registry.js";
14
14
  import { TxRunner } from "./tx-runner.js";
15
15
  import { registerEnabledBuiltinServices } from "./services/builtin-handlers.js";
16
16
  import { syncModuleRuntimeState } from "./module-runtime-sync.js";
17
- import { readJson } from "@sfmc-bds/sdk/node/config";
17
+ import { ensureJson, patchJson, readJson } from "@sfmc-bds/sdk/node/config";
18
+ import { join } from "node:path";
18
19
  import { createModuleConfigRoutes } from "./routes/module-config-routes.js";
19
20
  import { createDbRoutes } from "./routes/db-routes.js";
20
21
  import { createServiceRoutes } from "./routes/service-routes.js";
21
22
  import { jsonV2Fail } from "./routes/_shared.js";
22
- import { createConfigRoutes } from "./routes/config.js";
23
- import { createHealthRoutes } from "./routes/health.js";
23
+ import { createStatusRoutes } from "./routes/status.js";
24
+ import { createQqBindRoutes } from "./routes/qq-bind.js";
25
+ import { createQqJoinRoutes } from "./routes/qq-join.js";
26
+ import { createQqEventsRoutes } from "./routes/qq-events.js";
24
27
  import { createMessagesRoutes } from "./routes/messages.js";
25
28
  import { createModuleRoutes } from "./routes/modules.js";
26
- import { forwardToQQBridge, makeLLBotConfig } from "./domain/bridge.js";
29
+ import { createConfigRoutes } from "./routes/config.js";
30
+ import { createHealthRoutes } from "./routes/health.js";
31
+ import { forwardToQQBridge, makeOutboundConfig } from "./domain/bridge.js";
32
+ import {
33
+ createQqEventsAggregator,
34
+ resolveQqEventsConfig
35
+ } from "./domain/qq-events.js";
27
36
  import { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from "./lib/module-state.js";
28
37
  import { body as sharedBody, json as sharedJson } from "./lib/http.js";
29
38
  if (!assertNodeVersion(22, 13)) {
@@ -107,7 +116,6 @@ function buildModuleList() {
107
116
  configKey,
108
117
  config_key: configKey,
109
118
  display_name: String(raw.name || configKey),
110
- type: String(raw.type || "feature"),
111
119
  description: String(raw.description || ""),
112
120
  default_enabled: raw.enabledByDefault !== false,
113
121
  can_disable: moduleCanDisable(raw),
@@ -158,22 +166,96 @@ function setModuleEnabled(mod, enabled) {
158
166
  });
159
167
  }
160
168
  const healthRoutes = createHealthRoutes();
169
+ const statusRoutes = createStatusRoutes({ query });
170
+ const qqBindRoutes = createQqBindRoutes({ query, body, json });
171
+ const QQ_LINK_CONFIG_KEY = "qq_link";
172
+ const QQ_LINK_DEFAULTS = {
173
+ allowlist_enabled: true,
174
+ require_approval: true,
175
+ /** 是否将 QQ 群主/群管视作 SFMC 管理员;仅改本文件,API/群聊不可写 */
176
+ treat_group_admins_as_admins: false
177
+ };
178
+ function asConfigBool(v, fallback) {
179
+ if (v === void 0 || v === null) return fallback;
180
+ if (typeof v === "boolean") return v;
181
+ const s = String(v).trim().toLowerCase();
182
+ if (s === "true" || s === "1" || s === "yes" || s === "on") return true;
183
+ if (s === "false" || s === "0" || s === "no" || s === "off") return false;
184
+ return fallback;
185
+ }
186
+ function qqLinkConfigFile() {
187
+ return join(env.PROJECT_ROOT, "configs", `${QQ_LINK_CONFIG_KEY}.json`);
188
+ }
189
+ function readJoinFlags() {
190
+ const disk = ensureJson(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });
191
+ return {
192
+ allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),
193
+ requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),
194
+ treatGroupAdminsAsAdmins: asConfigBool(
195
+ disk.treat_group_admins_as_admins,
196
+ QQ_LINK_DEFAULTS.treat_group_admins_as_admins
197
+ )
198
+ };
199
+ }
200
+ function writeJoinFlags(partial) {
201
+ const file = qqLinkConfigFile();
202
+ ensureJson(file, { ...QQ_LINK_DEFAULTS });
203
+ const patch = {};
204
+ if (partial.allowlistEnabled !== void 0) patch.allowlist_enabled = partial.allowlistEnabled;
205
+ if (partial.requireApproval !== void 0) patch.require_approval = partial.requireApproval;
206
+ if (Object.keys(patch).length > 0) {
207
+ patchJson(file, patch);
208
+ }
209
+ return readJoinFlags();
210
+ }
211
+ {
212
+ const created = !readJson(qqLinkConfigFile());
213
+ ensureJson(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });
214
+ if (created) {
215
+ log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);
216
+ }
217
+ }
218
+ const qqJoinRoutes = createQqJoinRoutes({
219
+ query,
220
+ body,
221
+ json,
222
+ getAdminOpenids: () => {
223
+ const raw = env.qqconfig["qq_admin_openids"];
224
+ if (!Array.isArray(raw)) return [];
225
+ return raw.map((x) => String(x).trim()).filter(Boolean);
226
+ },
227
+ getJoinFlags: () => readJoinFlags(),
228
+ setJoinFlags: (partial) => writeJoinFlags(partial)
229
+ });
230
+ function currentOutbound() {
231
+ return makeOutboundConfig({
232
+ QQ_BACKEND: env.QQ_BACKEND,
233
+ LLBOT_HOST: env.LLBOT_HOST,
234
+ LLBOT_PORT: env.LLBOT_PORT,
235
+ LLBOT_TOKEN: env.LLBOT_TOKEN,
236
+ QQ_GROUP_ID: env.QQ_GROUP_ID,
237
+ QQ_APP_ID: env.QQ_APP_ID,
238
+ QQ_APP_SECRET: env.QQ_APP_SECRET,
239
+ QQ_SANDBOX: env.QQ_SANDBOX,
240
+ QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,
241
+ MCTOQQ_PREFIX: env.MCTOQQ_PREFIX
242
+ });
243
+ }
244
+ const qqEventsAggregator = createQqEventsAggregator({
245
+ getConfig: () => resolveQqEventsConfig(env.qqconfig["qq_events"]),
246
+ getOutbound: () => currentOutbound()
247
+ });
248
+ const qqEventsRoutes = createQqEventsRoutes({
249
+ body,
250
+ json,
251
+ aggregator: qqEventsAggregator
252
+ });
161
253
  const messagesRoutes = createMessagesRoutes({
162
254
  query,
163
255
  body,
164
256
  json,
165
- forwardToQQBridge: (channelId, fromName, content, fromId) => forwardToQQBridge(
166
- makeLLBotConfig({
167
- LLBOT_HOST: env.LLBOT_HOST,
168
- LLBOT_PORT: env.LLBOT_PORT,
169
- LLBOT_TOKEN: env.LLBOT_TOKEN,
170
- QQ_GROUP_ID: env.QQ_GROUP_ID
171
- }),
172
- channelId,
173
- fromName,
174
- content,
175
- fromId
176
- )
257
+ getBridgeChannelId: () => String(env.QQ_BRIDGE_CHANNEL_ID ?? "").trim(),
258
+ forwardToQQBridge: (channelId, fromName, content, fromId) => forwardToQQBridge(currentOutbound(), channelId, fromName, content, fromId)
177
259
  });
178
260
  const configRoutes = createConfigRoutes({
179
261
  json,
@@ -232,8 +314,8 @@ async function handle(req, res) {
232
314
  permissions: manifest?.permissions ?? []
233
315
  };
234
316
  } else {
235
- const PUBLIC_GET = path === "/api/health" || method === "GET" && (path === "/api/sfmc/modules" || path === "/api/sfmc/modules/catalog" || path.startsWith("/api/sfmc/modules/"));
236
- const NEEDS_AUTH = !PUBLIC_GET && method !== "GET";
317
+ const PUBLIC_GET = path === "/api/health" || path === "/api/sfmc/status" || path === "/api/sfmc/qq/join/apply-queue" || path === "/api/sfmc/qq/join/settings" || path === "/api/sfmc/qq/admin/action-queue" || method === "GET" && (path === "/api/sfmc/modules" || path === "/api/sfmc/modules/catalog" || path.startsWith("/api/sfmc/modules/") || path === "/api/sfmc/qq/bind/me" || path === "/api/sfmc/qq/join/pending");
318
+ const NEEDS_AUTH = !PUBLIC_GET && method !== "GET" && !(path.startsWith("/api/sfmc/qq/bind/") || path.startsWith("/api/sfmc/qq/join/") || path.startsWith("/api/sfmc/qq/admin/") || path.startsWith("/api/sfmc/qq/events"));
237
319
  if (env.AUTH_TOKEN && NEEDS_AUTH) {
238
320
  const auth = req.headers["authorization"] || "";
239
321
  const provided = auth.startsWith("Bearer ") ? auth.slice(7) : req.headers["x-db-token"] || "";
@@ -265,6 +347,10 @@ async function handle(req, res) {
265
347
  const ctxBase = { path, method, params, req, res };
266
348
  if (await moduleRoutesInstance(ctxBase)) return;
267
349
  if (await healthRoutes(ctxBase)) return;
350
+ if (await statusRoutes(ctxBase)) return;
351
+ if (await qqBindRoutes(ctxBase)) return;
352
+ if (await qqJoinRoutes(ctxBase)) return;
353
+ if (await qqEventsRoutes(ctxBase)) return;
268
354
  if (await messagesRoutes(ctxBase)) return;
269
355
  if (await configRoutes(ctxBase)) return;
270
356
  json(res, { success: false, error: "not_found" }, 404);
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/**\n * index.ts \u2014 \u5165\u53E3(v2 \u542F\u52A8\u987A\u5E8F)\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\n\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { loadEnv } from \"./env.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { TxRunner } from \"./tx-runner.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\n\nimport { readJson } from \"@sfmc-bds/sdk/node/config\";\n\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\nimport { jsonV2Fail } from \"./routes/_shared.js\";\n\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { createMessagesRoutes } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\n\nimport { forwardToQQBridge, makeLLBotConfig } from \"./domain/bridge.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>)\n .map((m) => String(m.id || \"\"))\n .filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find(\n (m) => String(m.id) === id\n );\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<typeof loadedManifest.modules[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(\n `[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`\n);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(\n `[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`\n );\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n type: String((raw as Record<string, unknown>).type || \"feature\"),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(\n makeLLBotConfig({\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n }),\n channelId,\n fromName,\n content,\n fromId\n ),\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/modules\" || path === \"/api/sfmc/modules/catalog\" || path.startsWith(\"/api/sfmc/modules/\")));\n const NEEDS_AUTH = !PUBLIC_GET && method !== \"GET\";\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (moduleAuthCtx && (path.startsWith(\"/api/sfmc/db/\") || path.startsWith(\"/api/sfmc/services\") || (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as { path: string; method: string; params: URLSearchParams; req: http.IncomingMessage; res: http.ServerResponse };\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, env, query, schemaRegistry, serviceRegistry, txRunner, enabledManifests, moduleAuth };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\n\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { loadEnv } from \"./env.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { TxRunner } from \"./tx-runner.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\n\nimport { ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport { join } from \"node:path\";\n\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\nimport { jsonV2Fail } from \"./routes/_shared.js\";\n\nimport { createStatusRoutes } from \"./routes/status.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createMessagesRoutes } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport {\n createQqEventsAggregator,\n resolveQqEventsConfig,\n} from \"./domain/qq-events.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>)\n .map((m) => String(m.id || \"\"))\n .filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find(\n (m) => String(m.id) === id\n );\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<typeof loadedManifest.modules[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(\n `[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`\n);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(\n `[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`\n );\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\nconst statusRoutes = createStatusRoutes({ query });\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u6A21\u5757 qq-link \u7684\u914D\u7F6E\u6587\u4EF6\uFF08\u975E SDK qq_config\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u4EC5\u6539\u672C\u6587\u4EF6\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: false,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u6A21\u5757\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u6A21\u5757\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = env.qqconfig[\"qq_admin_openids\"];\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n MCTOQQ_PREFIX: env.MCTOQQ_PREFIX,\n });\n}\n\nconst qqEventsAggregator = createQqEventsAggregator({\n getConfig: () => resolveQqEventsConfig(env.qqconfig[\"qq_events\"]),\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n aggregator: qqEventsAggregator,\n});\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getBridgeChannelId: () => String(env.QQ_BRIDGE_CHANNEL_ID ?? \"\").trim(),\n forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, fromName, content, fromId),\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n path === \"/api/sfmc/qq/admin/action-queue\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/admin/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (moduleAuthCtx && (path.startsWith(\"/api/sfmc/db/\") || path.startsWith(\"/api/sfmc/services\") || (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as { path: string; method: string; params: URLSearchParams; req: http.IncomingMessage; res: http.ServerResponse };\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, env, query, schemaRegistry, serviceRegistry, txRunner, enabledManifests, moduleAuth };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,28 +1,34 @@
1
1
  /**
2
- * lib/idempotency.ts — db.idempotent() 后端
2
+ * lib/idempotency-store.ts — 幂等操作底层持久化存储
3
3
  *
4
- * 表:sfmc__idempotent (db-tables.ts 创建)
5
- * key: (module_id, action, key) → cached value?
4
+ * 底层表:`sfmc__idempotent`
5
+ * 主键复合维度:`module_id` + `action` + `key`
6
6
  *
7
- * probe(module_id, action, key):
8
- * - 命中 {replayed:true, cached}
9
- * - 不命中 {replayed:false}
7
+ * 探针机制:
8
+ * - `probe(moduleId, action, key)`:探测是否已执行过;命中则返回 `{ replayed: true, cached }`,未命中返回 `{ replayed: false }`
9
+ * - `commit(moduleId, action, key, value?)`:写入成功执行记录与缓存结果
10
10
  *
11
- * commit(module_id, action, key, value?):
12
- * - INSERT … (replace) — 即使没 probe 也允许直接 commit
13
- *
14
- * 事务内调用一律走 tx-runner 不行(commit 必须在事务外完成,跨事务持久化);
15
- * 这里单独端点 POST /api/sfmc/db/idempotent/{probe,commit},不走 tx。
11
+ * 事务与持久化边界:
12
+ * 幂等提交独立于单次业务事务,确保跨事务持久化去重。
16
13
  */
17
14
  import type { DatabaseSync } from "node:sqlite";
15
+ /** 幂等存储器接口。 */
18
16
  export interface IdempotencyStore {
17
+ /** 探测指定的动作与幂等键是否已经执行过。 */
19
18
  probe(moduleId: string, action: string, key: string): Promise<{
20
19
  replayed: boolean;
21
20
  cached?: unknown;
22
21
  }>;
22
+ /** 提交并持久化已执行的幂等动作与结果数据。 */
23
23
  commit(moduleId: string, action: string, key: string, value?: unknown): Promise<{
24
24
  ok: boolean;
25
25
  }>;
26
26
  }
27
+ /**
28
+ * 创建基于 SQLite 的幂等存储操作实例。
29
+ *
30
+ * @param db SQLite 数据库连接。
31
+ * @returns 幂等存储器对象。
32
+ */
27
33
  export declare function createIdempotencyStore(db: DatabaseSync): IdempotencyStore;
28
34
  //# sourceMappingURL=idempotency-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency-store.d.ts","sourceRoot":"","sources":["../../src/lib/idempotency-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACvG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAClG;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,YAAY,GAAG,gBAAgB,CAgCzE"}
1
+ {"version":3,"file":"idempotency-store.d.ts","sourceRoot":"","sources":["../../src/lib/idempotency-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,eAAe;AACf,MAAM,WAAW,gBAAgB;IAC/B,0BAA0B;IAC1B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACvG,2BAA2B;IAC3B,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAClG;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,YAAY,GAAG,gBAAgB,CAiCzE"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/idempotency-store.ts"],
4
- "sourcesContent": ["/**\n * lib/idempotency.ts \u2014 db.idempotent() \u540E\u7AEF\n *\n * \u8868:sfmc__idempotent (db-tables.ts \u521B\u5EFA)\n * key: (module_id, action, key) \u2192 cached value?\n *\n * probe(module_id, action, key):\n * - \u547D\u4E2D \u2192 {replayed:true, cached}\n * - \u4E0D\u547D\u4E2D \u2192 {replayed:false}\n *\n * commit(module_id, action, key, value?):\n * - INSERT \u2026 (replace) \u2014 \u5373\u4F7F\u6CA1 probe \u4E5F\u5141\u8BB8\u76F4\u63A5 commit\n *\n * \u4E8B\u52A1\u5185\u8C03\u7528\u4E00\u5F8B\u8D70 tx-runner \u4E0D\u884C(commit \u5FC5\u987B\u5728\u4E8B\u52A1\u5916\u5B8C\u6210,\u8DE8\u4E8B\u52A1\u6301\u4E45\u5316);\n * \u8FD9\u91CC\u5355\u72EC\u7AEF\u70B9 POST /api/sfmc/db/idempotent/{probe,commit},\u4E0D\u8D70 tx\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { isValidIdempotencyKey } from \"./idempotency.js\";\n\nexport interface IdempotencyStore {\n probe(moduleId: string, action: string, key: string): Promise<{ replayed: boolean; cached?: unknown }>;\n commit(moduleId: string, action: string, key: string, value?: unknown): Promise<{ ok: boolean }>;\n}\n\nexport function createIdempotencyStore(db: DatabaseSync): IdempotencyStore {\n const probeStmt = db.prepare(\n \"SELECT value FROM sfmc__idempotent WHERE module_id=? AND action=? AND key=?\"\n );\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO sfmc__idempotent (module_id, action, key, value, created_at)\n VALUES (?, ?, ?, ?, ?)`\n );\n\n return {\n async probe(moduleId, action, key) {\n if (!isValidIdempotencyKey(key)) throw new Error(\"invalid idempotency key\");\n const row = probeStmt.get(moduleId, action, key) as { value: string | null } | undefined;\n if (!row) return { replayed: false };\n let cached: unknown = null;\n if (row.value) {\n try {\n cached = JSON.parse(row.value);\n } catch {\n cached = row.value;\n }\n }\n return { replayed: true, cached };\n },\n\n async commit(moduleId, action, key, value?) {\n if (!isValidIdempotencyKey(key)) throw new Error(\"invalid idempotency key\");\n const json = value === undefined ? null : JSON.stringify(value);\n insertStmt.run(moduleId, action, key, json, new Date().toISOString());\n return { ok: true };\n },\n };\n}\n"],
5
- "mappings": "AAkBA,SAAS,6BAA6B;AAO/B,SAAS,uBAAuB,IAAoC;AACzE,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA,EACF;AACA,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA;AAAA,EAEF;AAEA,SAAO;AAAA,IACL,MAAM,MAAM,UAAU,QAAQ,KAAK;AACjC,UAAI,CAAC,sBAAsB,GAAG,EAAG,OAAM,IAAI,MAAM,yBAAyB;AAC1E,YAAM,MAAM,UAAU,IAAI,UAAU,QAAQ,GAAG;AAC/C,UAAI,CAAC,IAAK,QAAO,EAAE,UAAU,MAAM;AACnC,UAAI,SAAkB;AACtB,UAAI,IAAI,OAAO;AACb,YAAI;AACF,mBAAS,KAAK,MAAM,IAAI,KAAK;AAAA,QAC/B,QAAQ;AACN,mBAAS,IAAI;AAAA,QACf;AAAA,MACF;AACA,aAAO,EAAE,UAAU,MAAM,OAAO;AAAA,IAClC;AAAA,IAEA,MAAM,OAAO,UAAU,QAAQ,KAAK,OAAQ;AAC1C,UAAI,CAAC,sBAAsB,GAAG,EAAG,OAAM,IAAI,MAAM,yBAAyB;AAC1E,YAAM,OAAO,UAAU,SAAY,OAAO,KAAK,UAAU,KAAK;AAC9D,iBAAW,IAAI,UAAU,QAAQ,KAAK,OAAM,oBAAI,KAAK,GAAE,YAAY,CAAC;AACpE,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["/**\n * lib/idempotency-store.ts \u2014 \u5E42\u7B49\u64CD\u4F5C\u5E95\u5C42\u6301\u4E45\u5316\u5B58\u50A8\n *\n * \u5E95\u5C42\u8868\uFF1A`sfmc__idempotent`\n * \u4E3B\u952E\u590D\u5408\u7EF4\u5EA6\uFF1A`module_id` + `action` + `key`\n *\n * \u63A2\u9488\u673A\u5236\uFF1A\n * - `probe(moduleId, action, key)`\uFF1A\u63A2\u6D4B\u662F\u5426\u5DF2\u6267\u884C\u8FC7\uFF1B\u547D\u4E2D\u5219\u8FD4\u56DE `{ replayed: true, cached }`\uFF0C\u672A\u547D\u4E2D\u8FD4\u56DE `{ replayed: false }`\n * - `commit(moduleId, action, key, value?)`\uFF1A\u5199\u5165\u6210\u529F\u6267\u884C\u8BB0\u5F55\u4E0E\u7F13\u5B58\u7ED3\u679C\n *\n * \u4E8B\u52A1\u4E0E\u6301\u4E45\u5316\u8FB9\u754C\uFF1A\n * \u5E42\u7B49\u63D0\u4EA4\u72EC\u7ACB\u4E8E\u5355\u6B21\u4E1A\u52A1\u4E8B\u52A1\uFF0C\u786E\u4FDD\u8DE8\u4E8B\u52A1\u6301\u4E45\u5316\u53BB\u91CD\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { isValidIdempotencyKey } from \"./idempotency.js\";\n\n/** \u5E42\u7B49\u5B58\u50A8\u5668\u63A5\u53E3\u3002 */\nexport interface IdempotencyStore {\n /** \u63A2\u6D4B\u6307\u5B9A\u7684\u52A8\u4F5C\u4E0E\u5E42\u7B49\u952E\u662F\u5426\u5DF2\u7ECF\u6267\u884C\u8FC7\u3002 */\n probe(moduleId: string, action: string, key: string): Promise<{ replayed: boolean; cached?: unknown }>;\n /** \u63D0\u4EA4\u5E76\u6301\u4E45\u5316\u5DF2\u6267\u884C\u7684\u5E42\u7B49\u52A8\u4F5C\u4E0E\u7ED3\u679C\u6570\u636E\u3002 */\n commit(moduleId: string, action: string, key: string, value?: unknown): Promise<{ ok: boolean }>;\n}\n\n/**\n * \u521B\u5EFA\u57FA\u4E8E SQLite \u7684\u5E42\u7B49\u5B58\u50A8\u64CD\u4F5C\u5B9E\u4F8B\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u3002\n * @returns \u5E42\u7B49\u5B58\u50A8\u5668\u5BF9\u8C61\u3002\n */\nexport function createIdempotencyStore(db: DatabaseSync): IdempotencyStore {\n\n const probeStmt = db.prepare(\n \"SELECT value FROM sfmc__idempotent WHERE module_id=? AND action=? AND key=?\"\n );\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO sfmc__idempotent (module_id, action, key, value, created_at)\n VALUES (?, ?, ?, ?, ?)`\n );\n\n return {\n async probe(moduleId, action, key) {\n if (!isValidIdempotencyKey(key)) throw new Error(\"invalid idempotency key\");\n const row = probeStmt.get(moduleId, action, key) as { value: string | null } | undefined;\n if (!row) return { replayed: false };\n let cached: unknown = null;\n if (row.value) {\n try {\n cached = JSON.parse(row.value);\n } catch {\n cached = row.value;\n }\n }\n return { replayed: true, cached };\n },\n\n async commit(moduleId, action, key, value?) {\n if (!isValidIdempotencyKey(key)) throw new Error(\"invalid idempotency key\");\n const json = value === undefined ? null : JSON.stringify(value);\n insertStmt.run(moduleId, action, key, json, new Date().toISOString());\n return { ok: true };\n },\n };\n}\n"],
5
+ "mappings": "AAeA,SAAS,6BAA6B;AAgB/B,SAAS,uBAAuB,IAAoC;AAEzE,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA,EACF;AACA,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA;AAAA,EAEF;AAEA,SAAO;AAAA,IACL,MAAM,MAAM,UAAU,QAAQ,KAAK;AACjC,UAAI,CAAC,sBAAsB,GAAG,EAAG,OAAM,IAAI,MAAM,yBAAyB;AAC1E,YAAM,MAAM,UAAU,IAAI,UAAU,QAAQ,GAAG;AAC/C,UAAI,CAAC,IAAK,QAAO,EAAE,UAAU,MAAM;AACnC,UAAI,SAAkB;AACtB,UAAI,IAAI,OAAO;AACb,YAAI;AACF,mBAAS,KAAK,MAAM,IAAI,KAAK;AAAA,QAC/B,QAAQ;AACN,mBAAS,IAAI;AAAA,QACf;AAAA,MACF;AACA,aAAO,EAAE,UAAU,MAAM,OAAO;AAAA,IAClC;AAAA,IAEA,MAAM,OAAO,UAAU,QAAQ,KAAK,OAAQ;AAC1C,UAAI,CAAC,sBAAsB,GAAG,EAAG,OAAM,IAAI,MAAM,yBAAyB;AAC1E,YAAM,OAAO,UAAU,SAAY,OAAO,KAAK,UAAU,KAAK;AAC9D,iBAAW,IAAI,UAAU,QAAQ,KAAK,OAAM,oBAAI,KAAK,GAAE,YAAY,CAAC;AACpE,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,8 @@
1
1
  /**
2
- * 验证幂等键格式
2
+ * 校验幂等键格式是否合法(仅允许 1~128 位的字母、数字及 `_.:-` 字符)。
3
3
  *
4
- * @export
5
- * @param {string} key
6
- * @return {*} {(boolean | string)}
4
+ * @param key 待校验的幂等键。
5
+ * @returns 格式合法返回 `true`,否则返回 `false`。
7
6
  */
8
7
  export declare function isValidIdempotencyKey(key: string): boolean;
9
8
  //# sourceMappingURL=idempotency.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/lib/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAI1D"}
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/lib/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAK1D"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/idempotency.ts"],
4
- "sourcesContent": ["/**\n * \u9A8C\u8BC1\u5E42\u7B49\u952E\u683C\u5F0F\n *\n * @export\n * @param {string} key\n * @return {*} {(boolean | string)}\n */\nexport function isValidIdempotencyKey(key: string): boolean {\n if (!key || typeof key !== \"string\") return false;\n const trimmed = key.trim();\n return trimmed.length > 0 && /^[A-Za-z0-9_.:-]{1,128}$/.test(trimmed);\n}\n"],
5
- "mappings": "AAOO,SAAS,sBAAsB,KAAsB;AAC1D,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,UAAU,IAAI,KAAK;AACzB,SAAO,QAAQ,SAAS,KAAK,2BAA2B,KAAK,OAAO;AACtE;",
4
+ "sourcesContent": ["/**\n * \u6821\u9A8C\u5E42\u7B49\u952E\u683C\u5F0F\u662F\u5426\u5408\u6CD5\uFF08\u4EC5\u5141\u8BB8 1~128 \u4F4D\u7684\u5B57\u6BCD\u3001\u6570\u5B57\u53CA `_.:-` \u5B57\u7B26\uFF09\u3002\n *\n * @param key \u5F85\u6821\u9A8C\u7684\u5E42\u7B49\u952E\u3002\n * @returns \u683C\u5F0F\u5408\u6CD5\u8FD4\u56DE `true`\uFF0C\u5426\u5219\u8FD4\u56DE `false`\u3002\n */\nexport function isValidIdempotencyKey(key: string): boolean {\n\n if (!key || typeof key !== \"string\") return false;\n const trimmed = key.trim();\n return trimmed.length > 0 && /^[A-Za-z0-9_.:-]{1,128}$/.test(trimmed);\n}\n"],
5
+ "mappings": "AAMO,SAAS,sBAAsB,KAAsB;AAE1D,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,UAAU,IAAI,KAAK;AACzB,SAAO,QAAQ,SAAS,KAAK,2BAA2B,KAAK,OAAO;AACtE;",
6
6
  "names": []
7
7
  }