@sfmc-bds/db-server 0.1.0 → 0.2.0-beta.0

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 (112) hide show
  1. package/README.md +1 -1
  2. package/dist/db-tables.js +16 -17
  3. package/dist/db-tables.js.map +7 -1
  4. package/dist/domain/bridge.js +54 -77
  5. package/dist/domain/bridge.js.map +7 -1
  6. package/dist/domain/economy.d.ts +83 -2
  7. package/dist/domain/economy.d.ts.map +1 -1
  8. package/dist/domain/economy.js +292 -281
  9. package/dist/domain/economy.js.map +7 -1
  10. package/dist/domain/schema.js +480 -426
  11. package/dist/domain/schema.js.map +7 -1
  12. package/dist/domain/transaction.js +32 -80
  13. package/dist/domain/transaction.js.map +7 -1
  14. package/dist/env.d.ts.map +1 -1
  15. package/dist/env.js +85 -84
  16. package/dist/env.js.map +7 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +218 -240
  20. package/dist/index.js.map +7 -1
  21. package/dist/lib/http.js +30 -39
  22. package/dist/lib/http.js.map +7 -1
  23. package/dist/lib/idempotency-store.js +34 -47
  24. package/dist/lib/idempotency-store.js.map +7 -1
  25. package/dist/lib/idempotency.js +8 -13
  26. package/dist/lib/idempotency.js.map +7 -1
  27. package/dist/lib/identifiers.js +18 -14
  28. package/dist/lib/identifiers.js.map +7 -1
  29. package/dist/lib/log.d.ts +3 -6
  30. package/dist/lib/log.d.ts.map +1 -1
  31. package/dist/lib/log.js +13 -14
  32. package/dist/lib/log.js.map +7 -1
  33. package/dist/lib/module-state.d.ts.map +1 -1
  34. package/dist/lib/module-state.js +32 -21
  35. package/dist/lib/module-state.js.map +7 -1
  36. package/dist/lib/order-by.d.ts +17 -0
  37. package/dist/lib/order-by.d.ts.map +1 -0
  38. package/dist/lib/order-by.js +24 -0
  39. package/dist/lib/order-by.js.map +7 -0
  40. package/dist/lib/runtime.js +16 -31
  41. package/dist/lib/runtime.js.map +7 -1
  42. package/dist/lib/sql-helpers.js +9 -27
  43. package/dist/lib/sql-helpers.js.map +7 -1
  44. package/dist/lib/sqlite.js +40 -52
  45. package/dist/lib/sqlite.js.map +7 -1
  46. package/dist/manifest-loader.d.ts.map +1 -1
  47. package/dist/manifest-loader.js +154 -195
  48. package/dist/manifest-loader.js.map +7 -1
  49. package/dist/manifest.d.ts +3 -7
  50. package/dist/manifest.d.ts.map +1 -1
  51. package/dist/manifest.js +76 -115
  52. package/dist/manifest.js.map +7 -1
  53. package/dist/module-auth.d.ts +17 -6
  54. package/dist/module-auth.d.ts.map +1 -1
  55. package/dist/module-auth.js +93 -107
  56. package/dist/module-auth.js.map +7 -1
  57. package/dist/module-runtime-sync.d.ts +37 -0
  58. package/dist/module-runtime-sync.d.ts.map +1 -0
  59. package/dist/module-runtime-sync.js +54 -0
  60. package/dist/module-runtime-sync.js.map +7 -0
  61. package/dist/permission-gate.d.ts.map +1 -1
  62. package/dist/permission-gate.js +55 -86
  63. package/dist/permission-gate.js.map +7 -1
  64. package/dist/routes/_shared.d.ts +7 -2
  65. package/dist/routes/_shared.d.ts.map +1 -1
  66. package/dist/routes/_shared.js +16 -25
  67. package/dist/routes/_shared.js.map +7 -1
  68. package/dist/routes/config.d.ts +11 -1
  69. package/dist/routes/config.d.ts.map +1 -1
  70. package/dist/routes/config.js +167 -213
  71. package/dist/routes/config.js.map +7 -1
  72. package/dist/routes/db-routes.d.ts +6 -10
  73. package/dist/routes/db-routes.d.ts.map +1 -1
  74. package/dist/routes/db-routes.js +198 -172
  75. package/dist/routes/db-routes.js.map +7 -1
  76. package/dist/routes/health.js +15 -20
  77. package/dist/routes/health.js.map +7 -1
  78. package/dist/routes/messages.d.ts.map +1 -1
  79. package/dist/routes/messages.js +56 -63
  80. package/dist/routes/messages.js.map +7 -1
  81. package/dist/routes/module-config-routes.d.ts.map +1 -1
  82. package/dist/routes/module-config-routes.js +92 -111
  83. package/dist/routes/module-config-routes.js.map +7 -1
  84. package/dist/routes/modules.d.ts.map +1 -1
  85. package/dist/routes/modules.js +82 -88
  86. package/dist/routes/modules.js.map +7 -1
  87. package/dist/routes/service-routes.d.ts.map +1 -1
  88. package/dist/routes/service-routes.js +54 -61
  89. package/dist/routes/service-routes.js.map +7 -1
  90. package/dist/runtime.test.js +248 -49
  91. package/dist/runtime.test.js.map +7 -1
  92. package/dist/schema-registry.d.ts.map +1 -1
  93. package/dist/schema-registry.js +119 -160
  94. package/dist/schema-registry.js.map +7 -1
  95. package/dist/server.js +69 -72
  96. package/dist/server.js.map +7 -1
  97. package/dist/service-registry.d.ts.map +1 -1
  98. package/dist/service-registry.js +60 -67
  99. package/dist/service-registry.js.map +7 -1
  100. package/dist/services/builtin-handlers.d.ts +4 -0
  101. package/dist/services/builtin-handlers.d.ts.map +1 -1
  102. package/dist/services/builtin-handlers.js +34 -20
  103. package/dist/services/builtin-handlers.js.map +7 -1
  104. package/dist/services/economy-handlers.js +143 -132
  105. package/dist/services/economy-handlers.js.map +7 -1
  106. package/dist/tx-runner.d.ts +35 -11
  107. package/dist/tx-runner.d.ts.map +1 -1
  108. package/dist/tx-runner.js +293 -250
  109. package/dist/tx-runner.js.map +7 -1
  110. package/dist/where.js +71 -94
  111. package/dist/where.js.map +7 -1
  112. package/package.json +4 -4
package/dist/manifest.js CHANGED
@@ -1,128 +1,89 @@
1
- /**
2
- * manifest.ts — runtime SAPI module manifest reader.
3
- *
4
- * At db-server startup, scan `modules/packages/<id>/sapi/manifest.json` for
5
- * every installed module and aggregate them into a single in-memory manifest.
6
- * No pre-built `modules/_manifests/module-manifests.json` is required; the
7
- * per-module files are the single source of truth (the SEA reads them at
8
- * runtime too).
9
- *
10
- * Behavior:
11
- * - Missing file: skip that module (WARN).
12
- * - Corrupt JSON: skip + WARN.
13
- * - schemaVersion > supported: WARN, continue best-effort.
14
- * - schemaVersion < supported: refuse to load that module.
15
- *
16
- * Reconcile: every route declared in a module's manifest is checked against
17
- * the prefix table; mismatches WARN but do not block startup.
18
- */
19
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
20
2
  import { fileURLToPath } from "node:url";
21
3
  import { dirname, resolve } from "node:path";
22
4
  const SUPPORTED_SCHEMA = 1;
23
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
- /** Resolve where db-server should look for `modules/packages/`.
25
- *
26
- * 优先级:
27
- * 1. `process.env.SFMC_PACKAGES_DIR` —— sfmc/SEA 的 spawnService 在启动 db 时注入
28
- * (SEA 模式下 exe 目录, npm 模式下 process.cwd())
29
- * 2. `__dirname` 上溯两级到仓库根 —— 仅当 db-server 独立启动时兜底(CLI 调试)
30
- *
31
- * SEA 模式必须走 env 路径:SEA CJS bundle 里 `__dirname` 是虚拟路径,
32
- * 不能用 `import.meta.url` 解析;靠 SFMC_PACKAGES_DIR 才是稳定的。
33
- */
34
- export function defaultPackagesDir() {
35
- return process.env.SFMC_PACKAGES_DIR || resolve(__dirname, "..", "..", "modules", "packages");
6
+ function defaultPackagesDir() {
7
+ return process.env.SFMC_PACKAGES_DIR || resolve(__dirname, "..", "..", "modules", "packages");
36
8
  }
37
- /**
38
- * Read every `modules/packages/<id>/sapi/manifest.json` and aggregate.
39
- * Modules without a manifest are silently skipped (the SAPI side will fall
40
- * back to a default-empty surface); modules with a corrupt or too-new
41
- * manifest produce warnings.
42
- */
43
- export function loadManifest(packagesDir = defaultPackagesDir()) {
44
- const modules = {};
45
- if (!existsSync(packagesDir)) {
46
- console.warn(`[manifest] ${packagesDir} does not exist; db-server will run with zero modules`);
47
- return { schemaVersion: SUPPORTED_SCHEMA, generatedAt: new Date().toISOString(), modules };
9
+ function loadManifest(packagesDir = defaultPackagesDir()) {
10
+ const modules = {};
11
+ if (!existsSync(packagesDir)) {
12
+ console.warn(`[manifest] ${packagesDir} does not exist; db-server will run with zero modules`);
13
+ return { schemaVersion: SUPPORTED_SCHEMA, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), modules };
14
+ }
15
+ const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
16
+ for (const id of ids) {
17
+ const manifestPath = resolve(packagesDir, id, "sapi", "manifest.json");
18
+ if (!existsSync(manifestPath)) continue;
19
+ let raw;
20
+ try {
21
+ raw = readFileSync(manifestPath, "utf8");
22
+ } catch (err) {
23
+ console.warn(`[manifest] ${id}: cannot read ${manifestPath}: ${err.message}`);
24
+ continue;
48
25
  }
49
- const ids = readdirSync(packagesDir, { withFileTypes: true })
50
- .filter((e) => e.isDirectory())
51
- .map((e) => e.name)
52
- .sort();
53
- for (const id of ids) {
54
- const manifestPath = resolve(packagesDir, id, "sapi", "manifest.json");
55
- if (!existsSync(manifestPath))
56
- continue;
57
- let raw;
58
- try {
59
- raw = readFileSync(manifestPath, "utf8");
60
- }
61
- catch (err) {
62
- console.warn(`[manifest] ${id}: cannot read ${manifestPath}: ${err.message}`);
63
- continue;
64
- }
65
- let parsed;
66
- try {
67
- parsed = JSON.parse(raw);
68
- }
69
- catch (err) {
70
- console.warn(`[manifest] ${id}: ${manifestPath} is not valid JSON: ${err.message}`);
71
- continue;
72
- }
73
- if (typeof parsed.schemaVersion === "number" && parsed.schemaVersion > SUPPORTED_SCHEMA) {
74
- console.warn(`[manifest] ${id}: schemaVersion=${parsed.schemaVersion} > supported ${SUPPORTED_SCHEMA}; parsing best-effort`);
75
- }
76
- modules[id] = {
77
- name: parsed.name,
78
- type: parsed.type,
79
- configKey: parsed.configKey,
80
- requires: Array.isArray(parsed.requires) ? parsed.requires : [],
81
- handlers: Array.isArray(parsed.handlers) ? parsed.handlers : [],
82
- routes: Array.isArray(parsed.routes) ? parsed.routes : [],
83
- migrations: Array.isArray(parsed.migrations) ? parsed.migrations : [],
84
- notes: parsed.notes,
85
- };
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(raw);
29
+ } catch (err) {
30
+ console.warn(`[manifest] ${id}: ${manifestPath} is not valid JSON: ${err.message}`);
31
+ continue;
86
32
  }
87
- return {
88
- schemaVersion: SUPPORTED_SCHEMA,
89
- generatedAt: new Date().toISOString(),
90
- modules,
91
- };
92
- }
93
- export function summarize(m) {
94
- let routeCount = 0;
95
- let handlerCount = 0;
96
- let migrationCount = 0;
97
- for (const entry of Object.values(m.modules)) {
98
- routeCount += entry.routes.length;
99
- handlerCount += entry.handlers?.length ?? 0;
100
- migrationCount += entry.migrations.length;
33
+ if (typeof parsed.schemaVersion === "number" && parsed.schemaVersion > SUPPORTED_SCHEMA) {
34
+ console.warn(`[manifest] ${id}: schemaVersion=${parsed.schemaVersion} > supported ${SUPPORTED_SCHEMA}; parsing best-effort`);
101
35
  }
102
- return {
103
- moduleCount: Object.keys(m.modules).length,
104
- routeCount,
105
- handlerCount,
106
- migrationCount,
107
- moduleIds: Object.keys(m.modules).sort(),
36
+ modules[id] = {
37
+ name: parsed.name,
38
+ type: parsed.type,
39
+ configKey: parsed.configKey,
40
+ requires: Array.isArray(parsed.requires) ? parsed.requires : [],
41
+ handlers: Array.isArray(parsed.handlers) ? parsed.handlers : [],
42
+ routes: Array.isArray(parsed.routes) ? parsed.routes : [],
43
+ migrations: Array.isArray(parsed.migrations) ? parsed.migrations : [],
44
+ notes: parsed.notes
108
45
  };
46
+ }
47
+ return {
48
+ schemaVersion: SUPPORTED_SCHEMA,
49
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50
+ modules
51
+ };
52
+ }
53
+ function summarize(m) {
54
+ let routeCount = 0;
55
+ let handlerCount = 0;
56
+ let migrationCount = 0;
57
+ for (const entry of Object.values(m.modules)) {
58
+ routeCount += entry.routes.length;
59
+ handlerCount += entry.handlers?.length ?? 0;
60
+ migrationCount += entry.migrations.length;
61
+ }
62
+ return {
63
+ moduleCount: Object.keys(m.modules).length,
64
+ routeCount,
65
+ handlerCount,
66
+ migrationCount,
67
+ moduleIds: Object.keys(m.modules).sort()
68
+ };
109
69
  }
110
- /**
111
- * Reconcile manifest routes against the routes the db-server currently serves.
112
- * Returns a list of warnings for routes declared in the manifest that no route
113
- * file covers. Routes already covered by `routes/*.ts` files are not flagged.
114
- */
115
- export function reconcile(m, knownRoutePrefixes) {
116
- const warnings = [];
117
- for (const [moduleId, entry] of Object.entries(m.modules)) {
118
- for (const route of entry.routes) {
119
- const prefix = route.path.split("/").slice(0, 4).join("/");
120
- const matched = knownRoutePrefixes.some((p) => prefix.startsWith(p.split("/").slice(0, 4).join("/")));
121
- if (!matched) {
122
- warnings.push(`${moduleId}: route ${route.method} ${route.path} is not covered by db-server`);
123
- }
124
- }
70
+ function reconcile(m, knownRoutePrefixes) {
71
+ const warnings = [];
72
+ for (const [moduleId, entry] of Object.entries(m.modules)) {
73
+ for (const route of entry.routes) {
74
+ const prefix = route.path.split("/").slice(0, 4).join("/");
75
+ const matched = knownRoutePrefixes.some((p) => prefix.startsWith(p.split("/").slice(0, 4).join("/")));
76
+ if (!matched) {
77
+ warnings.push(`${moduleId}: route ${route.method} ${route.path} is not covered by db-server`);
78
+ }
125
79
  }
126
- return warnings;
80
+ }
81
+ return warnings;
127
82
  }
128
- //# sourceMappingURL=manifest.js.map
83
+ export {
84
+ defaultPackagesDir,
85
+ loadManifest,
86
+ reconcile,
87
+ summarize
88
+ };
89
+ //# sourceMappingURL=manifest.js.map
@@ -1 +1,7 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/manifest.ts"],
4
+ "sourcesContent": ["/**\n * manifest.ts \u2014 runtime SAPI module manifest reader.\n *\n * At db-server startup, scan `modules/packages/<id>/sapi/manifest.json` for\n * every installed module and aggregate them into a single in-memory manifest.\n * No pre-built `modules/_manifests/module-manifests.json` is required; the\n * per-module files are the single source of truth.\n *\n * Behavior:\n * - Missing file: skip that module (WARN).\n * - Corrupt JSON: skip + WARN.\n * - schemaVersion > supported: WARN, continue best-effort.\n * - schemaVersion < supported: refuse to load that module.\n *\n * Reconcile: every route declared in a module's manifest is checked against\n * the prefix table; mismatches WARN but do not block startup.\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\n\nexport interface ModuleManifestRoute {\n method: string;\n path: string;\n handler: string;\n}\n\nexport interface ModuleManifestMigration {\n name: string;\n version: number;\n}\n\nexport interface ModuleManifestEntry {\n name?: string | undefined;\n type?: string | undefined;\n configKey?: string | undefined;\n requires?: string[] | undefined;\n handlers?: string[] | undefined;\n routes: ModuleManifestRoute[];\n migrations: ModuleManifestMigration[];\n notes?: string | undefined;\n}\n\nexport interface ModuleManifest {\n schemaVersion: number;\n generatedAt: string;\n modules: Record<string, ModuleManifestEntry>;\n}\n\nconst SUPPORTED_SCHEMA = 1;\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport type PackagesDirResolver = () => string;\n\n/** Resolve where db-server should look for `modules/packages/`.\n *\n * \u4F18\u5148\u7EA7:\n * 1. `process.env.SFMC_PACKAGES_DIR` \u2014\u2014 sfmc spawnService \u542F\u52A8 db \u65F6\u6CE8\u5165\n * (\u901A\u5E38\u4E0E SFMC_ROOT \u5BF9\u9F50)\n * 2. `__dirname` \u4E0A\u6EAF\u4E24\u7EA7\u5230\u4ED3\u5E93\u6839 \u2014\u2014 \u4EC5\u5F53 db-server \u72EC\u7ACB\u542F\u52A8\u65F6\u515C\u5E95(CLI \u8C03\u8BD5)\n */\nexport function defaultPackagesDir(): string {\n return process.env.SFMC_PACKAGES_DIR || resolve(__dirname, \"..\", \"..\", \"modules\", \"packages\");\n}\n\n/**\n * Read every `modules/packages/<id>/sapi/manifest.json` and aggregate.\n * Modules without a manifest are silently skipped (the SAPI side will fall\n * back to a default-empty surface); modules with a corrupt or too-new\n * manifest produce warnings.\n */\nexport function loadManifest(packagesDir: string = defaultPackagesDir()): ModuleManifest {\n const modules: Record<string, ModuleManifestEntry> = {};\n if (!existsSync(packagesDir)) {\n console.warn(`[manifest] ${packagesDir} does not exist; db-server will run with zero modules`);\n return { schemaVersion: SUPPORTED_SCHEMA, generatedAt: new Date().toISOString(), modules };\n }\n const ids = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n for (const id of ids) {\n const manifestPath = resolve(packagesDir, id, \"sapi\", \"manifest.json\");\n if (!existsSync(manifestPath)) continue;\n let raw: string;\n try {\n raw = readFileSync(manifestPath, \"utf8\");\n } catch (err) {\n console.warn(`[manifest] ${id}: cannot read ${manifestPath}: ${(err as Error).message}`);\n continue;\n }\n let parsed: Partial<ModuleManifestEntry> & { schemaVersion?: number };\n try {\n parsed = JSON.parse(raw) as typeof parsed;\n } catch (err) {\n console.warn(`[manifest] ${id}: ${manifestPath} is not valid JSON: ${(err as Error).message}`);\n continue;\n }\n if (typeof parsed.schemaVersion === \"number\" && parsed.schemaVersion > SUPPORTED_SCHEMA) {\n console.warn(`[manifest] ${id}: schemaVersion=${parsed.schemaVersion} > supported ${SUPPORTED_SCHEMA}; parsing best-effort`);\n }\n modules[id] = {\n name: parsed.name,\n type: parsed.type,\n configKey: parsed.configKey,\n requires: Array.isArray(parsed.requires) ? parsed.requires : [],\n handlers: Array.isArray(parsed.handlers) ? parsed.handlers : [],\n routes: Array.isArray(parsed.routes) ? parsed.routes : [],\n migrations: Array.isArray(parsed.migrations) ? parsed.migrations : [],\n notes: parsed.notes,\n };\n }\n return {\n schemaVersion: SUPPORTED_SCHEMA,\n generatedAt: new Date().toISOString(),\n modules,\n };\n}\n\nexport interface ManifestSummary {\n moduleCount: number;\n routeCount: number;\n handlerCount: number;\n migrationCount: number;\n moduleIds: string[];\n}\n\nexport function summarize(m: ModuleManifest): ManifestSummary {\n let routeCount = 0;\n let handlerCount = 0;\n let migrationCount = 0;\n for (const entry of Object.values(m.modules)) {\n routeCount += entry.routes.length;\n handlerCount += entry.handlers?.length ?? 0;\n migrationCount += entry.migrations.length;\n }\n return {\n moduleCount: Object.keys(m.modules).length,\n routeCount,\n handlerCount,\n migrationCount,\n moduleIds: Object.keys(m.modules).sort(),\n };\n}\n\n/**\n * Reconcile manifest routes against the routes the db-server currently serves.\n * Returns a list of warnings for routes declared in the manifest that no route\n * file covers. Routes already covered by `routes/*.ts` files are not flagged.\n */\nexport function reconcile(m: ModuleManifest, knownRoutePrefixes: string[]): string[] {\n const warnings: string[] = [];\n for (const [moduleId, entry] of Object.entries(m.modules)) {\n for (const route of entry.routes) {\n const prefix = route.path.split(\"/\").slice(0, 4).join(\"/\");\n const matched = knownRoutePrefixes.some((p) => prefix.startsWith(p.split(\"/\").slice(0, 4).join(\"/\")));\n if (!matched) {\n warnings.push(`${moduleId}: route ${route.method} ${route.path} is not covered by db-server`);\n }\n }\n }\n return warnings;\n}"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -4,15 +4,15 @@
4
4
  * 两套并存:
5
5
  * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc 管控 / 平台内部用
6
6
  * - `moduleTokens[moduleId]`:db-server 给每个 enabled 模块在启动时派生,
7
- * 写到 data/module-tokens.json,SAPI host-bootstrap 启动时读,按
8
- * `setDbModuleContext(moduleId, token)` 注入。
7
+ * 写到「与数据库同目录」的 module-tokens.json(跟 db_config.dbDir 走,勿写死 ROOT/data),
8
+ * SAPI loopback API 注入 setDbModuleContext。
9
9
  *
10
10
  * 派生公式:
11
11
  * module_token = HMAC-SHA256(auth_secret, "sfmc-module:" + moduleId).hex
12
12
  *
13
13
  * auth_secret 优先级:
14
14
  * 1. env.AUTH_TOKEN
15
- * 2. 启动时随机生成(无 AUTH_TOKEN 时),写到 data/module-tokens.json,
15
+ * 2. 启动时随机生成(无 AUTH_TOKEN 时),写到 module-tokens.json,
16
16
  * 只警告一次(每次重启模块 token 都变,SAPI host 必须重读文件)
17
17
  *
18
18
  * 校验:
@@ -27,20 +27,31 @@ export interface ModuleAuthMap {
27
27
  /** 用于派生 token 的 secret (env.AUTH_TOKEN 或本次启动随机生成) */
28
28
  secret: string;
29
29
  }
30
+ /** token 文件与 SQLite 同目录(由 dbDir 决定,避免向导选目录前就创建 ROOT/data) */
31
+ export declare function moduleTokensFile(dbPath: string): string;
30
32
  export declare function deriveToken(moduleId: string, secret: string): string;
31
33
  /**
32
34
  * 启动时调用一次:
33
35
  * - 把 env.AUTH_TOKEN 当 secret
34
36
  * - 给每个 enabled moduleId 派生 token
35
- * - 写 data/module-tokens.json (SAPI host-bootstrap 读)
37
+ * - 写 module-tokens.json(与 DB 同目录)
36
38
  * - 控制台打印摘要(env var SFMC_LOG_MODULE_TOKEN=1 时打完整)
37
39
  */
38
40
  export declare function buildModuleAuth(opts: {
39
- projectRoot: string;
41
+ dbPath: string;
40
42
  envAuthToken: string;
41
43
  enabledModuleIds: string[];
42
44
  }): ModuleAuthMap;
43
- export declare function loadModuleAuth(projectRoot: string): ModuleAuthMap | null;
45
+ export declare function loadModuleAuth(dbPath: string): ModuleAuthMap | null;
46
+ /** 把当前 auth map 写回 module-tokens.json(热更新 token 时复用) */
47
+ export declare function persistModuleAuth(dbPath: string, auth: ModuleAuthMap, envAuthToken: string): void;
48
+ /**
49
+ * 确保 moduleId 在 auth.tokens 中有派生值(复用现有 secret,勿重建随机 secret)。
50
+ * @returns 是否新写入了 token
51
+ */
52
+ export declare function ensureModuleToken(auth: ModuleAuthMap, moduleId: string): boolean;
53
+ /** 禁用时撤销内存中的 token(磁盘由 persistModuleAuth 同步) */
54
+ export declare function revokeModuleToken(auth: ModuleAuthMap, moduleId: string): boolean;
44
55
  /**
45
56
  * 校验请求方身份:
46
57
  * - Bearer 头 / X-Module-Token 二选一
@@ -1 +1 @@
1
- {"version":3,"file":"module-auth.d.ts","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,GAAG,aAAa,CAsChB;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAIxE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC/B,GAAG,MAAM,GAAG,IAAI,CAuBhB"}
1
+ {"version":3,"file":"module-auth.d.ts","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,GAAG,aAAa,CAqChB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAInE;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAQjG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC/B,GAAG,MAAM,GAAG,IAAI,CAuBhB"}
@@ -1,118 +1,104 @@
1
- /**
2
- * module-auth.ts — 模块身份的 HMAC token 派生与校验
3
- *
4
- * 两套并存:
5
- * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc 管控 / 平台内部用
6
- * - `moduleTokens[moduleId]`:db-server 给每个 enabled 模块在启动时派生,
7
- * 写到 data/module-tokens.json,SAPI host-bootstrap 启动时读,按
8
- * `setDbModuleContext(moduleId, token)` 注入。
9
- *
10
- * 派生公式:
11
- * module_token = HMAC-SHA256(auth_secret, "sfmc-module:" + moduleId).hex
12
- *
13
- * auth_secret 优先级:
14
- * 1. env.AUTH_TOKEN
15
- * 2. 启动时随机生成(无 AUTH_TOKEN 时),写到 data/module-tokens.json,
16
- * 只警告一次(每次重启模块 token 都变,SAPI host 必须重读文件)
17
- *
18
- * 校验:
19
- * - HTTP `Authorization: Bearer <module_token>` 头
20
- * - Query `?moduleId=<id>`
21
- * - 两都匹配 = 调用方身份
22
- * - 校验调用方 moduleId 在 enabled 列表(否则 403)
23
- */
24
1
  import { readJson, writeJson } from "@sfmc-bds/sdk/node/config";
25
2
  import { createHmac, randomBytes } from "node:crypto";
26
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
27
4
  import { log } from "./lib/log.js";
28
5
  const IDENT = /^[A-Za-z0-9_\-]+$/;
29
6
  function safeId(id) {
30
- if (!IDENT.test(id))
31
- throw new Error(`[auth] invalid module id: ${id}`);
32
- return id;
7
+ if (!IDENT.test(id)) throw new Error(`[auth] invalid module id: ${id}`);
8
+ return id;
33
9
  }
34
- export function deriveToken(moduleId, secret) {
35
- return createHmac("sha256", secret).update(`sfmc-module:${safeId(moduleId)}`).digest("hex");
10
+ function moduleTokensFile(dbPath) {
11
+ return join(dirname(dbPath), "module-tokens.json");
36
12
  }
37
- /**
38
- * 启动时调用一次:
39
- * - 把 env.AUTH_TOKEN 当 secret
40
- * - 给每个 enabled moduleId 派生 token
41
- * - data/module-tokens.json (SAPI host-bootstrap 读)
42
- * - 控制台打印摘要(env var SFMC_LOG_MODULE_TOKEN=1 时打完整)
43
- */
44
- export function buildModuleAuth(opts) {
45
- const secret = opts.envAuthToken || randomBytes(32).toString("hex");
46
- const tokens = {};
47
- for (const id of opts.enabledModuleIds) {
48
- tokens[id] = deriveToken(id, secret);
49
- }
50
- const outDir = join(opts.projectRoot, "data");
51
- const outFile = join(outDir, "module-tokens.json");
52
- writeJson(outFile, {
53
- tokens,
54
- secret,
55
- generatedAt: new Date().toISOString(),
56
- secretGenerated: !opts.envAuthToken,
57
- });
58
- log.success(`[auth] 已为 ${opts.enabledModuleIds.length} 个模块派生 HMAC token,写入 ${outFile}`);
59
- if (!opts.envAuthToken) {
60
- log.warn("[auth] 未配置 env.AUTH_TOKEN(冲突 config http_auth),本次启动使用了随机 secret;"
61
- + " 重启后 token 会变,host-bootstrap 必须每次重新读 module-tokens.json");
62
- }
63
- if (process.env.SFMC_LOG_MODULE_TOKEN === "1") {
64
- log.info("[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):");
65
- for (const [id, t] of Object.entries(tokens)) {
66
- console.log(` ${id} = ${t}`);
67
- }
68
- }
69
- else {
70
- const lines = Object.entries(tokens)
71
- .map(([id, t]) => ` ${id} = ${t.slice(0, 6)}…${t.slice(-4)}`)
72
- .join("\n");
73
- log.info(`[auth] token 摘要:\n${lines}`);
13
+ function deriveToken(moduleId, secret) {
14
+ return createHmac("sha256", secret).update(`sfmc-module:${safeId(moduleId)}`).digest("hex");
15
+ }
16
+ function buildModuleAuth(opts) {
17
+ const secret = opts.envAuthToken || randomBytes(32).toString("hex");
18
+ const tokens = {};
19
+ for (const id of opts.enabledModuleIds) {
20
+ tokens[id] = deriveToken(id, secret);
21
+ }
22
+ const outFile = moduleTokensFile(opts.dbPath);
23
+ writeJson(outFile, {
24
+ tokens,
25
+ secret,
26
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
27
+ secretGenerated: !opts.envAuthToken
28
+ });
29
+ log.success(
30
+ `[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`
31
+ );
32
+ if (!opts.envAuthToken) {
33
+ log.warn(
34
+ "[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN(\u51B2\u7A81 config http_auth),\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret; \u91CD\u542F\u540E token \u4F1A\u53D8,host-bootstrap \u5FC5\u987B\u6BCF\u6B21\u91CD\u65B0\u8BFB module-tokens.json"
35
+ );
36
+ }
37
+ if (process.env.SFMC_LOG_MODULE_TOKEN === "1") {
38
+ log.info("[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):");
39
+ for (const [id, t] of Object.entries(tokens)) {
40
+ console.log(` ${id} = ${t}`);
74
41
  }
75
- return { tokens, secret };
42
+ } else {
43
+ const lines = Object.entries(tokens).map(([id, t]) => ` ${id} = ${t.slice(0, 6)}\u2026${t.slice(-4)}`).join("\n");
44
+ log.info(`[auth] token \u6458\u8981:
45
+ ${lines}`);
46
+ }
47
+ return { tokens, secret };
76
48
  }
77
- export function loadModuleAuth(projectRoot) {
78
- const parsed = readJson(join(projectRoot, "data", "module-tokens.json"));
79
- if (!parsed || !parsed.tokens || !parsed.secret)
80
- return null;
81
- return { tokens: parsed.tokens, secret: parsed.secret };
49
+ function loadModuleAuth(dbPath) {
50
+ const parsed = readJson(moduleTokensFile(dbPath));
51
+ if (!parsed || !parsed.tokens || !parsed.secret) return null;
52
+ return { tokens: parsed.tokens, secret: parsed.secret };
82
53
  }
83
- /**
84
- * 校验请求方身份:
85
- * - Bearer 头 / X-Module-Token 二选一
86
- * - ?moduleId=<id> 必须存在
87
- * - 模块必须在 enabledModuleIds 集合中
88
- * - token 必须 = deriveToken(moduleId, secret)
89
- * 返回模块 ID 或 null。
90
- */
91
- export function verifyModuleAuth(opts) {
92
- const moduleId = opts.params.get("moduleId") || "";
93
- if (!moduleId || !opts.enabledModuleIds.has(moduleId))
94
- return null;
95
- let provided = "";
96
- const auth = opts.headers["authorization"];
97
- const token = opts.headers["x-module-token"];
98
- if (typeof auth === "string" && auth.startsWith("Bearer ")) {
99
- provided = auth.slice(7);
100
- }
101
- else if (typeof token === "string") {
102
- provided = token;
103
- }
104
- if (!provided)
105
- return null;
106
- const expected = opts.auth.tokens[moduleId];
107
- if (!expected)
108
- return null;
109
- // constant-time 比较
110
- if (provided.length !== expected.length)
111
- return null;
112
- let diff = 0;
113
- for (let i = 0; i < provided.length; i++) {
114
- diff |= provided.charCodeAt(i) ^ expected.charCodeAt(i);
115
- }
116
- return diff === 0 ? moduleId : null;
54
+ function persistModuleAuth(dbPath, auth, envAuthToken) {
55
+ const outFile = moduleTokensFile(dbPath);
56
+ writeJson(outFile, {
57
+ tokens: auth.tokens,
58
+ secret: auth.secret,
59
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
60
+ secretGenerated: !envAuthToken
61
+ });
62
+ }
63
+ function ensureModuleToken(auth, moduleId) {
64
+ if (auth.tokens[moduleId]) return false;
65
+ auth.tokens[moduleId] = deriveToken(moduleId, auth.secret);
66
+ return true;
67
+ }
68
+ function revokeModuleToken(auth, moduleId) {
69
+ if (!(moduleId in auth.tokens)) return false;
70
+ delete auth.tokens[moduleId];
71
+ return true;
72
+ }
73
+ function verifyModuleAuth(opts) {
74
+ const moduleId = opts.params.get("moduleId") || "";
75
+ if (!moduleId || !opts.enabledModuleIds.has(moduleId)) return null;
76
+ let provided = "";
77
+ const auth = opts.headers["authorization"];
78
+ const token = opts.headers["x-module-token"];
79
+ if (typeof auth === "string" && auth.startsWith("Bearer ")) {
80
+ provided = auth.slice(7);
81
+ } else if (typeof token === "string") {
82
+ provided = token;
83
+ }
84
+ if (!provided) return null;
85
+ const expected = opts.auth.tokens[moduleId];
86
+ if (!expected) return null;
87
+ if (provided.length !== expected.length) return null;
88
+ let diff = 0;
89
+ for (let i = 0; i < provided.length; i++) {
90
+ diff |= provided.charCodeAt(i) ^ expected.charCodeAt(i);
91
+ }
92
+ return diff === 0 ? moduleId : null;
117
93
  }
118
- //# sourceMappingURL=module-auth.js.map
94
+ export {
95
+ buildModuleAuth,
96
+ deriveToken,
97
+ ensureModuleToken,
98
+ loadModuleAuth,
99
+ moduleTokensFile,
100
+ persistModuleAuth,
101
+ revokeModuleToken,
102
+ verifyModuleAuth
103
+ };
104
+ //# sourceMappingURL=module-auth.js.map
@@ -1 +1,7 @@
1
- {"version":3,"file":"module-auth.js","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"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"}
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/module-auth.ts"],
4
+ "sourcesContent": ["/**\n * module-auth.ts \u2014 \u6A21\u5757\u8EAB\u4EFD\u7684 HMAC token \u6D3E\u751F\u4E0E\u6821\u9A8C\n *\n * \u4E24\u5957\u5E76\u5B58:\n * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc \u7BA1\u63A7 / \u5E73\u53F0\u5185\u90E8\u7528\n * - `moduleTokens[moduleId]`:db-server \u7ED9\u6BCF\u4E2A enabled \u6A21\u5757\u5728\u542F\u52A8\u65F6\u6D3E\u751F,\n * \u5199\u5230\u300C\u4E0E\u6570\u636E\u5E93\u540C\u76EE\u5F55\u300D\u7684 module-tokens.json\uFF08\u8DDF db_config.dbDir \u8D70\uFF0C\u52FF\u5199\u6B7B ROOT/data\uFF09,\n * SAPI \u7ECF loopback API \u6CE8\u5165 setDbModuleContext\u3002\n *\n * \u6D3E\u751F\u516C\u5F0F:\n * module_token = HMAC-SHA256(auth_secret, \"sfmc-module:\" + moduleId).hex\n *\n * auth_secret \u4F18\u5148\u7EA7:\n * 1. env.AUTH_TOKEN\n * 2. \u542F\u52A8\u65F6\u968F\u673A\u751F\u6210(\u65E0 AUTH_TOKEN \u65F6),\u5199\u5230 module-tokens.json,\n * \u53EA\u8B66\u544A\u4E00\u6B21(\u6BCF\u6B21\u91CD\u542F\u6A21\u5757 token \u90FD\u53D8,SAPI host \u5FC5\u987B\u91CD\u8BFB\u6587\u4EF6)\n *\n * \u6821\u9A8C:\n * - HTTP `Authorization: Bearer <module_token>` \u5934\n * - Query `?moduleId=<id>`\n * - \u4E24\u90FD\u5339\u914D = \u8C03\u7528\u65B9\u8EAB\u4EFD\n * - \u6821\u9A8C\u8C03\u7528\u65B9 moduleId \u5728 enabled \u5217\u8868(\u5426\u5219 403)\n */\n\nimport { readJson, writeJson, type TokenStore } from \"@sfmc-bds/sdk/node/config\";\nimport { createHmac, randomBytes } from \"node:crypto\";\nimport { dirname, join } from \"node:path\";\nimport { log } from \"./lib/log.js\";\n\nexport interface ModuleAuthMap {\n /** moduleId \u2192 hex token */\n tokens: Record<string, string>;\n /** \u7528\u4E8E\u6D3E\u751F token \u7684 secret (env.AUTH_TOKEN \u6216\u672C\u6B21\u542F\u52A8\u968F\u673A\u751F\u6210) */\n secret: string;\n}\n\nconst IDENT = /^[A-Za-z0-9_\\-]+$/;\n\nfunction safeId(id: string): string {\n if (!IDENT.test(id)) throw new Error(`[auth] invalid module id: ${id}`);\n return id;\n}\n\n/** token \u6587\u4EF6\u4E0E SQLite \u540C\u76EE\u5F55\uFF08\u7531 dbDir \u51B3\u5B9A\uFF0C\u907F\u514D\u5411\u5BFC\u9009\u76EE\u5F55\u524D\u5C31\u521B\u5EFA ROOT/data\uFF09 */\nexport function moduleTokensFile(dbPath: string): string {\n return join(dirname(dbPath), \"module-tokens.json\");\n}\n\nexport function deriveToken(moduleId: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(`sfmc-module:${safeId(moduleId)}`).digest(\"hex\");\n}\n\n/**\n * \u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21:\n * - \u628A env.AUTH_TOKEN \u5F53 secret\n * - \u7ED9\u6BCF\u4E2A enabled moduleId \u6D3E\u751F token\n * - \u5199 module-tokens.json\uFF08\u4E0E DB \u540C\u76EE\u5F55\uFF09\n * - \u63A7\u5236\u53F0\u6253\u5370\u6458\u8981(env var SFMC_LOG_MODULE_TOKEN=1 \u65F6\u6253\u5B8C\u6574)\n */\nexport function buildModuleAuth(opts: {\n dbPath: string;\n envAuthToken: string;\n enabledModuleIds: string[];\n}): ModuleAuthMap {\n const secret = opts.envAuthToken || randomBytes(32).toString(\"hex\");\n const tokens: Record<string, string> = {};\n for (const id of opts.enabledModuleIds) {\n tokens[id] = deriveToken(id, secret);\n }\n\n const outFile = moduleTokensFile(opts.dbPath);\n writeJson(outFile, {\n tokens,\n secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !opts.envAuthToken,\n });\n\n log.success(\n `[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`\n );\n if (!opts.envAuthToken) {\n log.warn(\n \"[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN(\u51B2\u7A81 config http_auth),\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;\"\n + \" \u91CD\u542F\u540E token \u4F1A\u53D8,host-bootstrap \u5FC5\u987B\u6BCF\u6B21\u91CD\u65B0\u8BFB module-tokens.json\"\n );\n }\n if (process.env.SFMC_LOG_MODULE_TOKEN === \"1\") {\n log.info(\"[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):\");\n for (const [id, t] of Object.entries(tokens)) {\n console.log(` ${id} = ${t}`);\n }\n } else {\n const lines = Object.entries(tokens)\n .map(([id, t]) => ` ${id} = ${t.slice(0, 6)}\u2026${t.slice(-4)}`)\n .join(\"\\n\");\n log.info(`[auth] token \u6458\u8981:\\n${lines}`);\n }\n\n return { tokens, secret };\n}\n\nexport function loadModuleAuth(dbPath: string): ModuleAuthMap | null {\n const parsed = readJson<TokenStore>(moduleTokensFile(dbPath));\n if (!parsed || !parsed.tokens || !parsed.secret) return null;\n return { tokens: parsed.tokens, secret: parsed.secret };\n}\n\n/** \u628A\u5F53\u524D auth map \u5199\u56DE module-tokens.json(\u70ED\u66F4\u65B0 token \u65F6\u590D\u7528) */\nexport function persistModuleAuth(dbPath: string, auth: ModuleAuthMap, envAuthToken: string): void {\n const outFile = moduleTokensFile(dbPath);\n writeJson(outFile, {\n tokens: auth.tokens,\n secret: auth.secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !envAuthToken,\n } satisfies TokenStore);\n}\n\n/**\n * \u786E\u4FDD moduleId \u5728 auth.tokens \u4E2D\u6709\u6D3E\u751F\u503C(\u590D\u7528\u73B0\u6709 secret,\u52FF\u91CD\u5EFA\u968F\u673A secret)\u3002\n * @returns \u662F\u5426\u65B0\u5199\u5165\u4E86 token\n */\nexport function ensureModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (auth.tokens[moduleId]) return false;\n auth.tokens[moduleId] = deriveToken(moduleId, auth.secret);\n return true;\n}\n\n/** \u7981\u7528\u65F6\u64A4\u9500\u5185\u5B58\u4E2D\u7684 token(\u78C1\u76D8\u7531 persistModuleAuth \u540C\u6B65) */\nexport function revokeModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (!(moduleId in auth.tokens)) return false;\n delete auth.tokens[moduleId];\n return true;\n}\n\n/**\n * \u6821\u9A8C\u8BF7\u6C42\u65B9\u8EAB\u4EFD:\n * - Bearer \u5934 / X-Module-Token \u4E8C\u9009\u4E00\n * - ?moduleId=<id> \u5FC5\u987B\u5B58\u5728\n * - \u6A21\u5757\u5FC5\u987B\u5728 enabledModuleIds \u96C6\u5408\u4E2D\n * - token \u5FC5\u987B = deriveToken(moduleId, secret)\n * \u8FD4\u56DE\u6A21\u5757 ID \u6216 null\u3002\n */\nexport function verifyModuleAuth(opts: {\n headers: NodeJS.Dict<string | string[]>;\n params: URLSearchParams;\n auth: ModuleAuthMap;\n enabledModuleIds: Set<string>;\n}): string | null {\n const moduleId = opts.params.get(\"moduleId\") || \"\";\n if (!moduleId || !opts.enabledModuleIds.has(moduleId)) return null;\n\n let provided = \"\";\n const auth = opts.headers[\"authorization\"];\n const token = opts.headers[\"x-module-token\"];\n if (typeof auth === \"string\" && auth.startsWith(\"Bearer \")) {\n provided = auth.slice(7);\n } else if (typeof token === \"string\") {\n provided = token;\n }\n if (!provided) return null;\n\n const expected = opts.auth.tokens[moduleId];\n if (!expected) return null;\n // constant-time \u6BD4\u8F83\n if (provided.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < provided.length; i++) {\n diff |= provided.charCodeAt(i) ^ expected.charCodeAt(i);\n }\n return diff === 0 ? moduleId : null;\n}\n"],
5
+ "mappings": "AAwBA,SAAS,UAAU,iBAAkC;AACrD,SAAS,YAAY,mBAAmB;AACxC,SAAS,SAAS,YAAY;AAC9B,SAAS,WAAW;AASpB,MAAM,QAAQ;AAEd,SAAS,OAAO,IAAoB;AAClC,MAAI,CAAC,MAAM,KAAK,EAAE,EAAG,OAAM,IAAI,MAAM,6BAA6B,EAAE,EAAE;AACtE,SAAO;AACT;AAGO,SAAS,iBAAiB,QAAwB;AACvD,SAAO,KAAK,QAAQ,MAAM,GAAG,oBAAoB;AACnD;AAEO,SAAS,YAAY,UAAkB,QAAwB;AACpE,SAAO,WAAW,UAAU,MAAM,EAAE,OAAO,eAAe,OAAO,QAAQ,CAAC,EAAE,EAAE,OAAO,KAAK;AAC5F;AASO,SAAS,gBAAgB,MAId;AAChB,QAAM,SAAS,KAAK,gBAAgB,YAAY,EAAE,EAAE,SAAS,KAAK;AAClE,QAAM,SAAiC,CAAC;AACxC,aAAW,MAAM,KAAK,kBAAkB;AACtC,WAAO,EAAE,IAAI,YAAY,IAAI,MAAM;AAAA,EACrC;AAEA,QAAM,UAAU,iBAAiB,KAAK,MAAM;AAC5C,YAAU,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,iBAAiB,CAAC,KAAK;AAAA,EACzB,CAAC;AAED,MAAI;AAAA,IACF,uBAAa,KAAK,iBAAiB,MAAM,2DAAwB,OAAO;AAAA,EAC1E;AACA,MAAI,CAAC,KAAK,cAAc;AACtB,QAAI;AAAA,MACF;AAAA,IAEF;AAAA,EACF;AACA,MAAI,QAAQ,IAAI,0BAA0B,KAAK;AAC7C,QAAI,KAAK,iDAAiD;AAC1D,eAAW,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC5C,cAAQ,IAAI,KAAK,EAAE,MAAM,CAAC,EAAE;AAAA,IAC9B;AAAA,EACF,OAAO;AACL,UAAM,QAAQ,OAAO,QAAQ,MAAM,EAChC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,SAAI,EAAE,MAAM,EAAE,CAAC,EAAE,EAC5D,KAAK,IAAI;AACZ,QAAI,KAAK;AAAA,EAAqB,KAAK,EAAE;AAAA,EACvC;AAEA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEO,SAAS,eAAe,QAAsC;AACnE,QAAM,SAAS,SAAqB,iBAAiB,MAAM,CAAC;AAC5D,MAAI,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,OAAO,OAAQ,QAAO;AACxD,SAAO,EAAE,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO;AACxD;AAGO,SAAS,kBAAkB,QAAgB,MAAqB,cAA4B;AACjG,QAAM,UAAU,iBAAiB,MAAM;AACvC,YAAU,SAAS;AAAA,IACjB,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,iBAAiB,CAAC;AAAA,EACpB,CAAsB;AACxB;AAMO,SAAS,kBAAkB,MAAqB,UAA2B;AAChF,MAAI,KAAK,OAAO,QAAQ,EAAG,QAAO;AAClC,OAAK,OAAO,QAAQ,IAAI,YAAY,UAAU,KAAK,MAAM;AACzD,SAAO;AACT;AAGO,SAAS,kBAAkB,MAAqB,UAA2B;AAChF,MAAI,EAAE,YAAY,KAAK,QAAS,QAAO;AACvC,SAAO,KAAK,OAAO,QAAQ;AAC3B,SAAO;AACT;AAUO,SAAS,iBAAiB,MAKf;AAChB,QAAM,WAAW,KAAK,OAAO,IAAI,UAAU,KAAK;AAChD,MAAI,CAAC,YAAY,CAAC,KAAK,iBAAiB,IAAI,QAAQ,EAAG,QAAO;AAE9D,MAAI,WAAW;AACf,QAAM,OAAO,KAAK,QAAQ,eAAe;AACzC,QAAM,QAAQ,KAAK,QAAQ,gBAAgB;AAC3C,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,SAAS,GAAG;AAC1D,eAAW,KAAK,MAAM,CAAC;AAAA,EACzB,WAAW,OAAO,UAAU,UAAU;AACpC,eAAW;AAAA,EACb;AACA,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,WAAW,KAAK,KAAK,OAAO,QAAQ;AAC1C,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAQ,SAAS,WAAW,CAAC,IAAI,SAAS,WAAW,CAAC;AAAA,EACxD;AACA,SAAO,SAAS,IAAI,WAAW;AACjC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * module-runtime-sync.ts — enable/disable 时同步进程内运行态
3
+ *
4
+ * lock 落盘后,内存态(enabledSet / enabledManifests / moduleAuth.tokens /
5
+ * builtin service handlers)必须与之一致,否则「只重启 BDS」拿不到新 token
6
+ * (DIP:路由只依赖注入的集合,不感知启停实现)。
7
+ *
8
+ * DRY:token 落盘 / 内置 handler 启停复用 module-auth 与 builtin-handlers,
9
+ * 不在此处再写一份派生与卸载逻辑。
10
+ */
11
+ import type { ModuleManifestV2 } from "./manifest-loader.js";
12
+ import { type ModuleAuthMap } from "./module-auth.js";
13
+ import { type BuiltinServiceDeps } from "./services/builtin-handlers.js";
14
+ import type { ServiceRegistry } from "./service-registry.js";
15
+ export type LoadedModules = {
16
+ modules: Record<string, ModuleManifestV2 | undefined>;
17
+ };
18
+ export type SyncModuleRuntimeOpts = {
19
+ moduleId: string;
20
+ enabled: boolean;
21
+ /** SQLite 路径;token 文件写在同目录 */
22
+ dbPath: string;
23
+ /** 与 buildModuleAuth 一致:无 AUTH_TOKEN 时 secretGenerated=true */
24
+ envAuthToken: string;
25
+ enabledSet: Set<string>;
26
+ enabledManifests: Map<string, ModuleManifestV2>;
27
+ loadedManifest: LoadedModules;
28
+ moduleAuth: ModuleAuthMap;
29
+ serviceRegistry: ServiceRegistry;
30
+ builtinDeps: BuiltinServiceDeps;
31
+ };
32
+ /**
33
+ * 同步 enable/disable 后的进程内集合与 token 文件。
34
+ * 调用方负责先写 lock;本函数不碰磁盘 lock。
35
+ */
36
+ export declare function syncModuleRuntimeState(opts: SyncModuleRuntimeOpts): void;
37
+ //# sourceMappingURL=module-runtime-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-runtime-sync.d.ts","sourceRoot":"","sources":["../src/module-runtime-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,cAAc,EAAE,aAAa,CAAC;IAC9B,UAAU,EAAE,aAAa,CAAC;IAC1B,eAAe,EAAE,eAAe,CAAC;IACjC,WAAW,EAAE,kBAAkB,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAyCxE"}