@sfmc-bds/db-server 0.4.0 → 0.5.1
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 +24 -4
- package/dist/domain/metrics.d.ts.map +1 -1
- package/dist/domain/metrics.js +93 -31
- package/dist/domain/metrics.js.map +2 -2
- package/dist/domain/qq-bindings.d.ts +13 -0
- package/dist/domain/qq-bindings.d.ts.map +1 -0
- package/dist/domain/qq-bindings.js +38 -0
- package/dist/domain/qq-bindings.js.map +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +2 -2
- package/dist/module-auth.d.ts +1 -1
- package/dist/module-auth.d.ts.map +1 -1
- package/dist/module-auth.js +3 -1
- package/dist/module-auth.js.map +2 -2
- package/dist/routes/status.d.ts.map +1 -1
- package/dist/routes/status.js +14 -4
- package/dist/routes/status.js.map +2 -2
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +11 -8
- package/dist/service-registry.js.map +2 -2
- package/package.json +22 -4
package/dist/domain/metrics.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type QueryFn } from "../lib/sqlite.js";
|
|
2
|
+
/** 运行指标写入去重间隔。读历史时一小时内的点数按这个间隔封顶,避免再被固定 720 条截断。 */
|
|
3
|
+
export declare const RUNTIME_SAMPLE_MS = 3000;
|
|
4
|
+
/** 资源指标写入去重间隔。 */
|
|
5
|
+
export declare const RESOURCE_SAMPLE_MS = 8000;
|
|
6
|
+
/** 不抽稀的最长窗口,与原先「最近一小时」一致。 */
|
|
7
|
+
export declare const METRICS_RAW_WINDOW_MS = 3600000;
|
|
8
|
+
/** 落库保留时长,与 prune 删除条件一致。请求更长的范围会被截到这里。 */
|
|
9
|
+
export declare const METRICS_RETENTION_MS: number;
|
|
10
|
+
/**
|
|
11
|
+
* 把调用方给出的窗口收成落库能覆盖的范围。
|
|
12
|
+
* 缺省仍是一小时,旧客户端不传 spanMs 时行为不变。
|
|
13
|
+
*/
|
|
14
|
+
export declare function clampMetricsSpan(value: unknown): number;
|
|
3
15
|
export interface MetricsSample {
|
|
4
16
|
recordedAt: number;
|
|
5
17
|
bootId: string;
|
|
@@ -22,7 +34,7 @@ export declare class ResourceHistory {
|
|
|
22
34
|
private lastPrune;
|
|
23
35
|
constructor(query: QueryFn);
|
|
24
36
|
append(sample: ResourceSample): void;
|
|
25
|
-
read(now?: number): ResourceSample[];
|
|
37
|
+
read(now?: number, spanMs?: number): ResourceSample[];
|
|
26
38
|
}
|
|
27
39
|
export declare class MetricsHistory {
|
|
28
40
|
private query;
|
|
@@ -31,6 +43,14 @@ export declare class MetricsHistory {
|
|
|
31
43
|
constructor(query: QueryFn);
|
|
32
44
|
append(sample: MetricsSample): void;
|
|
33
45
|
private prune;
|
|
34
|
-
read(now?: number): MetricsSample[];
|
|
46
|
+
read(now?: number, spanMs?: number): MetricsSample[];
|
|
35
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* 只读打开已落盘的指标库。文件不存在或仍被占用时返回 null,不创建文件、不建表。
|
|
50
|
+
* 使用场景:数据服务进程没在跑时,管理通道仍要画出停服前的曲线。
|
|
51
|
+
*/
|
|
52
|
+
export declare function readPersistedMetrics(filePath: string, now: number, spanMs: number): {
|
|
53
|
+
history: MetricsSample[];
|
|
54
|
+
resourceHistory: ResourceSample[];
|
|
55
|
+
} | null;
|
|
36
56
|
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/domain/metrics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/domain/metrics.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE7D,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AACvC,kBAAkB;AAClB,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AACxC,6BAA6B;AAC7B,eAAO,MAAM,qBAAqB,UAAY,CAAC;AAC/C,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,QAA6B,CAAC;AAO/D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIvD;AAED,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,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAID,qBAAa,eAAe;IAGd,OAAO,CAAC,KAAK;IAFzB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAAK;IACtB,YAAoB,KAAK,EAAE,OAAO,EAEjC;IACD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAgBnC;IACD,IAAI,CAAC,GAAG,SAAa,EAAE,MAAM,SAAwB,GAAG,cAAc,EAAE,CAEvE;CACF;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,EAAE,MAAM,SAAwB,GAAG,aAAa,EAAE,CAEtE;CACF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAAE,GAAG,IAAI,CAiB1J"}
|
package/dist/domain/metrics.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { createQuery } from "../lib/sqlite.js";
|
|
4
|
+
const RUNTIME_SAMPLE_MS = 3e3;
|
|
5
|
+
const RESOURCE_SAMPLE_MS = 8e3;
|
|
6
|
+
const METRICS_RAW_WINDOW_MS = 36e5;
|
|
7
|
+
const METRICS_RETENTION_MS = 72 * METRICS_RAW_WINDOW_MS;
|
|
8
|
+
const METRIC_TABLES = {
|
|
9
|
+
runtime: "sfmc_runtime_metrics",
|
|
10
|
+
resource: "sfmc_resource_metrics"
|
|
11
|
+
};
|
|
12
|
+
function clampMetricsSpan(value) {
|
|
13
|
+
if (value === void 0) return METRICS_RAW_WINDOW_MS;
|
|
14
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new Error("\u975E\u6CD5\u65F6\u95F4\u8303\u56F4");
|
|
15
|
+
return Math.min(value, METRICS_RETENTION_MS);
|
|
16
|
+
}
|
|
1
17
|
const DIMENSIONS = ["minecraft:overworld", "minecraft:nether", "minecraft:the_end"];
|
|
2
18
|
const count = (value) => value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 1e8;
|
|
3
19
|
function parseMetrics(data, now) {
|
|
@@ -27,7 +43,7 @@ class ResourceHistory {
|
|
|
27
43
|
lastWrite = 0;
|
|
28
44
|
lastPrune = 0;
|
|
29
45
|
append(sample) {
|
|
30
|
-
if (sample.recordedAt - this.lastWrite <
|
|
46
|
+
if (sample.recordedAt - this.lastWrite < RESOURCE_SAMPLE_MS) return;
|
|
31
47
|
if (sample.hostUsedMb === null && sample.dbMb === null && sample.bdsMb === null) return;
|
|
32
48
|
if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return;
|
|
33
49
|
this.query("INSERT OR REPLACE INTO sfmc_resource_metrics (recorded_at, sample) VALUES (?, ?)", [
|
|
@@ -35,7 +51,7 @@ class ResourceHistory {
|
|
|
35
51
|
JSON.stringify(sample)
|
|
36
52
|
]);
|
|
37
53
|
if (sample.recordedAt - this.lastPrune > 6e4) {
|
|
38
|
-
this.query("DELETE FROM sfmc_resource_metrics WHERE recorded_at < ?", [sample.recordedAt -
|
|
54
|
+
this.query("DELETE FROM sfmc_resource_metrics WHERE recorded_at < ?", [sample.recordedAt - METRICS_RETENTION_MS]);
|
|
39
55
|
this.query(
|
|
40
56
|
"DELETE FROM sfmc_resource_metrics WHERE recorded_at NOT IN (SELECT recorded_at FROM sfmc_resource_metrics ORDER BY recorded_at DESC LIMIT 60000)"
|
|
41
57
|
);
|
|
@@ -43,20 +59,8 @@ class ResourceHistory {
|
|
|
43
59
|
}
|
|
44
60
|
this.lastWrite = sample.recordedAt;
|
|
45
61
|
}
|
|
46
|
-
read(now = Date.now()) {
|
|
47
|
-
|
|
48
|
-
"SELECT sample FROM sfmc_resource_metrics WHERE recorded_at >= ? ORDER BY recorded_at DESC LIMIT 720",
|
|
49
|
-
[now - 36e5]
|
|
50
|
-
);
|
|
51
|
-
return rows.reverse().flatMap((row) => {
|
|
52
|
-
try {
|
|
53
|
-
const sample = JSON.parse(row.sample);
|
|
54
|
-
if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return [];
|
|
55
|
-
return [sample];
|
|
56
|
-
} catch {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
});
|
|
62
|
+
read(now = Date.now(), spanMs = METRICS_RAW_WINDOW_MS) {
|
|
63
|
+
return parseResourceRows(readMetricRows(this.query, "resource", now, spanMs));
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
class MetricsHistory {
|
|
@@ -68,7 +72,7 @@ class MetricsHistory {
|
|
|
68
72
|
lastWrite = 0;
|
|
69
73
|
lastPrune = 0;
|
|
70
74
|
append(sample) {
|
|
71
|
-
if (sample.recordedAt - this.lastWrite <
|
|
75
|
+
if (sample.recordedAt - this.lastWrite < RUNTIME_SAMPLE_MS) return;
|
|
72
76
|
this.query("INSERT OR REPLACE INTO sfmc_runtime_metrics (recorded_at, sample) VALUES (?, ?)", [
|
|
73
77
|
sample.recordedAt,
|
|
74
78
|
JSON.stringify(sample)
|
|
@@ -80,28 +84,86 @@ class MetricsHistory {
|
|
|
80
84
|
this.lastWrite = sample.recordedAt;
|
|
81
85
|
}
|
|
82
86
|
prune(now) {
|
|
83
|
-
this.query("DELETE FROM sfmc_runtime_metrics WHERE recorded_at < ?", [now -
|
|
87
|
+
this.query("DELETE FROM sfmc_runtime_metrics WHERE recorded_at < ?", [now - METRICS_RETENTION_MS]);
|
|
84
88
|
this.query(
|
|
85
89
|
"DELETE FROM sfmc_runtime_metrics WHERE recorded_at NOT IN (SELECT recorded_at FROM sfmc_runtime_metrics ORDER BY recorded_at DESC LIMIT 60000)"
|
|
86
90
|
);
|
|
87
91
|
}
|
|
88
|
-
read(now = Date.now()) {
|
|
89
|
-
|
|
90
|
-
"SELECT sample FROM sfmc_runtime_metrics WHERE recorded_at >= ? ORDER BY recorded_at DESC LIMIT 720",
|
|
91
|
-
[now - 36e5]
|
|
92
|
-
);
|
|
93
|
-
return rows.reverse().flatMap((row) => {
|
|
94
|
-
try {
|
|
95
|
-
return [JSON.parse(row.sample)];
|
|
96
|
-
} catch {
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
});
|
|
92
|
+
read(now = Date.now(), spanMs = METRICS_RAW_WINDOW_MS) {
|
|
93
|
+
return parseRuntimeRows(readMetricRows(this.query, "runtime", now, spanMs));
|
|
100
94
|
}
|
|
101
95
|
}
|
|
96
|
+
function readPersistedMetrics(filePath, now, spanMs) {
|
|
97
|
+
if (!existsSync(filePath)) return null;
|
|
98
|
+
let db;
|
|
99
|
+
try {
|
|
100
|
+
db = new DatabaseSync(filePath, { readOnly: true });
|
|
101
|
+
db.exec("PRAGMA query_only = ON");
|
|
102
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
103
|
+
const query = createQuery(db);
|
|
104
|
+
return {
|
|
105
|
+
history: parseRuntimeRows(readMetricRows(query, "runtime", now, spanMs)),
|
|
106
|
+
resourceHistory: parseResourceRows(readMetricRows(query, "resource", now, spanMs))
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
} finally {
|
|
111
|
+
try {
|
|
112
|
+
db?.close();
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function readMetricRows(query, kind, now, spanMs) {
|
|
118
|
+
const table = METRIC_TABLES[kind];
|
|
119
|
+
const span = clampMetricsSpan(spanMs);
|
|
120
|
+
const intervalMs = kind === "runtime" ? RUNTIME_SAMPLE_MS : RESOURCE_SAMPLE_MS;
|
|
121
|
+
const latestRows = query(`SELECT MAX(recorded_at) AS latest FROM ${table}`);
|
|
122
|
+
if (!Array.isArray(latestRows)) return [];
|
|
123
|
+
const latest = latestRows[0]?.latest;
|
|
124
|
+
if (typeof latest !== "number") return [];
|
|
125
|
+
const end = Math.min(latest, now);
|
|
126
|
+
const since = end - span;
|
|
127
|
+
const hourPoints = Math.ceil(METRICS_RAW_WINDOW_MS / intervalMs);
|
|
128
|
+
const rows = span <= METRICS_RAW_WINDOW_MS ? query(
|
|
129
|
+
`SELECT sample FROM (SELECT sample, recorded_at FROM ${table} WHERE recorded_at >= ? AND recorded_at <= ? ORDER BY recorded_at DESC LIMIT ?) ORDER BY recorded_at ASC`,
|
|
130
|
+
[since, end, Math.ceil(span / intervalMs)]
|
|
131
|
+
) : query(
|
|
132
|
+
// 绑定参数在 SQLite 里是浮点,直接相除不会按整数分桶,所以先 CAST 成整数。
|
|
133
|
+
`SELECT sample FROM (SELECT sample, recorded_at FROM (SELECT sample, recorded_at, ROW_NUMBER() OVER (PARTITION BY recorded_at / CAST(? AS INTEGER) ORDER BY recorded_at DESC) AS rn FROM ${table} WHERE recorded_at >= ? AND recorded_at <= ?) WHERE rn = 1 ORDER BY recorded_at DESC LIMIT ?) ORDER BY recorded_at ASC`,
|
|
134
|
+
[Math.ceil(span / hourPoints), since, end, hourPoints]
|
|
135
|
+
);
|
|
136
|
+
return Array.isArray(rows) ? rows : [];
|
|
137
|
+
}
|
|
138
|
+
function parseRuntimeRows(rows) {
|
|
139
|
+
return rows.flatMap((row) => {
|
|
140
|
+
try {
|
|
141
|
+
return [JSON.parse(row.sample)];
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function parseResourceRows(rows) {
|
|
148
|
+
return rows.flatMap((row) => {
|
|
149
|
+
try {
|
|
150
|
+
const sample = JSON.parse(row.sample);
|
|
151
|
+
if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return [];
|
|
152
|
+
return [sample];
|
|
153
|
+
} catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
102
158
|
export {
|
|
159
|
+
METRICS_RAW_WINDOW_MS,
|
|
160
|
+
METRICS_RETENTION_MS,
|
|
103
161
|
MetricsHistory,
|
|
162
|
+
RESOURCE_SAMPLE_MS,
|
|
163
|
+
RUNTIME_SAMPLE_MS,
|
|
104
164
|
ResourceHistory,
|
|
105
|
-
|
|
165
|
+
clampMetricsSpan,
|
|
166
|
+
parseMetrics,
|
|
167
|
+
readPersistedMetrics
|
|
106
168
|
};
|
|
107
169
|
//# sourceMappingURL=metrics.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 interface ResourceSample {\n recordedAt: number;\n hostUsedMb: number | null;\n dbMb: number | null;\n bdsMb: number | null;\n}\nconst memoryMb = (value: unknown) =>\n value === null || (typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 8_388_608);\n\nexport class ResourceHistory {\n private lastWrite = 0;\n private lastPrune = 0;\n constructor(private query: QueryFn) {\n query(\"CREATE TABLE IF NOT EXISTS sfmc_resource_metrics (recorded_at INTEGER PRIMARY KEY, sample TEXT NOT NULL)\");\n }\n append(sample: ResourceSample): void {\n if (sample.recordedAt - this.lastWrite < 8_000) return;\n if (sample.hostUsedMb === null && sample.dbMb === null && sample.bdsMb === null) return;\n if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return;\n this.query(\"INSERT OR REPLACE INTO sfmc_resource_metrics (recorded_at, sample) VALUES (?, ?)\", [\n sample.recordedAt,\n JSON.stringify(sample),\n ]);\n if (sample.recordedAt - this.lastPrune > 60_000) {\n this.query(\"DELETE FROM sfmc_resource_metrics WHERE recorded_at < ?\", [sample.recordedAt - 72 * 3600_000]);\n this.query(\n \"DELETE FROM sfmc_resource_metrics WHERE recorded_at NOT IN (SELECT recorded_at FROM sfmc_resource_metrics ORDER BY recorded_at DESC LIMIT 60000)\"\n );\n this.lastPrune = sample.recordedAt;\n }\n this.lastWrite = sample.recordedAt;\n }\n read(now = Date.now()): ResourceSample[] {\n const rows = this.query(\n \"SELECT sample FROM sfmc_resource_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 const sample = JSON.parse(row.sample) as ResourceSample;\n if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return [];\n return [sample];\n } catch {\n return [];\n }\n });\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": "
|
|
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 { existsSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { createQuery, type QueryFn } from \"../lib/sqlite.js\";\n\n/** \u8FD0\u884C\u6307\u6807\u5199\u5165\u53BB\u91CD\u95F4\u9694\u3002\u8BFB\u5386\u53F2\u65F6\u4E00\u5C0F\u65F6\u5185\u7684\u70B9\u6570\u6309\u8FD9\u4E2A\u95F4\u9694\u5C01\u9876\uFF0C\u907F\u514D\u518D\u88AB\u56FA\u5B9A 720 \u6761\u622A\u65AD\u3002 */\nexport const RUNTIME_SAMPLE_MS = 3_000;\n/** \u8D44\u6E90\u6307\u6807\u5199\u5165\u53BB\u91CD\u95F4\u9694\u3002 */\nexport const RESOURCE_SAMPLE_MS = 8_000;\n/** \u4E0D\u62BD\u7A00\u7684\u6700\u957F\u7A97\u53E3\uFF0C\u4E0E\u539F\u5148\u300C\u6700\u8FD1\u4E00\u5C0F\u65F6\u300D\u4E00\u81F4\u3002 */\nexport const METRICS_RAW_WINDOW_MS = 3_600_000;\n/** \u843D\u5E93\u4FDD\u7559\u65F6\u957F\uFF0C\u4E0E prune \u5220\u9664\u6761\u4EF6\u4E00\u81F4\u3002\u8BF7\u6C42\u66F4\u957F\u7684\u8303\u56F4\u4F1A\u88AB\u622A\u5230\u8FD9\u91CC\u3002 */\nexport const METRICS_RETENTION_MS = 72 * METRICS_RAW_WINDOW_MS;\n\nconst METRIC_TABLES = {\n runtime: \"sfmc_runtime_metrics\",\n resource: \"sfmc_resource_metrics\",\n} as const;\n\n/**\n * \u628A\u8C03\u7528\u65B9\u7ED9\u51FA\u7684\u7A97\u53E3\u6536\u6210\u843D\u5E93\u80FD\u8986\u76D6\u7684\u8303\u56F4\u3002\n * \u7F3A\u7701\u4ECD\u662F\u4E00\u5C0F\u65F6\uFF0C\u65E7\u5BA2\u6237\u7AEF\u4E0D\u4F20 spanMs \u65F6\u884C\u4E3A\u4E0D\u53D8\u3002\n */\nexport function clampMetricsSpan(value: unknown): number {\n if (value === undefined) return METRICS_RAW_WINDOW_MS;\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value <= 0) throw new Error(\"\u975E\u6CD5\u65F6\u95F4\u8303\u56F4\");\n return Math.min(value, METRICS_RETENTION_MS);\n}\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 interface ResourceSample {\n recordedAt: number;\n hostUsedMb: number | null;\n dbMb: number | null;\n bdsMb: number | null;\n}\nconst memoryMb = (value: unknown) =>\n value === null || (typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 8_388_608);\n\nexport class ResourceHistory {\n private lastWrite = 0;\n private lastPrune = 0;\n constructor(private query: QueryFn) {\n query(\"CREATE TABLE IF NOT EXISTS sfmc_resource_metrics (recorded_at INTEGER PRIMARY KEY, sample TEXT NOT NULL)\");\n }\n append(sample: ResourceSample): void {\n if (sample.recordedAt - this.lastWrite < RESOURCE_SAMPLE_MS) return;\n if (sample.hostUsedMb === null && sample.dbMb === null && sample.bdsMb === null) return;\n if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return;\n this.query(\"INSERT OR REPLACE INTO sfmc_resource_metrics (recorded_at, sample) VALUES (?, ?)\", [\n sample.recordedAt,\n JSON.stringify(sample),\n ]);\n if (sample.recordedAt - this.lastPrune > 60_000) {\n this.query(\"DELETE FROM sfmc_resource_metrics WHERE recorded_at < ?\", [sample.recordedAt - METRICS_RETENTION_MS]);\n this.query(\n \"DELETE FROM sfmc_resource_metrics WHERE recorded_at NOT IN (SELECT recorded_at FROM sfmc_resource_metrics ORDER BY recorded_at DESC LIMIT 60000)\"\n );\n this.lastPrune = sample.recordedAt;\n }\n this.lastWrite = sample.recordedAt;\n }\n read(now = Date.now(), spanMs = METRICS_RAW_WINDOW_MS): ResourceSample[] {\n return parseResourceRows(readMetricRows(this.query, \"resource\", now, spanMs));\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 < RUNTIME_SAMPLE_MS) 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 - METRICS_RETENTION_MS]);\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(), spanMs = METRICS_RAW_WINDOW_MS): MetricsSample[] {\n return parseRuntimeRows(readMetricRows(this.query, \"runtime\", now, spanMs));\n }\n}\n\n/**\n * \u53EA\u8BFB\u6253\u5F00\u5DF2\u843D\u76D8\u7684\u6307\u6807\u5E93\u3002\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u4ECD\u88AB\u5360\u7528\u65F6\u8FD4\u56DE null\uFF0C\u4E0D\u521B\u5EFA\u6587\u4EF6\u3001\u4E0D\u5EFA\u8868\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u6570\u636E\u670D\u52A1\u8FDB\u7A0B\u6CA1\u5728\u8DD1\u65F6\uFF0C\u7BA1\u7406\u901A\u9053\u4ECD\u8981\u753B\u51FA\u505C\u670D\u524D\u7684\u66F2\u7EBF\u3002\n */\nexport function readPersistedMetrics(filePath: string, now: number, spanMs: number): { history: MetricsSample[]; resourceHistory: ResourceSample[] } | null {\n if (!existsSync(filePath)) return null;\n let db: DatabaseSync | undefined;\n try {\n db = new DatabaseSync(filePath, { readOnly: true });\n db.exec(\"PRAGMA query_only = ON\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n const query = createQuery(db);\n return {\n history: parseRuntimeRows(readMetricRows(query, \"runtime\", now, spanMs)),\n resourceHistory: parseResourceRows(readMetricRows(query, \"resource\", now, spanMs)),\n };\n } catch {\n return null;\n } finally {\n try { db?.close(); } catch { /* \u53EA\u8BFB\u8FDE\u63A5\u5173\u95ED\u5931\u8D25\u65F6\u8C03\u7528\u65B9\u4ECD\u6309\u6CA1\u6709\u843D\u76D8\u5904\u7406\u3002 */ }\n }\n}\n\n/**\n * \u7A97\u53E3\u53F3\u7AEF\u5BF9\u9F50\u6700\u540E\u4E00\u6761\u771F\u5B9E\u91C7\u6837\uFF0C\u6240\u4EE5\u505C\u670D\u540E\u8BF7\u6C42\u300C\u6700\u8FD1 15 \u5206\u949F\u300D\u770B\u5230\u7684\u662F\u505C\u670D\u524D\u7684 15 \u5206\u949F\u3002\n * \u4E0D\u8D85\u8FC7\u4E00\u5C0F\u65F6\u65F6\u8FD4\u56DE\u539F\u59CB\u70B9\uFF1B\u66F4\u957F\u5219\u6BCF\u4E2A\u65F6\u95F4\u6876\u4FDD\u7559\u4E00\u6761\u5DF2\u843D\u76D8\u7684\u70B9\uFF0C\u4E0D\u8865\u9020\u91C7\u6837\u3002\n */\nfunction readMetricRows(query: QueryFn, kind: keyof typeof METRIC_TABLES, now: number, spanMs: number): { sample: string }[] {\n const table = METRIC_TABLES[kind];\n const span = clampMetricsSpan(spanMs);\n const intervalMs = kind === \"runtime\" ? RUNTIME_SAMPLE_MS : RESOURCE_SAMPLE_MS;\n const latestRows = query(`SELECT MAX(recorded_at) AS latest FROM ${table}`);\n if (!Array.isArray(latestRows)) return [];\n const latest = (latestRows[0] as { latest: number | null } | undefined)?.latest;\n if (typeof latest !== \"number\") return [];\n const end = Math.min(latest, now);\n const since = end - span;\n const hourPoints = Math.ceil(METRICS_RAW_WINDOW_MS / intervalMs);\n const rows = span <= METRICS_RAW_WINDOW_MS\n ? query(\n `SELECT sample FROM (SELECT sample, recorded_at FROM ${table} WHERE recorded_at >= ? AND recorded_at <= ? ORDER BY recorded_at DESC LIMIT ?) ORDER BY recorded_at ASC`,\n [since, end, Math.ceil(span / intervalMs)]\n )\n : query(\n // \u7ED1\u5B9A\u53C2\u6570\u5728 SQLite \u91CC\u662F\u6D6E\u70B9\uFF0C\u76F4\u63A5\u76F8\u9664\u4E0D\u4F1A\u6309\u6574\u6570\u5206\u6876\uFF0C\u6240\u4EE5\u5148 CAST \u6210\u6574\u6570\u3002\n `SELECT sample FROM (SELECT sample, recorded_at FROM (SELECT sample, recorded_at, ROW_NUMBER() OVER (PARTITION BY recorded_at / CAST(? AS INTEGER) ORDER BY recorded_at DESC) AS rn FROM ${table} WHERE recorded_at >= ? AND recorded_at <= ?) WHERE rn = 1 ORDER BY recorded_at DESC LIMIT ?) ORDER BY recorded_at ASC`,\n [Math.ceil(span / hourPoints), since, end, hourPoints]\n );\n return Array.isArray(rows) ? rows as { sample: string }[] : [];\n}\n\nfunction parseRuntimeRows(rows: { sample: string }[]): MetricsSample[] {\n return rows.flatMap((row) => {\n try {\n return [JSON.parse(row.sample) as MetricsSample];\n } catch {\n return [];\n }\n });\n}\n\nfunction parseResourceRows(rows: { sample: string }[]): ResourceSample[] {\n return rows.flatMap((row) => {\n try {\n const sample = JSON.parse(row.sample) as ResourceSample;\n if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return [];\n return [sample];\n } catch {\n return [];\n }\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 与 sfmc_qq_bindings 的列对应。 */
|
|
2
|
+
export interface QqBindingRecord {
|
|
3
|
+
playerName: string;
|
|
4
|
+
playerXuid: string;
|
|
5
|
+
qqUserOpenid: string;
|
|
6
|
+
qqBackend: string;
|
|
7
|
+
boundAt: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 打开业务库并读取绑定表。文件或表不存在时返回空数组,不创建数据库。
|
|
11
|
+
*/
|
|
12
|
+
export declare function readQqBindings(filePath: string): QqBindingRecord[];
|
|
13
|
+
//# sourceMappingURL=qq-bindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-bindings.d.ts","sourceRoot":"","sources":["../../src/domain/qq-bindings.ts"],"names":[],"mappings":"AAOA,+BAA+B;AAC/B,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAiClE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
const text = (value) => typeof value === "string" ? value : "";
|
|
4
|
+
function readQqBindings(filePath) {
|
|
5
|
+
if (!existsSync(filePath)) return [];
|
|
6
|
+
let db;
|
|
7
|
+
try {
|
|
8
|
+
db = new DatabaseSync(filePath, { readOnly: true });
|
|
9
|
+
db.exec("PRAGMA query_only = ON");
|
|
10
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
11
|
+
const rows = db.prepare(
|
|
12
|
+
"SELECT qq_user_openid, player_xuid, player_name, qq_backend, bound_at FROM sfmc_qq_bindings ORDER BY bound_at DESC"
|
|
13
|
+
).all();
|
|
14
|
+
return rows.flatMap((row) => {
|
|
15
|
+
const playerXuid = text(row.player_xuid).trim();
|
|
16
|
+
const boundAt = typeof row.bound_at === "number" ? row.bound_at : Number(row.bound_at);
|
|
17
|
+
if (!playerXuid || !Number.isSafeInteger(boundAt)) return [];
|
|
18
|
+
return [{
|
|
19
|
+
playerName: text(row.player_name).trim(),
|
|
20
|
+
playerXuid,
|
|
21
|
+
qqUserOpenid: text(row.qq_user_openid).trim(),
|
|
22
|
+
qqBackend: text(row.qq_backend).trim(),
|
|
23
|
+
boundAt
|
|
24
|
+
}];
|
|
25
|
+
});
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
} finally {
|
|
29
|
+
try {
|
|
30
|
+
db?.close();
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
readQqBindings
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=qq-bindings.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/domain/qq-bindings.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * \u53EA\u8BFB\u5217\u51FA QQ \u7ED1\u5B9A\u8BB0\u5F55\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u684C\u9762\u7AEF\u300C\u5141\u8BB8\u540D\u5355\u300D\u5C55\u793A\u8C01\u5DF2\u7ECF\u7ED1\u5B9A\u3002\u7ED1\u5B9A\u5199\u5165\u4ECD\u7531 /api/sfmc/qq/bind/confirm \u5B8C\u6210\uFF0C\u8FD9\u91CC\u4E0D\u5EFA\u8868\u3001\u4E0D\u6539\u6587\u4EF6\u3002\n */\nimport { existsSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\n\n/** \u4E0E sfmc_qq_bindings \u7684\u5217\u5BF9\u5E94\u3002 */\nexport interface QqBindingRecord {\n playerName: string;\n playerXuid: string;\n qqUserOpenid: string;\n qqBackend: string;\n boundAt: number;\n}\n\nconst text = (value: unknown) => (typeof value === \"string\" ? value : \"\");\n\n/**\n * \u6253\u5F00\u4E1A\u52A1\u5E93\u5E76\u8BFB\u53D6\u7ED1\u5B9A\u8868\u3002\u6587\u4EF6\u6216\u8868\u4E0D\u5B58\u5728\u65F6\u8FD4\u56DE\u7A7A\u6570\u7EC4\uFF0C\u4E0D\u521B\u5EFA\u6570\u636E\u5E93\u3002\n */\nexport function readQqBindings(filePath: string): QqBindingRecord[] {\n if (!existsSync(filePath)) return [];\n let db: DatabaseSync | undefined;\n try {\n db = new DatabaseSync(filePath, { readOnly: true });\n db.exec(\"PRAGMA query_only = ON\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n const rows = db.prepare(\n \"SELECT qq_user_openid, player_xuid, player_name, qq_backend, bound_at FROM sfmc_qq_bindings ORDER BY bound_at DESC\"\n ).all() as Array<{\n qq_user_openid: unknown;\n player_xuid: unknown;\n player_name: unknown;\n qq_backend: unknown;\n bound_at: unknown;\n }>;\n return rows.flatMap((row) => {\n const playerXuid = text(row.player_xuid).trim();\n const boundAt = typeof row.bound_at === \"number\" ? row.bound_at : Number(row.bound_at);\n if (!playerXuid || !Number.isSafeInteger(boundAt)) return [];\n return [{\n playerName: text(row.player_name).trim(),\n playerXuid,\n qqUserOpenid: text(row.qq_user_openid).trim(),\n qqBackend: text(row.qq_backend).trim(),\n boundAt,\n }];\n });\n } catch {\n return [];\n } finally {\n try { db?.close(); } catch { /* \u53EA\u8BFB\u8FDE\u63A5\u5173\u95ED\u5931\u8D25\u65F6\u6309\u6CA1\u6709\u7ED1\u5B9A\u8BB0\u5F55\u5904\u7406\u3002 */ }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAW7B,MAAM,OAAO,CAAC,UAAoB,OAAO,UAAU,WAAW,QAAQ;AAK/D,SAAS,eAAe,UAAqC;AAClE,MAAI,CAAC,WAAW,QAAQ,EAAG,QAAO,CAAC;AACnC,MAAI;AACJ,MAAI;AACF,SAAK,IAAI,aAAa,UAAU,EAAE,UAAU,KAAK,CAAC;AAClD,OAAG,KAAK,wBAAwB;AAChC,OAAG,KAAK,4BAA4B;AACpC,UAAM,OAAO,GAAG;AAAA,MACd;AAAA,IACF,EAAE,IAAI;AAON,WAAO,KAAK,QAAQ,CAAC,QAAQ;AAC3B,YAAM,aAAa,KAAK,IAAI,WAAW,EAAE,KAAK;AAC9C,YAAM,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW,OAAO,IAAI,QAAQ;AACrF,UAAI,CAAC,cAAc,CAAC,OAAO,cAAc,OAAO,EAAG,QAAO,CAAC;AAC3D,aAAO,CAAC;AAAA,QACN,YAAY,KAAK,IAAI,WAAW,EAAE,KAAK;AAAA,QACvC;AAAA,QACA,cAAc,KAAK,IAAI,cAAc,EAAE,KAAK;AAAA,QAC5C,WAAW,KAAK,IAAI,UAAU,EAAE,KAAK;AAAA,QACrC;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,CAAC;AAAA,EACV,UAAE;AACA,QAAI;AAAE,UAAI,MAAM;AAAA,IAAG,QAAQ;AAAA,IAA4B;AAAA,EACzD;AACF;",
|
|
6
|
+
"names": []
|
|
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;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;
|
|
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;AAQH,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
|
@@ -10,7 +10,7 @@ import { log } from "./lib/log.js";
|
|
|
10
10
|
import { assertNodeVersion } from "./lib/runtime.js";
|
|
11
11
|
import { createQuery, openDatabase } from "./lib/sqlite.js";
|
|
12
12
|
import { loadManifestV2 } from "./manifest-loader.js";
|
|
13
|
-
import { buildModuleAuth, verifyModuleAuth } from "./module-auth.js";
|
|
13
|
+
import { buildModuleAuth, ensureModuleToken, persistModuleAuth, verifyModuleAuth } from "./module-auth.js";
|
|
14
14
|
import { syncModuleRuntimeState } from "./module-runtime-sync.js";
|
|
15
15
|
import { SchemaRegistry } from "./schema-registry.js";
|
|
16
16
|
import { createServer, startConsole } from "./server.js";
|
|
@@ -73,6 +73,10 @@ const moduleAuth = buildModuleAuth({
|
|
|
73
73
|
envAuthToken: env.AUTH_TOKEN,
|
|
74
74
|
enabledModuleIds: [...enabledSet]
|
|
75
75
|
});
|
|
76
|
+
if (ensureModuleToken(moduleAuth, PLATFORM_SERVICE_OWNER)) {
|
|
77
|
+
persistModuleAuth(env.DB_PATH, moduleAuth, env.AUTH_TOKEN);
|
|
78
|
+
log.info(`[auth] \u5DF2\u6D3E\u751F\u5E73\u53F0\u5BBF\u4E3B token: ${PLATFORM_SERVICE_OWNER}`);
|
|
79
|
+
}
|
|
76
80
|
const schemaRegistry = new SchemaRegistry(db);
|
|
77
81
|
const serviceRegistry = new ServiceRegistry();
|
|
78
82
|
const idempotent = createIdempotencyStore(db);
|
|
@@ -242,7 +246,7 @@ function writeJoinFlags(partial) {
|
|
|
242
246
|
const created = !readJson(qqLinkConfigFile());
|
|
243
247
|
ensureJson(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });
|
|
244
248
|
if (created) {
|
|
245
|
-
log.info(`\u5DF2\u5199\u5165\
|
|
249
|
+
log.info(`\u5DF2\u5199\u5165\u5E73\u53F0\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);
|
|
246
250
|
}
|
|
247
251
|
}
|
|
248
252
|
const qqJoinRoutes = createQqJoinRoutes({
|
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 { 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 { createQqEventsDispatcher, 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 qqEventsDispatcher = createQqEventsDispatcher({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n dispatcher: qqEventsDispatcher,\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": "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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, ensureModuleToken, persistModuleAuth, 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 { createQqEventsDispatcher, 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// \u5E73\u53F0\u5BBF\u4E3B\uFF08QQ \u7ED1\u5B9A/\u8D26\u53F7\u5FEB\u7167\uFF09\u9700\u8981\u7A33\u5B9A token\uFF0C\u7ECF configs/all.module_tokens \u4E0B\u53D1\u5230 BDS\nif (ensureModuleToken(moduleAuth, PLATFORM_SERVICE_OWNER)) {\n persistModuleAuth(env.DB_PATH, moduleAuth, env.AUTH_TOKEN);\n log.info(`[auth] \u5DF2\u6D3E\u751F\u5E73\u53F0\u5BBF\u4E3B token: ${PLATFORM_SERVICE_OWNER}`);\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/** \u5E73\u53F0 QQ \u7ED1\u5B9A\u76F8\u5173\u914D\u7F6E\uFF08\u975E SDK qq_config\uFF1B\u539F qq-link \u6A21\u5757\u914D\u7F6E\u952E\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 // \u5E73\u53F0\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\u5E73\u53F0\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\u5E73\u53F0\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 qqEventsDispatcher = createQqEventsDispatcher({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n dispatcher: qqEventsDispatcher,\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": "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",
|
|
6
6
|
"names": ["path"]
|
|
7
7
|
}
|
package/dist/module-auth.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export declare function revokeModuleToken(auth: ModuleAuthMap, moduleId: string)
|
|
|
56
56
|
* 校验请求方身份:
|
|
57
57
|
* - Bearer 头 / X-Module-Token 二选一
|
|
58
58
|
* - ?moduleId=<id> 必须存在
|
|
59
|
-
* - 模块必须在 enabledModuleIds
|
|
59
|
+
* - 模块必须在 enabledModuleIds 集合中(平台宿主 `_platform` 除外)
|
|
60
60
|
* - token 必须 = deriveToken(moduleId, secret)
|
|
61
61
|
* 返回模块 ID 或 null。
|
|
62
62
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-auth.d.ts","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;
|
|
1
|
+
{"version":3,"file":"module-auth.d.ts","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,GAAG,aAAa,CAgChB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAInE;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAQjG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC/B,GAAG,MAAM,GAAG,IAAI,CAyBhB"}
|
package/dist/module-auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PLATFORM_SERVICE_OWNER } from "@sfmc-bds/sdk/contracts";
|
|
1
2
|
import { readJson, writeJson } from "@sfmc-bds/sdk/node/config";
|
|
2
3
|
import { createHmac, randomBytes } from "node:crypto";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
@@ -68,7 +69,8 @@ function revokeModuleToken(auth, moduleId) {
|
|
|
68
69
|
}
|
|
69
70
|
function verifyModuleAuth(opts) {
|
|
70
71
|
const moduleId = opts.params.get("moduleId") || "";
|
|
71
|
-
if (!moduleId
|
|
72
|
+
if (!moduleId) return null;
|
|
73
|
+
if (moduleId !== PLATFORM_SERVICE_OWNER && !opts.enabledModuleIds.has(moduleId)) return null;
|
|
72
74
|
let provided = "";
|
|
73
75
|
const auth = opts.headers["authorization"];
|
|
74
76
|
const token = opts.headers["x-module-token"];
|
package/dist/module-auth.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/module-auth.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * module-auth.ts \u2014 \u6A21\u5757\u8EAB\u4EFD\u7684 HMAC token \u6D3E\u751F\u4E0E\u6821\u9A8C\n *\n * \u4E24\u5957\u5E76\u5B58:\n * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc \u7BA1\u63A7 / \u5E73\u53F0\u5185\u90E8\u7528\n * - `moduleTokens[moduleId]`:db-server \u7ED9\u6BCF\u4E2A enabled \u6A21\u5757\u5728\u542F\u52A8\u65F6\u6D3E\u751F,\n * \u5199\u5230\u300C\u4E0E\u6570\u636E\u5E93\u540C\u76EE\u5F55\u300D\u7684 module-tokens.json\uFF08\u8DDF db_config.dbDir \u8D70\uFF0C\u52FF\u5199\u6B7B ROOT/data\uFF09,\n * SAPI \u7ECF loopback API \u6CE8\u5165 setDbModuleContext\u3002\n *\n * \u6D3E\u751F\u516C\u5F0F:\n * module_token = HMAC-SHA256(auth_secret, \"sfmc-module:\" + moduleId).hex\n *\n * auth_secret \u4F18\u5148\u7EA7:\n * 1. env.AUTH_TOKEN\n * 2. \u542F\u52A8\u65F6\u968F\u673A\u751F\u6210(\u65E0 AUTH_TOKEN \u65F6),\u5199\u5230 module-tokens.json,\n * \u53EA\u8B66\u544A\u4E00\u6B21(\u6BCF\u6B21\u91CD\u542F\u6A21\u5757 token \u90FD\u53D8,SAPI host \u5FC5\u987B\u91CD\u8BFB\u6587\u4EF6)\n *\n * \u6821\u9A8C:\n * - HTTP `Authorization: Bearer <module_token>` \u5934\n * - Query `?moduleId=<id>`\n * - \u4E24\u90FD\u5339\u914D = \u8C03\u7528\u65B9\u8EAB\u4EFD\n * - \u6821\u9A8C\u8C03\u7528\u65B9 moduleId \u5728 enabled \u5217\u8868(\u5426\u5219 403)\n */\n\nimport { readJson, writeJson, type TokenStore } from \"@sfmc-bds/sdk/node/config\";\nimport { createHmac, randomBytes } from \"node:crypto\";\nimport { dirname, join } from \"node:path\";\nimport { log } from \"./lib/log.js\";\n\nexport interface ModuleAuthMap {\n /** moduleId \u2192 hex token */\n tokens: Record<string, string>;\n /** \u7528\u4E8E\u6D3E\u751F token \u7684 secret (env.AUTH_TOKEN \u6216\u672C\u6B21\u542F\u52A8\u968F\u673A\u751F\u6210) */\n secret: string;\n}\n\nconst IDENT = /^[A-Za-z0-9_\\-]+$/;\n\nfunction safeId(id: string): string {\n if (!IDENT.test(id)) throw new Error(`[auth] invalid module id: ${id}`);\n return id;\n}\n\n/** token \u6587\u4EF6\u4E0E SQLite \u540C\u76EE\u5F55\uFF08\u7531 dbDir \u51B3\u5B9A\uFF0C\u907F\u514D\u5411\u5BFC\u9009\u76EE\u5F55\u524D\u5C31\u521B\u5EFA ROOT/data\uFF09 */\nexport function moduleTokensFile(dbPath: string): string {\n return join(dirname(dbPath), \"module-tokens.json\");\n}\n\nexport function deriveToken(moduleId: string, secret: string): string {\n return createHmac(\"sha256\", secret)\n .update(`sfmc-module:${safeId(moduleId)}`)\n .digest(\"hex\");\n}\n\n/**\n * \u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21:\n * - \u628A env.AUTH_TOKEN \u5F53 secret\n * - \u7ED9\u6BCF\u4E2A enabled moduleId \u6D3E\u751F token\n * - \u5199 module-tokens.json\uFF08\u4E0E DB \u540C\u76EE\u5F55\uFF09\n * - \u63A7\u5236\u53F0\u6253\u5370\u6458\u8981(env var SFMC_LOG_MODULE_TOKEN=1 \u65F6\u6253\u5B8C\u6574)\n */\nexport function buildModuleAuth(opts: {\n dbPath: string;\n envAuthToken: string;\n enabledModuleIds: string[];\n}): ModuleAuthMap {\n const secret = opts.envAuthToken || randomBytes(32).toString(\"hex\");\n const tokens: Record<string, string> = {};\n for (const id of opts.enabledModuleIds) {\n tokens[id] = deriveToken(id, secret);\n }\n\n const outFile = moduleTokensFile(opts.dbPath);\n writeJson(outFile, {\n tokens,\n secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !opts.envAuthToken,\n });\n\n log.success(`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`);\n if (!opts.envAuthToken) {\n log.warn(\"[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN,\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;\");\n }\n if (process.env.SFMC_LOG_MODULE_TOKEN === \"1\") {\n log.info(\"[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):\");\n for (const [id, t] of Object.entries(tokens)) {\n log.info(` ${id} = ${t}`);\n }\n } else {\n const lines = Object.entries(tokens)\n .map(([id, t]) => ` ${id} = ${t.slice(0, 6)}\u2026${t.slice(-4)}`)\n .join(\"\\n\");\n log.info(`[auth] token \u6458\u8981:\\n${lines}`);\n }\n\n return { tokens, secret };\n}\n\nexport function loadModuleAuth(dbPath: string): ModuleAuthMap | null {\n const parsed = readJson<TokenStore>(moduleTokensFile(dbPath));\n if (!parsed || !parsed.tokens || !parsed.secret) return null;\n return { tokens: parsed.tokens, secret: parsed.secret };\n}\n\n/** \u628A\u5F53\u524D auth map \u5199\u56DE module-tokens.json(\u70ED\u66F4\u65B0 token \u65F6\u590D\u7528) */\nexport function persistModuleAuth(dbPath: string, auth: ModuleAuthMap, envAuthToken: string): void {\n const outFile = moduleTokensFile(dbPath);\n writeJson(outFile, {\n tokens: auth.tokens,\n secret: auth.secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !envAuthToken,\n } satisfies TokenStore);\n}\n\n/**\n * \u786E\u4FDD moduleId \u5728 auth.tokens \u4E2D\u6709\u6D3E\u751F\u503C(\u590D\u7528\u73B0\u6709 secret,\u52FF\u91CD\u5EFA\u968F\u673A secret)\u3002\n * @returns \u662F\u5426\u65B0\u5199\u5165\u4E86 token\n */\nexport function ensureModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (auth.tokens[moduleId]) return false;\n auth.tokens[moduleId] = deriveToken(moduleId, auth.secret);\n return true;\n}\n\n/** \u7981\u7528\u65F6\u64A4\u9500\u5185\u5B58\u4E2D\u7684 token(\u78C1\u76D8\u7531 persistModuleAuth \u540C\u6B65) */\nexport function revokeModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (!(moduleId in auth.tokens)) return false;\n delete auth.tokens[moduleId];\n return true;\n}\n\n/**\n * \u6821\u9A8C\u8BF7\u6C42\u65B9\u8EAB\u4EFD:\n * - Bearer \u5934 / X-Module-Token \u4E8C\u9009\u4E00\n * - ?moduleId=<id> \u5FC5\u987B\u5B58\u5728\n * - \u6A21\u5757\u5FC5\u987B\u5728 enabledModuleIds \u96C6\u5408\u4E2D\n * - token \u5FC5\u987B = deriveToken(moduleId, secret)\n * \u8FD4\u56DE\u6A21\u5757 ID \u6216 null\u3002\n */\nexport function verifyModuleAuth(opts: {\n headers: NodeJS.Dict<string | string[]>;\n params: URLSearchParams;\n auth: ModuleAuthMap;\n enabledModuleIds: Set<string>;\n}): string | null {\n const moduleId = opts.params.get(\"moduleId\") || \"\";\n if (!moduleId
|
|
5
|
-
"mappings": "AAwBA,SAAS,UAAU,iBAAkC;AACrD,SAAS,YAAY,mBAAmB;AACxC,SAAS,SAAS,YAAY;AAC9B,SAAS,WAAW;AASpB,MAAM,QAAQ;AAEd,SAAS,OAAO,IAAoB;AAClC,MAAI,CAAC,MAAM,KAAK,EAAE,EAAG,OAAM,IAAI,MAAM,6BAA6B,EAAE,EAAE;AACtE,SAAO;AACT;AAGO,SAAS,iBAAiB,QAAwB;AACvD,SAAO,KAAK,QAAQ,MAAM,GAAG,oBAAoB;AACnD;AAEO,SAAS,YAAY,UAAkB,QAAwB;AACpE,SAAO,WAAW,UAAU,MAAM,EAC/B,OAAO,eAAe,OAAO,QAAQ,CAAC,EAAE,EACxC,OAAO,KAAK;AACjB;AASO,SAAS,gBAAgB,MAId;AAChB,QAAM,SAAS,KAAK,gBAAgB,YAAY,EAAE,EAAE,SAAS,KAAK;AAClE,QAAM,SAAiC,CAAC;AACxC,aAAW,MAAM,KAAK,kBAAkB;AACtC,WAAO,EAAE,IAAI,YAAY,IAAI,MAAM;AAAA,EACrC;AAEA,QAAM,UAAU,iBAAiB,KAAK,MAAM;AAC5C,YAAU,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,iBAAiB,CAAC,KAAK;AAAA,EACzB,CAAC;AAED,MAAI,QAAQ,uBAAa,KAAK,iBAAiB,MAAM,2DAAwB,OAAO,EAAE;AACtF,MAAI,CAAC,KAAK,cAAc;AACtB,QAAI,KAAK,yGAA6C;AAAA,EACxD;AACA,MAAI,QAAQ,IAAI,0BAA0B,KAAK;AAC7C,QAAI,KAAK,iDAAiD;AAC1D,eAAW,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC5C,UAAI,KAAK,KAAK,EAAE,MAAM,CAAC,EAAE;AAAA,IAC3B;AAAA,EACF,OAAO;AACL,UAAM,QAAQ,OAAO,QAAQ,MAAM,EAChC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,SAAI,EAAE,MAAM,EAAE,CAAC,EAAE,EAC5D,KAAK,IAAI;AACZ,QAAI,KAAK;AAAA,EAAqB,KAAK,EAAE;AAAA,EACvC;AAEA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEO,SAAS,eAAe,QAAsC;AACnE,QAAM,SAAS,SAAqB,iBAAiB,MAAM,CAAC;AAC5D,MAAI,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,OAAO,OAAQ,QAAO;AACxD,SAAO,EAAE,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO;AACxD;AAGO,SAAS,kBAAkB,QAAgB,MAAqB,cAA4B;AACjG,QAAM,UAAU,iBAAiB,MAAM;AACvC,YAAU,SAAS;AAAA,IACjB,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,iBAAiB,CAAC;AAAA,EACpB,CAAsB;AACxB;AAMO,SAAS,kBAAkB,MAAqB,UAA2B;AAChF,MAAI,KAAK,OAAO,QAAQ,EAAG,QAAO;AAClC,OAAK,OAAO,QAAQ,IAAI,YAAY,UAAU,KAAK,MAAM;AACzD,SAAO;AACT;AAGO,SAAS,kBAAkB,MAAqB,UAA2B;AAChF,MAAI,EAAE,YAAY,KAAK,QAAS,QAAO;AACvC,SAAO,KAAK,OAAO,QAAQ;AAC3B,SAAO;AACT;AAUO,SAAS,iBAAiB,MAKf;AAChB,QAAM,WAAW,KAAK,OAAO,IAAI,UAAU,KAAK;AAChD,MAAI,CAAC,
|
|
4
|
+
"sourcesContent": ["/**\n * module-auth.ts \u2014 \u6A21\u5757\u8EAB\u4EFD\u7684 HMAC token \u6D3E\u751F\u4E0E\u6821\u9A8C\n *\n * \u4E24\u5957\u5E76\u5B58:\n * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc \u7BA1\u63A7 / \u5E73\u53F0\u5185\u90E8\u7528\n * - `moduleTokens[moduleId]`:db-server \u7ED9\u6BCF\u4E2A enabled \u6A21\u5757\u5728\u542F\u52A8\u65F6\u6D3E\u751F,\n * \u5199\u5230\u300C\u4E0E\u6570\u636E\u5E93\u540C\u76EE\u5F55\u300D\u7684 module-tokens.json\uFF08\u8DDF db_config.dbDir \u8D70\uFF0C\u52FF\u5199\u6B7B ROOT/data\uFF09,\n * SAPI \u7ECF loopback API \u6CE8\u5165 setDbModuleContext\u3002\n *\n * \u6D3E\u751F\u516C\u5F0F:\n * module_token = HMAC-SHA256(auth_secret, \"sfmc-module:\" + moduleId).hex\n *\n * auth_secret \u4F18\u5148\u7EA7:\n * 1. env.AUTH_TOKEN\n * 2. \u542F\u52A8\u65F6\u968F\u673A\u751F\u6210(\u65E0 AUTH_TOKEN \u65F6),\u5199\u5230 module-tokens.json,\n * \u53EA\u8B66\u544A\u4E00\u6B21(\u6BCF\u6B21\u91CD\u542F\u6A21\u5757 token \u90FD\u53D8,SAPI host \u5FC5\u987B\u91CD\u8BFB\u6587\u4EF6)\n *\n * \u6821\u9A8C:\n * - HTTP `Authorization: Bearer <module_token>` \u5934\n * - Query `?moduleId=<id>`\n * - \u4E24\u90FD\u5339\u914D = \u8C03\u7528\u65B9\u8EAB\u4EFD\n * - \u6821\u9A8C\u8C03\u7528\u65B9 moduleId \u5728 enabled \u5217\u8868(\u5426\u5219 403)\n */\n\nimport { PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\nimport { readJson, writeJson, type TokenStore } from \"@sfmc-bds/sdk/node/config\";\nimport { createHmac, randomBytes } from \"node:crypto\";\nimport { dirname, join } from \"node:path\";\nimport { log } from \"./lib/log.js\";\n\nexport interface ModuleAuthMap {\n /** moduleId \u2192 hex token */\n tokens: Record<string, string>;\n /** \u7528\u4E8E\u6D3E\u751F token \u7684 secret (env.AUTH_TOKEN \u6216\u672C\u6B21\u542F\u52A8\u968F\u673A\u751F\u6210) */\n secret: string;\n}\n\nconst IDENT = /^[A-Za-z0-9_\\-]+$/;\n\nfunction safeId(id: string): string {\n if (!IDENT.test(id)) throw new Error(`[auth] invalid module id: ${id}`);\n return id;\n}\n\n/** token \u6587\u4EF6\u4E0E SQLite \u540C\u76EE\u5F55\uFF08\u7531 dbDir \u51B3\u5B9A\uFF0C\u907F\u514D\u5411\u5BFC\u9009\u76EE\u5F55\u524D\u5C31\u521B\u5EFA ROOT/data\uFF09 */\nexport function moduleTokensFile(dbPath: string): string {\n return join(dirname(dbPath), \"module-tokens.json\");\n}\n\nexport function deriveToken(moduleId: string, secret: string): string {\n return createHmac(\"sha256\", secret)\n .update(`sfmc-module:${safeId(moduleId)}`)\n .digest(\"hex\");\n}\n\n/**\n * \u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21:\n * - \u628A env.AUTH_TOKEN \u5F53 secret\n * - \u7ED9\u6BCF\u4E2A enabled moduleId \u6D3E\u751F token\n * - \u5199 module-tokens.json\uFF08\u4E0E DB \u540C\u76EE\u5F55\uFF09\n * - \u63A7\u5236\u53F0\u6253\u5370\u6458\u8981(env var SFMC_LOG_MODULE_TOKEN=1 \u65F6\u6253\u5B8C\u6574)\n */\nexport function buildModuleAuth(opts: {\n dbPath: string;\n envAuthToken: string;\n enabledModuleIds: string[];\n}): ModuleAuthMap {\n const secret = opts.envAuthToken || randomBytes(32).toString(\"hex\");\n const tokens: Record<string, string> = {};\n for (const id of opts.enabledModuleIds) {\n tokens[id] = deriveToken(id, secret);\n }\n\n const outFile = moduleTokensFile(opts.dbPath);\n writeJson(outFile, {\n tokens,\n secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !opts.envAuthToken,\n });\n\n log.success(`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`);\n if (!opts.envAuthToken) {\n log.warn(\"[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN,\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;\");\n }\n if (process.env.SFMC_LOG_MODULE_TOKEN === \"1\") {\n log.info(\"[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):\");\n for (const [id, t] of Object.entries(tokens)) {\n log.info(` ${id} = ${t}`);\n }\n } else {\n const lines = Object.entries(tokens)\n .map(([id, t]) => ` ${id} = ${t.slice(0, 6)}\u2026${t.slice(-4)}`)\n .join(\"\\n\");\n log.info(`[auth] token \u6458\u8981:\\n${lines}`);\n }\n\n return { tokens, secret };\n}\n\nexport function loadModuleAuth(dbPath: string): ModuleAuthMap | null {\n const parsed = readJson<TokenStore>(moduleTokensFile(dbPath));\n if (!parsed || !parsed.tokens || !parsed.secret) return null;\n return { tokens: parsed.tokens, secret: parsed.secret };\n}\n\n/** \u628A\u5F53\u524D auth map \u5199\u56DE module-tokens.json(\u70ED\u66F4\u65B0 token \u65F6\u590D\u7528) */\nexport function persistModuleAuth(dbPath: string, auth: ModuleAuthMap, envAuthToken: string): void {\n const outFile = moduleTokensFile(dbPath);\n writeJson(outFile, {\n tokens: auth.tokens,\n secret: auth.secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !envAuthToken,\n } satisfies TokenStore);\n}\n\n/**\n * \u786E\u4FDD moduleId \u5728 auth.tokens \u4E2D\u6709\u6D3E\u751F\u503C(\u590D\u7528\u73B0\u6709 secret,\u52FF\u91CD\u5EFA\u968F\u673A secret)\u3002\n * @returns \u662F\u5426\u65B0\u5199\u5165\u4E86 token\n */\nexport function ensureModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (auth.tokens[moduleId]) return false;\n auth.tokens[moduleId] = deriveToken(moduleId, auth.secret);\n return true;\n}\n\n/** \u7981\u7528\u65F6\u64A4\u9500\u5185\u5B58\u4E2D\u7684 token(\u78C1\u76D8\u7531 persistModuleAuth \u540C\u6B65) */\nexport function revokeModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (!(moduleId in auth.tokens)) return false;\n delete auth.tokens[moduleId];\n return true;\n}\n\n/**\n * \u6821\u9A8C\u8BF7\u6C42\u65B9\u8EAB\u4EFD:\n * - Bearer \u5934 / X-Module-Token \u4E8C\u9009\u4E00\n * - ?moduleId=<id> \u5FC5\u987B\u5B58\u5728\n * - \u6A21\u5757\u5FC5\u987B\u5728 enabledModuleIds \u96C6\u5408\u4E2D\uFF08\u5E73\u53F0\u5BBF\u4E3B `_platform` \u9664\u5916\uFF09\n * - token \u5FC5\u987B = deriveToken(moduleId, secret)\n * \u8FD4\u56DE\u6A21\u5757 ID \u6216 null\u3002\n */\nexport function verifyModuleAuth(opts: {\n headers: NodeJS.Dict<string | string[]>;\n params: URLSearchParams;\n auth: ModuleAuthMap;\n enabledModuleIds: Set<string>;\n}): string | null {\n const moduleId = opts.params.get(\"moduleId\") || \"\";\n if (!moduleId) return null;\n // \u5E73\u53F0\u5BBF\u4E3B\u4E0D\u5728\u6A21\u5757 lock \u7684 enabled \u5217\u8868\u91CC\uFF0C\u4F46\u4ECD\u9700\u6709\u6548 token\n if (moduleId !== PLATFORM_SERVICE_OWNER && !opts.enabledModuleIds.has(moduleId)) return null;\n\n let provided = \"\";\n const auth = opts.headers[\"authorization\"];\n const token = opts.headers[\"x-module-token\"];\n if (typeof auth === \"string\" && auth.startsWith(\"Bearer \")) {\n provided = auth.slice(7);\n } else if (typeof token === \"string\") {\n provided = token;\n }\n if (!provided) return null;\n\n const expected = opts.auth.tokens[moduleId];\n if (!expected) return null;\n // constant-time \u6BD4\u8F83\n if (provided.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < provided.length; i++) {\n diff |= provided.charCodeAt(i) ^ expected.charCodeAt(i);\n }\n return diff === 0 ? moduleId : null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"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,
|
|
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,CAoIjG;AAED,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/routes/status.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { collectSystemStatus } from "../domain/system-status.js";
|
|
2
2
|
import { PROJECT_ROOT } from "../project-root.js";
|
|
3
|
-
import { MetricsHistory, ResourceHistory, parseMetrics } from "../domain/metrics.js";
|
|
3
|
+
import { METRICS_RAW_WINDOW_MS, MetricsHistory, ResourceHistory, clampMetricsSpan, parseMetrics } from "../domain/metrics.js";
|
|
4
4
|
import { collectProcessResources } from "../domain/process-resources.js";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import pathModule from "node:path";
|
|
@@ -55,7 +55,7 @@ function createStatusRoutes({ collectSystem, projectRoot, query }) {
|
|
|
55
55
|
}, 15e3);
|
|
56
56
|
timer.unref();
|
|
57
57
|
}
|
|
58
|
-
return async function handle({ path, method, req, res }) {
|
|
58
|
+
return async function handle({ path, method, params, req, res }) {
|
|
59
59
|
if (path === "/api/sfmc/status/live" || path === "/api/sfmc/metrics/live") {
|
|
60
60
|
if (method !== "POST") {
|
|
61
61
|
json(res, { success: false, error: "not_found" }, 404);
|
|
@@ -112,15 +112,25 @@ function createStatusRoutes({ collectSystem, projectRoot, query }) {
|
|
|
112
112
|
const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && bds?.pid === ownedPid;
|
|
113
113
|
const metricFresh = owned && metrics !== null && bds?.state === "running" && now - metrics.recordedAt <= 6e4 && (bdsAgeMs === null || now - metrics.recordedAt <= bdsAgeMs + 2e3);
|
|
114
114
|
const processes = await captureResources(system);
|
|
115
|
+
let span = METRICS_RAW_WINDOW_MS;
|
|
116
|
+
const rawSpan = params.get("spanMs");
|
|
117
|
+
if (rawSpan) {
|
|
118
|
+
try {
|
|
119
|
+
span = clampMetricsSpan(Number(rawSpan));
|
|
120
|
+
} catch {
|
|
121
|
+
json(res, { success: false, error: "invalid_span" }, 400);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
115
125
|
json(res, {
|
|
116
126
|
fresh: metricFresh,
|
|
117
127
|
current: metricFresh ? metrics : null,
|
|
118
128
|
updatedAt: metrics?.recordedAt ?? null,
|
|
119
|
-
history: history?.read(now) ?? [],
|
|
129
|
+
history: history?.read(now, span) ?? [],
|
|
120
130
|
host: system?.host ?? null,
|
|
121
131
|
processes,
|
|
122
132
|
resourcesUpdatedAt: system ? Date.now() : null,
|
|
123
|
-
resourceHistory: resources?.read(now) ?? [],
|
|
133
|
+
resourceHistory: resources?.read(now, span) ?? [],
|
|
124
134
|
note: metricFresh ? void 0 : bds?.state === "stopped" ? "\u670D\u52A1\u5668\u672A\u8FD0\u884C" : "\u7B49\u5F85\u5E73\u53F0\u6307\u6807\u540C\u6B65"
|
|
125
135
|
});
|
|
126
136
|
return true;
|
|
@@ -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 { MetricsHistory, ResourceHistory, 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 const resources = query ? new ResourceHistory(query) : undefined;\n let lastCapture = 0;\n const captureResources = async (system: SystemStatusSnapshot | null) => {\n if (!system) return null;\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 && system.bds.pid === ownedPid;\n const processes = {\n db: await collectProcessResources(system.db.pid, system.db.running),\n bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null,\n };\n resources?.append({\n recordedAt: Date.now(),\n hostUsedMb: system.host.memory.usedMb,\n dbMb: processes.db.memoryMb,\n bdsMb: processes.bds?.memoryMb ?? null,\n });\n lastCapture = Date.now();\n return processes;\n };\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 if (resources) {\n const timer = setInterval(() => {\n if (Date.now() - lastCapture < 12_000) return;\n void getSystem().then(system => captureResources(system)).catch(() => undefined);\n }, 15_000);\n timer.unref();\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 = await captureResources(system);\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 resourceHistory: resources?.read(now) ?? [],\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,iBAAiB,oBAAwC;
|
|
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 { METRICS_RAW_WINDOW_MS, MetricsHistory, ResourceHistory, clampMetricsSpan, 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 const resources = query ? new ResourceHistory(query) : undefined;\n let lastCapture = 0;\n const captureResources = async (system: SystemStatusSnapshot | null) => {\n if (!system) return null;\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 && system.bds.pid === ownedPid;\n const processes = {\n db: await collectProcessResources(system.db.pid, system.db.running),\n bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null,\n };\n resources?.append({\n recordedAt: Date.now(),\n hostUsedMb: system.host.memory.usedMb,\n dbMb: processes.db.memoryMb,\n bdsMb: processes.bds?.memoryMb ?? null,\n });\n lastCapture = Date.now();\n return processes;\n };\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 if (resources) {\n const timer = setInterval(() => {\n if (Date.now() - lastCapture < 12_000) return;\n void getSystem().then(system => captureResources(system)).catch(() => undefined);\n }, 15_000);\n timer.unref();\n }\n\n return async function handle({ path, method, params, 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 = await captureResources(system);\n let span = METRICS_RAW_WINDOW_MS;\n const rawSpan = params.get(\"spanMs\");\n if (rawSpan) {\n try { span = clampMetricsSpan(Number(rawSpan)); } catch {\n json(res, { success: false, error: \"invalid_span\" }, 400);\n return true;\n }\n }\n json(res, { fresh: metricFresh, current: metricFresh ? metrics : null, updatedAt: metrics?.recordedAt ?? null,\n history: history?.read(now, span) ?? [], host: system?.host ?? null, processes, resourcesUpdatedAt: system ? Date.now() : null,\n resourceHistory: resources?.read(now, span) ?? [],\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": "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",
|
|
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;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,
|
|
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,CA0C5B;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
|
@@ -51,17 +51,20 @@ class ServiceRegistry {
|
|
|
51
51
|
if (!platformService && !enabled.has(handler.moduleId)) {
|
|
52
52
|
throw new DispatchError(`service "${name}" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, "forbidden", 403);
|
|
53
53
|
}
|
|
54
|
+
const platformCaller = callerModuleId === PLATFORM_SERVICE_OWNER;
|
|
54
55
|
const caller = enabled.get(callerModuleId);
|
|
55
|
-
if (!caller) {
|
|
56
|
+
if (!platformCaller && !caller) {
|
|
56
57
|
throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, "forbidden", 403);
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
if (!platformCaller) {
|
|
60
|
+
const declared = caller.services.requires.find((r) => r.name === name);
|
|
61
|
+
if (!declared && callerModuleId !== handler.moduleId) {
|
|
62
|
+
throw new DispatchError(
|
|
63
|
+
`${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E "${name}"`,
|
|
64
|
+
"not_in_requires",
|
|
65
|
+
403
|
|
66
|
+
);
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
69
|
try {
|
|
67
70
|
const result = await handler.handle({
|
|
@@ -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\";\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
|
|
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;
|
|
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 // \u5E73\u53F0\u5BBF\u4E3B\u53EF\u8C03\u7528\u4EFB\u610F\u5DF2\u6CE8\u518C\u670D\u52A1\uFF08QQ \u8D26\u53F7\u5FEB\u7167\u7B49\u53EF\u9009\u4F9D\u8D56\uFF09\uFF1B\u5176\u5B83\u8C03\u7528\u65B9\u4ECD\u8D70 enabled + requires\n const platformCaller = callerModuleId === PLATFORM_SERVICE_OWNER;\n const caller = enabled.get(callerModuleId);\n if (!platformCaller && !caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n if (!platformCaller) {\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 }\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;AAEA,UAAM,iBAAiB,mBAAmB;AAC1C,UAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,QAAI,CAAC,kBAAkB,CAAC,QAAQ;AAC9B,YAAM,IAAI,cAAc,sBAAO,cAAc,mBAAc,aAAa,GAAG;AAAA,IAC7E;AACA,QAAI,CAAC,gBAAgB;AACnB,YAAM,WAAW,OAAQ,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAEtE,UAAI,CAAC,YAAY,mBAAmB,QAAQ,UAAU;AACpD,cAAM,IAAI;AAAA,UACR,GAAG,cAAc,0DAAsC,IAAI;AAAA,UAC3D;AAAA,UACA;AAAA,QACF;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.5.1",
|
|
4
4
|
"description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
|
|
5
5
|
"author": "Shiroha7z",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -26,17 +26,35 @@
|
|
|
26
26
|
},
|
|
27
27
|
"main": "dist/index.js",
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./metrics-store": {
|
|
36
|
+
"types": "./dist/domain/metrics.d.ts",
|
|
37
|
+
"import": "./dist/domain/metrics.js",
|
|
38
|
+
"default": "./dist/domain/metrics.js"
|
|
39
|
+
},
|
|
40
|
+
"./qq-bindings": {
|
|
41
|
+
"types": "./dist/domain/qq-bindings.d.ts",
|
|
42
|
+
"import": "./dist/domain/qq-bindings.js",
|
|
43
|
+
"default": "./dist/domain/qq-bindings.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
29
47
|
"files": [
|
|
30
48
|
"dist",
|
|
31
49
|
"README.md"
|
|
32
50
|
],
|
|
33
51
|
"dependencies": {
|
|
34
52
|
"sql-template-strings": "^2.2.2",
|
|
35
|
-
"@sfmc-bds/
|
|
36
|
-
"@sfmc-bds/
|
|
53
|
+
"@sfmc-bds/sdk": "0.4.0",
|
|
54
|
+
"@sfmc-bds/bds-tools": "0.2.8"
|
|
37
55
|
},
|
|
38
56
|
"devDependencies": {
|
|
39
|
-
"@sfmc-bds/tools": "0.2.
|
|
57
|
+
"@sfmc-bds/tools": "0.2.8"
|
|
40
58
|
},
|
|
41
59
|
"publishConfig": {
|
|
42
60
|
"access": "public",
|