@sfmc-bds/db-server 0.2.0-beta.1 → 0.2.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +663 -0
- package/dist/domain/bridge.d.ts +57 -23
- package/dist/domain/bridge.d.ts.map +1 -1
- package/dist/domain/bridge.js +77 -6
- package/dist/domain/bridge.js.map +2 -2
- package/dist/domain/bridge.test.d.ts +2 -0
- package/dist/domain/bridge.test.d.ts.map +1 -0
- package/dist/domain/bridge.test.js +87 -0
- package/dist/domain/bridge.test.js.map +7 -0
- package/dist/domain/economy.d.ts +38 -7
- package/dist/domain/economy.d.ts.map +1 -1
- package/dist/domain/economy.js.map +2 -2
- package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
- package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
- package/dist/domain/messages-bridge-filter.test.js +20 -0
- package/dist/domain/messages-bridge-filter.test.js.map +7 -0
- package/dist/domain/player-session.d.ts +8 -0
- package/dist/domain/player-session.d.ts.map +1 -0
- package/dist/domain/player-session.js +16 -0
- package/dist/domain/player-session.js.map +7 -0
- package/dist/domain/player-session.test.d.ts +2 -0
- package/dist/domain/player-session.test.d.ts.map +1 -0
- package/dist/domain/player-session.test.js +33 -0
- package/dist/domain/player-session.test.js.map +7 -0
- package/dist/domain/qq-bind-status.test.d.ts +2 -0
- package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
- package/dist/domain/qq-bind-status.test.js +211 -0
- package/dist/domain/qq-bind-status.test.js.map +7 -0
- package/dist/domain/qq-events.d.ts +84 -0
- package/dist/domain/qq-events.d.ts.map +1 -0
- package/dist/domain/qq-events.js +193 -0
- package/dist/domain/qq-events.js.map +7 -0
- package/dist/domain/qq-events.test.d.ts +5 -0
- package/dist/domain/qq-events.test.d.ts.map +1 -0
- package/dist/domain/qq-events.test.js +170 -0
- package/dist/domain/qq-events.test.js.map +7 -0
- package/dist/domain/qq-join.test.d.ts +2 -0
- package/dist/domain/qq-join.test.d.ts.map +1 -0
- package/dist/domain/qq-join.test.js +353 -0
- package/dist/domain/qq-join.test.js.map +7 -0
- package/dist/domain/schema.d.ts +15 -18
- package/dist/domain/schema.d.ts.map +1 -1
- package/dist/domain/schema.js +104 -453
- package/dist/domain/schema.js.map +2 -2
- package/dist/domain/system-status.d.ts +75 -0
- package/dist/domain/system-status.d.ts.map +1 -0
- package/dist/domain/system-status.js +181 -0
- package/dist/domain/system-status.js.map +7 -0
- package/dist/domain/system-status.test.d.ts +5 -0
- package/dist/domain/system-status.test.d.ts.map +1 -0
- package/dist/domain/system-status.test.js +19 -0
- package/dist/domain/system-status.test.js.map +7 -0
- package/dist/domain/transaction.d.ts +5 -4
- package/dist/domain/transaction.d.ts.map +1 -1
- package/dist/domain/transaction.js.map +2 -2
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +57 -9
- package/dist/env.js.map +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -38
- package/dist/index.js.map +2 -2
- package/dist/lib/idempotency-store.d.ts +17 -11
- package/dist/lib/idempotency-store.d.ts.map +1 -1
- package/dist/lib/idempotency-store.js.map +2 -2
- package/dist/lib/idempotency.d.ts +3 -4
- package/dist/lib/idempotency.d.ts.map +1 -1
- package/dist/lib/idempotency.js.map +2 -2
- package/dist/lib/log.d.ts.map +1 -1
- package/dist/lib/log.js +3 -6
- package/dist/lib/log.js.map +2 -2
- package/dist/lib/module-state.d.ts +38 -8
- package/dist/lib/module-state.d.ts.map +1 -1
- package/dist/lib/module-state.js.map +2 -2
- package/dist/lib/order-by.d.ts +8 -6
- package/dist/lib/order-by.d.ts.map +1 -1
- package/dist/lib/order-by.js.map +2 -2
- package/dist/lib/sql-helpers.d.ts +25 -13
- package/dist/lib/sql-helpers.d.ts.map +1 -1
- package/dist/lib/sql-helpers.js.map +2 -2
- package/dist/lib/sqlite.d.ts +14 -5
- package/dist/lib/sqlite.d.ts.map +1 -1
- package/dist/lib/sqlite.js.map +2 -2
- package/dist/manifest-loader.d.ts +5 -2
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +16 -6
- package/dist/manifest-loader.js.map +2 -2
- package/dist/manifest.d.ts +1 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +13 -8
- package/dist/manifest.js.map +2 -2
- package/dist/module-auth.d.ts.map +1 -1
- package/dist/module-auth.js +2 -6
- package/dist/module-auth.js.map +2 -2
- package/dist/permission-gate.d.ts.map +1 -1
- package/dist/permission-gate.js +8 -2
- package/dist/permission-gate.js.map +2 -2
- package/dist/project-root.d.ts +6 -0
- package/dist/project-root.d.ts.map +1 -0
- package/dist/project-root.js +9 -0
- package/dist/project-root.js.map +7 -0
- package/dist/routes/_shared.d.ts +14 -5
- package/dist/routes/_shared.d.ts.map +1 -1
- package/dist/routes/_shared.js.map +2 -2
- package/dist/routes/config.d.ts +8 -11
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +17 -23
- package/dist/routes/config.js.map +2 -2
- package/dist/routes/db-routes.d.ts +8 -2
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js +5 -12
- package/dist/routes/db-routes.js.map +2 -2
- package/dist/routes/messages.d.ts +6 -2
- package/dist/routes/messages.d.ts.map +1 -1
- package/dist/routes/messages.js +17 -3
- package/dist/routes/messages.js.map +2 -2
- package/dist/routes/module-config-routes.d.ts +13 -9
- package/dist/routes/module-config-routes.d.ts.map +1 -1
- package/dist/routes/module-config-routes.js.map +2 -2
- package/dist/routes/modules.d.ts +6 -0
- package/dist/routes/modules.d.ts.map +1 -1
- package/dist/routes/modules.js.map +2 -2
- package/dist/routes/qq-bind.d.ts +24 -0
- package/dist/routes/qq-bind.d.ts.map +1 -0
- package/dist/routes/qq-bind.js +179 -0
- package/dist/routes/qq-bind.js.map +7 -0
- package/dist/routes/qq-events.d.ts +20 -0
- package/dist/routes/qq-events.d.ts.map +1 -0
- package/dist/routes/qq-events.js +39 -0
- package/dist/routes/qq-events.js.map +7 -0
- package/dist/routes/qq-join.d.ts +46 -0
- package/dist/routes/qq-join.d.ts.map +1 -0
- package/dist/routes/qq-join.js +277 -0
- package/dist/routes/qq-join.js.map +7 -0
- package/dist/routes/service-routes.d.ts +6 -0
- package/dist/routes/service-routes.d.ts.map +1 -1
- package/dist/routes/service-routes.js.map +2 -2
- package/dist/routes/status.d.ts +19 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +74 -0
- package/dist/routes/status.js.map +7 -0
- package/dist/runtime.test.js +216 -4
- package/dist/runtime.test.js.map +2 -2
- package/dist/schema-registry.d.ts +17 -16
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +76 -6
- package/dist/schema-registry.js.map +2 -2
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js.map +2 -2
- package/dist/service-registry.d.ts +39 -9
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +30 -2
- package/dist/service-registry.js.map +2 -2
- package/dist/services/builtin-handlers.d.ts +27 -7
- package/dist/services/builtin-handlers.d.ts.map +1 -1
- package/dist/services/builtin-handlers.js.map +2 -2
- package/dist/tx-runner.d.ts +48 -9
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +99 -27
- package/dist/tx-runner.js.map +2 -2
- package/package.json +26 -24
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/manifest-loader.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * manifest-loader.ts \u2014 \u8BFB v2 manifest,\u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n *\n * v2 schema:\n * {\n * \"schemaVersion\": 2,\n * \"id\": \"feature-land\",\n * \"name\": \"\u9886\u5730\",\n * \"type\": \"feature\",\n * \"configKey\": \"land\",\n * \"requires\": [\"feature-economy\"],\n * \"permissions\": [\"db:read:lands\", \"service:economy.account\"],\n * \"services\": {\n * \"provides\": [{ \"name\": \"lands.byOwner\", \"input\": {...}, \"output\": {...} }],\n * \"requires\": [{ \"name\": \"economy.account\" }]\n * },\n * \"notes\": \"...\"\n * }\n *\n * \u4E0D\u8981 routes / tables / migrations / seeds / handlers / events\u3002\n *\n * \u8C03\u7528\u65B9:db-server/src/index.ts \u542F\u52A8\u65F6\u4E00\u6B21 loadManifestV2,\n * throw \u5373 crash\u3002\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { defaultPackagesDir } from \"./manifest.js\";\nimport { validateManifestPermissions } from \"./permission-gate.js\";\n\nexport interface ManifestServiceIO {\n type: \"object\" | \"array\" | \"string\" | \"number\" | \"boolean\" | \"null\";\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\nexport interface ServiceEntry {\n name: string;\n input?: ManifestServiceIO | undefined;\n output?: ManifestServiceIO | undefined;\n}\n\nexport interface ModuleManifestV2 {\n schemaVersion: 2;\n id: string;\n name: string;\n type: \"core\" | \"feature\";\n configKey: string;\n requires: string[];\n permissions: string[];\n services: { provides: ServiceEntry[]; requires: ServiceEntry[] };\n notes?: string | undefined;\n}\n\nconst VALID_TYPES = new Set([\"core\", \"feature\"]);\nconst IDENT = /^[A-Za-z0-9_-]+$/;\n\n/** v1 / \u7F3A schemaVersion:\u8DF3\u8FC7(\u76F4\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2) */\nexport class ModuleV1SkippedError extends Error {\n constructor(public moduleId: string, public declaredVersion: unknown) {\n super(`moduleId=${moduleId} schemaVersion=${String(declaredVersion)} (\u9700\u8981 2),\u8DF3\u8FC7`);\n }\n}\n\nfunction assertModuleObject(modId: string, obj: Record<string, unknown>): void {\n if (typeof obj.schemaVersion !== \"number\") {\n throw new Error(`[manifest] ${modId}: schemaVersion \u7F3A\u5931`);\n }\n if (obj.schemaVersion !== 2) {\n throw new Error(`[manifest] ${modId}: schemaVersion=${obj.schemaVersion} (\u9700\u8981 2)`);\n }\n if (typeof obj.id !== \"string\" || !IDENT.test(obj.id)) {\n throw new Error(`[manifest] ${modId}: id \u7F3A\u5931\u6216\u975E\u6CD5 \"${String(obj.id)}\"`);\n }\n if (typeof obj.name !== \"string\" || obj.name.length === 0) {\n throw new Error(`[manifest] ${modId}: name \u7F3A\u5931`);\n }\n if (typeof obj.type !== \"string\" || !VALID_TYPES.has(obj.type)) {\n throw new Error(`[manifest] ${modId}: type \u5FC5\u987B\u662F core|feature (\"${String(obj.type)}\")`);\n }\n if (typeof obj.configKey !== \"string\" || obj.configKey.length === 0) {\n throw new Error(`[manifest] ${modId}: configKey \u7F3A\u5931`);\n }\n if (Array.isArray(obj.routes)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 routes \u5B57\u6BB5,\u6539\u7528 db.defineTable + platform routes`);\n }\n if (Array.isArray(obj.migrations)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 migrations \u5B57\u6BB5,\u6539\u7528 db.defineTable \u81EA\u52A8\u5EFA\u8868`);\n }\n if (Array.isArray(obj.tables)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 tables \u5B57\u6BB5,\u6539\u7528 db.defineTable`);\n }\n if (Array.isArray(obj.events)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 events \u5B57\u6BB5,\u6539\u7528 platform event-bus`);\n }\n}\n\nfunction parseServices(modId: string, raw: unknown): { provides: ServiceEntry[]; requires: ServiceEntry[] } {\n if (raw === undefined) return { provides: [], requires: [] };\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(`[manifest] ${modId}: services \u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const obj = raw as Record<string, unknown>;\n const provides = parseServiceList(modId, \"provides\", obj.provides);\n const requires = parseServiceList(modId, \"requires\", obj.requires);\n for (const r of requires) {\n if (provides.find((p) => p.name === r.name)) {\n throw new Error(`[manifest] ${modId}: service \"${r.name}\" \u65E2 provides \u53C8 requires`);\n }\n }\n return { provides, requires };\n}\n\nfunction parseServiceList(modId: string, dir: string, raw: unknown): ServiceEntry[] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw)) throw new Error(`[manifest] ${modId}: services.${dir} \u5FC5\u987B\u662F\u6570\u7EC4`);\n const out: ServiceEntry[] = [];\n for (const item of raw) {\n if (typeof item !== \"object\" || item === null) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const e = item as Record<string, unknown>;\n if (typeof e.name !== \"string\" || e.name.length === 0) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u7F3A\u5C11 name`);\n }\n const seen = new Set<string>();\n if (seen.has(e.name)) {\n throw new Error(`[manifest] ${modId}: services.${dir} name \u91CD\u590D \"${e.name}\"`);\n }\n seen.add(e.name);\n out.push({ name: e.name, input: e.input as ManifestServiceIO | undefined, output: e.output as ManifestServiceIO | undefined });\n }\n return out;\n}\n\nfunction parseMod(modId: string, raw: Record<string, unknown>): ModuleManifestV2 {\n if (raw.schemaVersion !== 2) {\n throw new ModuleV1SkippedError(modId, raw.schemaVersion);\n }\n assertModuleObject(modId, raw);\n const requires = Array.isArray(raw.requires) ? (raw.requires as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n const permissions = Array.isArray(raw.permissions) ? (raw.permissions as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n validateManifestPermissions(modId, permissions);\n return {\n schemaVersion: 2,\n id: raw.id as string,\n name: raw.name as string,\n type: raw.type as \"core\" | \"feature\",\n configKey: raw.configKey as string,\n requires,\n permissions,\n services: parseServices(modId, raw.services),\n notes: typeof raw.notes === \"string\" ? raw.notes : undefined,\n };\n}\n\nexport interface LoadedManifests {\n modules: Record<string, ModuleManifestV2>;\n providesMap: Map<string, string>; // service name \u2192 moduleId\n}\n\n/**\n * \u542F\u52A8\u671F\u8C03\u3002throw = \u542F\u52A8\u5931\u8D25\u3002\n * \u6CE8:\u4E0D\u8BFB v1 manifest\u3002\u53EA\u6709 schemaVersion=2 \u624D\u8D70\u3002\n */\nexport function loadManifestV2(packagesDir: string = defaultPackagesDir()): LoadedManifests {\n if (!existsSync(packagesDir)) {\n throw new Error(`[manifest] ${packagesDir} \u4E0D\u5B58\u5728;\u65E0\u6CD5\u542F\u52A8 v2 \u534F\u8BAE`);\n }\n const ids = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n const modules: Record<string, ModuleManifestV2> = {};\n const providesMap = new Map<string, string>();\n\n for (const id of ids) {\n const p = resolve(packagesDir, id, \"sapi\", \"manifest.json\");\n if (!existsSync(p)) continue;\n let raw: string;\n try {\n raw = readFileSync(p, \"utf8\");\n } catch (e) {\n throw new Error(`[manifest] ${id}: \u8BFB ${p} \u5931\u8D25: ${(e as Error).message}`);\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(`[manifest] ${id}: ${p} \u975E\u5408\u6CD5 JSON: ${(e as Error).message}`);\n }\n try {\n const m = parseMod(id, parsed);\n modules[m.id] = m;\n for (const s of m.services.provides) {\n const prev = providesMap.get(s.name);\n if (prev) {\n throw new Error(`[manifest] service \"${s.name}\" \u5DF2\u88AB ${prev} provides,${m.id} \u62A2\u6CE8`);\n }\n providesMap.set(s.name, m.id);\n }\n } catch (e) {\n if (e instanceof ModuleV1SkippedError) {\n // PoC \u517C\u5BB9:\u4E0D\u662F v2 \u7684\u6A21\u5757\u8DF3\u8FC7,\u7B49\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2 \u540E\u6539\u4E3A throw\n console.warn(`[manifest] ${id}: ${e.message}; v1 \u8DF3\u8FC7`);\n continue;\n }\n throw e;\n }\n }\n\n // requires:\u6821\u9A8C\u76EE\u6807 service \u5728\u67D0\u4E2A provides \u91CC\n for (const m of Object.values(modules)) {\n for (const r of m.services.requires) {\n const owner = providesMap.get(r.name);\n if (!owner) {\n throw new Error(`[manifest] ${m.id}.services.requires.${r.name} \u6CA1\u6709\u6A21\u5757 provides`);\n }\n }\n // requires \u6A21\u5757\u4F9D\u8D56\u4E5F\u8981\u4E48 enabled \u8981\u4E48\u672C\u8EAB installed (\u542F\u52A8\u671F\u4E0D\u5F3A\u6C42 enabled;\u8FD0\u884C\u65F6\u518D\u67E5)\n for (const dep of m.requires) {\n if (!modules[dep]) {\n throw new Error(`[manifest] ${m.id}.requires.${dep} \u6CA1\u627E\u5230\u5BF9\u5E94\u6A21\u5757`);\n }\n }\n }\n\n return { modules, providesMap };\n}\n\n/**\n * \u7ED9\u5B9A moduleId \u96C6\u5408,\u8FC7\u6EE4\u51FA enabled (= lock \u6587\u4EF6 enabled \u7684) \u5B50\u96C6\u3002\n * map(moduleId \u2192 manifest) \u2014 \u6CA1\u88C5\u5219\u8DF3\u8FC7\u3002\n */\nexport function filterEnabled(\n loaded: LoadedManifests,\n enabledIds: ReadonlySet<string>\n): Record<string, ModuleManifestV2> {\n const out: Record<string, ModuleManifestV2> = {};\n for (const [id, m] of Object.entries(loaded.modules)) {\n if (enabledIds.has(id)) out[id] = m;\n }\n return out;\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * manifest-loader.ts \u2014 \u8BFB v2 manifest,\u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n *\n * v2 schema:\n * {\n * \"schemaVersion\": 2,\n * \"id\": \"feature-land\",\n * \"name\": \"\u9886\u5730\",\n * \"configKey\": \"land\",\n * \"requires\": [\"feature-economy\"],\n * \"permissions\": [\"db:read:lands\", \"service:economy.account\"],\n * \"services\": {\n * \"provides\": [{ \"name\": \"lands.byOwner\", \"input\": {...}, \"output\": {...} }],\n * \"requires\": [{ \"name\": \"economy.account\" }]\n * },\n * \"notes\": \"...\"\n * }\n *\n * \u4E0D\u8981 routes / tables / migrations / seeds / handlers / events\u3002\n *\n * \u8C03\u7528\u65B9:db-server/src/index.ts \u542F\u52A8\u65F6\u4E00\u6B21 loadManifestV2,\n * throw \u5373 crash\u3002\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { defaultPackagesDir } from \"./manifest.js\";\nimport { validateManifestPermissions } from \"./permission-gate.js\";\n\nexport interface ManifestServiceIO {\n type: \"object\" | \"array\" | \"string\" | \"number\" | \"boolean\" | \"null\";\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\nexport interface ServiceEntry {\n name: string;\n input?: ManifestServiceIO | undefined;\n output?: ManifestServiceIO | undefined;\n}\n\nexport interface ModuleManifestV2 {\n schemaVersion: 2;\n id: string;\n name: string;\n configKey: string;\n requires: string[];\n permissions: string[];\n services: { provides: ServiceEntry[]; requires: ServiceEntry[] };\n enabledByDefault?: boolean | undefined;\n canDisable?: boolean | undefined;\n notes?: string | undefined;\n}\n\nconst IDENT = /^[A-Za-z0-9_-]+$/;\n\n/** v1 / \u7F3A schemaVersion:\u8DF3\u8FC7(\u76F4\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2) */\nexport class ModuleV1SkippedError extends Error {\n constructor(public moduleId: string, public declaredVersion: unknown) {\n super(`moduleId=${moduleId} schemaVersion=${String(declaredVersion)} (\u9700\u8981 2),\u8DF3\u8FC7`);\n }\n}\n\nfunction assertModuleObject(modId: string, obj: Record<string, unknown>): void {\n if (typeof obj.schemaVersion !== \"number\") {\n throw new Error(`[manifest] ${modId}: schemaVersion \u7F3A\u5931`);\n }\n if (obj.schemaVersion !== 2) {\n throw new Error(`[manifest] ${modId}: schemaVersion=${obj.schemaVersion} (\u9700\u8981 2)`);\n }\n if (typeof obj.id !== \"string\" || !IDENT.test(obj.id)) {\n throw new Error(`[manifest] ${modId}: id \u7F3A\u5931\u6216\u975E\u6CD5 \"${String(obj.id)}\"`);\n }\n if (typeof obj.name !== \"string\" || obj.name.length === 0) {\n throw new Error(`[manifest] ${modId}: name \u7F3A\u5931`);\n }\n if (obj.enabledByDefault !== undefined && typeof obj.enabledByDefault !== \"boolean\") {\n throw new Error(`[manifest] ${modId}: enabledByDefault \u5FC5\u987B\u662F boolean`);\n }\n if (obj.canDisable !== undefined && typeof obj.canDisable !== \"boolean\") {\n throw new Error(`[manifest] ${modId}: canDisable \u5FC5\u987B\u662F boolean`);\n }\n if (typeof obj.configKey !== \"string\" || obj.configKey.length === 0) {\n throw new Error(`[manifest] ${modId}: configKey \u7F3A\u5931`);\n }\n if (Array.isArray(obj.routes)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 routes \u5B57\u6BB5,\u6539\u7528 db.defineTable + platform routes`);\n }\n if (Array.isArray(obj.migrations)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 migrations \u5B57\u6BB5,\u6539\u7528 db.defineTable \u81EA\u52A8\u5EFA\u8868`);\n }\n if (Array.isArray(obj.tables)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 tables \u5B57\u6BB5,\u6539\u7528 db.defineTable`);\n }\n if (Array.isArray(obj.events)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 events \u5B57\u6BB5,\u6539\u7528 platform event-bus`);\n }\n}\n\nfunction parseServices(modId: string, raw: unknown): { provides: ServiceEntry[]; requires: ServiceEntry[] } {\n if (raw === undefined) return { provides: [], requires: [] };\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(`[manifest] ${modId}: services \u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const obj = raw as Record<string, unknown>;\n const provides = parseServiceList(modId, \"provides\", obj.provides);\n const requires = parseServiceList(modId, \"requires\", obj.requires);\n for (const r of requires) {\n if (provides.find((p) => p.name === r.name)) {\n throw new Error(`[manifest] ${modId}: service \"${r.name}\" \u65E2 provides \u53C8 requires`);\n }\n }\n return { provides, requires };\n}\n\nfunction parseServiceList(modId: string, dir: string, raw: unknown): ServiceEntry[] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw)) throw new Error(`[manifest] ${modId}: services.${dir} \u5FC5\u987B\u662F\u6570\u7EC4`);\n const out: ServiceEntry[] = [];\n for (const item of raw) {\n if (typeof item !== \"object\" || item === null) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const e = item as Record<string, unknown>;\n if (typeof e.name !== \"string\" || e.name.length === 0) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u7F3A\u5C11 name`);\n }\n const seen = new Set<string>();\n if (seen.has(e.name)) {\n throw new Error(`[manifest] ${modId}: services.${dir} name \u91CD\u590D \"${e.name}\"`);\n }\n seen.add(e.name);\n out.push({ name: e.name, input: e.input as ManifestServiceIO | undefined, output: e.output as ManifestServiceIO | undefined });\n }\n return out;\n}\n\nfunction parseMod(modId: string, raw: Record<string, unknown>): ModuleManifestV2 {\n if (raw.schemaVersion !== 2) {\n throw new ModuleV1SkippedError(modId, raw.schemaVersion);\n }\n assertModuleObject(modId, raw);\n const requires = Array.isArray(raw.requires) ? (raw.requires as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n const permissions = Array.isArray(raw.permissions) ? (raw.permissions as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n validateManifestPermissions(modId, permissions);\n return {\n schemaVersion: 2,\n id: raw.id as string,\n name: raw.name as string,\n configKey: raw.configKey as string,\n requires,\n permissions,\n services: parseServices(modId, raw.services),\n ...(typeof raw.enabledByDefault === \"boolean\" ? { enabledByDefault: raw.enabledByDefault } : {}),\n ...(typeof raw.canDisable === \"boolean\" ? { canDisable: raw.canDisable } : {}),\n notes: typeof raw.notes === \"string\" ? raw.notes : undefined,\n };\n}\n\nexport interface LoadedManifests {\n modules: Record<string, ModuleManifestV2>;\n providesMap: Map<string, string>; // service name \u2192 moduleId\n}\n\n/**\n * \u542F\u52A8\u671F\u8C03\u3002throw = \u542F\u52A8\u5931\u8D25\u3002\n * \u6CE8:\u4E0D\u8BFB v1 manifest\u3002\u53EA\u6709 schemaVersion=2 \u624D\u8D70\u3002\n *\n * \u5F62\u72B6\u6821\u9A8C\uFF1A\u6838\u5FC3\u5B57\u6BB5\u53E5\u5F0F\u53EF\u4E0E `@sfmc-bds/sdk/validation` \u5BF9\u9F50\uFF1B\u672C\u52A0\u8F7D\u5668\u53E6\u6709\n * \u5F03\u7528\u5B57\u6BB5\u62D2\u7EDD\u3001service \u51B2\u7A81\u3001\u6743\u9650\u95E8\u7B49\u57DF\u89C4\u5219\uFF0C\u6545\u6682\u672A\u6574\u6BB5\u59D4\u6258 validateManifest\u3002\n */\nexport function loadManifestV2(packagesDir: string = defaultPackagesDir()): LoadedManifests {\n if (!existsSync(packagesDir)) {\n throw new Error(`[manifest] ${packagesDir} \u4E0D\u5B58\u5728;\u65E0\u6CD5\u542F\u52A8 v2 \u534F\u8BAE`);\n }\n const ids = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => {\n if (e.name.startsWith(\".\")) return false;\n try {\n return statSync(resolve(packagesDir, e.name)).isDirectory();\n } catch {\n return false;\n }\n })\n .map((e) => e.name);\n const modules: Record<string, ModuleManifestV2> = {};\n const providesMap = new Map<string, string>();\n\n for (const id of ids) {\n const p = resolve(packagesDir, id, \"sapi\", \"manifest.json\");\n if (!existsSync(p)) continue;\n let raw: string;\n try {\n raw = readFileSync(p, \"utf8\");\n } catch (e) {\n throw new Error(`[manifest] ${id}: \u8BFB ${p} \u5931\u8D25: ${(e as Error).message}`);\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(`[manifest] ${id}: ${p} \u975E\u5408\u6CD5 JSON: ${(e as Error).message}`);\n }\n try {\n const m = parseMod(id, parsed);\n modules[m.id] = m;\n for (const s of m.services.provides) {\n const prev = providesMap.get(s.name);\n if (prev) {\n throw new Error(`[manifest] service \"${s.name}\" \u5DF2\u88AB ${prev} provides,${m.id} \u62A2\u6CE8`);\n }\n providesMap.set(s.name, m.id);\n }\n } catch (e) {\n if (e instanceof ModuleV1SkippedError) {\n // PoC \u517C\u5BB9:\u4E0D\u662F v2 \u7684\u6A21\u5757\u8DF3\u8FC7,\u7B49\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2 \u540E\u6539\u4E3A throw\n console.warn(`[manifest] ${id}: ${e.message}; v1 \u8DF3\u8FC7`);\n continue;\n }\n throw e;\n }\n }\n\n // requires:\u6821\u9A8C\u76EE\u6807 service \u5728\u67D0\u4E2A provides \u91CC\n for (const m of Object.values(modules)) {\n for (const r of m.services.requires) {\n const owner = providesMap.get(r.name);\n if (!owner) {\n throw new Error(`[manifest] ${m.id}.services.requires.${r.name} \u6CA1\u6709\u6A21\u5757 provides`);\n }\n }\n // requires \u6A21\u5757\u4F9D\u8D56\u4E5F\u8981\u4E48 enabled \u8981\u4E48\u672C\u8EAB installed (\u542F\u52A8\u671F\u4E0D\u5F3A\u6C42 enabled;\u8FD0\u884C\u65F6\u518D\u67E5)\n for (const dep of m.requires) {\n if (!modules[dep]) {\n throw new Error(`[manifest] ${m.id}.requires.${dep} \u6CA1\u627E\u5230\u5BF9\u5E94\u6A21\u5757`);\n }\n }\n }\n\n return { modules, providesMap };\n}\n\n/**\n * \u7ED9\u5B9A moduleId \u96C6\u5408,\u8FC7\u6EE4\u51FA enabled (= lock \u6587\u4EF6 enabled \u7684) \u5B50\u96C6\u3002\n * map(moduleId \u2192 manifest) \u2014 \u6CA1\u88C5\u5219\u8DF3\u8FC7\u3002\n */\nexport function filterEnabled(\n loaded: LoadedManifests,\n enabledIds: ReadonlySet<string>\n): Record<string, ModuleManifestV2> {\n const out: Record<string, ModuleManifestV2> = {};\n for (const [id, m] of Object.entries(loaded.modules)) {\n if (enabledIds.has(id)) out[id] = m;\n }\n return out;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ export interface ModuleManifestMigration {
|
|
|
26
26
|
}
|
|
27
27
|
export interface ModuleManifestEntry {
|
|
28
28
|
name?: string | undefined;
|
|
29
|
-
type?: string | undefined;
|
|
30
29
|
configKey?: string | undefined;
|
|
31
30
|
requires?: string[] | undefined;
|
|
32
31
|
handlers?: string[] | undefined;
|
|
@@ -45,7 +44,7 @@ export type PackagesDirResolver = () => string;
|
|
|
45
44
|
* 优先级:
|
|
46
45
|
* 1. `process.env.SFMC_PACKAGES_DIR` —— sfmc spawnService 启动 db 时注入
|
|
47
46
|
* (通常与 SFMC_ROOT 对齐)
|
|
48
|
-
* 2.
|
|
47
|
+
* 2. PROJECT_ROOT/modules/packages —— 独立启动时兜底
|
|
49
48
|
*/
|
|
50
49
|
export declare function defaultPackagesDir(): string;
|
|
51
50
|
/**
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC9C;AAID,MAAM,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,WAAW,GAAE,MAA6B,GAAG,cAAc,CAoDvF;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,cAAc,GAAG,eAAe,CAgB5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAYnF"}
|
package/dist/manifest.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PROJECT_ROOT } from "./project-root.js";
|
|
4
4
|
const SUPPORTED_SCHEMA = 1;
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
5
|
function defaultPackagesDir() {
|
|
7
|
-
return process.env.SFMC_PACKAGES_DIR ||
|
|
6
|
+
return process.env.SFMC_PACKAGES_DIR || join(PROJECT_ROOT, "modules", "packages");
|
|
8
7
|
}
|
|
9
8
|
function loadManifest(packagesDir = defaultPackagesDir()) {
|
|
10
9
|
const modules = {};
|
|
@@ -12,9 +11,16 @@ function loadManifest(packagesDir = defaultPackagesDir()) {
|
|
|
12
11
|
console.warn(`[manifest] ${packagesDir} does not exist; db-server will run with zero modules`);
|
|
13
12
|
return { schemaVersion: SUPPORTED_SCHEMA, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), modules };
|
|
14
13
|
}
|
|
15
|
-
const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) =>
|
|
14
|
+
const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) => {
|
|
15
|
+
if (e.name.startsWith(".")) return false;
|
|
16
|
+
try {
|
|
17
|
+
return statSync(join(packagesDir, e.name)).isDirectory();
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}).map((e) => e.name).sort();
|
|
16
22
|
for (const id of ids) {
|
|
17
|
-
const manifestPath =
|
|
23
|
+
const manifestPath = join(packagesDir, id, "sapi", "manifest.json");
|
|
18
24
|
if (!existsSync(manifestPath)) continue;
|
|
19
25
|
let raw;
|
|
20
26
|
try {
|
|
@@ -35,7 +41,6 @@ function loadManifest(packagesDir = defaultPackagesDir()) {
|
|
|
35
41
|
}
|
|
36
42
|
modules[id] = {
|
|
37
43
|
name: parsed.name,
|
|
38
|
-
type: parsed.type,
|
|
39
44
|
configKey: parsed.configKey,
|
|
40
45
|
requires: Array.isArray(parsed.requires) ? parsed.requires : [],
|
|
41
46
|
handlers: Array.isArray(parsed.handlers) ? parsed.handlers : [],
|
package/dist/manifest.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 {
|
|
5
|
-
"mappings": "AAkBA,SAAS,YAAY,aAAa,
|
|
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, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { PROJECT_ROOT } from \"./project-root.js\";\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 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;\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. PROJECT_ROOT/modules/packages \u2014\u2014 \u72EC\u7ACB\u542F\u52A8\u65F6\u515C\u5E95\n */\nexport function defaultPackagesDir(): string {\n return process.env.SFMC_PACKAGES_DIR || join(PROJECT_ROOT, \"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) => {\n if (e.name.startsWith(\".\")) return false;\n try {\n return statSync(join(packagesDir, e.name)).isDirectory();\n } catch {\n return false;\n }\n })\n .map((e) => e.name)\n .sort();\n for (const id of ids) {\n const manifestPath = join(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 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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,
|
|
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,CAIpE;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,CAgChB;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"}
|
package/dist/module-auth.js
CHANGED
|
@@ -26,13 +26,9 @@ function buildModuleAuth(opts) {
|
|
|
26
26
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
27
|
secretGenerated: !opts.envAuthToken
|
|
28
28
|
});
|
|
29
|
-
log.success(
|
|
30
|
-
`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`
|
|
31
|
-
);
|
|
29
|
+
log.success(`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`);
|
|
32
30
|
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
|
-
);
|
|
31
|
+
log.warn("[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN,\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;");
|
|
36
32
|
}
|
|
37
33
|
if (process.env.SFMC_LOG_MODULE_TOKEN === "1") {
|
|
38
34
|
log.info("[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):");
|
package/dist/module-auth.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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(
|
|
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,
|
|
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)\n .update(`sfmc-module:${safeId(moduleId)}`)\n .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(`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`);\n if (!opts.envAuthToken) {\n log.warn(\"[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN,\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;\");\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 log.info(` ${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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../src/permission-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AACnC,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,sBAAsB;IACrC,gBAAgB;IAChB,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,sBAAsB;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAoBD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,IAAI,
|
|
1
|
+
{"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../src/permission-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AACnC,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,sBAAsB;IACrC,gBAAgB;IAChB,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,sBAAsB;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAoBD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,IAAI,CAcN;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAChD,QAAQ,EAAE,MAAM,GACf,OAAO,CAUT;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAChD,QAAQ,EAAE,MAAM,GACf,IAAI,CAIN;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAG3B,QAAQ,EAAE,MAAM;IAAS,UAAU,EAAE,MAAM;IAF9D,IAAI,SAAuB;IAC3B,MAAM,SAAO;IACb,YAAmB,QAAQ,EAAE,MAAM,EAAS,UAAU,EAAE,MAAM,EAE7D;CACF;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;aACf,MAAM,UAAU,MAAM;aACtB,OAAO,UAAU,MAAM;aACvB,UAAU,QAAQ,MAAM;aACxB,WAAW,QAAQ,MAAM;aACzB,OAAO,SAAS,MAAM;CACd,CAAC;AAEX,eAAO,MAAM,wBAAwB,aAAkB,CAAC"}
|
package/dist/permission-gate.js
CHANGED
|
@@ -7,13 +7,19 @@ const PERMISSION_KEYS = /* @__PURE__ */ new Set([
|
|
|
7
7
|
]);
|
|
8
8
|
function validPermissionKey(k) {
|
|
9
9
|
if (/^service:[A-Za-z0-9_.]+$/.test(k)) return true;
|
|
10
|
-
return /^[a-z]+:[a-z]+:[A-Za-z0-9_
|
|
10
|
+
return /^[a-z]+:[a-z]+:[A-Za-z0-9_.*]+$/.test(k);
|
|
11
11
|
}
|
|
12
12
|
function validateManifestPermissions(moduleId, permissions) {
|
|
13
13
|
const seen = /* @__PURE__ */ new Set();
|
|
14
14
|
for (const k of permissions) {
|
|
15
15
|
if (!k || typeof k !== "string") throw new Error(`[perm] ${moduleId}: permission \u4E0D\u662F\u5B57\u7B26\u4E32 "${k}"`);
|
|
16
|
-
if (!validPermissionKey(k))
|
|
16
|
+
if (!validPermissionKey(k)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`[perm] ${moduleId}: permission \u683C\u5F0F\u4E0D\u5408\u6CD5 "${k}"\u3002
|
|
19
|
+
manifest.permissions \u4EC5\u7528\u4E8E\u58F0\u660E\u5E73\u53F0\u5E95\u5C42\u8D44\u6E90\u6743\u9650\uFF08db:read:*, db:write:*, config:read:*, config:write:*, service:*\uFF09\u3002
|
|
20
|
+
\u73A9\u5BB6\u547D\u4EE4\u4E0E\u6E38\u620F\u5185\u6743\u9650\u8BF7\u5728\u4EE3\u7801\u4E2D\u4F7F\u7528 Command.register / Permission.register \u767B\u8BB0\uFF0C\u4E0D\u8981\u5199\u5165 manifest.json\u3002`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
17
23
|
if (seen.has(k)) throw new Error(`[perm] ${moduleId}: permission \u91CD\u590D "${k}"`);
|
|
18
24
|
seen.add(k);
|
|
19
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/permission-gate.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * permission-gate.ts \u2014 \u6A21\u5757\u6743\u9650\u6821\u9A8C\n *\n * \u542F\u52A8\u671F:manifest.permissions \u5168\u5728\u767D\u540D\u5355(PERMISSION_KEYS),\u5426\u5219\u629B\u9519\u542F\u52A8\u5931\u8D25\u3002\n * (\u767D\u540D\u5355 = db:read:*, db:write:*, config:read:*, config:write:*,\n * service:* + \u901A\u914D ** / <table>)\n *\n * \u8FD0\u884C\u65F6:\u53D1\u8D77 db / config / service \u8C03\u7528\u65F6,gatesdb.<op>.requirePermission =\n * \"db:read:<table>\" / \"db:write:<table>\" / \"config:read:<key>\" /\n * \"config:write:<key>\" / \"service:<name>\"\n *\n * \u8BBE\u8BA1:gate \u4E0D\u66FF\u8DEF\u7531\u62E6\u622A,\u662F\u4E1A\u52A1\u903B\u8F91\u81EA\u5DF1\u68C0\u67E5(\u907F\u514D\u6BCF\u6761\u8DEF\u7531\u90FD\u9A8C)\u3002\n * \u8DEF\u7531\u5C42 fail-closed:\u4E0D\u901A\u8FC7 manifest.permissions \u8868\u7684\u8C03\u7528 = \u76F4\u63A5 403\u3002\n */\n\nexport type PermissionKey = string;\nexport type PermissionLevel = \"Any\" | \"Member\" | \"OP\" | \"Admin\";\n\nexport interface ManifestPermissionRule {\n /** \u6A21\u5757\u58F0\u660E\u7684\u6743\u9650\u5168\u96C6 */\n permissions: PermissionKey[];\n /** \u8DE8\u6A21\u5757\u8C03\u7528\u5BF9\u5E94\u662F\u5426\u9700\u8981\u663E\u5F0F\u58F0\u660E */\n serviceRequires: string[];\n}\n\nconst PERMISSION_KEYS = new Set<string>([\n // db:* \u8D70\u6A21\u5F0F\u5339\u914D db:read:* / db:write:*\n // \u914D\u7F6E\u6743\u9650\n \"config:read:*\",\n \"config:write:*\",\n // service \u5728\u8C03\u7528\u65F6\u6309 service \u540D\u5B57\u5339\u914D service:* \u901A\u914D\n]);\n\nfunction validPermissionKey(k: string): boolean {\n // \u4E09\u7C7B\u5408\u6CD5\u683C\u5F0F:\n // db:<op>:<table> (\u4F8B db:read:lands / db:write:land_members)\n // config:<op>:<key> (\u4F8B config:read:land / config:write:afk)\n // service:<name> (\u4F8B service:economy.debit / service:land.byId \u2014 \u540D\u5B57\u53EF\u542B .)\n // \u4E0D\u5141\u8BB8 raw \"*\"(\u8981\u6C42\u660E\u786E\u5BF9\u8C61) \u2014 \u8C03\u8BD5\u65F6\u4E00\u5F8B Resource-level\n if (/^service:[A-Za-z0-9_.]+$/.test(k)) return true;\n return /^[a-z]+:[a-z]+:[A-Za-z0-9_
|
|
5
|
-
"mappings": "AAyBA,MAAM,kBAAkB,oBAAI,IAAY;AAAA;AAAA;AAAA,EAGtC;AAAA,EACA;AAAA;AAEF,CAAC;AAED,SAAS,mBAAmB,GAAoB;AAM9C,MAAI,2BAA2B,KAAK,CAAC,EAAG,QAAO;AAC/C,SAAO,
|
|
4
|
+
"sourcesContent": ["/**\n * permission-gate.ts \u2014 \u6A21\u5757\u6743\u9650\u6821\u9A8C\n *\n * \u542F\u52A8\u671F:manifest.permissions \u5168\u5728\u767D\u540D\u5355(PERMISSION_KEYS),\u5426\u5219\u629B\u9519\u542F\u52A8\u5931\u8D25\u3002\n * (\u767D\u540D\u5355 = db:read:*, db:write:*, config:read:*, config:write:*,\n * service:* + \u901A\u914D ** / <table>)\n *\n * \u8FD0\u884C\u65F6:\u53D1\u8D77 db / config / service \u8C03\u7528\u65F6,gatesdb.<op>.requirePermission =\n * \"db:read:<table>\" / \"db:write:<table>\" / \"config:read:<key>\" /\n * \"config:write:<key>\" / \"service:<name>\"\n *\n * \u8BBE\u8BA1:gate \u4E0D\u66FF\u8DEF\u7531\u62E6\u622A,\u662F\u4E1A\u52A1\u903B\u8F91\u81EA\u5DF1\u68C0\u67E5(\u907F\u514D\u6BCF\u6761\u8DEF\u7531\u90FD\u9A8C)\u3002\n * \u8DEF\u7531\u5C42 fail-closed:\u4E0D\u901A\u8FC7 manifest.permissions \u8868\u7684\u8C03\u7528 = \u76F4\u63A5 403\u3002\n */\n\nexport type PermissionKey = string;\nexport type PermissionLevel = \"Any\" | \"Member\" | \"OP\" | \"Admin\";\n\nexport interface ManifestPermissionRule {\n /** \u6A21\u5757\u58F0\u660E\u7684\u6743\u9650\u5168\u96C6 */\n permissions: PermissionKey[];\n /** \u8DE8\u6A21\u5757\u8C03\u7528\u5BF9\u5E94\u662F\u5426\u9700\u8981\u663E\u5F0F\u58F0\u660E */\n serviceRequires: string[];\n}\n\nconst PERMISSION_KEYS = new Set<string>([\n // db:* \u8D70\u6A21\u5F0F\u5339\u914D db:read:* / db:write:*\n // \u914D\u7F6E\u6743\u9650\n \"config:read:*\",\n \"config:write:*\",\n // service \u5728\u8C03\u7528\u65F6\u6309 service \u540D\u5B57\u5339\u914D service:* \u901A\u914D\n]);\n\nfunction validPermissionKey(k: string): boolean {\n // \u4E09\u7C7B\u5408\u6CD5\u683C\u5F0F:\n // db:<op>:<table> (\u4F8B db:read:lands / db:write:land_members)\n // config:<op>:<key> (\u4F8B config:read:land / config:write:afk)\n // service:<name> (\u4F8B service:economy.debit / service:land.byId \u2014 \u540D\u5B57\u53EF\u542B .)\n // \u4E0D\u5141\u8BB8 raw \"*\"(\u8981\u6C42\u660E\u786E\u5BF9\u8C61) \u2014 \u8C03\u8BD5\u65F6\u4E00\u5F8B Resource-level\n if (/^service:[A-Za-z0-9_.]+$/.test(k)) return true;\n return /^[a-z]+:[a-z]+:[A-Za-z0-9_.*]+$/.test(k);\n}\n\n/**\n * \u542F\u52A8\u671F\u4E00\u6B21\u6027\u6821\u9A8C:manifest.permissions \u662F\u5426\u90FD\u5408\u89C4\u3002\n * \u5931\u8D25 = \u629B\u9519(\u542F\u52A8\u5931\u8D25)\u3002\n */\nexport function validateManifestPermissions(\n moduleId: string,\n permissions: readonly string[]\n): void {\n const seen = new Set<string>();\n for (const k of permissions) {\n if (!k || typeof k !== \"string\") throw new Error(`[perm] ${moduleId}: permission \u4E0D\u662F\u5B57\u7B26\u4E32 \"${k}\"`);\n if (!validPermissionKey(k)) {\n throw new Error(\n `[perm] ${moduleId}: permission \u683C\u5F0F\u4E0D\u5408\u6CD5 \"${k}\"\u3002\\n` +\n ` manifest.permissions \u4EC5\u7528\u4E8E\u58F0\u660E\u5E73\u53F0\u5E95\u5C42\u8D44\u6E90\u6743\u9650\uFF08db:read:*, db:write:*, config:read:*, config:write:*, service:*\uFF09\u3002\\n` +\n ` \u73A9\u5BB6\u547D\u4EE4\u4E0E\u6E38\u620F\u5185\u6743\u9650\u8BF7\u5728\u4EE3\u7801\u4E2D\u4F7F\u7528 Command.register / Permission.register \u767B\u8BB0\uFF0C\u4E0D\u8981\u5199\u5165 manifest.json\u3002`\n );\n }\n if (seen.has(k)) throw new Error(`[perm] ${moduleId}: permission \u91CD\u590D \"${k}\"`);\n seen.add(k);\n }\n}\n\n/**\n * \u8FD0\u884C\u65F6:\u6A21\u5757 id \u662F\u5426\u62E5\u6709\u6240\u9700 permission\u3002\n * \u5F53\u524D\u5B9E\u73B0:\u5FC5\u987B\u7CBE\u786E\u7B49\u4E8E\u624D\u653E\u884C,\u4E0D\u89E3\u91CA\u901A\u914D\u3002\n * (PoC \u7B80\u5316;\u751F\u4EA7\u53EF\u52A0 db:read:* \u2192 \u901A\u914D)\n */\nexport function moduleHasPermission(\n modulePermissions: readonly string[] | undefined,\n required: string\n): boolean {\n if (!modulePermissions) return false;\n if (modulePermissions.includes(required)) return true;\n // db:read:* / db:write:* / config:read:* / config:write:* \u901A\u914D\n const parts = required.split(\":\");\n if (parts.length === 3) {\n const wildcard = `${parts[0]}:${parts[1]}:*`;\n if (modulePermissions.includes(wildcard)) return true;\n }\n return false;\n}\n\nexport function assertModulePermission(\n moduleId: string,\n modulePermissions: readonly string[] | undefined,\n required: string\n): void {\n if (!moduleHasPermission(modulePermissions, required)) {\n throw new PermissionDeniedError(moduleId, required);\n }\n}\n\nexport class PermissionDeniedError extends Error {\n code = \"permission_denied\";\n status = 403;\n constructor(public moduleId: string, public permission: string) {\n super(`[perm] \u6A21\u5757 ${moduleId} \u7F3A\u5C11\u6743\u9650 \"${permission}\"`);\n }\n}\n\n/**\n * \u6240\u6709 platform \u6D3E\u53D1\u7684 required permission key \u8BA1\u7B97\u52A9\u624B\u3002\n */\nexport const Perm = {\n dbRead: (table: string) => `db:read:${table}`,\n dbWrite: (table: string) => `db:write:${table}`,\n configRead: (key: string) => `config:read:${key}`,\n configWrite: (key: string) => `config:write:${key}`,\n service: (name: string) => `service:${name}`,\n} as const;\n\nexport const PlatformTablePermissions = PERMISSION_KEYS;\n"],
|
|
5
|
+
"mappings": "AAyBA,MAAM,kBAAkB,oBAAI,IAAY;AAAA;AAAA;AAAA,EAGtC;AAAA,EACA;AAAA;AAEF,CAAC;AAED,SAAS,mBAAmB,GAAoB;AAM9C,MAAI,2BAA2B,KAAK,CAAC,EAAG,QAAO;AAC/C,SAAO,kCAAkC,KAAK,CAAC;AACjD;AAMO,SAAS,4BACd,UACA,aACM;AACN,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,aAAa;AAC3B,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,OAAM,IAAI,MAAM,UAAU,QAAQ,gDAAuB,CAAC,GAAG;AAC9F,QAAI,CAAC,mBAAmB,CAAC,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR,UAAU,QAAQ,gDAAuB,CAAC;AAAA;AAAA;AAAA,MAG5C;AAAA,IACF;AACA,QAAI,KAAK,IAAI,CAAC,EAAG,OAAM,IAAI,MAAM,UAAU,QAAQ,8BAAoB,CAAC,GAAG;AAC3E,SAAK,IAAI,CAAC;AAAA,EACZ;AACF;AAOO,SAAS,oBACd,mBACA,UACS;AACT,MAAI,CAAC,kBAAmB,QAAO;AAC/B,MAAI,kBAAkB,SAAS,QAAQ,EAAG,QAAO;AAEjD,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,WAAW,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACxC,QAAI,kBAAkB,SAAS,QAAQ,EAAG,QAAO;AAAA,EACnD;AACA,SAAO;AACT;AAEO,SAAS,uBACd,UACA,mBACA,UACM;AACN,MAAI,CAAC,oBAAoB,mBAAmB,QAAQ,GAAG;AACrD,UAAM,IAAI,sBAAsB,UAAU,QAAQ;AAAA,EACpD;AACF;AAEO,MAAM,8BAA8B,MAAM;AAAA,EAG/C,YAAmB,UAAyB,YAAoB;AAC9D,UAAM,uBAAa,QAAQ,8BAAU,UAAU,GAAG;AADjC;AAAyB;AAAA,EAE5C;AAAA,EAFmB;AAAA,EAAyB;AAAA,EAF5C,OAAO;AAAA,EACP,SAAS;AAIX;AAKO,MAAM,OAAO;AAAA,EAClB,QAAQ,CAAC,UAAkB,WAAW,KAAK;AAAA,EAC3C,SAAS,CAAC,UAAkB,YAAY,KAAK;AAAA,EAC7C,YAAY,CAAC,QAAgB,eAAe,GAAG;AAAA,EAC/C,aAAa,CAAC,QAAgB,gBAAgB,GAAG;AAAA,EACjD,SAAS,CAAC,SAAiB,WAAW,IAAI;AAC5C;AAEO,MAAM,2BAA2B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../src/project-root.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,wCAAwC;AACxC,eAAO,MAAM,YAAY,EAAE,MAAiC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { resolveRuntimeRoot } from "@sfmc-bds/sdk/node/config";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const PROJECT_ROOT = resolveRuntimeRoot(HERE);
|
|
6
|
+
export {
|
|
7
|
+
PROJECT_ROOT
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=project-root.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/project-root.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * project-root.ts \u2014 \u4ED3\u6839\u552F\u4E00\u89E3\u6790\u70B9\uFF08DRY/DIP\uFF1Aenv / log \u52FF\u5404\u81EA\u4E0A\u6EAF\uFF09\n */\n\nimport { resolveRuntimeRoot } from \"@sfmc-bds/sdk/node/config\";\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\n\n/** SFMC_ROOT > \u4ECE\u672C\u6587\u4EF6\u5411\u4E0A\u627E sfmc-monorepo */\nexport const PROJECT_ROOT: string = resolveRuntimeRoot(HERE);\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAE9B,MAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG5C,MAAM,eAAuB,mBAAmB,IAAI;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/routes/_shared.d.ts
CHANGED
|
@@ -59,14 +59,23 @@ export type RouteFactory = (deps: Partial<RouteDeps>) => RouteHandler;
|
|
|
59
59
|
export declare const json: typeof sharedJson;
|
|
60
60
|
export declare const body: typeof sharedBody;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* 标准失败响应信封生成工具(格式:`{ ok: false, error, code? }` + 可选 extra 字段)。
|
|
63
|
+
* 路由层与鉴权中间件统一调用此助手,杜绝与 `{ success: false }` 字段混用。
|
|
64
|
+
*
|
|
65
|
+
* @param res HTTP 响应对象。
|
|
66
|
+
* @param error 错误简短描述。
|
|
67
|
+
* @param status HTTP 状态码。
|
|
68
|
+
* @param code 可选的精细业务错误码。
|
|
69
|
+
* @param extra 可选的附加字段字典。
|
|
65
70
|
*/
|
|
66
71
|
export declare function jsonV2Fail(res: ServerResponse, error: string, status: number, code?: string, extra?: Record<string, unknown>): void;
|
|
67
72
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
73
|
+
* 标准成功响应信封生成工具(格式:`{ ok: true, ...fields }`)。
|
|
74
|
+
* 与 `jsonV2Fail` 对称统一。
|
|
75
|
+
*
|
|
76
|
+
* @param res HTTP 响应对象。
|
|
77
|
+
* @param fields 响应正文字段字典。
|
|
78
|
+
* @param status HTTP 状态码(默认为 200)。
|
|
70
79
|
*/
|
|
71
80
|
export declare function jsonV2Ok(res: ServerResponse, fields?: Record<string, unknown>, status?: number): void;
|
|
72
81
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/routes/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,IAAI,IAAI,UAAU,EAAE,IAAI,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,OAAO,EAAE,CAAC;AAExB,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpF,IAAI,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjE,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC7D,oBAAoB,CAAC,EAAE,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,KACf;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;CACxD,CAAC;AAEF,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,qBAAqB;IACrB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEzE,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,YAAY,CAAC;AAEtE,sCAAsC;AACtC,eAAO,MAAM,IAAI,mBAAa,CAAC;AAC/B,eAAO,MAAM,IAAI,mBAAa,CAAC;AAE/B
|
|
1
|
+
{"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/routes/_shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/_shared.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/_shared.ts \u2014 \u8DEF\u7531\u6A21\u5757\u5171\u4EAB\u7C7B\u578B + \u5DE5\u5382\n *\n * \u6240\u6709 db-server \u8DEF\u7531\u7EDF\u4E00:\n * - deps: { query, db, body, json, ... } \u2190 \u5171\u4EAB\u6CE8\u5165\n * - ctx: { path, method, params, req, res } \u2190 \u5171\u4EAB\u8BF7\u6C42\u4E0A\u4E0B\u6587\n * - return: boolean \u2190 true \u8868\u793A\u5DF2\u5904\u7406\n *\n * http \u5DE5\u5177 (json / body) \u6765\u81EA lib/http.ts\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { Method } from \"../lib/http.js\";\nimport { body as sharedBody, json as sharedJson } from \"../lib/http.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nexport type { Method } from \"../lib/http.js\";\nexport type { QueryFn };\n\n/** shared deps factory \u2014 \u8DEF\u7531\u53EF\u58F0\u660E\u6700\u5C0F\u5316 deps \u5B50\u96C6 */\nexport type RouteDeps = {\n query: QueryFn;\n db: DatabaseSync;\n json: (res: ServerResponse, data: Record<string, unknown>, status?: number) => void;\n body: (req: IncomingMessage) => Promise<Record<string, unknown>>;\n params: URLSearchParams;\n projectRoot: string;\n monitorState?: { metrics: object | null; players: object[] };\n ensureEconomyAccount?: (\n playerId: string,\n playerName: string\n ) => { balance: number; player_id: string; version: number };\n economyResult?: (account: object | undefined) => { balance: number; version: number } | null;\n forwardToQQBridge?: (channelId: string, fromName: string, content: string, fromId: string) => void;\n land?: Record<string, (...args: unknown[]) => unknown>;\n};\n\n/** v2 \u6A21\u5757\u8EAB\u4EFD \u2014 \u7531 handle \u6821\u9A8C\u540E\u5199\u5165\u8DEF\u7531 ctx(\u52FF\u6302 req \u79C1\u6709\u5B57\u6BB5,LoD)\u3002 */\nexport type ModuleAuth = { id: string; permissions: string[] };\n\nexport type RouteCtx = {\n path: string;\n method: Method | string;\n params: URLSearchParams;\n req: IncomingMessage;\n res: ServerResponse;\n /** v2 \u8DEF\u7531\u4E13\u7528:\u6A21\u5757\u9274\u6743\u7ED3\u679C */\n moduleAuth?: ModuleAuth;\n};\n\nexport type RouteHandler = (ctx: RouteCtx) => Promise<boolean> | boolean;\n\nexport type RouteFactory = (deps: Partial<RouteDeps>) => RouteHandler;\n\n/** \u4FBF\u6377 re-export\uFF1A\u8DEF\u7531\u9ED8\u8BA4\u4ECE\u8FD9\u91CC\u53D6 json/body */\nexport const json = sharedJson;\nexport const body = sharedBody;\n\n/**\n *
|
|
5
|
-
"mappings": "AAcA,SAAS,QAAQ,YAAY,QAAQ,kBAAkB;AA0ChD,MAAM,OAAO;AACb,MAAM,OAAO;
|
|
4
|
+
"sourcesContent": ["/**\n * routes/_shared.ts \u2014 \u8DEF\u7531\u6A21\u5757\u5171\u4EAB\u7C7B\u578B + \u5DE5\u5382\n *\n * \u6240\u6709 db-server \u8DEF\u7531\u7EDF\u4E00:\n * - deps: { query, db, body, json, ... } \u2190 \u5171\u4EAB\u6CE8\u5165\n * - ctx: { path, method, params, req, res } \u2190 \u5171\u4EAB\u8BF7\u6C42\u4E0A\u4E0B\u6587\n * - return: boolean \u2190 true \u8868\u793A\u5DF2\u5904\u7406\n *\n * http \u5DE5\u5177 (json / body) \u6765\u81EA lib/http.ts\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { Method } from \"../lib/http.js\";\nimport { body as sharedBody, json as sharedJson } from \"../lib/http.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nexport type { Method } from \"../lib/http.js\";\nexport type { QueryFn };\n\n/** shared deps factory \u2014 \u8DEF\u7531\u53EF\u58F0\u660E\u6700\u5C0F\u5316 deps \u5B50\u96C6 */\nexport type RouteDeps = {\n query: QueryFn;\n db: DatabaseSync;\n json: (res: ServerResponse, data: Record<string, unknown>, status?: number) => void;\n body: (req: IncomingMessage) => Promise<Record<string, unknown>>;\n params: URLSearchParams;\n projectRoot: string;\n monitorState?: { metrics: object | null; players: object[] };\n ensureEconomyAccount?: (\n playerId: string,\n playerName: string\n ) => { balance: number; player_id: string; version: number };\n economyResult?: (account: object | undefined) => { balance: number; version: number } | null;\n forwardToQQBridge?: (channelId: string, fromName: string, content: string, fromId: string) => void;\n land?: Record<string, (...args: unknown[]) => unknown>;\n};\n\n/** v2 \u6A21\u5757\u8EAB\u4EFD \u2014 \u7531 handle \u6821\u9A8C\u540E\u5199\u5165\u8DEF\u7531 ctx(\u52FF\u6302 req \u79C1\u6709\u5B57\u6BB5,LoD)\u3002 */\nexport type ModuleAuth = { id: string; permissions: string[] };\n\nexport type RouteCtx = {\n path: string;\n method: Method | string;\n params: URLSearchParams;\n req: IncomingMessage;\n res: ServerResponse;\n /** v2 \u8DEF\u7531\u4E13\u7528:\u6A21\u5757\u9274\u6743\u7ED3\u679C */\n moduleAuth?: ModuleAuth;\n};\n\nexport type RouteHandler = (ctx: RouteCtx) => Promise<boolean> | boolean;\n\nexport type RouteFactory = (deps: Partial<RouteDeps>) => RouteHandler;\n\n/** \u4FBF\u6377 re-export\uFF1A\u8DEF\u7531\u9ED8\u8BA4\u4ECE\u8FD9\u91CC\u53D6 json/body */\nexport const json = sharedJson;\nexport const body = sharedBody;\n\n/**\n * \u6807\u51C6\u5931\u8D25\u54CD\u5E94\u4FE1\u5C01\u751F\u6210\u5DE5\u5177\uFF08\u683C\u5F0F\uFF1A`{ ok: false, error, code? }` + \u53EF\u9009 extra \u5B57\u6BB5\uFF09\u3002\n * \u8DEF\u7531\u5C42\u4E0E\u9274\u6743\u4E2D\u95F4\u4EF6\u7EDF\u4E00\u8C03\u7528\u6B64\u52A9\u624B\uFF0C\u675C\u7EDD\u4E0E `{ success: false }` \u5B57\u6BB5\u6DF7\u7528\u3002\n *\n * @param res HTTP \u54CD\u5E94\u5BF9\u8C61\u3002\n * @param error \u9519\u8BEF\u7B80\u77ED\u63CF\u8FF0\u3002\n * @param status HTTP \u72B6\u6001\u7801\u3002\n * @param code \u53EF\u9009\u7684\u7CBE\u7EC6\u4E1A\u52A1\u9519\u8BEF\u7801\u3002\n * @param extra \u53EF\u9009\u7684\u9644\u52A0\u5B57\u6BB5\u5B57\u5178\u3002\n */\nexport function jsonV2Fail(\n res: ServerResponse,\n error: string,\n status: number,\n code?: string,\n extra?: Record<string, unknown>\n): void {\n const payload: Record<string, unknown> = { ...(extra ?? {}), ok: false, error };\n if (code) payload.code = code;\n sharedJson(res, payload, status);\n}\n\n/**\n * \u6807\u51C6\u6210\u529F\u54CD\u5E94\u4FE1\u5C01\u751F\u6210\u5DE5\u5177\uFF08\u683C\u5F0F\uFF1A`{ ok: true, ...fields }`\uFF09\u3002\n * \u4E0E `jsonV2Fail` \u5BF9\u79F0\u7EDF\u4E00\u3002\n *\n * @param res HTTP \u54CD\u5E94\u5BF9\u8C61\u3002\n * @param fields \u54CD\u5E94\u6B63\u6587\u5B57\u6BB5\u5B57\u5178\u3002\n * @param status HTTP \u72B6\u6001\u7801\uFF08\u9ED8\u8BA4\u4E3A 200\uFF09\u3002\n */\nexport function jsonV2Ok(\n res: ServerResponse,\n fields: Record<string, unknown> = {},\n status = 200\n): void {\n sharedJson(res, { ...fields, ok: true }, status);\n}\n\n\nexport {};\n\n"],
|
|
5
|
+
"mappings": "AAcA,SAAS,QAAQ,YAAY,QAAQ,kBAAkB;AA0ChD,MAAM,OAAO;AACb,MAAM,OAAO;AAYb,SAAS,WACd,KACA,OACA,QACA,MACA,OACM;AACN,QAAM,UAAmC,EAAE,GAAI,SAAS,CAAC,GAAI,IAAI,OAAO,MAAM;AAC9E,MAAI,KAAM,SAAQ,OAAO;AACzB,aAAW,KAAK,SAAS,MAAM;AACjC;AAUO,SAAS,SACd,KACA,SAAkC,CAAC,GACnC,SAAS,KACH;AACN,aAAW,KAAK,EAAE,GAAG,QAAQ,IAAI,KAAK,GAAG,MAAM;AACjD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/routes/config.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* routes/config.ts —
|
|
2
|
+
* routes/config.ts — 配置路由
|
|
3
3
|
*
|
|
4
4
|
* 路由列表:
|
|
5
|
-
* GET /api/sfmc/configs/all — SAPI
|
|
6
|
-
* GET /api/sfmc/settings — 平铺 settings.json
|
|
7
|
-
* GET /api/sfmc/settings/:key — 含 land:* / bridge_channel_id 等 fallback
|
|
8
|
-
* GET /api/sfmc/areas
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* GET /api/sfmc/grids — grids.json
|
|
13
|
-
* GET /api/sfmc/peace_filters — peace_filters.json
|
|
14
|
-
* GET /api/sfmc/qa — questions.json
|
|
5
|
+
* GET /api/sfmc/configs/all — SAPI ConfigManager 启动快照(仅平台域)
|
|
6
|
+
* GET /api/sfmc/settings — 平铺 settings.json(legacy)
|
|
7
|
+
* GET /api/sfmc/settings/:key — 含 land:* / bridge_channel_id 等 fallback
|
|
8
|
+
* GET /api/sfmc/areas|permissions|… — legacy 只读 JSON(文件名对 db-server 为黑箱)
|
|
9
|
+
*
|
|
10
|
+
* DIP:模块/legacy JSON 不进 SDK ConfigName;本路由用 configDir + 文件名拼接路径。
|
|
11
|
+
* 模块私有配置的读写权威入口是 module-config-routes(configs/:configKey)。
|
|
15
12
|
*/
|
|
16
13
|
interface Deps {
|
|
17
14
|
json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAmCD,iBAAS,kBAAkB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,IAAI,iDAyGhF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAuErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/routes/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { configDir, configPath, readJson } from "@sfmc-bds/sdk/node/config";
|
|
2
3
|
function isMetaKey(k) {
|
|
3
4
|
return k === "$schema" || String(k).startsWith("_");
|
|
4
5
|
}
|
|
@@ -28,40 +29,33 @@ function arrayOrEmpty(v) {
|
|
|
28
29
|
return Array.isArray(v) ? v : [];
|
|
29
30
|
}
|
|
30
31
|
function createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }) {
|
|
31
|
-
const
|
|
32
|
+
const readPlatform = (name) => readJson(configPath(projectRoot, name));
|
|
33
|
+
const readOpaque = (fileName) => readJson(join(configDir(projectRoot), fileName));
|
|
32
34
|
function getAllConfigs() {
|
|
33
35
|
const modules = typeof listModules === "function" ? listModules() : [];
|
|
34
36
|
const module_tokens = typeof getModuleTokens === "function" ? getModuleTokens() : {};
|
|
35
37
|
return {
|
|
36
|
-
// 与 /api/sfmc/modules 同源;ConfigManager.init 一次拉齐启停态(DRY)
|
|
37
38
|
modules,
|
|
38
|
-
// loopback-only 下发;SAPI 无 fs,靠此注入模块身份(DIP)
|
|
39
39
|
module_tokens,
|
|
40
|
-
settings: stripMeta(
|
|
41
|
-
|
|
42
|
-
permissions: getPermissions(),
|
|
43
|
-
banned_items: getBannedItems(),
|
|
44
|
-
clean: getClean(),
|
|
45
|
-
grids: getGrids(),
|
|
46
|
-
peace_filters: getPeaceFilters(),
|
|
47
|
-
questions: getQA()
|
|
40
|
+
settings: stripMeta(readOpaque("settings.json")),
|
|
41
|
+
permissions: getPermissions()
|
|
48
42
|
};
|
|
49
43
|
}
|
|
50
44
|
function getSettingsFlat() {
|
|
51
|
-
const obj = stripMeta(
|
|
45
|
+
const obj = stripMeta(readOpaque("settings.json"));
|
|
52
46
|
return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));
|
|
53
47
|
}
|
|
54
48
|
function getSettingByKey(key) {
|
|
55
|
-
const settings =
|
|
49
|
+
const settings = readOpaque("settings.json") ?? {};
|
|
56
50
|
if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {
|
|
57
51
|
return { value: settings[key], source: "settings.json" };
|
|
58
52
|
}
|
|
59
53
|
if (key === "bridge_channel_id") {
|
|
60
|
-
const qq =
|
|
54
|
+
const qq = readPlatform("qq_config.json") ?? {};
|
|
61
55
|
if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: "qq_config.json" };
|
|
62
56
|
}
|
|
63
57
|
if (key.startsWith("land:")) {
|
|
64
|
-
const land =
|
|
58
|
+
const land = readOpaque("land.json") ?? {};
|
|
65
59
|
if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {
|
|
66
60
|
return { value: land[key], source: "land.json" };
|
|
67
61
|
}
|
|
@@ -69,28 +63,28 @@ function createConfigRoutes({ json, projectRoot, listModules, getModuleTokens })
|
|
|
69
63
|
return { value: null };
|
|
70
64
|
}
|
|
71
65
|
function getAreas() {
|
|
72
|
-
return arrayOrEmpty(
|
|
66
|
+
return arrayOrEmpty(readOpaque("areas.json")).filter((r) => r && r.module && r.dimension != null).map((r) => stripMetaDeep(r));
|
|
73
67
|
}
|
|
74
68
|
function getPermissions() {
|
|
75
|
-
return arrayOrEmpty(
|
|
69
|
+
return arrayOrEmpty(readPlatform("permissions.json")).filter((r) => r && r.player_name).map((r) => stripMetaDeep(r));
|
|
76
70
|
}
|
|
77
71
|
function getBannedItems() {
|
|
78
|
-
return arrayOrEmpty(
|
|
72
|
+
return arrayOrEmpty(readOpaque("banned_items.json")).filter(
|
|
79
73
|
(i) => typeof i === "string" && i && !i.startsWith("_")
|
|
80
74
|
);
|
|
81
75
|
}
|
|
82
76
|
function getClean() {
|
|
83
|
-
const c = stripMetaDeep(
|
|
77
|
+
const c = stripMetaDeep(readOpaque("clean.json") ?? {});
|
|
84
78
|
return { item_max: c.item_max ?? 192, poll_interval: c.poll_interval ?? 60 };
|
|
85
79
|
}
|
|
86
80
|
function getGrids() {
|
|
87
|
-
return arrayOrEmpty(
|
|
81
|
+
return arrayOrEmpty(readOpaque("grids.json")).filter((r) => r && r.name).map((r) => stripMetaDeep(r));
|
|
88
82
|
}
|
|
89
83
|
function getPeaceFilters() {
|
|
90
|
-
return arrayOrEmpty(
|
|
84
|
+
return arrayOrEmpty(readOpaque("peace_filters.json")).filter((r) => r && r.family).map((r) => stripMetaDeep(r));
|
|
91
85
|
}
|
|
92
86
|
function getQA() {
|
|
93
|
-
return arrayOrEmpty(
|
|
87
|
+
return arrayOrEmpty(readOpaque("questions.json")).filter((r) => r && r.question).map((r, idx) => {
|
|
94
88
|
const clean = stripMetaDeep(r);
|
|
95
89
|
return {
|
|
96
90
|
id: idx + 1,
|