@sfmc-bds/db-server 0.2.3 → 0.3.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.
- package/dist/domain/metrics.d.ts +22 -0
- package/dist/domain/metrics.d.ts.map +1 -0
- package/dist/domain/metrics.js +64 -0
- package/dist/domain/metrics.js.map +7 -0
- package/dist/domain/process-resources.d.ts +8 -0
- package/dist/domain/process-resources.d.ts.map +1 -0
- package/dist/domain/process-resources.js +41 -0
- package/dist/domain/process-resources.js.map +7 -0
- package/dist/domain/schema.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +2 -2
- package/dist/manifest-loader.d.ts +0 -2
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +9 -9
- package/dist/manifest-loader.js.map +2 -2
- package/dist/routes/qq-join.d.ts +0 -3
- package/dist/routes/qq-join.d.ts.map +1 -1
- package/dist/routes/qq-join.js +1 -53
- package/dist/routes/qq-join.js.map +2 -2
- package/dist/routes/status.d.ts +3 -1
- package/dist/routes/status.d.ts.map +1 -1
- package/dist/routes/status.js +53 -4
- package/dist/routes/status.js.map +2 -2
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +3 -1
- package/dist/service-registry.js.map +2 -2
- package/package.json +4 -4
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** 运行指标的受控校验与有界历史;只记录平台上报,不接受任意表名或 SQL。 */
|
|
2
|
+
import type { QueryFn } from "../lib/sqlite.js";
|
|
3
|
+
export interface MetricsSample {
|
|
4
|
+
recordedAt: number;
|
|
5
|
+
bootId: string;
|
|
6
|
+
tps: number | null;
|
|
7
|
+
onlineCount: number;
|
|
8
|
+
entities: Record<string, number | null>;
|
|
9
|
+
entitiesUpdatedAt: number | null;
|
|
10
|
+
chunkEstimate: number | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseMetrics(data: Record<string, unknown>, now: number): MetricsSample | null;
|
|
13
|
+
export declare class MetricsHistory {
|
|
14
|
+
private query;
|
|
15
|
+
private lastWrite;
|
|
16
|
+
private lastPrune;
|
|
17
|
+
constructor(query: QueryFn);
|
|
18
|
+
append(sample: MetricsSample): void;
|
|
19
|
+
private prune;
|
|
20
|
+
read(now?: number): MetricsSample[];
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/domain/metrics.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAKD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAoC7F;AAED,qBAAa,cAAc;IAGb,OAAO,CAAC,KAAK;IAFzB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAAK;IACtB,YAAoB,KAAK,EAAE,OAAO,EAEjC;IACD,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAYlC;IACD,OAAO,CAAC,KAAK;IAMb,IAAI,CAAC,GAAG,SAAa,GAAG,aAAa,EAAE,CAYtC;CACF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const DIMENSIONS = ["minecraft:overworld", "minecraft:nether", "minecraft:the_end"];
|
|
2
|
+
const count = (value) => value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 1e8;
|
|
3
|
+
function parseMetrics(data, now) {
|
|
4
|
+
if (!Array.isArray(data.players) || data.players.length > 1e3) return null;
|
|
5
|
+
if (typeof data.bootId !== "string" || !/^[a-zA-Z0-9-]{1,96}$/.test(data.bootId) || !(data.tps === null || typeof data.tps === "number" && Number.isFinite(data.tps) && data.tps >= 0 && data.tps <= 20) || !count(data.chunkEstimate) || typeof data.onlineCount !== "number" || data.onlineCount !== data.players.length || !data.entities || typeof data.entities !== "object" || Array.isArray(data.entities) || !(data.entitiesUpdatedAt === null || typeof data.entitiesUpdatedAt === "number" && Number.isSafeInteger(data.entitiesUpdatedAt) && data.entitiesUpdatedAt > 0 && data.entitiesUpdatedAt <= now + 2e3))
|
|
6
|
+
return null;
|
|
7
|
+
const raw = data.entities;
|
|
8
|
+
if (Object.keys(raw).some((key) => !DIMENSIONS.includes(key)) || DIMENSIONS.some((key) => !count(raw[key])))
|
|
9
|
+
return null;
|
|
10
|
+
return {
|
|
11
|
+
recordedAt: now,
|
|
12
|
+
bootId: data.bootId,
|
|
13
|
+
tps: data.tps,
|
|
14
|
+
onlineCount: data.onlineCount,
|
|
15
|
+
entities: { ...raw },
|
|
16
|
+
entitiesUpdatedAt: data.entitiesUpdatedAt,
|
|
17
|
+
chunkEstimate: data.chunkEstimate
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
class MetricsHistory {
|
|
21
|
+
constructor(query) {
|
|
22
|
+
this.query = query;
|
|
23
|
+
query("CREATE TABLE IF NOT EXISTS sfmc_runtime_metrics (recorded_at INTEGER PRIMARY KEY, sample TEXT NOT NULL)");
|
|
24
|
+
}
|
|
25
|
+
query;
|
|
26
|
+
lastWrite = 0;
|
|
27
|
+
lastPrune = 0;
|
|
28
|
+
append(sample) {
|
|
29
|
+
if (sample.recordedAt - this.lastWrite < 3e3) return;
|
|
30
|
+
this.query("INSERT OR REPLACE INTO sfmc_runtime_metrics (recorded_at, sample) VALUES (?, ?)", [
|
|
31
|
+
sample.recordedAt,
|
|
32
|
+
JSON.stringify(sample)
|
|
33
|
+
]);
|
|
34
|
+
if (sample.recordedAt - this.lastPrune > 6e4) {
|
|
35
|
+
this.prune(sample.recordedAt);
|
|
36
|
+
this.lastPrune = sample.recordedAt;
|
|
37
|
+
}
|
|
38
|
+
this.lastWrite = sample.recordedAt;
|
|
39
|
+
}
|
|
40
|
+
prune(now) {
|
|
41
|
+
this.query("DELETE FROM sfmc_runtime_metrics WHERE recorded_at < ?", [now - 72 * 36e5]);
|
|
42
|
+
this.query(
|
|
43
|
+
"DELETE FROM sfmc_runtime_metrics WHERE recorded_at NOT IN (SELECT recorded_at FROM sfmc_runtime_metrics ORDER BY recorded_at DESC LIMIT 60000)"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
read(now = Date.now()) {
|
|
47
|
+
const rows = this.query(
|
|
48
|
+
"SELECT sample FROM sfmc_runtime_metrics WHERE recorded_at >= ? ORDER BY recorded_at DESC LIMIT 720",
|
|
49
|
+
[now - 36e5]
|
|
50
|
+
);
|
|
51
|
+
return rows.reverse().flatMap((row) => {
|
|
52
|
+
try {
|
|
53
|
+
return [JSON.parse(row.sample)];
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
MetricsHistory,
|
|
62
|
+
parseMetrics
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/domain/metrics.ts"],
|
|
4
|
+
"sourcesContent": ["/** \u8FD0\u884C\u6307\u6807\u7684\u53D7\u63A7\u6821\u9A8C\u4E0E\u6709\u754C\u5386\u53F2\uFF1B\u53EA\u8BB0\u5F55\u5E73\u53F0\u4E0A\u62A5\uFF0C\u4E0D\u63A5\u53D7\u4EFB\u610F\u8868\u540D\u6216 SQL\u3002 */\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nexport interface MetricsSample {\n recordedAt: number;\n bootId: string;\n tps: number | null;\n onlineCount: number;\n entities: Record<string, number | null>;\n entitiesUpdatedAt: number | null;\n chunkEstimate: number | null;\n}\nconst DIMENSIONS = [\"minecraft:overworld\", \"minecraft:nether\", \"minecraft:the_end\"];\nconst count = (value: unknown) =>\n value === null || (typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0 && value <= 100_000_000);\n\nexport function parseMetrics(data: Record<string, unknown>, now: number): MetricsSample | null {\n if (!Array.isArray(data.players) || data.players.length > 1000) return null;\n if (\n typeof data.bootId !== \"string\" ||\n !/^[a-zA-Z0-9-]{1,96}$/.test(data.bootId) ||\n !(\n data.tps === null ||\n (typeof data.tps === \"number\" && Number.isFinite(data.tps) && data.tps >= 0 && data.tps <= 20)\n ) ||\n !count(data.chunkEstimate) ||\n typeof data.onlineCount !== \"number\" ||\n data.onlineCount !== (data.players as unknown[]).length ||\n !data.entities ||\n typeof data.entities !== \"object\" ||\n Array.isArray(data.entities) ||\n !(\n data.entitiesUpdatedAt === null ||\n (typeof data.entitiesUpdatedAt === \"number\" &&\n Number.isSafeInteger(data.entitiesUpdatedAt) &&\n data.entitiesUpdatedAt > 0 &&\n data.entitiesUpdatedAt <= now + 2000)\n )\n )\n return null;\n const raw = data.entities as Record<string, unknown>;\n if (Object.keys(raw).some((key) => !DIMENSIONS.includes(key)) || DIMENSIONS.some((key) => !count(raw[key])))\n return null;\n return {\n recordedAt: now,\n bootId: data.bootId,\n tps: data.tps as number | null,\n onlineCount: data.onlineCount,\n entities: { ...raw } as Record<string, number | null>,\n entitiesUpdatedAt: data.entitiesUpdatedAt as number | null,\n chunkEstimate: data.chunkEstimate as number | null,\n };\n}\n\nexport class MetricsHistory {\n private lastWrite = 0;\n private lastPrune = 0;\n constructor(private query: QueryFn) {\n query(\"CREATE TABLE IF NOT EXISTS sfmc_runtime_metrics (recorded_at INTEGER PRIMARY KEY, sample TEXT NOT NULL)\");\n }\n append(sample: MetricsSample): void {\n // \u540C\u4E00\u91C7\u6837\u7A97\u53E3\u53BB\u91CD\uFF0C\u9632\u6B62\u5F02\u5E38\u91CD\u8BD5\u9020\u6210\u65E0\u754C\u843D\u5E93\u3002\n if (sample.recordedAt - this.lastWrite < 3000) return;\n this.query(\"INSERT OR REPLACE INTO sfmc_runtime_metrics (recorded_at, sample) VALUES (?, ?)\", [\n sample.recordedAt,\n JSON.stringify(sample),\n ]);\n if (sample.recordedAt - this.lastPrune > 60_000) {\n this.prune(sample.recordedAt);\n this.lastPrune = sample.recordedAt;\n }\n this.lastWrite = sample.recordedAt;\n }\n private prune(now: number): void {\n this.query(\"DELETE FROM sfmc_runtime_metrics WHERE recorded_at < ?\", [now - 72 * 3600_000]);\n this.query(\n \"DELETE FROM sfmc_runtime_metrics WHERE recorded_at NOT IN (SELECT recorded_at FROM sfmc_runtime_metrics ORDER BY recorded_at DESC LIMIT 60000)\"\n );\n }\n read(now = Date.now()): MetricsSample[] {\n const rows = this.query(\n \"SELECT sample FROM sfmc_runtime_metrics WHERE recorded_at >= ? ORDER BY recorded_at DESC LIMIT 720\",\n [now - 3600_000]\n ) as { sample: string }[];\n return rows.reverse().flatMap((row) => {\n try {\n return [JSON.parse(row.sample) as MetricsSample];\n } catch {\n return [];\n }\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "AAYA,MAAM,aAAa,CAAC,uBAAuB,oBAAoB,mBAAmB;AAClF,MAAM,QAAQ,CAAC,UACb,UAAU,QAAS,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS,KAAK,SAAS;AAEjG,SAAS,aAAa,MAA+B,KAAmC;AAC7F,MAAI,CAAC,MAAM,QAAQ,KAAK,OAAO,KAAK,KAAK,QAAQ,SAAS,IAAM,QAAO;AACvE,MACE,OAAO,KAAK,WAAW,YACvB,CAAC,uBAAuB,KAAK,KAAK,MAAM,KACxC,EACE,KAAK,QAAQ,QACZ,OAAO,KAAK,QAAQ,YAAY,OAAO,SAAS,KAAK,GAAG,KAAK,KAAK,OAAO,KAAK,KAAK,OAAO,OAE7F,CAAC,MAAM,KAAK,aAAa,KACzB,OAAO,KAAK,gBAAgB,YAC5B,KAAK,gBAAiB,KAAK,QAAsB,UACjD,CAAC,KAAK,YACN,OAAO,KAAK,aAAa,YACzB,MAAM,QAAQ,KAAK,QAAQ,KAC3B,EACE,KAAK,sBAAsB,QAC1B,OAAO,KAAK,sBAAsB,YACjC,OAAO,cAAc,KAAK,iBAAiB,KAC3C,KAAK,oBAAoB,KACzB,KAAK,qBAAqB,MAAM;AAGpC,WAAO;AACT,QAAM,MAAM,KAAK;AACjB,MAAI,OAAO,KAAK,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,WAAW,SAAS,GAAG,CAAC,KAAK,WAAW,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;AACxG,WAAO;AACT,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,KAAK,KAAK;AAAA,IACV,aAAa,KAAK;AAAA,IAClB,UAAU,EAAE,GAAG,IAAI;AAAA,IACnB,mBAAmB,KAAK;AAAA,IACxB,eAAe,KAAK;AAAA,EACtB;AACF;AAEO,MAAM,eAAe;AAAA,EAG1B,YAAoB,OAAgB;AAAhB;AAClB,UAAM,yGAAyG;AAAA,EACjH;AAAA,EAFoB;AAAA,EAFZ,YAAY;AAAA,EACZ,YAAY;AAAA,EAIpB,OAAO,QAA6B;AAElC,QAAI,OAAO,aAAa,KAAK,YAAY,IAAM;AAC/C,SAAK,MAAM,mFAAmF;AAAA,MAC5F,OAAO;AAAA,MACP,KAAK,UAAU,MAAM;AAAA,IACvB,CAAC;AACD,QAAI,OAAO,aAAa,KAAK,YAAY,KAAQ;AAC/C,WAAK,MAAM,OAAO,UAAU;AAC5B,WAAK,YAAY,OAAO;AAAA,IAC1B;AACA,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EACQ,MAAM,KAAmB;AAC/B,SAAK,MAAM,0DAA0D,CAAC,MAAM,KAAK,IAAQ,CAAC;AAC1F,SAAK;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EACA,KAAK,MAAM,KAAK,IAAI,GAAoB;AACtC,UAAM,OAAO,KAAK;AAAA,MAChB;AAAA,MACA,CAAC,MAAM,IAAQ;AAAA,IACjB;AACA,WAAO,KAAK,QAAQ,EAAE,QAAQ,CAAC,QAAQ;AACrC,UAAI;AACF,eAAO,CAAC,KAAK,MAAM,IAAI,MAAM,CAAkB;AAAA,MACjD,QAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ProcessResources {
|
|
2
|
+
pid: number;
|
|
3
|
+
running: boolean;
|
|
4
|
+
memoryMb: number | null;
|
|
5
|
+
cpuSeconds: number | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function collectProcessResources(pid: number, running: boolean): Promise<ProcessResources>;
|
|
8
|
+
//# sourceMappingURL=process-resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-resources.d.ts","sourceRoot":"","sources":["../../src/domain/process-resources.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmCtG"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execute = promisify(execFile);
|
|
4
|
+
async function collectProcessResources(pid, running) {
|
|
5
|
+
const result = { pid, running, memoryMb: null, cpuSeconds: null };
|
|
6
|
+
if (!running || !Number.isSafeInteger(pid) || pid <= 0) return result;
|
|
7
|
+
if (pid === process.pid) {
|
|
8
|
+
const cpu = process.cpuUsage();
|
|
9
|
+
return {
|
|
10
|
+
...result,
|
|
11
|
+
memoryMb: Math.round(process.memoryUsage().rss / 1048576),
|
|
12
|
+
cpuSeconds: (cpu.user + cpu.system) / 1e6
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
const script = `$p=Get-Process -Id ${pid} -ErrorAction Stop; @{memoryMb=[Math]::Round($p.WorkingSet64/1MB);cpuSeconds=$p.CPU}|ConvertTo-Json -Compress`;
|
|
18
|
+
const { stdout } = await execute("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
19
|
+
windowsHide: true,
|
|
20
|
+
timeout: 2e3
|
|
21
|
+
});
|
|
22
|
+
const data = JSON.parse(stdout);
|
|
23
|
+
if (Number.isFinite(data.memoryMb) && data.memoryMb >= 0) result.memoryMb = data.memoryMb;
|
|
24
|
+
if (typeof data.cpuSeconds === "number" && Number.isFinite(data.cpuSeconds) && data.cpuSeconds >= 0)
|
|
25
|
+
result.cpuSeconds = data.cpuSeconds;
|
|
26
|
+
} else if (process.platform === "linux") {
|
|
27
|
+
const { stdout } = await execute("ps", ["-p", String(pid), "-o", "rss=", "-o", "time="], { timeout: 2e3 });
|
|
28
|
+
const match = stdout.trim().match(/^(\d+)\s+(?:(\d+)-)?(\d+):(\d+):(\d+)$/);
|
|
29
|
+
if (match) {
|
|
30
|
+
result.memoryMb = Math.round(Number(match[1]) / 1024);
|
|
31
|
+
result.cpuSeconds = Number(match[2] ?? 0) * 86400 + Number(match[3]) * 3600 + Number(match[4]) * 60 + Number(match[5]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
collectProcessResources
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=process-resources.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/domain/process-resources.ts"],
|
|
4
|
+
"sourcesContent": ["/** \u53D7\u63A7\u8FDB\u7A0B\u8D44\u6E90\u91C7\u96C6\uFF1A\u4EC5\u5141\u8BB8\u5DF2\u6838\u9A8C\u7684\u6570\u5B57 PID\uFF0C\u5931\u8D25\u4FDD\u7559\u672A\u77E5\u3002 */\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nconst execute = promisify(execFile);\nexport interface ProcessResources {\n pid: number;\n running: boolean;\n memoryMb: number | null;\n cpuSeconds: number | null;\n}\n\nexport async function collectProcessResources(pid: number, running: boolean): Promise<ProcessResources> {\n const result: ProcessResources = { pid, running, memoryMb: null, cpuSeconds: null };\n if (!running || !Number.isSafeInteger(pid) || pid <= 0) return result;\n if (pid === process.pid) {\n const cpu = process.cpuUsage();\n return {\n ...result,\n memoryMb: Math.round(process.memoryUsage().rss / 1048576),\n cpuSeconds: (cpu.user + cpu.system) / 1_000_000,\n };\n }\n try {\n if (process.platform === \"win32\") {\n const script = `$p=Get-Process -Id ${pid} -ErrorAction Stop; @{memoryMb=[Math]::Round($p.WorkingSet64/1MB);cpuSeconds=$p.CPU}|ConvertTo-Json -Compress`;\n const { stdout } = await execute(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n windowsHide: true,\n timeout: 2000,\n });\n const data = JSON.parse(stdout) as { memoryMb: number; cpuSeconds: number | null };\n if (Number.isFinite(data.memoryMb) && data.memoryMb >= 0) result.memoryMb = data.memoryMb;\n if (typeof data.cpuSeconds === \"number\" && Number.isFinite(data.cpuSeconds) && data.cpuSeconds >= 0)\n result.cpuSeconds = data.cpuSeconds;\n } else if (process.platform === \"linux\") {\n const { stdout } = await execute(\"ps\", [\"-p\", String(pid), \"-o\", \"rss=\", \"-o\", \"time=\"], { timeout: 2000 });\n const match = stdout.trim().match(/^(\\d+)\\s+(?:(\\d+)-)?(\\d+):(\\d+):(\\d+)$/);\n if (match) {\n result.memoryMb = Math.round(Number(match[1]) / 1024);\n result.cpuSeconds =\n Number(match[2] ?? 0) * 86400 + Number(match[3]) * 3600 + Number(match[4]) * 60 + Number(match[5]);\n }\n }\n } catch {\n /* \u65E0\u6743\u9650\u3001\u8FDB\u7A0B\u9000\u51FA\u3001\u91C7\u96C6\u8D85\u65F6\u5747\u4E0D\u4F2A\u62A5 0\u3002 */\n }\n return result;\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,MAAM,UAAU,UAAU,QAAQ;AAQlC,eAAsB,wBAAwB,KAAa,SAA6C;AACtG,QAAM,SAA2B,EAAE,KAAK,SAAS,UAAU,MAAM,YAAY,KAAK;AAClF,MAAI,CAAC,WAAW,CAAC,OAAO,cAAc,GAAG,KAAK,OAAO,EAAG,QAAO;AAC/D,MAAI,QAAQ,QAAQ,KAAK;AACvB,UAAM,MAAM,QAAQ,SAAS;AAC7B,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,KAAK,MAAM,QAAQ,YAAY,EAAE,MAAM,OAAO;AAAA,MACxD,aAAa,IAAI,OAAO,IAAI,UAAU;AAAA,IACxC;AAAA,EACF;AACA,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM,SAAS,sBAAsB,GAAG;AACxC,YAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,kBAAkB,CAAC,cAAc,mBAAmB,YAAY,MAAM,GAAG;AAAA,QACxG,aAAa;AAAA,QACb,SAAS;AAAA,MACX,CAAC;AACD,YAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,UAAI,OAAO,SAAS,KAAK,QAAQ,KAAK,KAAK,YAAY,EAAG,QAAO,WAAW,KAAK;AACjF,UAAI,OAAO,KAAK,eAAe,YAAY,OAAO,SAAS,KAAK,UAAU,KAAK,KAAK,cAAc;AAChG,eAAO,aAAa,KAAK;AAAA,IAC7B,WAAW,QAAQ,aAAa,SAAS;AACvC,YAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,MAAM,OAAO,GAAG,EAAE,SAAS,IAAK,CAAC;AAC1G,YAAM,QAAQ,OAAO,KAAK,EAAE,MAAM,wCAAwC;AAC1E,UAAI,OAAO;AACT,eAAO,WAAW,KAAK,MAAM,OAAO,MAAM,CAAC,CAAC,IAAI,IAAI;AACpD,eAAO,aACL,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,QAAQ,OAAO,MAAM,CAAC,CAAC,IAAI,OAAO,OAAO,MAAM,CAAC,CAAC,IAAI,KAAK,OAAO,MAAM,CAAC,CAAC;AAAA,MACrG;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/domain/schema.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * domain/schema.ts \u2014 \u6570\u636E\u5E93 Schema \u521D\u59CB\u5316\u5B9A\u4E49\uFF08\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\uFF09\n *\n * \u96C6\u4E2D\u7BA1\u7406\u5E73\u53F0\u6838\u5FC3\u6570\u636E\u8868\u7684 CREATE TABLE IF NOT EXISTS \u4E0E CREATE INDEX IF NOT EXISTS DDL \u8BED\u53E5\u3002\n * \u8C03\u7528\u65B9\u5F0F\uFF1AinitSchema(db) \u2014\u2014 \u5728 openDatabase \u4E4B\u540E\u3001\u4E1A\u52A1\u67E5\u8BE2\u542F\u52A8\u524D\u6267\u884C\u3002\n *\n * \u67B6\u6784\u7EA6\u5B9A\uFF1A\n * \u4E1A\u52A1\u6A21\u5757\uFF08activity-log, coop, land, chat, economy \u7B49\uFF09\u79C1\u6709\u6570\u636E\u8868\u5747\u7531\u5404\u81EA\u6A21\u5757\u5728 SAPI \u8FD0\u884C\u671F\u901A\u8FC7\n * db.defineTable(...) \u81EA\u4E3B\u58F0\u660E\u4E0E\u7EF4\u62A4\uFF0C\u4E0D\u5728\u6B64\u5904\u9759\u6001\u786C\u7F16\u7801\uFF0C\u907F\u514D\u5217\u5B9A\u4E49\u4E0E\u7D22\u5F15\u51B2\u7A81\u3002\n *\n * \u6B64\u5904\u4EC5\u5B9A\u4E49\u5E73\u53F0\u6838\u5FC3\u8FD0\u884C\u3001\u8DE8\u670D\u52A1\u5171\u4EAB\uFF08qq-bridge / HTTP \u7AEF\u70B9 / status \u63A2\u6D3B\uFF09\u7684\u57FA\u7840\u5E95\u5EA7\u8868\uFF1A\n * 1. \u4E16\u754C\u4E0E\u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\u5FEB\u7167\uFF08sfmc_world, sfmc_players\uFF09\n * 2. \u8DE8\u8FDB\u7A0B\u804A\u5929\u6D88\u606F\u805A\u5408\uFF08sfmc_chat_messages\uFF09\n * 3. QQ \u8EAB\u4EFD\u7ED1\u5B9A\u4E0E\u5BA1\u6279\u961F\u5217\uFF08sfmc_qq_*\uFF09\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * \u521D\u59CB\u5316\u6570\u636E\u5E93\u7ED3\u6784\uFF0C\u521B\u5EFA\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\u4E0E\u7D22\u5F15\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function initSchema(db: DatabaseSync): void {\n // (1) \u4E16\u754C\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_world (\n id TEXT PRIMARY KEY,\n seed TEXT DEFAULT '',\n difficulty TEXT DEFAULT '',\n spawn_x INTEGER DEFAULT 0,\n spawn_y INTEGER DEFAULT 0,\n spawn_z INTEGER DEFAULT 0,\n gamerules TEXT DEFAULT '{}',\n day INTEGER DEFAULT 0,\n absolute_time INTEGER DEFAULT 0,\n moon_phase INTEGER DEFAULT 0,\n updated_at INTEGER DEFAULT 0\n )`);\n\n // (2) \u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n xuid TEXT DEFAULT '',\n last_online INTEGER DEFAULT 0,\n level INTEGER DEFAULT 0,\n total_xp INTEGER DEFAULT 0,\n tags TEXT DEFAULT '',\n spawn_dimension TEXT DEFAULT '',\n spawn_x REAL,\n spawn_y REAL,\n spawn_z REAL,\n dimension TEXT DEFAULT '',\n x REAL,\n y REAL,\n z REAL,\n game_mode TEXT DEFAULT '',\n snapshot_hash TEXT DEFAULT '',\n active_channel TEXT DEFAULT '',\n subscribed_channels TEXT DEFAULT '[]',\n updated_at INTEGER DEFAULT 0\n )`);\n const playerColumns = new Map(\n (db.prepare(`PRAGMA table_info(\"sfmc_players\")`).all() as Array<{ name: string }>).map((column) => [\n column.name,\n true,\n ])\n );\n const missingPlayerColumns: Array<[string, string]> = [\n [\"spawn_dimension\", \"TEXT DEFAULT ''\"],\n [\"spawn_x\", \"REAL\"],\n [\"spawn_y\", \"REAL\"],\n [\"spawn_z\", \"REAL\"],\n [\"dimension\", \"TEXT DEFAULT ''\"],\n [\"x\", \"REAL\"],\n [\"y\", \"REAL\"],\n [\"z\", \"REAL\"],\n [\"game_mode\", \"TEXT DEFAULT ''\"],\n [\"snapshot_hash\", \"TEXT DEFAULT ''\"],\n [\"active_channel\", \"TEXT DEFAULT ''\"],\n [\"subscribed_channels\", \"TEXT DEFAULT '[]'\"],\n ];\n for (const [name, definition] of missingPlayerColumns) {\n if (!playerColumns.has(name)) db.exec(`ALTER TABLE sfmc_players ADD COLUMN \"${name}\" ${definition}`);\n }\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`);\n\n // (3) \u804A\u5929\u6D88\u606F\u805A\u5408\uFF08\u4F9B routes/messages \u4E0E qq-bridge \u53CC\u5411\u540C\u6B65\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_messages (\n id TEXT PRIMARY KEY NOT NULL,\n channel_id TEXT NOT NULL DEFAULT '',\n from_id TEXT NOT NULL DEFAULT '',\n from_name TEXT NOT NULL DEFAULT '',\n type TEXT NOT NULL DEFAULT 'text',\n content TEXT NOT NULL DEFAULT '',\n attachment TEXT DEFAULT NULL,\n show_timestamp INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_chat_messages_channel_time ON sfmc_chat_messages(channel_id, created_at ASC);\n CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON sfmc_chat_messages(created_at ASC)`);\n\n // (4) QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0\u8868\uFF1Bqq-bridge / \u6E38\u620F\u6A21\u5757\u5171\u7528 HTTP\uFF0C\u975E\u6A21\u5757\u79C1\u6709\u8868\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (\n code TEXT PRIMARY KEY NOT NULL,\n qq_user_openid TEXT NOT NULL,\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (\n qq_user_openid TEXT PRIMARY KEY NOT NULL,\n player_xuid TEXT NOT NULL UNIQUE,\n player_name TEXT NOT NULL DEFAULT '',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n bound_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);\n CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`);\n\n // (5) QQ \u5165\u670D\u5BA1\u6279
|
|
4
|
+
"sourcesContent": ["/**\n * domain/schema.ts \u2014 \u6570\u636E\u5E93 Schema \u521D\u59CB\u5316\u5B9A\u4E49\uFF08\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\uFF09\n *\n * \u96C6\u4E2D\u7BA1\u7406\u5E73\u53F0\u6838\u5FC3\u6570\u636E\u8868\u7684 CREATE TABLE IF NOT EXISTS \u4E0E CREATE INDEX IF NOT EXISTS DDL \u8BED\u53E5\u3002\n * \u8C03\u7528\u65B9\u5F0F\uFF1AinitSchema(db) \u2014\u2014 \u5728 openDatabase \u4E4B\u540E\u3001\u4E1A\u52A1\u67E5\u8BE2\u542F\u52A8\u524D\u6267\u884C\u3002\n *\n * \u67B6\u6784\u7EA6\u5B9A\uFF1A\n * \u4E1A\u52A1\u6A21\u5757\uFF08activity-log, coop, land, chat, economy \u7B49\uFF09\u79C1\u6709\u6570\u636E\u8868\u5747\u7531\u5404\u81EA\u6A21\u5757\u5728 SAPI \u8FD0\u884C\u671F\u901A\u8FC7\n * db.defineTable(...) \u81EA\u4E3B\u58F0\u660E\u4E0E\u7EF4\u62A4\uFF0C\u4E0D\u5728\u6B64\u5904\u9759\u6001\u786C\u7F16\u7801\uFF0C\u907F\u514D\u5217\u5B9A\u4E49\u4E0E\u7D22\u5F15\u51B2\u7A81\u3002\n *\n * \u6B64\u5904\u4EC5\u5B9A\u4E49\u5E73\u53F0\u6838\u5FC3\u8FD0\u884C\u3001\u8DE8\u670D\u52A1\u5171\u4EAB\uFF08qq-bridge / HTTP \u7AEF\u70B9 / status \u63A2\u6D3B\uFF09\u7684\u57FA\u7840\u5E95\u5EA7\u8868\uFF1A\n * 1. \u4E16\u754C\u4E0E\u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\u5FEB\u7167\uFF08sfmc_world, sfmc_players\uFF09\n * 2. \u8DE8\u8FDB\u7A0B\u804A\u5929\u6D88\u606F\u805A\u5408\uFF08sfmc_chat_messages\uFF09\n * 3. QQ \u8EAB\u4EFD\u7ED1\u5B9A\u4E0E\u5BA1\u6279\u961F\u5217\uFF08sfmc_qq_*\uFF09\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * \u521D\u59CB\u5316\u6570\u636E\u5E93\u7ED3\u6784\uFF0C\u521B\u5EFA\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\u4E0E\u7D22\u5F15\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function initSchema(db: DatabaseSync): void {\n // (1) \u4E16\u754C\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_world (\n id TEXT PRIMARY KEY,\n seed TEXT DEFAULT '',\n difficulty TEXT DEFAULT '',\n spawn_x INTEGER DEFAULT 0,\n spawn_y INTEGER DEFAULT 0,\n spawn_z INTEGER DEFAULT 0,\n gamerules TEXT DEFAULT '{}',\n day INTEGER DEFAULT 0,\n absolute_time INTEGER DEFAULT 0,\n moon_phase INTEGER DEFAULT 0,\n updated_at INTEGER DEFAULT 0\n )`);\n\n // (2) \u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n xuid TEXT DEFAULT '',\n last_online INTEGER DEFAULT 0,\n level INTEGER DEFAULT 0,\n total_xp INTEGER DEFAULT 0,\n tags TEXT DEFAULT '',\n spawn_dimension TEXT DEFAULT '',\n spawn_x REAL,\n spawn_y REAL,\n spawn_z REAL,\n dimension TEXT DEFAULT '',\n x REAL,\n y REAL,\n z REAL,\n game_mode TEXT DEFAULT '',\n snapshot_hash TEXT DEFAULT '',\n active_channel TEXT DEFAULT '',\n subscribed_channels TEXT DEFAULT '[]',\n updated_at INTEGER DEFAULT 0\n )`);\n const playerColumns = new Map(\n (db.prepare(`PRAGMA table_info(\"sfmc_players\")`).all() as Array<{ name: string }>).map((column) => [\n column.name,\n true,\n ])\n );\n const missingPlayerColumns: Array<[string, string]> = [\n [\"spawn_dimension\", \"TEXT DEFAULT ''\"],\n [\"spawn_x\", \"REAL\"],\n [\"spawn_y\", \"REAL\"],\n [\"spawn_z\", \"REAL\"],\n [\"dimension\", \"TEXT DEFAULT ''\"],\n [\"x\", \"REAL\"],\n [\"y\", \"REAL\"],\n [\"z\", \"REAL\"],\n [\"game_mode\", \"TEXT DEFAULT ''\"],\n [\"snapshot_hash\", \"TEXT DEFAULT ''\"],\n [\"active_channel\", \"TEXT DEFAULT ''\"],\n [\"subscribed_channels\", \"TEXT DEFAULT '[]'\"],\n ];\n for (const [name, definition] of missingPlayerColumns) {\n if (!playerColumns.has(name)) db.exec(`ALTER TABLE sfmc_players ADD COLUMN \"${name}\" ${definition}`);\n }\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`);\n\n // (3) \u804A\u5929\u6D88\u606F\u805A\u5408\uFF08\u4F9B routes/messages \u4E0E qq-bridge \u53CC\u5411\u540C\u6B65\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_messages (\n id TEXT PRIMARY KEY NOT NULL,\n channel_id TEXT NOT NULL DEFAULT '',\n from_id TEXT NOT NULL DEFAULT '',\n from_name TEXT NOT NULL DEFAULT '',\n type TEXT NOT NULL DEFAULT 'text',\n content TEXT NOT NULL DEFAULT '',\n attachment TEXT DEFAULT NULL,\n show_timestamp INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_chat_messages_channel_time ON sfmc_chat_messages(channel_id, created_at ASC);\n CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON sfmc_chat_messages(created_at ASC)`);\n\n // (4) QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0\u8868\uFF1Bqq-bridge / \u6E38\u620F\u6A21\u5757\u5171\u7528 HTTP\uFF0C\u975E\u6A21\u5757\u79C1\u6709\u8868\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (\n code TEXT PRIMARY KEY NOT NULL,\n qq_user_openid TEXT NOT NULL,\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (\n qq_user_openid TEXT PRIMARY KEY NOT NULL,\n player_xuid TEXT NOT NULL UNIQUE,\n player_name TEXT NOT NULL DEFAULT '',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n bound_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);\n CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`);\n\n // (5) QQ \u5165\u670D\u5BA1\u6279\u3002\u65E7\u8E22\u4EBA\u961F\u5217\u8868\u4FDD\u7559\uFF0C\u907F\u514D\u5DF2\u6709\u5E93\u7F3A\u8868\uFF1B\u5E73\u53F0\u4E0D\u518D\u5199\u5165\u6216\u6D88\u8D39\u3002\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_join_requests (\n id TEXT PRIMARY KEY NOT NULL,\n applicant_openid TEXT NOT NULL,\n player_name TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n decided_at INTEGER,\n decided_by TEXT,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_join_status ON sfmc_qq_join_requests(status, created_at)`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_admin_actions (\n id TEXT PRIMARY KEY NOT NULL,\n kind TEXT NOT NULL,\n target_name TEXT NOT NULL,\n reason TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`);\n\n // QQ \u8D26\u53F7\u9875\u5FEB\u7167\uFF1Aqq-link \u8C03\u7528\u7ECF\u6D4E / \u5728\u7EBF\u65F6\u957F\u670D\u52A1\u540E\u5199\u5165\uFF0C\u4F9B\u300C\u6211\u7684\u8D26\u53F7\u300D\u8BFB\u6700\u65B0\u6570\u5B57\u3002\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_account_profile (\n player_xuid TEXT PRIMARY KEY NOT NULL,\n player_name TEXT NOT NULL DEFAULT '',\n balance INTEGER,\n today_seconds INTEGER,\n month_seconds INTEGER,\n total_seconds INTEGER,\n updated_at INTEGER NOT NULL\n )`);\n}\n"],
|
|
5
5
|
"mappings": "AAuBO,SAAS,WAAW,IAAwB;AAEjD,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAad;AAGJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBd;AACJ,QAAM,gBAAgB,IAAI;AAAA,IACvB,GAAG,QAAQ,mCAAmC,EAAE,IAAI,EAA8B,IAAI,CAAC,WAAW;AAAA,MACjG,OAAO;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,uBAAgD;AAAA,IACpD,CAAC,mBAAmB,iBAAiB;AAAA,IACrC,CAAC,WAAW,MAAM;AAAA,IAClB,CAAC,WAAW,MAAM;AAAA,IAClB,CAAC,WAAW,MAAM;AAAA,IAClB,CAAC,aAAa,iBAAiB;AAAA,IAC/B,CAAC,KAAK,MAAM;AAAA,IACZ,CAAC,KAAK,MAAM;AAAA,IACZ,CAAC,KAAK,MAAM;AAAA,IACZ,CAAC,aAAa,iBAAiB;AAAA,IAC/B,CAAC,iBAAiB,iBAAiB;AAAA,IACnC,CAAC,kBAAkB,iBAAiB;AAAA,IACpC,CAAC,uBAAuB,mBAAmB;AAAA,EAC7C;AACA,aAAW,CAAC,MAAM,UAAU,KAAK,sBAAsB;AACrD,QAAI,CAAC,cAAc,IAAI,IAAI,EAAG,IAAG,KAAK,wCAAwC,IAAI,KAAK,UAAU,EAAE;AAAA,EACrG;AACA,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACkD;AAGpE,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAE8E;AAGhG,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAEiE;AAGnF,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EAC2E;AAC7F,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACoF;AAGtG,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASd;AACN;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA6B1C,QAAA,MAAM,GAAG,8BAAY,CAAC;AACtB,QAAA,MAAM,EAAE,oCAA4B,CAAC;AAGrC,QAAA,MAAM,KAAK,mCAAkB,CAAC;AAwB9B,QAAA,MAAM,gBAAgB,8DAA0E,CAAC;AAQjG,QAAA,MAAM,UAAU,0CAId,CAAC;AAGH,QAAA,MAAM,cAAc,gBAAyB,CAAC;AAC9C,QAAA,MAAM,eAAe,iBAAwB,CAAC;AAE9C,QAAA,MAAM,QAAQ,UAMZ,CAAC;AAufH,OAAO,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
+
import { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from "@sfmc-bds/sdk/contracts";
|
|
2
3
|
import { SQL } from "sql-template-strings";
|
|
3
4
|
import { createPlatformTables } from "./db-tables.js";
|
|
4
5
|
import { syncPlayerSession } from "./domain/player-session.js";
|
|
@@ -184,7 +185,18 @@ function setModuleEnabled(mod, enabled) {
|
|
|
184
185
|
});
|
|
185
186
|
}
|
|
186
187
|
const healthRoutes = createHealthRoutes();
|
|
187
|
-
const
|
|
188
|
+
const metricsDb = openDatabase(path.join(path.dirname(env.DB_PATH), "runtime-metrics.sqlite"));
|
|
189
|
+
const statusRoutes = createStatusRoutes({ query: createQuery(metricsDb) });
|
|
190
|
+
for (const name of PLATFORM_MONITORING_SERVICES) serviceRegistry.registerHandler(PLATFORM_SERVICE_OWNER, name, async () => {
|
|
191
|
+
const response = await fetch(`http://127.0.0.1:${env.PORT}/api/sfmc/metrics`, { signal: AbortSignal.timeout(1e4) });
|
|
192
|
+
if (!response.ok) throw new Error("\u5E73\u53F0\u8FD0\u884C\u6307\u6807\u8BFB\u53D6\u5931\u8D25");
|
|
193
|
+
const result = await response.json();
|
|
194
|
+
const tps = result.current?.tps ?? null;
|
|
195
|
+
const grade = tps === null ? "unknown" : tps >= 19.5 ? "green" : tps >= 15 ? "yellow" : tps >= 10 ? "gold" : "red";
|
|
196
|
+
if (name === "tps.current") return tps;
|
|
197
|
+
if (name === "tps.status") return { tps, grade, text: `\xA77[TPS] \xA7f${tps?.toFixed(2) ?? "\u672A\u77E5"} \xA77/ 20.00` };
|
|
198
|
+
return result.current ? { ...result.current, grade, totalLoadedChunks: result.current.chunkEstimate } : null;
|
|
199
|
+
});
|
|
188
200
|
const qqBindRoutes = createQqBindRoutes({ query, body, json });
|
|
189
201
|
const QQ_LINK_CONFIG_KEY = "qq_link";
|
|
190
202
|
const QQ_LINK_DEFAULTS = {
|
|
@@ -387,8 +399,8 @@ async function handle(req, res) {
|
|
|
387
399
|
permissions: manifest?.permissions ?? []
|
|
388
400
|
};
|
|
389
401
|
} else {
|
|
390
|
-
const PUBLIC_GET = path2 === "/api/health" || path2 === "/api/sfmc/status" || path2 === "/api/sfmc/qq/join/apply-queue" || path2 === "/api/sfmc/qq/join/settings" ||
|
|
391
|
-
const NEEDS_AUTH = !PUBLIC_GET && method !== "GET" && !(path2.startsWith("/api/sfmc/qq/bind/") || path2.startsWith("/api/sfmc/qq/join/") || path2.startsWith("/api/sfmc/qq/
|
|
402
|
+
const PUBLIC_GET = path2 === "/api/health" || path2 === "/api/sfmc/status" || path2 === "/api/sfmc/qq/join/apply-queue" || path2 === "/api/sfmc/qq/join/settings" || method === "GET" && (path2 === "/api/sfmc/content" || path2 === "/api/sfmc/modules" || path2 === "/api/sfmc/modules/catalog" || path2.startsWith("/api/sfmc/modules/") || path2 === "/api/sfmc/qq/bind/me" || path2 === "/api/sfmc/qq/join/pending");
|
|
403
|
+
const NEEDS_AUTH = !PUBLIC_GET && method !== "GET" && !(path2.startsWith("/api/sfmc/qq/bind/") || path2.startsWith("/api/sfmc/qq/join/") || path2.startsWith("/api/sfmc/qq/events"));
|
|
392
404
|
if (env.AUTH_TOKEN && NEEDS_AUTH) {
|
|
393
405
|
const auth = req.headers["authorization"] || "";
|
|
394
406
|
const provided = auth.startsWith("Bearer ") ? auth.slice(7) : req.headers["x-db-token"] || "";
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\nimport { SQL } from \"sql-template-strings\";\n\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { syncPlayerSession } from \"./domain/player-session.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { loadEnv } from \"./env.js\";\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { TxRunner } from \"./tx-runner.js\";\n\nimport { configPath, ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport path, { join } from \"node:path\";\n\nimport { jsonV2Fail } from \"./routes/_shared.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\n\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport { loadContentSnapshot } from \"./domain/installed-content.js\";\nimport { createQqEventsAggregator, resolveQqEventsConfig, type ResolvedQqEventsConfig } from \"./domain/qq-events.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createContentRoutes } from \"./routes/content.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { createMessagesRoutes, gameChatForwardPayload, type ChatChannelQQFlags } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createStatusRoutes } from \"./routes/status.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>).map((m) => String(m.id || \"\")).filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find((m) => String(m.id) === id);\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<(typeof loadedManifest.modules)[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(`[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(`[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`);\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/**\n * QQ\u300C\u6A21\u5757 / \u8D44\u6E90\u5305\u300D\u5FEB\u7167\u3002\u6BCF\u6B21\u8BF7\u6C42\u73B0\u8BFB catalog \u4E0E\u78C1\u76D8\uFF0C\u88C5\u5305\u540E\u4E0D\u7528\u91CD\u542F db-server\u3002\n */\nfunction loadContentSnapshotForRequest() {\n const modules = (buildModuleList() ?? []).flatMap((raw) => {\n if (!raw || typeof raw !== \"object\") return [];\n const row = raw as { id?: unknown; display_name?: unknown; enabled?: unknown };\n const id = String(row.id ?? \"\").trim();\n if (!id) return [];\n return [\n {\n id,\n display_name: String(row.display_name || id),\n enabled: row.enabled !== false,\n },\n ];\n });\n const cfg = readJson<{ bds_path?: string }>(configPath(env.PROJECT_ROOT, \"bds_updater.json\"));\n const rawBds = typeof cfg?.bds_path === \"string\" ? cfg.bds_path.trim() : \"\";\n const bdsRoot = rawBds ? (path.isAbsolute(rawBds) ? rawBds : path.resolve(env.PROJECT_ROOT, rawBds)) : null;\n return loadContentSnapshot({\n packagesDir: join(env.MODULES_DIR, \"packages\"),\n modules,\n bdsRoot,\n });\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\nconst statusRoutes = createStatusRoutes({});\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u6A21\u5757 qq-link \u7684\u914D\u7F6E\u6587\u4EF6\uFF08\u975E SDK qq_config\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u7F3A\u7701\u5F00\u542F\uFF0C\u4EC5\u6539\u672C\u6587\u4EF6\u53EF\u5173\u95ED\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: true,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u6A21\u5757\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u6A21\u5757\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction readQqEventsSettings(): ResolvedQqEventsConfig {\n const disk = readJson<Record<string, unknown>>(join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\"));\n if (!disk) throw new Error(\"qq_config.json \u65E0\u6CD5\u8BFB\u53D6\uFF0C\u4E8B\u4EF6\u8BBE\u7F6E\u672A\u4FEE\u6539\");\n return resolveQqEventsConfig(disk.qq_events);\n}\n\nfunction writeQqEventsSettings(patch: Partial<ResolvedQqEventsConfig>): ResolvedQqEventsConfig {\n const file = join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\");\n const settings = { ...readQqEventsSettings(), ...patch };\n patchJson(file, { qq_events: settings });\n return settings;\n}\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n });\n}\n\nconst qqEventsAggregator = createQqEventsAggregator({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n aggregator: qqEventsAggregator,\n getSettings: readQqEventsSettings,\n setSettings: writeQqEventsSettings,\n isAdmin: (openid, asGroupAdmin) => {\n const admins = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n return (\n (Array.isArray(admins) && admins.includes(openid)) || (asGroupAdmin && readJoinFlags().treatGroupAdminsAsAdmins)\n );\n },\n});\n\n/** \u8BFB\u53D6\u5355\u4E2A\u804A\u5929\u9891\u9053\u3002\u8868\u5C1A\u672A\u7531\u804A\u5929\u6A21\u5757\u5EFA\u51FA\u65F6\u8FD4\u56DE null\uFF0C\u4E0D\u8BA9\u8BF7\u6C42 500\u3002 */\nfunction readChatChannel(channelId: string): ChatChannelQQFlags | null {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels WHERE id = ${channelId}`);\n return Array.isArray(rows) ? ((rows[0] as ChatChannelQQFlags | undefined) ?? null) : null;\n } catch (err) {\n log.warn(`\u8BFB\u53D6\u804A\u5929\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** \u6D88\u606F\u6765\u6E90\u4E3A QQ \u7684\u9891\u9053\u3002QQ \u5165\u7AD9\u53EA\u6295\u5230\u8FD9\u4E9B\u9891\u9053\u3002 */\nfunction listQQSourceChannels(): ChatChannelQQFlags[] {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels`);\n if (!Array.isArray(rows)) return [];\n return (rows as ChatChannelQQFlags[]).filter((row) => row.source_qq === 1);\n } catch (err) {\n log.warn(`\u8BFB\u53D6 QQ \u6765\u6E90\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return [];\n }\n}\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getChannelForQQ: readChatChannel,\n listQQSourceChannels,\n forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, prefix, fromName, content, fromId),\n});\n\n// \u6E38\u620F\u53D1\u8A00\u8D70 db.tx \u63D2\u5165\uFF0C\u4E0D\u7ECF\u8FC7 /api/sfmc/messages\u3002\u63D0\u4EA4\u540E\u518D\u6309\u9891\u9053\u5F00\u5173\u8F6C\u53D1\u5230 QQ\u3002\ntxRunner.setAfterCommit((inserts) => {\n for (const item of inserts) {\n if (item.table !== \"sfmc_chat_messages\") continue;\n const payload = gameChatForwardPayload(item.row, readChatChannel(String(item.row.channel_id ?? \"\")));\n if (!payload) continue;\n forwardToQQBridge(\n currentOutbound(),\n payload.channelId,\n payload.prefix,\n payload.fromName,\n payload.content,\n payload.fromId,\n );\n }\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst contentRoutes = createContentRoutes({\n json,\n loadSnapshot: loadContentSnapshotForRequest,\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (\n ctx: Record<string, unknown>\n) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n path === \"/api/sfmc/qq/admin/action-queue\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/content\" ||\n path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/admin/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n if (path === \"/api/sfmc/player-session\" && method === \"POST\") {\n const data = (await body(req)) as Record<string, unknown>;\n const playerName = String(data.playerName ?? \"\").trim();\n const xuid = String(data.xuid ?? \"\").trim();\n if (!playerName || !xuid) {\n json(res, { success: false, error: \"playerName_and_xuid_required\" }, 400);\n return;\n }\n syncPlayerSession(query, { playerName, xuid });\n json(res, { success: true });\n return;\n }\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (\n moduleAuthCtx &&\n (path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))\n ) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as {\n path: string;\n method: string;\n params: URLSearchParams;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n };\n if (await contentRoutes(ctxBase)) return;\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, enabledManifests, env, moduleAuth, query, schemaRegistry, serviceRegistry, txRunner };\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\nimport { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\nimport { SQL } from \"sql-template-strings\";\n\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { syncPlayerSession } from \"./domain/player-session.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { loadEnv } from \"./env.js\";\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { TxRunner } from \"./tx-runner.js\";\n\nimport { configPath, ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport path, { join } from \"node:path\";\n\nimport { jsonV2Fail } from \"./routes/_shared.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\n\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport { loadContentSnapshot } from \"./domain/installed-content.js\";\nimport { createQqEventsAggregator, resolveQqEventsConfig, type ResolvedQqEventsConfig } from \"./domain/qq-events.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createContentRoutes } from \"./routes/content.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { createMessagesRoutes, gameChatForwardPayload, type ChatChannelQQFlags } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createStatusRoutes } from \"./routes/status.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>).map((m) => String(m.id || \"\")).filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find((m) => String(m.id) === id);\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<(typeof loadedManifest.modules)[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(`[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(`[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`);\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/**\n * QQ\u300C\u6A21\u5757 / \u8D44\u6E90\u5305\u300D\u5FEB\u7167\u3002\u6BCF\u6B21\u8BF7\u6C42\u73B0\u8BFB catalog \u4E0E\u78C1\u76D8\uFF0C\u88C5\u5305\u540E\u4E0D\u7528\u91CD\u542F db-server\u3002\n */\nfunction loadContentSnapshotForRequest() {\n const modules = (buildModuleList() ?? []).flatMap((raw) => {\n if (!raw || typeof raw !== \"object\") return [];\n const row = raw as { id?: unknown; display_name?: unknown; enabled?: unknown };\n const id = String(row.id ?? \"\").trim();\n if (!id) return [];\n return [\n {\n id,\n display_name: String(row.display_name || id),\n enabled: row.enabled !== false,\n },\n ];\n });\n const cfg = readJson<{ bds_path?: string }>(configPath(env.PROJECT_ROOT, \"bds_updater.json\"));\n const rawBds = typeof cfg?.bds_path === \"string\" ? cfg.bds_path.trim() : \"\";\n const bdsRoot = rawBds ? (path.isAbsolute(rawBds) ? rawBds : path.resolve(env.PROJECT_ROOT, rawBds)) : null;\n return loadContentSnapshot({\n packagesDir: join(env.MODULES_DIR, \"packages\"),\n modules,\n bdsRoot,\n });\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\n// \u6307\u6807\u72EC\u7ACB\u843D\u5E93\uFF0C\u907F\u514D\u5199\u5165\u4E1A\u52A1\u6A21\u5757\u6B63\u5728\u6301\u6709\u7684\u8DE8\u8BF7\u6C42\u4E8B\u52A1\u3002\nconst metricsDb = openDatabase(path.join(path.dirname(env.DB_PATH), \"runtime-metrics.sqlite\"));\nconst statusRoutes = createStatusRoutes({ query: createQuery(metricsDb) });\nfor (const name of PLATFORM_MONITORING_SERVICES) serviceRegistry.registerHandler(PLATFORM_SERVICE_OWNER, name, async () => {\n const response = await fetch(`http://127.0.0.1:${env.PORT}/api/sfmc/metrics`, { signal: AbortSignal.timeout(10_000) });\n if (!response.ok) throw new Error(\"\u5E73\u53F0\u8FD0\u884C\u6307\u6807\u8BFB\u53D6\u5931\u8D25\");\n const result = await response.json() as { current: { tps: number | null; chunkEstimate: number | null } | null };\n const tps = result.current?.tps ?? null;\n const grade = tps === null ? \"unknown\" : tps >= 19.5 ? \"green\" : tps >= 15 ? \"yellow\" : tps >= 10 ? \"gold\" : \"red\";\n if (name === \"tps.current\") return tps;\n if (name === \"tps.status\") return { tps, grade, text: `\u00A77[TPS] \u00A7f${tps?.toFixed(2) ?? \"\u672A\u77E5\"} \u00A77/ 20.00` };\n return result.current ? { ...result.current, grade, totalLoadedChunks: result.current.chunkEstimate } : null;\n});\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u6A21\u5757 qq-link \u7684\u914D\u7F6E\u6587\u4EF6\uFF08\u975E SDK qq_config\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u7F3A\u7701\u5F00\u542F\uFF0C\u4EC5\u6539\u672C\u6587\u4EF6\u53EF\u5173\u95ED\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: true,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u6A21\u5757\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u6A21\u5757\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction readQqEventsSettings(): ResolvedQqEventsConfig {\n const disk = readJson<Record<string, unknown>>(join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\"));\n if (!disk) throw new Error(\"qq_config.json \u65E0\u6CD5\u8BFB\u53D6\uFF0C\u4E8B\u4EF6\u8BBE\u7F6E\u672A\u4FEE\u6539\");\n return resolveQqEventsConfig(disk.qq_events);\n}\n\nfunction writeQqEventsSettings(patch: Partial<ResolvedQqEventsConfig>): ResolvedQqEventsConfig {\n const file = join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\");\n const settings = { ...readQqEventsSettings(), ...patch };\n patchJson(file, { qq_events: settings });\n return settings;\n}\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n });\n}\n\nconst qqEventsAggregator = createQqEventsAggregator({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n aggregator: qqEventsAggregator,\n getSettings: readQqEventsSettings,\n setSettings: writeQqEventsSettings,\n isAdmin: (openid, asGroupAdmin) => {\n const admins = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n return (\n (Array.isArray(admins) && admins.includes(openid)) || (asGroupAdmin && readJoinFlags().treatGroupAdminsAsAdmins)\n );\n },\n});\n\n/** \u8BFB\u53D6\u5355\u4E2A\u804A\u5929\u9891\u9053\u3002\u8868\u5C1A\u672A\u7531\u804A\u5929\u6A21\u5757\u5EFA\u51FA\u65F6\u8FD4\u56DE null\uFF0C\u4E0D\u8BA9\u8BF7\u6C42 500\u3002 */\nfunction readChatChannel(channelId: string): ChatChannelQQFlags | null {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels WHERE id = ${channelId}`);\n return Array.isArray(rows) ? ((rows[0] as ChatChannelQQFlags | undefined) ?? null) : null;\n } catch (err) {\n log.warn(`\u8BFB\u53D6\u804A\u5929\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** \u6D88\u606F\u6765\u6E90\u4E3A QQ \u7684\u9891\u9053\u3002QQ \u5165\u7AD9\u53EA\u6295\u5230\u8FD9\u4E9B\u9891\u9053\u3002 */\nfunction listQQSourceChannels(): ChatChannelQQFlags[] {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels`);\n if (!Array.isArray(rows)) return [];\n return (rows as ChatChannelQQFlags[]).filter((row) => row.source_qq === 1);\n } catch (err) {\n log.warn(`\u8BFB\u53D6 QQ \u6765\u6E90\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return [];\n }\n}\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getChannelForQQ: readChatChannel,\n listQQSourceChannels,\n forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, prefix, fromName, content, fromId),\n});\n\n// \u6E38\u620F\u53D1\u8A00\u8D70 db.tx \u63D2\u5165\uFF0C\u4E0D\u7ECF\u8FC7 /api/sfmc/messages\u3002\u63D0\u4EA4\u540E\u518D\u6309\u9891\u9053\u5F00\u5173\u8F6C\u53D1\u5230 QQ\u3002\ntxRunner.setAfterCommit((inserts) => {\n for (const item of inserts) {\n if (item.table !== \"sfmc_chat_messages\") continue;\n const payload = gameChatForwardPayload(item.row, readChatChannel(String(item.row.channel_id ?? \"\")));\n if (!payload) continue;\n forwardToQQBridge(\n currentOutbound(),\n payload.channelId,\n payload.prefix,\n payload.fromName,\n payload.content,\n payload.fromId,\n );\n }\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst contentRoutes = createContentRoutes({\n json,\n loadSnapshot: loadContentSnapshotForRequest,\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (\n ctx: Record<string, unknown>\n) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/content\" ||\n path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n if (path === \"/api/sfmc/player-session\" && method === \"POST\") {\n const data = (await body(req)) as Record<string, unknown>;\n const playerName = String(data.playerName ?? \"\").trim();\n const xuid = String(data.xuid ?? \"\").trim();\n if (!playerName || !xuid) {\n json(res, { success: false, error: \"playerName_and_xuid_required\" }, 400);\n return;\n }\n syncPlayerSession(query, { playerName, xuid });\n json(res, { success: true });\n return;\n }\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (\n moduleAuthCtx &&\n (path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))\n ) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as {\n path: string;\n method: string;\n params: URLSearchParams;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n };\n if (await contentRoutes(ctxBase)) return;\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, enabledManifests, env, moduleAuth, query, schemaRegistry, serviceRegistry, txRunner };\n"],
|
|
5
|
+
"mappings": "AAoBA,OAAO,UAAU;AACjB,SAAS,8BAA8B,8BAA8B;AACrE,SAAS,WAAW;AAEpB,SAAS,4BAA4B;AACrC,SAAS,yBAAyB;AAClC,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,8BAA8B;AACvC,SAAS,WAAW;AACpB,SAAS,yBAAyB;AAClC,SAAS,aAAa,oBAAoB;AAC1C,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB,wBAAwB;AAClD,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,cAAc,oBAAoB;AAC3C,SAAS,uBAAuB;AAChC,SAAS,sCAAsC;AAC/C,SAAS,gBAAgB;AAEzB,SAAS,YAAY,YAAY,WAAW,gBAAgB;AAC5D,OAAO,QAAQ,YAAY;AAE3B,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,2BAA2B;AAEpC,SAAS,mBAAmB,0BAA0B;AACtD,SAAS,2BAA2B;AACpC,SAAS,0BAA0B,6BAA0D;AAC7F,SAAS,QAAQ,YAAY,QAAQ,kBAAkB;AACvD,SAAS,WAAW,gBAAgB,gBAAgB,yBAAyB;AAC7E,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,sBAAsB,8BAAuD;AACtF,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC,SAAS,4BAA4B;AACrC,SAAS,0BAAiD;AAC1D,SAAS,0BAA0B;AAEnC,IAAI,CAAC,kBAAkB,IAAI,EAAE,GAAG;AAC9B,UAAQ,KAAK,CAAC;AAChB;AAEA,MAAM,MAAM,QAAQ;AACpB,MAAM,KAAK,aAAa,IAAI,OAAO;AACnC,qBAAqB,EAAE;AACvB,WAAW,EAAE;AACb,MAAM,QAAQ,YAAY,EAAE;AAG5B,MAAM,iBAAiB,eAAe;AACtC,IAAI;AAAA,EACF,wBAAwB,OAAO,KAAK,eAAe,OAAO,EAAE,MAAM,sBAAsB,eAAe,YAAY,IAAI;AACzH;AAGA,MAAM,WAAW,eAAe,IAAI,gBAAgB;AACpD,MAAM,gBAAgB,SAAkC,IAAI,mBAAmB,KAAK,EAAE,SAAS,CAAC,EAAE;AAClG,MAAM,aAAa,IAAI;AAAA,EACrB,MAAM,QAAQ,cAAc,OAAO,IAC9B,cAAc,QAAmC,IAAI,CAAC,MAAM,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,IAC7G,CAAC;AACP;AACA,MAAM,aAAa,oBAAI,IAAY;AACnC,WAAW,MAAM,OAAO,KAAK,eAAe,OAAO,GAAG;AACpD,MAAI,CAAC,WAAW,IAAI,EAAE,EAAG;AACzB,QAAM,eAAgB,cAAc,QAA2C,KAAK,CAAC,MAAM,OAAO,EAAE,EAAE,MAAM,EAAE;AAC9G,MAAI,CAAC,aAAc;AACnB,QAAM,iBAAiB,aAAa,qBAAqB;AACzD,MAAI,UAAU,UAAU,IAAI,cAAc,EAAG,YAAW,IAAI,EAAE;AAChE;AACA,MAAM,mBAAmB,oBAAI,IAAkE;AAC/F,WAAW,MAAM,YAAY;AAC3B,QAAM,IAAI,eAAe,QAAQ,EAAE;AACnC,MAAI,EAAG,kBAAiB,IAAI,IAAI,CAAC;AACnC;AACA,IAAI,KAAK,0BAA0B,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK,QAAQ,EAAE;AAGlF,MAAM,aAAa,gBAAgB;AAAA,EACjC,QAAQ,IAAI;AAAA,EACZ,cAAc,IAAI;AAAA,EAClB,kBAAkB,CAAC,GAAG,UAAU;AAClC,CAAC;AAGD,MAAM,iBAAiB,IAAI,eAAe,EAAE;AAC5C,MAAM,kBAAkB,IAAI,gBAAgB;AAC5C,MAAM,aAAa,uBAAuB,EAAE;AAC5C,MAAM,WAAW,IAAI,SAAS;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,SAAS;AACX,CAAC;AAGD;AACE,QAAM,UAAU,+BAA+B,iBAAiB,EAAE,OAAO,GAAG,GAAG,UAAU;AACzF,MAAI,UAAU,GAAG;AACf,QAAI,QAAQ,wBAAwB,gBAAgB,KAAK,EAAE,MAAM,kBAAkB,OAAO,oBAAoB;AAAA,EAChH;AACF;AAEA,MAAM,OAAO;AACb,MAAM,OAAO;AAGb,SAAS,oBAAoB;AAC3B,SAAO,MAAM,QAAQ,cAAc,OAAO,IAAI,cAAc,UAAU,CAAC;AACzE;AAEA,SAAS,kBAAkB;AACzB,QAAM,UAAU,kBAAkB;AAClC,SAAO,QACJ,IAAI,CAAC,QAAQ;AACZ,UAAM,QACH,IAAgC,SAAS,OAAQ,IAAgC,UAAU,WACtF,IAAgC,QAClC,CAAC;AACP,UAAM,KAAK,OAAQ,IAAgC,MAAM,EAAE,EAAE,KAAK;AAClE,UAAM,YAAY;AAAA,MACf,IAAgC,aAAc,IAAgC,cAAc;AAAA,IAC/F,EAAE,KAAK;AACP,QAAI,CAAC,MAAM,CAAC,UAAW,QAAO;AAC9B,UAAM,QAAQ,SAAS,QAAQ,EAAE;AACjC,UAAM,UAAU,UAAU,UAAU,IAAK,IAAgC,qBAAqB,KAAK;AACnG,WAAO;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX,MAAM;AAAA,MACN;AAAA,MACA,YAAY;AAAA,MACZ,cAAc,OAAQ,IAAgC,QAAQ,SAAS;AAAA,MACvE,aAAa,OAAQ,IAAgC,eAAe,EAAE;AAAA,MACtE,iBAAkB,IAAgC,qBAAqB;AAAA,MACvE,aAAa,iBAAiB,GAA8B;AAAA;AAAA,MAE5D,WAAW;AAAA,MACX,UAAU,MAAM,QAAS,IAAgC,QAAQ,IAC3D,IAAgC,SAAuB,OAAO,OAAO,EAAE,IAAI,MAAM,IACnF,CAAC;AAAA,MACL,UAAU,MAAM,QAAS,IAAgC,QAAQ,IAC3D,IAAgC,SAAuB,OAAO,OAAO,EAAE,IAAI,MAAM,IACnF,CAAC;AAAA,MACL,UAAU,MAAM,QAAS,IAAgC,QAAQ,IAC3D,IAAgC,SAAuB,OAAO,OAAO,EAAE,IAAI,MAAM,IACnF,CAAC;AAAA,MACL,OAAO;AAAA,QACL,MAAM,OAAO,MAAM,QAAQ,EAAE;AAAA,QAC7B,MAAM,OAAO,MAAM,QAAQ,EAAE;AAAA,QAC7B,MAAM,OAAO,MAAM,QAAQ,EAAE;AAAA,MAC/B;AAAA,MACA,YAAY,OAAO,aAAa;AAAA,MAChC,SAAS,CAAC,CAAC;AAAA,IACb;AAAA,EACF,CAAC,EACA,OAAO,OAAO;AACnB;AAKA,SAAS,gCAAgC;AACvC,QAAM,WAAW,gBAAgB,KAAK,CAAC,GAAG,QAAQ,CAAC,QAAQ;AACzD,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,CAAC;AAC7C,UAAM,MAAM;AACZ,UAAM,KAAK,OAAO,IAAI,MAAM,EAAE,EAAE,KAAK;AACrC,QAAI,CAAC,GAAI,QAAO,CAAC;AACjB,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,cAAc,OAAO,IAAI,gBAAgB,EAAE;AAAA,QAC3C,SAAS,IAAI,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,MAAM,SAAgC,WAAW,IAAI,cAAc,kBAAkB,CAAC;AAC5F,QAAM,SAAS,OAAO,KAAK,aAAa,WAAW,IAAI,SAAS,KAAK,IAAI;AACzE,QAAM,UAAU,SAAU,KAAK,WAAW,MAAM,IAAI,SAAS,KAAK,QAAQ,IAAI,cAAc,MAAM,IAAK;AACvG,SAAO,oBAAoB;AAAA,IACzB,aAAa,KAAK,IAAI,aAAa,UAAU;AAAA,IAC7C;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAGA,SAAS,iBAAiB,KAAuC;AAC/D,SAAO,IAAI,eAAe;AAC5B;AAEA,SAAS,mBAAmB,KAAa;AACvC,QAAM,IAAI,OAAO,OAAO,EAAE,EAAE,KAAK;AACjC,QAAM,UAAU,kBAAkB;AAClC,QAAM,MAAM,QAAQ;AAAA,IAClB,CAAC,MACC,OAAQ,EAA8B,MAAM,EAAE,MAAM,KACpD,OAAQ,EAA8B,aAAc,EAA8B,cAAc,EAAE,MAAM;AAAA,EAC5G;AACA,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,KAAK,OAAO,IAAI,MAAM,EAAE,EAAE,KAAK;AACrC,QAAM,YAAY,OAAO,IAAI,aAAa,IAAI,cAAc,EAAE,EAAE,KAAK;AACrE,MAAI,CAAC,MAAM,CAAC,UAAW,QAAO;AAE9B,SAAO,EAAE,IAAI,WAAW,YAAY,iBAAiB,GAAG,EAAE;AAC5D;AAEA,SAAS,iBAAiB,KAA0C,SAAkB;AAGpF,oBAAkB,UAAU,IAAI,IAAI,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC;AAE1D,iBAAe,IAAI,kBAAkB,QAAQ;AAG7C,yBAAuB;AAAA,IACrB,UAAU,IAAI;AAAA,IACd,SAAS,CAAC,CAAC;AAAA,IACX,QAAQ,IAAI;AAAA,IACZ,cAAc,IAAI;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,EAAE,OAAO,GAAG;AAAA,EAC3B,CAAC;AACH;AAGA,MAAM,eAAe,mBAAmB;AAExC,MAAM,YAAY,aAAa,KAAK,KAAK,KAAK,QAAQ,IAAI,OAAO,GAAG,wBAAwB,CAAC;AAC7F,MAAM,eAAe,mBAAmB,EAAE,OAAO,YAAY,SAAS,EAAE,CAAC;AACzE,WAAW,QAAQ,6BAA8B,iBAAgB,gBAAgB,wBAAwB,MAAM,YAAY;AACzH,QAAM,WAAW,MAAM,MAAM,oBAAoB,IAAI,IAAI,qBAAqB,EAAE,QAAQ,YAAY,QAAQ,GAAM,EAAE,CAAC;AACrH,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,8DAAY;AAC9C,QAAM,SAAS,MAAM,SAAS,KAAK;AACnC,QAAM,MAAM,OAAO,SAAS,OAAO;AACnC,QAAM,QAAQ,QAAQ,OAAO,YAAY,OAAO,OAAO,UAAU,OAAO,KAAK,WAAW,OAAO,KAAK,SAAS;AAC7G,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,aAAc,QAAO,EAAE,KAAK,OAAO,MAAM,mBAAa,KAAK,QAAQ,CAAC,KAAK,cAAI,gBAAa;AACvG,SAAO,OAAO,UAAU,EAAE,GAAG,OAAO,SAAS,OAAO,mBAAmB,OAAO,QAAQ,cAAc,IAAI;AAC1G,CAAC;AACD,MAAM,eAAe,mBAAmB,EAAE,OAAO,MAAM,KAAK,CAAC;AAG7D,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAAA,EACvB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA;AAAA,EAElB,8BAA8B;AAChC;AAEA,SAAS,aAAa,GAAY,UAA4B;AAC5D,MAAI,MAAM,UAAa,MAAM,KAAM,QAAO;AAC1C,MAAI,OAAO,MAAM,UAAW,QAAO;AACnC,QAAM,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE,YAAY;AACvC,MAAI,MAAM,UAAU,MAAM,OAAO,MAAM,SAAS,MAAM,KAAM,QAAO;AACnE,MAAI,MAAM,WAAW,MAAM,OAAO,MAAM,QAAQ,MAAM,MAAO,QAAO;AACpE,SAAO;AACT;AAEA,SAAS,mBAA2B;AAClC,SAAO,KAAK,IAAI,cAAc,WAAW,GAAG,kBAAkB,OAAO;AACvE;AAEA,SAAS,gBAAkC;AAEzC,QAAM,OAAO,WAAoC,iBAAiB,GAAG,EAAE,GAAG,iBAAiB,CAAC;AAC5F,SAAO;AAAA,IACL,kBAAkB,aAAa,KAAK,mBAAmB,iBAAiB,iBAAiB;AAAA,IACzF,iBAAiB,aAAa,KAAK,kBAAkB,iBAAiB,gBAAgB;AAAA,IACtF,0BAA0B;AAAA,MACxB,KAAK;AAAA,MACL,iBAAiB;AAAA,IACnB;AAAA,EACF;AACF;AAEA,SAAS,eAAe,SAAsD;AAC5E,QAAM,OAAO,iBAAiB;AAC9B,aAAoC,MAAM,EAAE,GAAG,iBAAiB,CAAC;AACjE,QAAM,QAAiC,CAAC;AACxC,MAAI,QAAQ,qBAAqB,OAAW,OAAM,oBAAoB,QAAQ;AAC9E,MAAI,QAAQ,oBAAoB,OAAW,OAAM,mBAAmB,QAAQ;AAE5E,MAAI,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AACjC,cAAU,MAAM,KAAK;AAAA,EACvB;AACA,SAAO,cAAc;AACvB;AAGA;AACE,QAAM,UAAU,CAAC,SAAS,iBAAiB,CAAC;AAC5C,aAAoC,iBAAiB,GAAG,EAAE,GAAG,iBAAiB,CAAC;AAC/E,MAAI,SAAS;AACX,QAAI,KAAK,mEAAsB,kBAAkB,OAAO;AAAA,EAC1D;AACF;AAEA,MAAM,eAAe,mBAAmB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB,MAAM;AACrB,UAAM,MAAO,IAAI,SAAS,UAAU,GAA+C;AACnF,QAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO,CAAC;AACjC,WAAO,IAAI,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAAA,EACxD;AAAA,EACA,cAAc,MAAM,cAAc;AAAA,EAClC,cAAc,CAAC,YAAY,eAAe,OAAO;AACnD,CAAC;AAED,SAAS,uBAA+C;AACtD,QAAM,OAAO,SAAkC,KAAK,IAAI,cAAc,WAAW,gBAAgB,CAAC;AAClG,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yFAA6B;AACxD,SAAO,sBAAsB,KAAK,SAAS;AAC7C;AAEA,SAAS,sBAAsB,OAAgE;AAC7F,QAAM,OAAO,KAAK,IAAI,cAAc,WAAW,gBAAgB;AAC/D,QAAM,WAAW,EAAE,GAAG,qBAAqB,GAAG,GAAG,MAAM;AACvD,YAAU,MAAM,EAAE,WAAW,SAAS,CAAC;AACvC,SAAO;AACT;AAEA,SAAS,kBAAkB;AACzB,SAAO,mBAAmB;AAAA,IACxB,YAAY,IAAI;AAAA,IAChB,YAAY,IAAI;AAAA,IAChB,YAAY,IAAI;AAAA,IAChB,aAAa,IAAI;AAAA,IACjB,aAAa,IAAI;AAAA,IACjB,WAAW,IAAI;AAAA,IACf,eAAe,IAAI;AAAA,IACnB,YAAY,IAAI;AAAA,IAChB,iBAAiB,IAAI;AAAA,EACvB,CAAC;AACH;AAEA,MAAM,qBAAqB,yBAAyB;AAAA,EAClD,WAAW;AAAA,EACX,aAAa,MAAM,gBAAgB;AACrC,CAAC;AACD,MAAM,iBAAiB,qBAAqB;AAAA,EAC1C;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS,CAAC,QAAQ,iBAAiB;AACjC,UAAM,SAAU,IAAI,SAAS,UAAU,GAA+C;AACtF,WACG,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,MAAM,KAAO,gBAAgB,cAAc,EAAE;AAAA,EAE3F;AACF,CAAC;AAGD,SAAS,gBAAgB,WAA8C;AACrE,MAAI;AACF,UAAM,OAAO,MAAM,kDAAkD,SAAS,EAAE;AAChF,WAAO,MAAM,QAAQ,IAAI,IAAM,KAAK,CAAC,KAAwC,OAAQ;AAAA,EACvF,SAAS,KAAK;AACZ,QAAI,KAAK,qDAAc,IAAc,OAAO,EAAE;AAC9C,WAAO;AAAA,EACT;AACF;AAGA,SAAS,uBAA6C;AACpD,MAAI;AACF,UAAM,OAAO,MAAM,qCAAqC;AACxD,QAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC;AAClC,WAAQ,KAA8B,OAAO,CAAC,QAAQ,IAAI,cAAc,CAAC;AAAA,EAC3E,SAAS,KAAK;AACZ,QAAI,KAAK,yDAAkB,IAAc,OAAO,EAAE;AAClD,WAAO,CAAC;AAAA,EACV;AACF;AAEA,MAAM,iBAAiB,qBAAqB;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,mBAAmB,CAAC,WAAmB,QAAgB,UAAkB,SAAiB,WACxF,kBAAkB,gBAAgB,GAAG,WAAW,QAAQ,UAAU,SAAS,MAAM;AACrF,CAAC;AAGD,SAAS,eAAe,CAAC,YAAY;AACnC,aAAW,QAAQ,SAAS;AAC1B,QAAI,KAAK,UAAU,qBAAsB;AACzC,UAAM,UAAU,uBAAuB,KAAK,KAAK,gBAAgB,OAAO,KAAK,IAAI,cAAc,EAAE,CAAC,CAAC;AACnG,QAAI,CAAC,QAAS;AACd;AAAA,MACE,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AACD,MAAM,eAAe,mBAAmB;AAAA,EACtC;AAAA,EACA,aAAa,IAAI;AAAA;AAAA,EAEjB,aAAa,MAAM,gBAAgB;AAAA,EACnC,iBAAiB,OAAO,EAAE,GAAG,WAAW,OAAO;AACjD,CAAC;AACD,MAAM,gBAAgB,oBAAoB;AAAA,EACxC;AAAA,EACA,cAAc;AAChB,CAAC;AACD,MAAM,uBAAuB,mBAAmB;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,MAAM,WAAW,eAAe,EAAE,gBAAgB,UAAU,YAAY,KAAK,CAAC;AAG9E,MAAM,gBAAgB,oBAAoB;AAAA,EACxC;AAAA,EACA,SAAS;AAAA,EACT;AACF,CAAC;AACD,MAAM,qBAAqB,yBAAyB;AAAA,EAClD,aAAa,IAAI;AAAA,EACjB,SAAS;AAAA,EACT;AACF,CAAC;AAGD,eAAe,OAAO,KAA2B,KAAyC;AACxF,QAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,QAAQ,WAAW,EAAE;AAC/E,QAAMA,QAAO,IAAI;AACjB,QAAM,SAAS,IAAI,UAAU;AAC7B,QAAM,SAAS,IAAI;AAGnB,QAAM,SAAS,IAAI,OAAO,iBAAiB;AAC3C,MAAI,UAAU,CAAC,OAAO,WAAW,MAAM,KAAK,WAAW,SAAS,WAAW,eAAe;AACxF,SAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD;AAAA,EACF;AAGA,QAAM,KAAK,GAAG;AAMd,QAAM,oBAAoBA,UAAS;AACnC,QAAM,kBACJA,MAAK,WAAW,eAAe,KAC/BA,MAAK,WAAW,oBAAoB,KACnC,8DAA8D,KAAKA,KAAI,KAAK,CAAC;AAChF,MAAI,gBAA8D;AAClE,MAAI,iBAAiB;AACnB,UAAM,KAAK,iBAAiB;AAAA,MAC1B,SAAS,IAAI;AAAA,MACb;AAAA,MACA,MAAM;AAAA,MACN,kBAAkB;AAAA,IACpB,CAAC;AACD,QAAI,CAAC,IAAI;AAEP,iBAAW,KAAK,yCAAyC,KAAK,cAAc;AAC5E;AAAA,IACF;AACA,UAAM,WAAW,iBAAiB,IAAI,EAAE;AACxC,oBAAgB;AAAA,MACd;AAAA,MACA,aAAa,UAAU,eAAe,CAAC;AAAA,IACzC;AAAA,EACF,OAAO;AAEL,UAAM,aACJA,UAAS,iBACTA,UAAS,sBACTA,UAAS,mCACTA,UAAS,gCACR,WAAW,UACTA,UAAS,uBACRA,UAAS,uBACTA,UAAS,+BACTA,MAAK,WAAW,oBAAoB,KACpCA,UAAS,0BACTA,UAAS;AAEf,UAAM,aACJ,CAAC,cACD,WAAW,SACX,EACEA,MAAK,WAAW,oBAAoB,KACpCA,MAAK,WAAW,oBAAoB,KACpCA,MAAK,WAAW,qBAAqB;AAEzC,QAAI,IAAI,cAAc,YAAY;AAChC,YAAM,OAAO,IAAI,QAAQ,eAAe,KAAK;AAC7C,YAAM,WAAW,KAAK,WAAW,SAAS,IAAI,KAAK,MAAM,CAAC,IAAK,IAAI,QAAQ,YAAY,KAAgB;AACvG,UAAI,aAAa,IAAI,YAAY;AAC/B,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,eAAe,GAAG,GAAG;AACxD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,QAAIA,UAAS,8BAA8B,WAAW,QAAQ;AAC5D,YAAM,OAAQ,MAAM,KAAK,GAAG;AAC5B,YAAM,aAAa,OAAO,KAAK,cAAc,EAAE,EAAE,KAAK;AACtD,YAAM,OAAO,OAAO,KAAK,QAAQ,EAAE,EAAE,KAAK;AAC1C,UAAI,CAAC,cAAc,CAAC,MAAM;AACxB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,+BAA+B,GAAG,GAAG;AACxE;AAAA,MACF;AACA,wBAAkB,OAAO,EAAE,YAAY,KAAK,CAAC;AAC7C,WAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAC3B;AAAA,IACF;AAEA,QACE,kBACCA,MAAK,WAAW,eAAe,KAC9BA,MAAK,WAAW,oBAAoB,KACnC,wCAAwC,KAAKA,KAAI,KAAK,CAAC,oBAC1D;AACA,YAAM,MAA+B;AAAA,QACnC,MAAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY;AAAA,MACd;AAGA,UAAI,MAAM,IAAI,MAAM,KAAK,GAAG;AAC5B,UAAIA,MAAK,WAAW,eAAe,GAAG;AACpC,YAAI,MAAM,SAAS,GAAG,EAAG;AAAA,MAC3B,WAAWA,MAAK,WAAW,oBAAoB,GAAG;AAChD,YAAI,MAAM,cAAc,GAAG,EAAG;AAAA,MAChC,OAAO;AACL,YAAI,MAAM,mBAAmB,GAAG,EAAG;AAAA,MACrC;AAAA,IACF;AAGA,UAAM,UAAU,EAAE,MAAAA,OAAM,QAAQ,QAAQ,KAAK,IAAI;AAOjD,QAAI,MAAM,cAAc,OAAO,EAAG;AAClC,QAAI,MAAM,qBAAqB,OAAO,EAAG;AACzC,QAAI,MAAM,aAAa,OAAO,EAAG;AACjC,QAAI,MAAM,aAAa,OAAO,EAAG;AACjC,QAAI,MAAM,aAAa,OAAO,EAAG;AACjC,QAAI,MAAM,aAAa,OAAO,EAAG;AACjC,QAAI,MAAM,eAAe,OAAO,EAAG;AACnC,QAAI,MAAM,eAAe,OAAO,EAAG;AACnC,QAAI,MAAM,aAAa,OAAO,EAAG;AAEjC,SAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,EACvD,SAAS,KAAK;AACZ,QAAI,IAAI,KAAK,QAAQ;AACrB,SAAK,KAAK,EAAE,SAAS,OAAO,OAAQ,IAAc,QAAQ,GAAG,GAAG;AAAA,EAClE;AACF;AAGA,MAAM,SAAS,aAAa;AAAA,EAC1B,KAAK,EAAE,MAAM,IAAI,MAAM,MAAM,IAAI,MAAM,YAAY,IAAI,WAAW;AAAA,EAClE;AACF,CAAC;AACD,aAAa,QAAQ,EAAE;",
|
|
6
6
|
"names": ["path"]
|
|
7
7
|
}
|
|
@@ -42,8 +42,6 @@ export interface ModuleManifestV2 {
|
|
|
42
42
|
provides: ServiceEntry[];
|
|
43
43
|
requires: ServiceEntry[];
|
|
44
44
|
};
|
|
45
|
-
enabledByDefault?: boolean | undefined;
|
|
46
|
-
canDisable?: boolean | undefined;
|
|
47
45
|
notes?: string | undefined;
|
|
48
46
|
}
|
|
49
47
|
/** v1 / 缺 schemaVersion:跳过(直到所有模块迁 v2) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE;QAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IACjE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAID,0CAA0C;AAC1C,qBAAa,oBAAqB,SAAQ,KAAK;IAC1B,QAAQ,EAAE,MAAM;IAAS,eAAe,EAAE,OAAO;IAApE,YAAmB,QAAQ,EAAE,MAAM,EAAS,eAAe,EAAE,OAAO,EAEnE;CACF;AA+FD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,WAAW,GAAE,MAA6B,GAAG,eAAe,CAuE1F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAMlC"}
|
package/dist/manifest-loader.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { defaultPackagesDir } from "./manifest.js";
|
|
4
4
|
import { validateManifestPermissions } from "./permission-gate.js";
|
|
5
|
+
import { isRetiredPlatformModule, PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from "@sfmc-bds/sdk/contracts";
|
|
5
6
|
const IDENT = /^[A-Za-z0-9_-]+$/;
|
|
6
7
|
class ModuleV1SkippedError extends Error {
|
|
7
8
|
constructor(moduleId, declaredVersion) {
|
|
@@ -25,11 +26,10 @@ function assertModuleObject(modId, obj) {
|
|
|
25
26
|
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
26
27
|
throw new Error(`[manifest] ${modId}: name \u7F3A\u5931`);
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throw new Error(`[manifest] ${modId}: canDisable \u5FC5\u987B\u662F boolean`);
|
|
29
|
+
for (const field of ["enabledByDefault", "canDisable"]) {
|
|
30
|
+
if (Object.prototype.hasOwnProperty.call(obj, field)) {
|
|
31
|
+
throw new Error(`[manifest] ${modId}: ${field} \u5DF2\u505C\u7528\uFF0C\u8BF7\u5220\u9664\uFF1B\u6A21\u5757\u542F\u505C\u7531\u670D\u4E3B\u901A\u8FC7 CLI/API \u7BA1\u7406`);
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
34
|
if (typeof obj.configKey !== "string" || obj.configKey.length === 0) {
|
|
35
35
|
throw new Error(`[manifest] ${modId}: configKey \u7F3A\u5931`);
|
|
@@ -99,8 +99,6 @@ function parseMod(modId, raw) {
|
|
|
99
99
|
requires,
|
|
100
100
|
permissions,
|
|
101
101
|
services: parseServices(modId, raw.services),
|
|
102
|
-
...typeof raw.enabledByDefault === "boolean" ? { enabledByDefault: raw.enabledByDefault } : {},
|
|
103
|
-
...typeof raw.canDisable === "boolean" ? { canDisable: raw.canDisable } : {},
|
|
104
102
|
notes: typeof raw.notes === "string" ? raw.notes : void 0
|
|
105
103
|
};
|
|
106
104
|
}
|
|
@@ -117,7 +115,7 @@ function loadManifestV2(packagesDir = defaultPackagesDir()) {
|
|
|
117
115
|
}
|
|
118
116
|
}).map((e) => e.name);
|
|
119
117
|
const modules = {};
|
|
120
|
-
const providesMap =
|
|
118
|
+
const providesMap = new Map(PLATFORM_MONITORING_SERVICES.map((name) => [name, PLATFORM_SERVICE_OWNER]));
|
|
121
119
|
for (const id of ids) {
|
|
122
120
|
const p = resolve(packagesDir, id, "sapi", "manifest.json");
|
|
123
121
|
if (!existsSync(p)) continue;
|
|
@@ -134,7 +132,9 @@ function loadManifestV2(packagesDir = defaultPackagesDir()) {
|
|
|
134
132
|
throw new Error(`[manifest] ${id}: ${p} \u975E\u5408\u6CD5 JSON: ${e.message}`);
|
|
135
133
|
}
|
|
136
134
|
try {
|
|
135
|
+
if (typeof parsed.id === "string" && isRetiredPlatformModule(parsed.id)) continue;
|
|
137
136
|
const m = parseMod(id, parsed);
|
|
137
|
+
if (m.id === PLATFORM_SERVICE_OWNER) throw new Error("_platform \u662F\u4FDD\u7559\u7684\u5E73\u53F0\u8EAB\u4EFD\uFF0C\u4E0D\u80FD\u7531\u6A21\u5757\u58F0\u660E");
|
|
138
138
|
modules[m.id] = m;
|
|
139
139
|
for (const s of m.services.provides) {
|
|
140
140
|
const prev = providesMap.get(s.name);
|
|
@@ -159,7 +159,7 @@ function loadManifestV2(packagesDir = defaultPackagesDir()) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
for (const dep of m.requires) {
|
|
162
|
-
if (!modules[dep]) {
|
|
162
|
+
if (!modules[dep] && !isRetiredPlatformModule(dep)) {
|
|
163
163
|
throw new Error(`[manifest] ${m.id}.requires.${dep} \u6CA1\u627E\u5230\u5BF9\u5E94\u6A21\u5757`);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -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 * \"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": "AAwBA,SAAS,YAAY,aAAa,cAAc,gBAAgB;AAChE,SAAS,eAAe;AACxB,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;
|
|
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\";\nimport { isRetiredPlatformModule, PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\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 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 for (const field of [\"enabledByDefault\", \"canDisable\"]) {\n if (Object.prototype.hasOwnProperty.call(obj, field)) {\n throw new Error(`[manifest] ${modId}: ${field} \u5DF2\u505C\u7528\uFF0C\u8BF7\u5220\u9664\uFF1B\u6A21\u5757\u542F\u505C\u7531\u670D\u4E3B\u901A\u8FC7 CLI/API \u7BA1\u7406`);\n }\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 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>(PLATFORM_MONITORING_SERVICES.map(name => [name, PLATFORM_SERVICE_OWNER]));\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 if (typeof parsed.id === \"string\" && isRetiredPlatformModule(parsed.id)) continue;\n const m = parseMod(id, parsed);\n if (m.id === PLATFORM_SERVICE_OWNER) throw new Error(\"_platform \u662F\u4FDD\u7559\u7684\u5E73\u53F0\u8EAB\u4EFD\uFF0C\u4E0D\u80FD\u7531\u6A21\u5757\u58F0\u660E\");\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] && !isRetiredPlatformModule(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/routes/qq-join.d.ts
CHANGED
|
@@ -8,9 +8,6 @@
|
|
|
8
8
|
* GET /api/sfmc/qq/join/pending
|
|
9
9
|
* GET /api/sfmc/qq/join/apply-queue
|
|
10
10
|
* POST /api/sfmc/qq/join/applied
|
|
11
|
-
* POST /api/sfmc/qq/admin/kick
|
|
12
|
-
* GET /api/sfmc/qq/admin/action-queue
|
|
13
|
-
* POST /api/sfmc/qq/admin/action-done
|
|
14
11
|
*/
|
|
15
12
|
import type { QueryFn } from "../lib/sqlite.js";
|
|
16
13
|
export type JoinFeatureFlags = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qq-join.d.ts","sourceRoot":"","sources":["../../src/routes/qq-join.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"qq-join.d.ts","sourceRoot":"","sources":["../../src/routes/qq-join.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,wBAAwB,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,oCAAoC;IACpC,eAAe,EAAE,MAAM,MAAM,EAAE,CAAC;IAChC,6BAA6B;IAC7B,YAAY,EAAE,MAAM,gBAAgB,CAAC;IACrC,uBAAuB;IACvB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,gBAAgB,CAAC;CACxE;AA4BD,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,IAAI,yCAO/F;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CA0NrB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/routes/qq-join.js
CHANGED
|
@@ -22,8 +22,7 @@ function createQqJoinRoutes({ query, body, json, getAdminOpenids, getJoinFlags,
|
|
|
22
22
|
res
|
|
23
23
|
}) {
|
|
24
24
|
const isJoin = path.startsWith("/api/sfmc/qq/join");
|
|
25
|
-
|
|
26
|
-
if (!isJoin && !isAdminPath) return false;
|
|
25
|
+
if (!isJoin) return false;
|
|
27
26
|
if (path === "/api/sfmc/qq/join/settings" && method === "GET") {
|
|
28
27
|
const flags = getJoinFlags();
|
|
29
28
|
json(res, {
|
|
@@ -216,57 +215,6 @@ function createQqJoinRoutes({ query, body, json, getAdminOpenids, getJoinFlags,
|
|
|
216
215
|
json(res, { success: true });
|
|
217
216
|
return true;
|
|
218
217
|
}
|
|
219
|
-
if (path === "/api/sfmc/qq/admin/kick" && method === "POST") {
|
|
220
|
-
const data = await body(req);
|
|
221
|
-
const openid = String(data.openid ?? data.requested_by ?? "").trim();
|
|
222
|
-
const target = String(data.target_name ?? data.name ?? "").trim();
|
|
223
|
-
const reason = String(data.reason ?? "QQ \u7BA1\u7406\u5458\u8E22\u51FA").trim().slice(0, 80);
|
|
224
|
-
if (!authorizeAdmin(getAdminOpenids, getJoinFlags, openid, data)) {
|
|
225
|
-
json(res, { success: false, error: "not_admin" }, 403);
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
if (!target) {
|
|
229
|
-
json(res, { success: false, error: "target_name_required" }, 400);
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
const id = newId("act");
|
|
233
|
-
const now = Date.now();
|
|
234
|
-
query(SQL`
|
|
235
|
-
INSERT INTO sfmc_qq_admin_actions
|
|
236
|
-
(id, kind, target_name, reason, requested_by, status, created_at)
|
|
237
|
-
VALUES (${id}, 'kick', ${target}, ${reason}, ${openid}, 'pending', ${now})
|
|
238
|
-
`);
|
|
239
|
-
json(res, { success: true, id });
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
if (path === "/api/sfmc/qq/admin/action-queue" && method === "GET") {
|
|
243
|
-
const rows = query(
|
|
244
|
-
SQL`SELECT id, kind, target_name, reason, created_at
|
|
245
|
-
FROM sfmc_qq_admin_actions WHERE status = 'pending'
|
|
246
|
-
ORDER BY created_at ASC LIMIT 32`
|
|
247
|
-
);
|
|
248
|
-
json(res, { success: true, queue: rows });
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
if (path === "/api/sfmc/qq/admin/action-done" && method === "POST") {
|
|
252
|
-
const data = await body(req);
|
|
253
|
-
const id = String(data.id ?? "").trim();
|
|
254
|
-
const ok = data.ok !== false && data.error == null;
|
|
255
|
-
const err = String(data.error ?? "").slice(0, 200);
|
|
256
|
-
if (!id) {
|
|
257
|
-
json(res, { success: false, error: "id_required" }, 400);
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
const now = Date.now();
|
|
261
|
-
const status = ok ? "done" : "failed";
|
|
262
|
-
query(SQL`
|
|
263
|
-
UPDATE sfmc_qq_admin_actions
|
|
264
|
-
SET status = ${status}, applied_at = ${now}, apply_error = ${ok ? "" : err}
|
|
265
|
-
WHERE id = ${id} AND status = 'pending'
|
|
266
|
-
`);
|
|
267
|
-
json(res, { success: true });
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
218
|
json(res, { success: false, error: "not_found" }, 404);
|
|
271
219
|
return true;
|
|
272
220
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/qq-join.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/qq-join.ts \u2014 \u5165\u670D\u5BA1\u6279\u72B6\u6001\u673A + BDS \u751F\u6548\u961F\u5217\uFF08\u4E0D\u5199 allowlist \u6587\u4EF6\uFF09\n *\n * GET /api/sfmc/qq/join/settings\n * POST /api/sfmc/qq/join/settings\n * POST /api/sfmc/qq/join/request\n * POST /api/sfmc/qq/join/decide\n * GET /api/sfmc/qq/join/pending\n * GET /api/sfmc/qq/join/apply-queue\n * POST /api/sfmc/qq/join/applied\n * POST /api/sfmc/qq/admin/kick\n * GET /api/sfmc/qq/admin/action-queue\n * POST /api/sfmc/qq/admin/action-done\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nexport type JoinFeatureFlags = {\n /** \u5165\u670D\u767D\u540D\u5355\u603B\u5F00\u5173 */\n allowlistEnabled: boolean;\n /** \u662F\u5426\u9700\u8981\u7BA1\u7406\u5458\u5BA1\u6279 */\n requireApproval: boolean;\n /**\n * \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u7406\u5458\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\u3002\u7F3A\u7701\u5F00\u542F\u3002\n * \u4EC5 configs/qq_link.json \u53EF\u6539\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199\u3002\n */\n treatGroupAdminsAsAdmins: boolean;\n};\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n /** \u5F53\u524D\u7BA1\u7406\u5458 openid \u5217\u8868\uFF08\u6765\u81EA qq_config\uFF09 */\n getAdminOpenids: () => string[];\n /** \u8BFB\u53D6\u5165\u670D\u5F00\u5173\uFF08\u5E94\u8BFB\u6700\u65B0 qq_config\uFF09 */\n getJoinFlags: () => JoinFeatureFlags;\n /** \u5199\u56DE\u5165\u670D\u5F00\u5173\uFF08\u6301\u4E45\u5316 + \u5185\u5B58\uFF09 */\n setJoinFlags: (partial: Partial<JoinFeatureFlags>) => JoinFeatureFlags;\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}_${Date.now().toString(36)}_${randomBytes(4).toString(\"hex\")}`;\n}\n\nfunction isAdmin(getAdminOpenids: () => string[], openid: string): boolean {\n const list = getAdminOpenids();\n if (!openid || list.length === 0) return false;\n return list.includes(openid);\n}\n\n/** openid \u5728\u540D\u5355\u5185\uFF0C\u6216\uFF08\u5F00\u5173\u5F00\u4E14 bridge \u58F0\u660E as_group_admin\uFF09 */\nfunction authorizeAdmin(\n getAdminOpenids: () => string[],\n getJoinFlags: () => JoinFeatureFlags,\n openid: string,\n data: Record<string, unknown>\n): boolean {\n if (isAdmin(getAdminOpenids, openid)) return true;\n const asGroup =\n data.as_group_admin === true ||\n data.as_group_admin === \"true\" ||\n data.is_group_admin === true ||\n data.is_group_admin === \"true\";\n return asGroup && getJoinFlags().treatGroupAdminsAsAdmins === true;\n}\n\nfunction createQqJoinRoutes({ query, body, json, getAdminOpenids, getJoinFlags, setJoinFlags }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n const isJoin = path.startsWith(\"/api/sfmc/qq/join\");\n const isAdminPath = path.startsWith(\"/api/sfmc/qq/admin\");\n if (!isJoin && !isAdminPath) return false;\n\n // \u2500\u2500 \u5165\u670D\u5F00\u5173\uFF08\u6587\u4EF6 + \u673A\u5668\u4EBA\u53EF\u6539\u767D\u540D\u5355/\u5BA1\u6279\uFF1B\u7FA4\u7BA1\u5F00\u5173\u53EA\u8BFB\uFF09\u2500\u2500\n if (path === \"/api/sfmc/qq/join/settings\" && method === \"GET\") {\n const flags = getJoinFlags();\n json(res, {\n success: true,\n allowlist_enabled: flags.allowlistEnabled,\n require_approval: flags.requireApproval,\n treat_group_admins_as_admins: flags.treatGroupAdminsAsAdmins,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/settings\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.decided_by ?? \"\").trim();\n if (\n Object.prototype.hasOwnProperty.call(data, \"treat_group_admins_as_admins\") ||\n Object.prototype.hasOwnProperty.call(data, \"treatGroupAdminsAsAdmins\")\n ) {\n json(\n res,\n {\n success: false,\n error: \"immutable_field\",\n note: \"treat_group_admins_as_admins \u53EA\u80FD\u6539 configs/qq_link.json\uFF0C\u7FA4\u804A\u4E0D\u53EF\u6539\",\n },\n 400\n );\n return true;\n }\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, openid, data)) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n const partial: Partial<JoinFeatureFlags> = {};\n if (Object.prototype.hasOwnProperty.call(data, \"allowlist_enabled\")) {\n partial.allowlistEnabled = data.allowlist_enabled === true || data.allowlist_enabled === \"true\";\n }\n if (Object.prototype.hasOwnProperty.call(data, \"require_approval\")) {\n partial.requireApproval = data.require_approval === true || data.require_approval === \"true\";\n }\n if (partial.allowlistEnabled === undefined && partial.requireApproval === undefined) {\n json(res, { success: false, error: \"no_settings\" }, 400);\n return true;\n }\n const flags = setJoinFlags(partial);\n json(res, {\n success: true,\n allowlist_enabled: flags.allowlistEnabled,\n require_approval: flags.requireApproval,\n treat_group_admins_as_admins: flags.treatGroupAdminsAsAdmins,\n });\n return true;\n }\n\n // \u2500\u2500 \u5165\u670D\u7533\u8BF7 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (path === \"/api/sfmc/qq/join/request\" && method === \"POST\") {\n const flags = getJoinFlags();\n if (!flags.allowlistEnabled) {\n json(res, { success: false, error: \"join_allowlist_disabled\" }, 403);\n return true;\n }\n const data = await body(req);\n const openid = String(data.openid ?? data.applicant_openid ?? \"\").trim();\n const playerName = String(data.player_name ?? data.name ?? \"\").trim();\n const backend = String(data.qq_backend ?? \"official\").trim() || \"official\";\n if (!openid) {\n json(res, { success: false, error: \"openid_required\" }, 400);\n return true;\n }\n if (!playerName || playerName.length > 32) {\n json(res, { success: false, error: \"player_name_required\" }, 400);\n return true;\n }\n const id = newId(\"join\");\n const now = Date.now();\n const autoApproved = !flags.requireApproval;\n const status = autoApproved ? \"approved\" : \"pending\";\n if (autoApproved) {\n query(SQL`\n INSERT INTO sfmc_qq_join_requests\n (id, applicant_openid, player_name, status, qq_backend, created_at, decided_at, decided_by)\n VALUES (${id}, ${openid}, ${playerName}, ${status}, ${backend}, ${now}, ${now}, 'auto')\n `);\n } else {\n query(SQL`\n INSERT INTO sfmc_qq_join_requests\n (id, applicant_openid, player_name, status, qq_backend, created_at)\n VALUES (${id}, ${openid}, ${playerName}, ${status}, ${backend}, ${now})\n `);\n }\n const admins = getAdminOpenids();\n json(res, {\n success: true,\n id,\n player_name: playerName,\n status,\n auto_approved: autoApproved,\n admin_notify: !autoApproved && admins.length > 0,\n note: autoApproved\n ? \"\u5DF2\u5173\u95ED\u5BA1\u6279\uFF0C\u7533\u8BF7\u5DF2\u81EA\u52A8\u901A\u8FC7\uFF0C\u7B49\u5F85 BDS \u5199\u5165\u767D\u540D\u5355\"\n : admins.length === 0\n ? \"\u672A\u914D\u7F6E official.admin_openids\uFF0C\u65E0\u6CD5\u901A\u77E5\u7BA1\u7406\u5458\"\n : undefined,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/decide\" && method === \"POST\") {\n const data = await body(req);\n const id = String(data.id ?? data.request_id ?? \"\").trim();\n const decision = String(data.decision ?? \"\").trim().toLowerCase();\n const decidedBy = String(data.decided_by ?? data.openid ?? \"\").trim();\n if (!id || (decision !== \"approve\" && decision !== \"reject\")) {\n json(res, { success: false, error: \"id_and_decision_required\" }, 400);\n return true;\n }\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, decidedBy, data)) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n const rows = query(SQL`SELECT * FROM sfmc_qq_join_requests WHERE id = ${id} LIMIT 1`) as Array<\n Record<string, unknown>\n >;\n const row = rows[0];\n if (!row) {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (String(row.status) !== \"pending\") {\n json(res, { success: false, error: \"already_decided\", status: row.status }, 409);\n return true;\n }\n const next = decision === \"approve\" ? \"approved\" : \"rejected\";\n const now = Date.now();\n query(SQL`\n UPDATE sfmc_qq_join_requests\n SET status = ${next}, decided_at = ${now}, decided_by = ${decidedBy}\n WHERE id = ${id}\n `);\n json(res, {\n success: true,\n id,\n status: next,\n player_name: row.player_name,\n applicant_openid: row.applicant_openid,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/pending\" && method === \"GET\") {\n const openid = String(params.get(\"openid\") ?? \"\").trim();\n if (openid) {\n const asGroup =\n params.get(\"as_group_admin\") === \"true\" || params.get(\"is_group_admin\") === \"true\";\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, openid, { as_group_admin: asGroup })) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n }\n const rows = query(\n SQL`SELECT id, applicant_openid, player_name, status, created_at\n FROM sfmc_qq_join_requests WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT 50`\n ) as Array<Record<string, unknown>>;\n json(res, { success: true, pending: rows });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/apply-queue\" && method === \"GET\") {\n const flags = getJoinFlags();\n if (!flags.allowlistEnabled) {\n json(res, { success: true, queue: [], note: \"join_allowlist_disabled\" });\n return true;\n }\n const rows = query(\n SQL`SELECT id, player_name, applicant_openid, decided_at\n FROM sfmc_qq_join_requests WHERE status = 'approved'\n ORDER BY decided_at ASC LIMIT 32`\n ) as Array<Record<string, unknown>>;\n json(res, { success: true, queue: rows });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/applied\" && method === \"POST\") {\n const data = await body(req);\n const id = String(data.id ?? \"\").trim();\n const ok = data.ok !== false && data.error == null;\n const err = String(data.error ?? \"\").slice(0, 200);\n if (!id) {\n json(res, { success: false, error: \"id_required\" }, 400);\n return true;\n }\n const now = Date.now();\n if (ok) {\n query(SQL`\n UPDATE sfmc_qq_join_requests\n SET status = 'applied', applied_at = ${now}, apply_error = NULL\n WHERE id = ${id} AND status = 'approved'\n `);\n } else {\n query(SQL`\n UPDATE sfmc_qq_join_requests\n SET status = 'apply_failed', applied_at = ${now}, apply_error = ${err}\n WHERE id = ${id} AND status = 'approved'\n `);\n }\n json(res, { success: true });\n return true;\n }\n\n // \u2500\u2500 \u8E22\u4EBA\u961F\u5217 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (path === \"/api/sfmc/qq/admin/kick\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.requested_by ?? \"\").trim();\n const target = String(data.target_name ?? data.name ?? \"\").trim();\n const reason = String(data.reason ?? \"QQ \u7BA1\u7406\u5458\u8E22\u51FA\").trim().slice(0, 80);\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, openid, data)) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n if (!target) {\n json(res, { success: false, error: \"target_name_required\" }, 400);\n return true;\n }\n const id = newId(\"act\");\n const now = Date.now();\n query(SQL`\n INSERT INTO sfmc_qq_admin_actions\n (id, kind, target_name, reason, requested_by, status, created_at)\n VALUES (${id}, 'kick', ${target}, ${reason}, ${openid}, 'pending', ${now})\n `);\n json(res, { success: true, id });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/admin/action-queue\" && method === \"GET\") {\n const rows = query(\n SQL`SELECT id, kind, target_name, reason, created_at\n FROM sfmc_qq_admin_actions WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT 32`\n ) as Array<Record<string, unknown>>;\n json(res, { success: true, queue: rows });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/admin/action-done\" && method === \"POST\") {\n const data = await body(req);\n const id = String(data.id ?? \"\").trim();\n const ok = data.ok !== false && data.error == null;\n const err = String(data.error ?? \"\").slice(0, 200);\n if (!id) {\n json(res, { success: false, error: \"id_required\" }, 400);\n return true;\n }\n const now = Date.now();\n const status = ok ? \"done\" : \"failed\";\n query(SQL`\n UPDATE sfmc_qq_admin_actions\n SET status = ${status}, applied_at = ${now}, apply_error = ${ok ? \"\" : err}\n WHERE id = ${id} AND status = 'pending'\n `);\n json(res, { success: true });\n return true;\n }\n\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n };\n}\n\nexport { createQqJoinRoutes };\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * routes/qq-join.ts \u2014 \u5165\u670D\u5BA1\u6279\u72B6\u6001\u673A + BDS \u751F\u6548\u961F\u5217\uFF08\u4E0D\u5199 allowlist \u6587\u4EF6\uFF09\n *\n * GET /api/sfmc/qq/join/settings\n * POST /api/sfmc/qq/join/settings\n * POST /api/sfmc/qq/join/request\n * POST /api/sfmc/qq/join/decide\n * GET /api/sfmc/qq/join/pending\n * GET /api/sfmc/qq/join/apply-queue\n * POST /api/sfmc/qq/join/applied\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nexport type JoinFeatureFlags = {\n /** \u5165\u670D\u767D\u540D\u5355\u603B\u5F00\u5173 */\n allowlistEnabled: boolean;\n /** \u662F\u5426\u9700\u8981\u7BA1\u7406\u5458\u5BA1\u6279 */\n requireApproval: boolean;\n /**\n * \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u7406\u5458\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\u3002\u7F3A\u7701\u5F00\u542F\u3002\n * \u4EC5 configs/qq_link.json \u53EF\u6539\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199\u3002\n */\n treatGroupAdminsAsAdmins: boolean;\n};\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n /** \u5F53\u524D\u7BA1\u7406\u5458 openid \u5217\u8868\uFF08\u6765\u81EA qq_config\uFF09 */\n getAdminOpenids: () => string[];\n /** \u8BFB\u53D6\u5165\u670D\u5F00\u5173\uFF08\u5E94\u8BFB\u6700\u65B0 qq_config\uFF09 */\n getJoinFlags: () => JoinFeatureFlags;\n /** \u5199\u56DE\u5165\u670D\u5F00\u5173\uFF08\u6301\u4E45\u5316 + \u5185\u5B58\uFF09 */\n setJoinFlags: (partial: Partial<JoinFeatureFlags>) => JoinFeatureFlags;\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}_${Date.now().toString(36)}_${randomBytes(4).toString(\"hex\")}`;\n}\n\nfunction isAdmin(getAdminOpenids: () => string[], openid: string): boolean {\n const list = getAdminOpenids();\n if (!openid || list.length === 0) return false;\n return list.includes(openid);\n}\n\n/** openid \u5728\u540D\u5355\u5185\uFF0C\u6216\uFF08\u5F00\u5173\u5F00\u4E14 bridge \u58F0\u660E as_group_admin\uFF09 */\nfunction authorizeAdmin(\n getAdminOpenids: () => string[],\n getJoinFlags: () => JoinFeatureFlags,\n openid: string,\n data: Record<string, unknown>\n): boolean {\n if (isAdmin(getAdminOpenids, openid)) return true;\n const asGroup =\n data.as_group_admin === true ||\n data.as_group_admin === \"true\" ||\n data.is_group_admin === true ||\n data.is_group_admin === \"true\";\n return asGroup && getJoinFlags().treatGroupAdminsAsAdmins === true;\n}\n\nfunction createQqJoinRoutes({ query, body, json, getAdminOpenids, getJoinFlags, setJoinFlags }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n const isJoin = path.startsWith(\"/api/sfmc/qq/join\");\n if (!isJoin) return false;\n\n // \u2500\u2500 \u5165\u670D\u5F00\u5173\uFF08\u6587\u4EF6 + \u673A\u5668\u4EBA\u53EF\u6539\u767D\u540D\u5355/\u5BA1\u6279\uFF1B\u7FA4\u7BA1\u5F00\u5173\u53EA\u8BFB\uFF09\u2500\u2500\n if (path === \"/api/sfmc/qq/join/settings\" && method === \"GET\") {\n const flags = getJoinFlags();\n json(res, {\n success: true,\n allowlist_enabled: flags.allowlistEnabled,\n require_approval: flags.requireApproval,\n treat_group_admins_as_admins: flags.treatGroupAdminsAsAdmins,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/settings\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.decided_by ?? \"\").trim();\n if (\n Object.prototype.hasOwnProperty.call(data, \"treat_group_admins_as_admins\") ||\n Object.prototype.hasOwnProperty.call(data, \"treatGroupAdminsAsAdmins\")\n ) {\n json(\n res,\n {\n success: false,\n error: \"immutable_field\",\n note: \"treat_group_admins_as_admins \u53EA\u80FD\u6539 configs/qq_link.json\uFF0C\u7FA4\u804A\u4E0D\u53EF\u6539\",\n },\n 400\n );\n return true;\n }\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, openid, data)) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n const partial: Partial<JoinFeatureFlags> = {};\n if (Object.prototype.hasOwnProperty.call(data, \"allowlist_enabled\")) {\n partial.allowlistEnabled = data.allowlist_enabled === true || data.allowlist_enabled === \"true\";\n }\n if (Object.prototype.hasOwnProperty.call(data, \"require_approval\")) {\n partial.requireApproval = data.require_approval === true || data.require_approval === \"true\";\n }\n if (partial.allowlistEnabled === undefined && partial.requireApproval === undefined) {\n json(res, { success: false, error: \"no_settings\" }, 400);\n return true;\n }\n const flags = setJoinFlags(partial);\n json(res, {\n success: true,\n allowlist_enabled: flags.allowlistEnabled,\n require_approval: flags.requireApproval,\n treat_group_admins_as_admins: flags.treatGroupAdminsAsAdmins,\n });\n return true;\n }\n\n // \u2500\u2500 \u5165\u670D\u7533\u8BF7 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (path === \"/api/sfmc/qq/join/request\" && method === \"POST\") {\n const flags = getJoinFlags();\n if (!flags.allowlistEnabled) {\n json(res, { success: false, error: \"join_allowlist_disabled\" }, 403);\n return true;\n }\n const data = await body(req);\n const openid = String(data.openid ?? data.applicant_openid ?? \"\").trim();\n const playerName = String(data.player_name ?? data.name ?? \"\").trim();\n const backend = String(data.qq_backend ?? \"official\").trim() || \"official\";\n if (!openid) {\n json(res, { success: false, error: \"openid_required\" }, 400);\n return true;\n }\n if (!playerName || playerName.length > 32) {\n json(res, { success: false, error: \"player_name_required\" }, 400);\n return true;\n }\n const id = newId(\"join\");\n const now = Date.now();\n const autoApproved = !flags.requireApproval;\n const status = autoApproved ? \"approved\" : \"pending\";\n if (autoApproved) {\n query(SQL`\n INSERT INTO sfmc_qq_join_requests\n (id, applicant_openid, player_name, status, qq_backend, created_at, decided_at, decided_by)\n VALUES (${id}, ${openid}, ${playerName}, ${status}, ${backend}, ${now}, ${now}, 'auto')\n `);\n } else {\n query(SQL`\n INSERT INTO sfmc_qq_join_requests\n (id, applicant_openid, player_name, status, qq_backend, created_at)\n VALUES (${id}, ${openid}, ${playerName}, ${status}, ${backend}, ${now})\n `);\n }\n const admins = getAdminOpenids();\n json(res, {\n success: true,\n id,\n player_name: playerName,\n status,\n auto_approved: autoApproved,\n admin_notify: !autoApproved && admins.length > 0,\n note: autoApproved\n ? \"\u5DF2\u5173\u95ED\u5BA1\u6279\uFF0C\u7533\u8BF7\u5DF2\u81EA\u52A8\u901A\u8FC7\uFF0C\u7B49\u5F85 BDS \u5199\u5165\u767D\u540D\u5355\"\n : admins.length === 0\n ? \"\u672A\u914D\u7F6E official.admin_openids\uFF0C\u65E0\u6CD5\u901A\u77E5\u7BA1\u7406\u5458\"\n : undefined,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/decide\" && method === \"POST\") {\n const data = await body(req);\n const id = String(data.id ?? data.request_id ?? \"\").trim();\n const decision = String(data.decision ?? \"\").trim().toLowerCase();\n const decidedBy = String(data.decided_by ?? data.openid ?? \"\").trim();\n if (!id || (decision !== \"approve\" && decision !== \"reject\")) {\n json(res, { success: false, error: \"id_and_decision_required\" }, 400);\n return true;\n }\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, decidedBy, data)) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n const rows = query(SQL`SELECT * FROM sfmc_qq_join_requests WHERE id = ${id} LIMIT 1`) as Array<\n Record<string, unknown>\n >;\n const row = rows[0];\n if (!row) {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (String(row.status) !== \"pending\") {\n json(res, { success: false, error: \"already_decided\", status: row.status }, 409);\n return true;\n }\n const next = decision === \"approve\" ? \"approved\" : \"rejected\";\n const now = Date.now();\n query(SQL`\n UPDATE sfmc_qq_join_requests\n SET status = ${next}, decided_at = ${now}, decided_by = ${decidedBy}\n WHERE id = ${id}\n `);\n json(res, {\n success: true,\n id,\n status: next,\n player_name: row.player_name,\n applicant_openid: row.applicant_openid,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/pending\" && method === \"GET\") {\n const openid = String(params.get(\"openid\") ?? \"\").trim();\n if (openid) {\n const asGroup =\n params.get(\"as_group_admin\") === \"true\" || params.get(\"is_group_admin\") === \"true\";\n if (!authorizeAdmin(getAdminOpenids, getJoinFlags, openid, { as_group_admin: asGroup })) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n }\n const rows = query(\n SQL`SELECT id, applicant_openid, player_name, status, created_at\n FROM sfmc_qq_join_requests WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT 50`\n ) as Array<Record<string, unknown>>;\n json(res, { success: true, pending: rows });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/apply-queue\" && method === \"GET\") {\n const flags = getJoinFlags();\n if (!flags.allowlistEnabled) {\n json(res, { success: true, queue: [], note: \"join_allowlist_disabled\" });\n return true;\n }\n const rows = query(\n SQL`SELECT id, player_name, applicant_openid, decided_at\n FROM sfmc_qq_join_requests WHERE status = 'approved'\n ORDER BY decided_at ASC LIMIT 32`\n ) as Array<Record<string, unknown>>;\n json(res, { success: true, queue: rows });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/join/applied\" && method === \"POST\") {\n const data = await body(req);\n const id = String(data.id ?? \"\").trim();\n const ok = data.ok !== false && data.error == null;\n const err = String(data.error ?? \"\").slice(0, 200);\n if (!id) {\n json(res, { success: false, error: \"id_required\" }, 400);\n return true;\n }\n const now = Date.now();\n if (ok) {\n query(SQL`\n UPDATE sfmc_qq_join_requests\n SET status = 'applied', applied_at = ${now}, apply_error = NULL\n WHERE id = ${id} AND status = 'approved'\n `);\n } else {\n query(SQL`\n UPDATE sfmc_qq_join_requests\n SET status = 'apply_failed', applied_at = ${now}, apply_error = ${err}\n WHERE id = ${id} AND status = 'approved'\n `);\n }\n json(res, { success: true });\n return true;\n }\n\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n };\n}\n\nexport { createQqJoinRoutes };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/routes/status.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/** GET /api/sfmc/status 与 BDS 实时快照入口。 */
|
|
2
2
|
import { type SystemStatusSnapshot } from "../domain/system-status.js";
|
|
3
|
+
import type { QueryFn } from "../lib/sqlite.js";
|
|
3
4
|
import { type RouteFactory } from "./_shared.js";
|
|
4
5
|
declare const FRESH_MS = 60000;
|
|
5
6
|
interface Deps {
|
|
6
7
|
collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;
|
|
7
8
|
projectRoot?: string;
|
|
9
|
+
query?: QueryFn;
|
|
8
10
|
}
|
|
9
|
-
declare function createStatusRoutes({ collectSystem, projectRoot }: Deps): ReturnType<RouteFactory>;
|
|
11
|
+
declare function createStatusRoutes({ collectSystem, projectRoot, query }: Deps): ReturnType<RouteFactory>;
|
|
10
12
|
export { createStatusRoutes, FRESH_MS };
|
|
11
13
|
//# sourceMappingURL=status.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAEzC,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAEzC,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAI5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIhD,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAE7D,QAAA,MAAM,QAAQ,QAAS,CAAC;AAQxB,UAAU,IAAI;IACZ,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,iBAAS,kBAAkB,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,CAgGjG;AAED,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/routes/status.js
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import { collectSystemStatus } from "../domain/system-status.js";
|
|
2
2
|
import { PROJECT_ROOT } from "../project-root.js";
|
|
3
|
+
import { MetricsHistory, parseMetrics } from "../domain/metrics.js";
|
|
4
|
+
import { collectProcessResources } from "../domain/process-resources.js";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import pathModule from "node:path";
|
|
7
|
+
import { stateDir } from "@sfmc-bds/sdk/node/config";
|
|
3
8
|
import { body, json } from "./_shared.js";
|
|
4
9
|
const FRESH_MS = 6e4;
|
|
5
|
-
function createStatusRoutes({ collectSystem, projectRoot }) {
|
|
10
|
+
function createStatusRoutes({ collectSystem, projectRoot, query }) {
|
|
6
11
|
const root = projectRoot ?? PROJECT_ROOT;
|
|
7
12
|
const collect = collectSystem ?? collectSystemStatus;
|
|
8
13
|
let live = null;
|
|
14
|
+
let metrics = null;
|
|
15
|
+
const history = query ? new MetricsHistory(query) : void 0;
|
|
16
|
+
let systemCache;
|
|
17
|
+
let collecting;
|
|
18
|
+
const getSystem = async () => {
|
|
19
|
+
if (systemCache && Date.now() - systemCache.at < 5e3) return systemCache.data;
|
|
20
|
+
collecting ??= collect(root).then((data) => {
|
|
21
|
+
systemCache = { data, at: Date.now() };
|
|
22
|
+
return data;
|
|
23
|
+
}).finally(() => {
|
|
24
|
+
collecting = void 0;
|
|
25
|
+
});
|
|
26
|
+
return collecting;
|
|
27
|
+
};
|
|
9
28
|
return async function handle({ path, method, req, res }) {
|
|
10
|
-
if (path === "/api/sfmc/status/live") {
|
|
29
|
+
if (path === "/api/sfmc/status/live" || path === "/api/sfmc/metrics/live") {
|
|
11
30
|
if (method !== "POST") {
|
|
12
31
|
json(res, { success: false, error: "not_found" }, 404);
|
|
13
32
|
return true;
|
|
@@ -20,6 +39,15 @@ function createStatusRoutes({ collectSystem, projectRoot }) {
|
|
|
20
39
|
json(res, { success: false, error: "invalid_live_status" }, 400);
|
|
21
40
|
return true;
|
|
22
41
|
}
|
|
42
|
+
if (path === "/api/sfmc/metrics/live") {
|
|
43
|
+
const sample = parseMetrics(data, Date.now());
|
|
44
|
+
if (!sample) {
|
|
45
|
+
json(res, { success: false, error: "invalid_metrics" }, 400);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
history?.append(sample);
|
|
49
|
+
metrics = sample;
|
|
50
|
+
}
|
|
23
51
|
live = {
|
|
24
52
|
online: raw.map((name) => ({ name: name.trim() })),
|
|
25
53
|
world: { day, difficulty },
|
|
@@ -28,14 +56,14 @@ function createStatusRoutes({ collectSystem, projectRoot }) {
|
|
|
28
56
|
json(res, { success: true });
|
|
29
57
|
return true;
|
|
30
58
|
}
|
|
31
|
-
if (path !== "/api/sfmc/status") return false;
|
|
59
|
+
if (path !== "/api/sfmc/status" && path !== "/api/sfmc/metrics") return false;
|
|
32
60
|
if (method !== "GET") {
|
|
33
61
|
json(res, { success: false, error: "not_found" }, 404);
|
|
34
62
|
return true;
|
|
35
63
|
}
|
|
36
64
|
let system = null;
|
|
37
65
|
try {
|
|
38
|
-
system = await
|
|
66
|
+
system = await getSystem();
|
|
39
67
|
} catch {
|
|
40
68
|
system = null;
|
|
41
69
|
}
|
|
@@ -45,6 +73,27 @@ function createStatusRoutes({ collectSystem, projectRoot }) {
|
|
|
45
73
|
const snapshot = live;
|
|
46
74
|
const fresh = snapshot !== null && bds?.state === "running" && now - snapshot.updatedAt <= FRESH_MS && (bdsAgeMs === null || now - snapshot.updatedAt <= bdsAgeMs + 2e3);
|
|
47
75
|
const current = fresh ? snapshot : null;
|
|
76
|
+
if (path === "/api/sfmc/metrics") {
|
|
77
|
+
let ownedPid = 0;
|
|
78
|
+
try {
|
|
79
|
+
ownedPid = Number(fs.readFileSync(pathModule.join(stateDir(root), "bds.pid"), "utf8").trim());
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && bds?.pid === ownedPid;
|
|
83
|
+
const metricFresh = owned && metrics !== null && bds?.state === "running" && now - metrics.recordedAt <= 6e4 && (bdsAgeMs === null || now - metrics.recordedAt <= bdsAgeMs + 2e3);
|
|
84
|
+
const processes = system ? { db: await collectProcessResources(system.db.pid, system.db.running), bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null } : null;
|
|
85
|
+
json(res, {
|
|
86
|
+
fresh: metricFresh,
|
|
87
|
+
current: metricFresh ? metrics : null,
|
|
88
|
+
updatedAt: metrics?.recordedAt ?? null,
|
|
89
|
+
history: history?.read(now) ?? [],
|
|
90
|
+
host: system?.host ?? null,
|
|
91
|
+
processes,
|
|
92
|
+
resourcesUpdatedAt: system ? Date.now() : null,
|
|
93
|
+
note: metricFresh ? void 0 : bds?.state === "stopped" ? "\u670D\u52A1\u5668\u672A\u8FD0\u884C" : "\u7B49\u5F85\u5E73\u53F0\u6307\u6807\u540C\u6B65"
|
|
94
|
+
});
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
48
97
|
json(res, {
|
|
49
98
|
online: current?.online ?? [],
|
|
50
99
|
world: current?.world ?? null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/status.ts"],
|
|
4
|
-
"sourcesContent": ["/** GET /api/sfmc/status \u4E0E BDS \u5B9E\u65F6\u5FEB\u7167\u5165\u53E3\u3002 */\n\nimport { collectSystemStatus, type SystemStatusSnapshot } from \"../domain/system-status.js\";\nimport { PROJECT_ROOT } from \"../project-root.js\";\nimport { body, json, type RouteFactory } from \"./_shared.js\";\n\nconst FRESH_MS = 60_000;\n\ninterface LiveSnapshot {\n online: Array<{ name: string }>;\n world: { day: number; difficulty: string };\n updatedAt: number;\n}\n\ninterface Deps {\n collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;\n projectRoot?: string;\n}\n\nfunction createStatusRoutes({ collectSystem, projectRoot }: Deps): ReturnType<RouteFactory> {\n const root = projectRoot ?? PROJECT_ROOT;\n const collect = collectSystem ?? collectSystemStatus;\n let live: LiveSnapshot | null = null;\n\n return async function handle({ path, method, req, res }): Promise<boolean> {\n if (path === \"/api/sfmc/status/live\") {\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n const data = await body(req);\n const raw = data.players;\n const day = data.day;\n const difficulty = data.difficulty;\n if (\n !Array.isArray(raw) || raw.length > 1000 ||\n !raw.every((name) => typeof name === \"string\" && name.trim().length > 0 && name.length <= 64) ||\n typeof day !== \"number\" || !Number.isSafeInteger(day) || day < 0 ||\n typeof difficulty !== \"string\" || difficulty.length > 32\n ) {\n json(res, { success: false, error: \"invalid_live_status\" }, 400);\n return true;\n }\n live = {\n online: raw.map((name: string) => ({ name: name.trim() })),\n world: { day, difficulty },\n updatedAt: Date.now(),\n };\n json(res, { success: true });\n return true;\n }\n\n if (path !== \"/api/sfmc/status\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n let system: SystemStatusSnapshot | null = null;\n try {\n system = await
|
|
5
|
-
"mappings": "AAEA,SAAS,2BAAsD;AAC/D,SAAS,oBAAoB;AAC7B,SAAS,MAAM,YAA+B;AAE9C,MAAM,WAAW;
|
|
4
|
+
"sourcesContent": ["/** GET /api/sfmc/status \u4E0E BDS \u5B9E\u65F6\u5FEB\u7167\u5165\u53E3\u3002 */\n\nimport { collectSystemStatus, type SystemStatusSnapshot } from \"../domain/system-status.js\";\nimport { PROJECT_ROOT } from \"../project-root.js\";\nimport { MetricsHistory, parseMetrics, type MetricsSample } from \"../domain/metrics.js\";\nimport { collectProcessResources } from \"../domain/process-resources.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\nimport fs from \"node:fs\";\nimport pathModule from \"node:path\";\nimport { stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { body, json, type RouteFactory } from \"./_shared.js\";\n\nconst FRESH_MS = 60_000;\n\ninterface LiveSnapshot {\n online: Array<{ name: string }>;\n world: { day: number; difficulty: string };\n updatedAt: number;\n}\n\ninterface Deps {\n collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;\n projectRoot?: string;\n query?: QueryFn;\n}\n\nfunction createStatusRoutes({ collectSystem, projectRoot, query }: Deps): ReturnType<RouteFactory> {\n const root = projectRoot ?? PROJECT_ROOT;\n const collect = collectSystem ?? collectSystemStatus;\n let live: LiveSnapshot | null = null;\n let metrics: MetricsSample | null = null;\n const history = query ? new MetricsHistory(query) : undefined;\n let systemCache: { data: SystemStatusSnapshot; at: number } | undefined;\n let collecting: Promise<SystemStatusSnapshot> | undefined;\n const getSystem = async () => {\n if (systemCache && Date.now() - systemCache.at < 5000) return systemCache.data;\n collecting ??= collect(root).then(data => { systemCache = { data, at: Date.now() }; return data; }).finally(() => { collecting = undefined; });\n return collecting;\n };\n\n return async function handle({ path, method, req, res }): Promise<boolean> {\n if (path === \"/api/sfmc/status/live\" || path === \"/api/sfmc/metrics/live\") {\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n const data = await body(req);\n const raw = data.players;\n const day = data.day;\n const difficulty = data.difficulty;\n if (\n !Array.isArray(raw) || raw.length > 1000 ||\n !raw.every((name) => typeof name === \"string\" && name.trim().length > 0 && name.length <= 64) ||\n typeof day !== \"number\" || !Number.isSafeInteger(day) || day < 0 ||\n typeof difficulty !== \"string\" || difficulty.length > 32\n ) {\n json(res, { success: false, error: \"invalid_live_status\" }, 400);\n return true;\n }\n if (path === \"/api/sfmc/metrics/live\") {\n const sample = parseMetrics(data, Date.now());\n if (!sample) { json(res, { success: false, error: \"invalid_metrics\" }, 400); return true; }\n history?.append(sample);\n metrics = sample;\n }\n live = {\n online: raw.map((name: string) => ({ name: name.trim() })),\n world: { day, difficulty },\n updatedAt: Date.now(),\n };\n json(res, { success: true });\n return true;\n }\n\n if (path !== \"/api/sfmc/status\" && path !== \"/api/sfmc/metrics\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n let system: SystemStatusSnapshot | null = null;\n try {\n system = await getSystem();\n } catch {\n system = null;\n }\n\n const now = Date.now();\n const bds = system?.bds;\n const bdsAgeMs = typeof bds?.uptimeSec === \"number\" ? bds.uptimeSec * 1000 : null;\n const snapshot = live;\n const fresh = snapshot !== null &&\n bds?.state === \"running\" &&\n now - snapshot.updatedAt <= FRESH_MS &&\n (bdsAgeMs === null || now - snapshot.updatedAt <= bdsAgeMs + 2_000);\n const current = fresh ? snapshot : null;\n\n if (path === \"/api/sfmc/metrics\") {\n // \u8D44\u6E90\u6307\u6807\u53EA\u8BA4\u5F53\u524D\u5B9E\u4F8B\u7684 PID\uFF0C\u4E0D\u80FD\u4F7F\u7528\u5168\u673A\u626B\u63CF\u627E\u5230\u7684\u53E6\u4E00\u5957 BDS\u3002\n let ownedPid = 0;\n try { ownedPid = Number(fs.readFileSync(pathModule.join(stateDir(root), \"bds.pid\"), \"utf8\").trim()); } catch { /* \u672A\u6258\u7BA1\u5B9E\u4F8B\u4E0D\u731C\u6D4B\u8FDB\u7A0B\u5F52\u5C5E\u3002 */ }\n const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && bds?.pid === ownedPid;\n const metricFresh = owned && metrics !== null && bds?.state === \"running\" && now - metrics.recordedAt <= 60_000 &&\n (bdsAgeMs === null || now - metrics.recordedAt <= bdsAgeMs + 2000);\n const processes = system ? { db: await collectProcessResources(system.db.pid, system.db.running), bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null } : null;\n json(res, { fresh: metricFresh, current: metricFresh ? metrics : null, updatedAt: metrics?.recordedAt ?? null,\n history: history?.read(now) ?? [], host: system?.host ?? null, processes, resourcesUpdatedAt: system ? Date.now() : null,\n note: metricFresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u5E73\u53F0\u6307\u6807\u540C\u6B65\" });\n return true;\n }\n\n json(res, {\n online: current?.online ?? [],\n world: current?.world ?? null,\n updatedAt: current?.updatedAt ?? null,\n host: system?.host ?? null,\n processes: system ? { db: system.db, bds: system.bds } : null,\n source: \"bds_script_live\",\n note: fresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u6E38\u620F\u5B9E\u65F6\u72B6\u6001\u540C\u6B65\",\n });\n return true;\n };\n}\n\nexport { createStatusRoutes, FRESH_MS };\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,2BAAsD;AAC/D,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB,oBAAwC;AACjE,SAAS,+BAA+B;AAExC,OAAO,QAAQ;AACf,OAAO,gBAAgB;AACvB,SAAS,gBAAgB;AACzB,SAAS,MAAM,YAA+B;AAE9C,MAAM,WAAW;AAcjB,SAAS,mBAAmB,EAAE,eAAe,aAAa,MAAM,GAAmC;AACjG,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,iBAAiB;AACjC,MAAI,OAA4B;AAChC,MAAI,UAAgC;AACpC,QAAM,UAAU,QAAQ,IAAI,eAAe,KAAK,IAAI;AACpD,MAAI;AACJ,MAAI;AACJ,QAAM,YAAY,YAAY;AAC5B,QAAI,eAAe,KAAK,IAAI,IAAI,YAAY,KAAK,IAAM,QAAO,YAAY;AAC1E,mBAAe,QAAQ,IAAI,EAAE,KAAK,UAAQ;AAAE,oBAAc,EAAE,MAAM,IAAI,KAAK,IAAI,EAAE;AAAG,aAAO;AAAA,IAAM,CAAC,EAAE,QAAQ,MAAM;AAAE,mBAAa;AAAA,IAAW,CAAC;AAC7I,WAAO;AAAA,EACT;AAEA,SAAO,eAAe,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,GAAqB;AACzE,QAAI,SAAS,2BAA2B,SAAS,0BAA0B;AACzE,UAAI,WAAW,QAAQ;AACrB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,eAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,YAAM,MAAM,KAAK;AACjB,YAAM,MAAM,KAAK;AACjB,YAAM,aAAa,KAAK;AACxB,UACE,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,SAAS,OACpC,CAAC,IAAI,MAAM,CAAC,SAAS,OAAO,SAAS,YAAY,KAAK,KAAK,EAAE,SAAS,KAAK,KAAK,UAAU,EAAE,KAC5F,OAAO,QAAQ,YAAY,CAAC,OAAO,cAAc,GAAG,KAAK,MAAM,KAC/D,OAAO,eAAe,YAAY,WAAW,SAAS,IACtD;AACA,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,sBAAsB,GAAG,GAAG;AAC/D,eAAO;AAAA,MACT;AACA,UAAI,SAAS,0BAA0B;AACrC,cAAM,SAAS,aAAa,MAAM,KAAK,IAAI,CAAC;AAC5C,YAAI,CAAC,QAAQ;AAAE,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,GAAG,GAAG;AAAG,iBAAO;AAAA,QAAM;AAC1F,iBAAS,OAAO,MAAM;AACtB,kBAAU;AAAA,MACZ;AACA,aAAO;AAAA,QACL,QAAQ,IAAI,IAAI,CAAC,UAAkB,EAAE,MAAM,KAAK,KAAK,EAAE,EAAE;AAAA,QACzD,OAAO,EAAE,KAAK,WAAW;AAAA,QACzB,WAAW,KAAK,IAAI;AAAA,MACtB;AACA,WAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,sBAAsB,SAAS,oBAAqB,QAAO;AACxE,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,QAAI,SAAsC;AAC1C,QAAI;AACF,eAAS,MAAM,UAAU;AAAA,IAC3B,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,QAAQ;AACpB,UAAM,WAAW,OAAO,KAAK,cAAc,WAAW,IAAI,YAAY,MAAO;AAC7E,UAAM,WAAW;AACjB,UAAM,QAAQ,aAAa,QACzB,KAAK,UAAU,aACf,MAAM,SAAS,aAAa,aAC3B,aAAa,QAAQ,MAAM,SAAS,aAAa,WAAW;AAC/D,UAAM,UAAU,QAAQ,WAAW;AAEnC,QAAI,SAAS,qBAAqB;AAEhC,UAAI,WAAW;AACf,UAAI;AAAE,mBAAW,OAAO,GAAG,aAAa,WAAW,KAAK,SAAS,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,KAAK,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAsB;AACnI,YAAM,QAAQ,OAAO,cAAc,QAAQ,KAAK,WAAW,KAAK,KAAK,QAAQ;AAC7E,YAAM,cAAc,SAAS,YAAY,QAAQ,KAAK,UAAU,aAAa,MAAM,QAAQ,cAAc,QACtG,aAAa,QAAQ,MAAM,QAAQ,cAAc,WAAW;AAC/D,YAAM,YAAY,SAAS,EAAE,IAAI,MAAM,wBAAwB,OAAO,GAAG,KAAK,OAAO,GAAG,OAAO,GAAG,KAAK,QAAQ,MAAM,wBAAwB,OAAO,IAAI,KAAK,OAAO,IAAI,OAAO,IAAI,KAAK,IAAI;AAC5L,WAAK,KAAK;AAAA,QAAE,OAAO;AAAA,QAAa,SAAS,cAAc,UAAU;AAAA,QAAM,WAAW,SAAS,cAAc;AAAA,QACvG,SAAS,SAAS,KAAK,GAAG,KAAK,CAAC;AAAA,QAAG,MAAM,QAAQ,QAAQ;AAAA,QAAM;AAAA,QAAW,oBAAoB,SAAS,KAAK,IAAI,IAAI;AAAA,QACpH,MAAM,cAAc,SAAY,KAAK,UAAU,YAAY,yCAAW;AAAA,MAAW,CAAC;AACpF,aAAO;AAAA,IACT;AAEA,SAAK,KAAK;AAAA,MACR,QAAQ,SAAS,UAAU,CAAC;AAAA,MAC5B,OAAO,SAAS,SAAS;AAAA,MACzB,WAAW,SAAS,aAAa;AAAA,MACjC,MAAM,QAAQ,QAAQ;AAAA,MACtB,WAAW,SAAS,EAAE,IAAI,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI;AAAA,MACzD,QAAQ;AAAA,MACR,MAAM,QAAQ,SAAY,KAAK,UAAU,YAAY,yCAAW;AAAA,IAClE,CAAC;AACD,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../src/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../src/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,YAAY,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,EAAE,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAO/E,mBAAmB;AACnB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAK5E;IAED;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEpC;IAED;;;;OAIG;IACH,IAAI,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAEhD;IAED;;;;;;;;;;;OAWG;IACG,QAAQ,CAEZ,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACtC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,gBAAgB,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAsC5B;CAEF;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IACxF,MAAM,EAAE,MAAM,CAAC;IACf,YACE,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,EACvF,MAAM,EAAE,MAAM,EAKf;CACF"}
|
package/dist/service-registry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from "@sfmc-bds/sdk/contracts";
|
|
1
2
|
class ServiceRegistry {
|
|
2
3
|
handlers = /* @__PURE__ */ new Map();
|
|
3
4
|
/**
|
|
@@ -46,7 +47,8 @@ class ServiceRegistry {
|
|
|
46
47
|
if (!handler) {
|
|
47
48
|
throw new DispatchError(`service "${name}" \u672A\u6CE8\u518C`, "no_such_service", 404);
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
const platformService = handler.moduleId === PLATFORM_SERVICE_OWNER && PLATFORM_MONITORING_SERVICES.some((value) => value === name);
|
|
51
|
+
if (!platformService && !enabled.has(handler.moduleId)) {
|
|
50
52
|
throw new DispatchError(`service "${name}" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, "forbidden", 403);
|
|
51
53
|
}
|
|
52
54
|
const caller = enabled.get(callerModuleId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/service-registry.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * service-registry.ts \u2014 \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\uFF08Service Registry\uFF09\n *\n * \u534F\u8BAE\u89C4\u8303\uFF1A\n * - HTTP GET `/api/sfmc/services/<name>?input=<urlencoded-json>`\n * - \u9274\u6743\u673A\u5236\uFF1ABearer module_token + \u67E5\u8BE2\u53C2\u6570 `?moduleId=<callerId>`\n *\n * \u8C03\u5EA6\u4E0E\u4E8B\u52A1\u8FB9\u754C\uFF1A\n * \u670D\u52A1\u5904\u7406\u5668\u6CE8\u518C\u5728 db-server \u8FDB\u7A0B\u5185\u5B58\u4E2D\u3002\n * \u5728\u5916\u5C42 `db.tx` \u4E8B\u52A1\u4F1A\u8BDD\u5185\u8C03\u7528 `tx.call` \u65F6\uFF0C\u4F1A\u901A\u8FC7 `ctx.tx = { query, db }` \u6CE8\u5165\u73B0\u6709\u4E8B\u52A1\u4E0A\u4E0B\u6587\uFF0C\n * \u5904\u7406\u5668\u590D\u7528\u5F53\u524D\u4E8B\u52A1\u8FDE\u63A5\uFF0C\u4E25\u7981\u91CD\u590D\u5F00\u542F\u5D4C\u5957\u4E8B\u52A1\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport type { QueryFn } from \"./lib/sqlite.js\";\n\nexport interface ServiceCallResult {\n ok: true;\n result: unknown;\n}\n\nexport interface ServiceTxContext {\n query: QueryFn;\n db: DatabaseSync;\n}\n\nexport interface ServiceDispatchContext {\n callerModuleId: string;\n payload: unknown;\n /** \u5916\u5C42 db.tx \u5DF2\u6253\u5F00\u65F6\u6CE8\u5165\uFF1B\u5904\u7406\u5668\u590D\u7528\u6B64\u8FDE\u63A5\uFF0C\u7981\u6B62\u5F00\u542F\u5D4C\u5957 BEGIN\u3002 */\n tx?: ServiceTxContext;\n}\n\nexport type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;\n\ninterface RegisteredHandler {\n moduleId: string;\n handle: ServiceHandler;\n}\n\n/** \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u7BA1\u7406\u5668\u3002 */\nexport class ServiceRegistry {\n private readonly handlers = new Map<string, RegisteredHandler>();\n\n /**\n * \u6CE8\u518C\u6307\u5B9A\u6A21\u5757\u63D0\u4F9B\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param moduleId \u670D\u52A1\u63D0\u4F9B\u65B9\u6A21\u5757 ID\u3002\n * @param name \u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param handle \u4E1A\u52A1\u5904\u7406\u51FD\u6570\u3002\n */\n registerHandler(moduleId: string, name: string, handle: ServiceHandler): void {\n if (this.handlers.has(name)) {\n throw new Error(`[service] \"${name}\" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C, ${moduleId} \u62A2\u6CE8`);\n }\n this.handlers.set(name, { moduleId, handle });\n }\n\n /**\n * \u6CE8\u9500\u6307\u5B9A\u540D\u79F0\u7684\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param name \u5F85\u6CE8\u9500\u7684\u670D\u52A1\u540D\u79F0\u3002\n */\n unregisterHandler(name: string): void {\n this.handlers.delete(name);\n }\n\n /**\n * \u83B7\u53D6\u5F53\u524D\u5DF2\u6CE8\u518C\u7684\u5168\u90E8\u8DE8\u6A21\u5757\u670D\u52A1\u5217\u8868\u3002\n *\n * @returns \u5305\u542B\u670D\u52A1\u540D\u4E0E\u6240\u5C5E\u6A21\u5757 ID \u7684\u5217\u8868\u3002\n */\n list(): Array<{ name: string; moduleId: string }> {\n return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));\n }\n\n /**\n * \u8C03\u5EA6\u5E76\u6267\u884C\u6307\u5B9A\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u3002\n * \u5305\u542B\u8C03\u7528\u65B9\u4E0E\u63D0\u4F9B\u65B9\u542F\u505C\u72B6\u6001\u6821\u9A8C\u3001\u58F0\u660E\u5F0F\u5951\u7EA6\uFF08`services.requires`\uFF09\u5339\u914D\u53CA\u5F02\u5E38\u5206\u6D41\u3002\n *\n * @param enabled \u5F53\u524D\u6240\u6709\u5DF2\u542F\u7528\u7684\u6A21\u5757\u6E05\u5355\u5B57\u5178\u3002\n * @param callerModuleId \u53D1\u8D77\u8C03\u7528\u7684\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param name \u76EE\u6807\u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param payload \u4F20\u5165\u8C03\u7528\u7684\u53C2\u6570\u6570\u636E\u3002\n * @param tx \u53EF\u9009\u7684\u5F53\u524D\u4E8B\u52A1\u4E0A\u4E0B\u6587\u3002\n * @returns \u670D\u52A1\u6267\u884C\u8FD4\u56DE\u7ED3\u679C\u3002\n * @throws {DispatchError} \u670D\u52A1\u672A\u627E\u5230\u3001\u672A\u58F0\u660E\u4F9D\u8D56\u6216\u9274\u6743\u5931\u8D25\u65F6\u629B\u51FA\u3002\n */\n async dispatch(\n\n enabled: Map<string, ModuleManifestV2>,\n callerModuleId: string,\n name: string,\n payload: unknown,\n tx?: ServiceTxContext\n ): Promise<ServiceCallResult> {\n const handler = this.handlers.get(name);\n if (!handler) {\n throw new DispatchError(`service \"${name}\" \u672A\u6CE8\u518C`, \"no_such_service\", 404);\n }\n if (!enabled.has(handler.moduleId)) {\n throw new DispatchError(`service \"${name}\" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, \"forbidden\", 403);\n }\n const caller = enabled.get(callerModuleId);\n if (!caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n const declared = caller.services.requires.find((r) => r.name === name);\n // \u63D0\u4F9B\u65B9\u8C03\u81EA\u5DF1\u7684 service \u514D requires(\u4F8B\u5982 economy \u767D\u76AE\u4E66\u8C03 stats.monthly)\n if (!declared && callerModuleId !== handler.moduleId) {\n throw new DispatchError(\n `${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E \"${name}\"`,\n \"not_in_requires\",\n 403\n );\n }\n try {\n const result = await handler.handle({\n callerModuleId,\n payload,\n ...(tx ? { tx } : {}),\n });\n return { ok: true, result };\n } catch (e) {\n // \u4FDD\u7559 handler \u629B\u51FA\u7684\u9886\u57DF/\u9274\u6743\u9519\u8BEF\u5951\u7EA6,\u52FF\u538B\u6210 500 internal(LSP)\n if (e instanceof DispatchError) throw e;\n const err = e as Error & { code?: string; status?: number };\n if (typeof err.status === \"number\" && err.code) {\n throw new DispatchError(err.message, err.code as DispatchError[\"code\"], err.status);\n }\n throw new DispatchError(`handler \u629B\u9519: ${err.message}`, \"internal\", 500);\n }\n }\n\n}\n\nexport class DispatchError extends Error {\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\";\n status: number;\n constructor(\n message: string,\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\",\n status: number\n ) {\n super(message);\n this.code = code;\n this.status = status;\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * service-registry.ts \u2014 \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\uFF08Service Registry\uFF09\n *\n * \u534F\u8BAE\u89C4\u8303\uFF1A\n * - HTTP GET `/api/sfmc/services/<name>?input=<urlencoded-json>`\n * - \u9274\u6743\u673A\u5236\uFF1ABearer module_token + \u67E5\u8BE2\u53C2\u6570 `?moduleId=<callerId>`\n *\n * \u8C03\u5EA6\u4E0E\u4E8B\u52A1\u8FB9\u754C\uFF1A\n * \u670D\u52A1\u5904\u7406\u5668\u6CE8\u518C\u5728 db-server \u8FDB\u7A0B\u5185\u5B58\u4E2D\u3002\n * \u5728\u5916\u5C42 `db.tx` \u4E8B\u52A1\u4F1A\u8BDD\u5185\u8C03\u7528 `tx.call` \u65F6\uFF0C\u4F1A\u901A\u8FC7 `ctx.tx = { query, db }` \u6CE8\u5165\u73B0\u6709\u4E8B\u52A1\u4E0A\u4E0B\u6587\uFF0C\n * \u5904\u7406\u5668\u590D\u7528\u5F53\u524D\u4E8B\u52A1\u8FDE\u63A5\uFF0C\u4E25\u7981\u91CD\u590D\u5F00\u542F\u5D4C\u5957\u4E8B\u52A1\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport type { QueryFn } from \"./lib/sqlite.js\";\nimport { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\n\nexport interface ServiceCallResult {\n ok: true;\n result: unknown;\n}\n\nexport interface ServiceTxContext {\n query: QueryFn;\n db: DatabaseSync;\n}\n\nexport interface ServiceDispatchContext {\n callerModuleId: string;\n payload: unknown;\n /** \u5916\u5C42 db.tx \u5DF2\u6253\u5F00\u65F6\u6CE8\u5165\uFF1B\u5904\u7406\u5668\u590D\u7528\u6B64\u8FDE\u63A5\uFF0C\u7981\u6B62\u5F00\u542F\u5D4C\u5957 BEGIN\u3002 */\n tx?: ServiceTxContext;\n}\n\nexport type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;\n\ninterface RegisteredHandler {\n moduleId: string;\n handle: ServiceHandler;\n}\n\n/** \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u7BA1\u7406\u5668\u3002 */\nexport class ServiceRegistry {\n private readonly handlers = new Map<string, RegisteredHandler>();\n\n /**\n * \u6CE8\u518C\u6307\u5B9A\u6A21\u5757\u63D0\u4F9B\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param moduleId \u670D\u52A1\u63D0\u4F9B\u65B9\u6A21\u5757 ID\u3002\n * @param name \u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param handle \u4E1A\u52A1\u5904\u7406\u51FD\u6570\u3002\n */\n registerHandler(moduleId: string, name: string, handle: ServiceHandler): void {\n if (this.handlers.has(name)) {\n throw new Error(`[service] \"${name}\" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C, ${moduleId} \u62A2\u6CE8`);\n }\n this.handlers.set(name, { moduleId, handle });\n }\n\n /**\n * \u6CE8\u9500\u6307\u5B9A\u540D\u79F0\u7684\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param name \u5F85\u6CE8\u9500\u7684\u670D\u52A1\u540D\u79F0\u3002\n */\n unregisterHandler(name: string): void {\n this.handlers.delete(name);\n }\n\n /**\n * \u83B7\u53D6\u5F53\u524D\u5DF2\u6CE8\u518C\u7684\u5168\u90E8\u8DE8\u6A21\u5757\u670D\u52A1\u5217\u8868\u3002\n *\n * @returns \u5305\u542B\u670D\u52A1\u540D\u4E0E\u6240\u5C5E\u6A21\u5757 ID \u7684\u5217\u8868\u3002\n */\n list(): Array<{ name: string; moduleId: string }> {\n return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));\n }\n\n /**\n * \u8C03\u5EA6\u5E76\u6267\u884C\u6307\u5B9A\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u3002\n * \u5305\u542B\u8C03\u7528\u65B9\u4E0E\u63D0\u4F9B\u65B9\u542F\u505C\u72B6\u6001\u6821\u9A8C\u3001\u58F0\u660E\u5F0F\u5951\u7EA6\uFF08`services.requires`\uFF09\u5339\u914D\u53CA\u5F02\u5E38\u5206\u6D41\u3002\n *\n * @param enabled \u5F53\u524D\u6240\u6709\u5DF2\u542F\u7528\u7684\u6A21\u5757\u6E05\u5355\u5B57\u5178\u3002\n * @param callerModuleId \u53D1\u8D77\u8C03\u7528\u7684\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param name \u76EE\u6807\u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param payload \u4F20\u5165\u8C03\u7528\u7684\u53C2\u6570\u6570\u636E\u3002\n * @param tx \u53EF\u9009\u7684\u5F53\u524D\u4E8B\u52A1\u4E0A\u4E0B\u6587\u3002\n * @returns \u670D\u52A1\u6267\u884C\u8FD4\u56DE\u7ED3\u679C\u3002\n * @throws {DispatchError} \u670D\u52A1\u672A\u627E\u5230\u3001\u672A\u58F0\u660E\u4F9D\u8D56\u6216\u9274\u6743\u5931\u8D25\u65F6\u629B\u51FA\u3002\n */\n async dispatch(\n\n enabled: Map<string, ModuleManifestV2>,\n callerModuleId: string,\n name: string,\n payload: unknown,\n tx?: ServiceTxContext\n ): Promise<ServiceCallResult> {\n const handler = this.handlers.get(name);\n if (!handler) {\n throw new DispatchError(`service \"${name}\" \u672A\u6CE8\u518C`, \"no_such_service\", 404);\n }\n const platformService = handler.moduleId === PLATFORM_SERVICE_OWNER && PLATFORM_MONITORING_SERVICES.some(value => value === name);\n if (!platformService && !enabled.has(handler.moduleId)) {\n throw new DispatchError(`service \"${name}\" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, \"forbidden\", 403);\n }\n const caller = enabled.get(callerModuleId);\n if (!caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n const declared = caller.services.requires.find((r) => r.name === name);\n // \u63D0\u4F9B\u65B9\u8C03\u81EA\u5DF1\u7684 service \u514D requires(\u4F8B\u5982 economy \u767D\u76AE\u4E66\u8C03 stats.monthly)\n if (!declared && callerModuleId !== handler.moduleId) {\n throw new DispatchError(\n `${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E \"${name}\"`,\n \"not_in_requires\",\n 403\n );\n }\n try {\n const result = await handler.handle({\n callerModuleId,\n payload,\n ...(tx ? { tx } : {}),\n });\n return { ok: true, result };\n } catch (e) {\n // \u4FDD\u7559 handler \u629B\u51FA\u7684\u9886\u57DF/\u9274\u6743\u9519\u8BEF\u5951\u7EA6,\u52FF\u538B\u6210 500 internal(LSP)\n if (e instanceof DispatchError) throw e;\n const err = e as Error & { code?: string; status?: number };\n if (typeof err.status === \"number\" && err.code) {\n throw new DispatchError(err.message, err.code as DispatchError[\"code\"], err.status);\n }\n throw new DispatchError(`handler \u629B\u9519: ${err.message}`, \"internal\", 500);\n }\n }\n\n}\n\nexport class DispatchError extends Error {\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\";\n status: number;\n constructor(\n message: string,\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\",\n status: number\n ) {\n super(message);\n this.code = code;\n this.status = status;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAgBA,SAAS,8BAA8B,8BAA8B;AA2B9D,MAAM,gBAAgB;AAAA,EACV,WAAW,oBAAI,IAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/D,gBAAgB,UAAkB,MAAc,QAA8B;AAC5E,QAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI,MAAM,cAAc,IAAI,kBAAQ,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,kBAAQ,QAAQ,eAAK;AAAA,IAClG;AACA,SAAK,SAAS,IAAI,MAAM,EAAE,UAAU,OAAO,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,MAAoB;AACpC,SAAK,SAAS,OAAO,IAAI;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAkD;AAChD,WAAO,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,SAEJ,SACA,gBACA,MACA,SACA,IAC4B;AAC5B,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,mBAAmB,GAAG;AAAA,IACzE;AACA,UAAM,kBAAkB,QAAQ,aAAa,0BAA0B,6BAA6B,KAAK,WAAS,UAAU,IAAI;AAChI,QAAI,CAAC,mBAAmB,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AACtD,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,QAAQ,QAAQ,mBAAc,aAAa,GAAG;AAAA,IACjG;AACA,UAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,cAAc,sBAAO,cAAc,mBAAc,aAAa,GAAG;AAAA,IAC7E;AACA,UAAM,WAAW,OAAO,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAErE,QAAI,CAAC,YAAY,mBAAmB,QAAQ,UAAU;AACpD,YAAM,IAAI;AAAA,QACR,GAAG,cAAc,0DAAsC,IAAI;AAAA,QAC3D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,OAAO;AAAA,QAClC;AAAA,QACA;AAAA,QACA,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,MACrB,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,OAAO;AAAA,IAC5B,SAAS,GAAG;AAEV,UAAI,aAAa,cAAe,OAAM;AACtC,YAAM,MAAM;AACZ,UAAI,OAAO,IAAI,WAAW,YAAY,IAAI,MAAM;AAC9C,cAAM,IAAI,cAAc,IAAI,SAAS,IAAI,MAA+B,IAAI,MAAM;AAAA,MACpF;AACA,YAAM,IAAI,cAAc,yBAAe,IAAI,OAAO,IAAI,YAAY,GAAG;AAAA,IACvE;AAAA,EACF;AAEF;AAEO,MAAM,sBAAsB,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EACA,YACE,SACA,MACA,QACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sfmc-bds/db-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
|
|
5
5
|
"author": "Shiroha7z",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"sql-template-strings": "^2.2.2",
|
|
35
|
-
"@sfmc-bds/bds-tools": "0.2.
|
|
36
|
-
"@sfmc-bds/sdk": "0.
|
|
35
|
+
"@sfmc-bds/bds-tools": "0.2.5",
|
|
36
|
+
"@sfmc-bds/sdk": "0.3.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@sfmc-bds/tools": "0.2.
|
|
39
|
+
"@sfmc-bds/tools": "0.2.5"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public",
|