@sfmc-bds/db-server 0.8.0 → 0.8.2

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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * cpu-usage.ts — 把累计 CPU 时间换算成占用率。
3
+ *
4
+ * 使用场景:运行指标的主机 CPU 与各进程 CPU 曲线(GET /api/sfmc/metrics 与资源历史落库)。
5
+ * 占用率口径与任务管理器一致:以整机全部逻辑核为 100%,各进程之和不超过主机。
6
+ * 两次读数间隔太短时沿用上一次的结果,避免 Linux ps 秒级精度在短窗口里跳成 0% / 100%。
7
+ */
8
+ import os from "node:os";
9
+ /** 两次读数的最短间隔(毫秒)。间隔不足时不更新基线,直接返回上一次的占用率。 */
10
+ export declare const CPU_MIN_WINDOW_MS = 5000;
11
+ /**
12
+ * 主机整机 CPU 占用率。
13
+ * 使用场景:status 路由每次采集资源时调用一次;第一次没有基线,返回 null,不拿开机以来的平均值冒充当前值。
14
+ */
15
+ export declare class HostCpuMeter {
16
+ private readCpus;
17
+ private minWindowMs;
18
+ private last;
19
+ /** @param readCpus 读取各核累计时间,默认 os.cpus();单独传入便于替换数据源。 */
20
+ constructor(readCpus?: () => os.CpuInfo[], minWindowMs?: number);
21
+ measure(now?: number): number | null;
22
+ }
23
+ /** 一个待换算的进程读数。key 是角色键(db、bds、daemon、sidecar:<id> 等),同一角色换了 PID 视为新进程。 */
24
+ export interface CpuReading {
25
+ key: string;
26
+ pid: number;
27
+ running: boolean;
28
+ cpuSeconds: number | null;
29
+ }
30
+ /**
31
+ * 各进程 CPU 占用率。
32
+ * 使用场景:status 路由把 BDS、数据服务和已登记进程的累计 CPU 秒数一并交给 measure,按角色键取回占用率。
33
+ * 本轮没出现的角色会被丢弃,附属进程反复启停时基线表不会无限增长。
34
+ */
35
+ export declare class ProcessCpuMeter {
36
+ private cores;
37
+ private minWindowMs;
38
+ private last;
39
+ /** @param cores 逻辑核数,默认取本机可用并行度。 */
40
+ constructor(cores?: number, minWindowMs?: number);
41
+ measure(rows: CpuReading[], now?: number): Map<string, number | null>;
42
+ }
43
+ //# sourceMappingURL=cpu-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpu-usage.d.ts","sourceRoot":"","sources":["../../src/domain/cpu-usage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AAOvC;;;GAGG;AACH,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,WAAW;IAJrB,OAAO,CAAC,IAAI,CAAoF;IAChG,yDAAyD;IACzD,YACU,QAAQ,GAAE,MAAM,EAAE,CAAC,OAAO,EAAY,EACtC,WAAW,SAAoB,EACrC;IAEJ,OAAO,CAAC,GAAG,SAAa,GAAG,MAAM,GAAG,IAAI,CAcvC;CACF;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAIxB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,WAAW;IAJrB,OAAO,CAAC,IAAI,CAA8F;IAC1G,oCAAoC;IACpC,YACU,KAAK,SAAyC,EAC9C,WAAW,SAAoB,EACrC;IAEJ,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,SAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAyBxE;CACF"}
@@ -0,0 +1,71 @@
1
+ import os from "node:os";
2
+ const CPU_MIN_WINDOW_MS = 5e3;
3
+ function toPercent(ratio) {
4
+ return Math.round(Math.min(100, Math.max(0, ratio * 100)) * 10) / 10;
5
+ }
6
+ class HostCpuMeter {
7
+ /** @param readCpus 读取各核累计时间,默认 os.cpus();单独传入便于替换数据源。 */
8
+ constructor(readCpus = os.cpus, minWindowMs = CPU_MIN_WINDOW_MS) {
9
+ this.readCpus = readCpus;
10
+ this.minWindowMs = minWindowMs;
11
+ }
12
+ readCpus;
13
+ minWindowMs;
14
+ last = null;
15
+ measure(now = Date.now()) {
16
+ if (this.last && now - this.last.at < this.minWindowMs) return this.last.percent;
17
+ let idle = 0;
18
+ let total = 0;
19
+ for (const cpu of this.readCpus()) {
20
+ const times = cpu.times;
21
+ idle += times.idle;
22
+ total += times.user + times.nice + times.sys + times.idle + times.irq;
23
+ }
24
+ const previous = this.last;
25
+ const deltaTotal = previous ? total - previous.total : 0;
26
+ const percent = previous && deltaTotal > 0 ? toPercent(1 - (idle - previous.idle) / deltaTotal) : null;
27
+ this.last = { idle, total, at: now, percent };
28
+ return percent;
29
+ }
30
+ }
31
+ class ProcessCpuMeter {
32
+ /** @param cores 逻辑核数,默认取本机可用并行度。 */
33
+ constructor(cores = Math.max(1, os.availableParallelism()), minWindowMs = CPU_MIN_WINDOW_MS) {
34
+ this.cores = cores;
35
+ this.minWindowMs = minWindowMs;
36
+ }
37
+ cores;
38
+ minWindowMs;
39
+ last = /* @__PURE__ */ new Map();
40
+ measure(rows, now = Date.now()) {
41
+ const next = /* @__PURE__ */ new Map();
42
+ const result = /* @__PURE__ */ new Map();
43
+ for (const row of rows) {
44
+ if (!row.running || row.pid <= 0 || row.cpuSeconds === null) {
45
+ result.set(row.key, null);
46
+ continue;
47
+ }
48
+ const previous = this.last.get(row.key);
49
+ const samePid = previous?.pid === row.pid;
50
+ if (previous && samePid && now - previous.at < this.minWindowMs) {
51
+ next.set(row.key, previous);
52
+ result.set(row.key, previous.percent);
53
+ continue;
54
+ }
55
+ const deltaCpu = samePid && previous ? row.cpuSeconds - previous.cpuSeconds : NaN;
56
+ const deltaWall = samePid && previous ? (now - previous.at) / 1e3 : 0;
57
+ const percent = deltaWall > 0 && deltaCpu >= 0 ? toPercent(deltaCpu / (deltaWall * this.cores)) : null;
58
+ const entry = { pid: row.pid, cpuSeconds: row.cpuSeconds, at: now, percent };
59
+ next.set(row.key, entry);
60
+ result.set(row.key, percent);
61
+ }
62
+ this.last = next;
63
+ return result;
64
+ }
65
+ }
66
+ export {
67
+ CPU_MIN_WINDOW_MS,
68
+ HostCpuMeter,
69
+ ProcessCpuMeter
70
+ };
71
+ //# sourceMappingURL=cpu-usage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/domain/cpu-usage.ts"],
4
+ "sourcesContent": ["/**\n * cpu-usage.ts \u2014 \u628A\u7D2F\u8BA1 CPU \u65F6\u95F4\u6362\u7B97\u6210\u5360\u7528\u7387\u3002\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A\u8FD0\u884C\u6307\u6807\u7684\u4E3B\u673A CPU \u4E0E\u5404\u8FDB\u7A0B CPU \u66F2\u7EBF\uFF08GET /api/sfmc/metrics \u4E0E\u8D44\u6E90\u5386\u53F2\u843D\u5E93\uFF09\u3002\n * \u5360\u7528\u7387\u53E3\u5F84\u4E0E\u4EFB\u52A1\u7BA1\u7406\u5668\u4E00\u81F4\uFF1A\u4EE5\u6574\u673A\u5168\u90E8\u903B\u8F91\u6838\u4E3A 100%\uFF0C\u5404\u8FDB\u7A0B\u4E4B\u548C\u4E0D\u8D85\u8FC7\u4E3B\u673A\u3002\n * \u4E24\u6B21\u8BFB\u6570\u95F4\u9694\u592A\u77ED\u65F6\u6CBF\u7528\u4E0A\u4E00\u6B21\u7684\u7ED3\u679C\uFF0C\u907F\u514D Linux ps \u79D2\u7EA7\u7CBE\u5EA6\u5728\u77ED\u7A97\u53E3\u91CC\u8DF3\u6210 0% / 100%\u3002\n */\nimport os from \"node:os\";\n\n/** \u4E24\u6B21\u8BFB\u6570\u7684\u6700\u77ED\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09\u3002\u95F4\u9694\u4E0D\u8DB3\u65F6\u4E0D\u66F4\u65B0\u57FA\u7EBF\uFF0C\u76F4\u63A5\u8FD4\u56DE\u4E0A\u4E00\u6B21\u7684\u5360\u7528\u7387\u3002 */\nexport const CPU_MIN_WINDOW_MS = 5_000;\n\n/** \u5360\u7528\u7387\u4FDD\u7559\u4E00\u4F4D\u5C0F\u6570\uFF0C\u5E76\u6536\u5728 0\u2013100 \u4E4B\u95F4\u3002 */\nfunction toPercent(ratio: number): number {\n return Math.round(Math.min(100, Math.max(0, ratio * 100)) * 10) / 10;\n}\n\n/**\n * \u4E3B\u673A\u6574\u673A CPU \u5360\u7528\u7387\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Astatus \u8DEF\u7531\u6BCF\u6B21\u91C7\u96C6\u8D44\u6E90\u65F6\u8C03\u7528\u4E00\u6B21\uFF1B\u7B2C\u4E00\u6B21\u6CA1\u6709\u57FA\u7EBF\uFF0C\u8FD4\u56DE null\uFF0C\u4E0D\u62FF\u5F00\u673A\u4EE5\u6765\u7684\u5E73\u5747\u503C\u5192\u5145\u5F53\u524D\u503C\u3002\n */\nexport class HostCpuMeter {\n private last: { idle: number; total: number; at: number; percent: number | null } | null = null;\n /** @param readCpus \u8BFB\u53D6\u5404\u6838\u7D2F\u8BA1\u65F6\u95F4\uFF0C\u9ED8\u8BA4 os.cpus()\uFF1B\u5355\u72EC\u4F20\u5165\u4FBF\u4E8E\u66FF\u6362\u6570\u636E\u6E90\u3002 */\n constructor(\n private readCpus: () => os.CpuInfo[] = os.cpus,\n private minWindowMs = CPU_MIN_WINDOW_MS,\n ) {}\n\n measure(now = Date.now()): number | null {\n if (this.last && now - this.last.at < this.minWindowMs) return this.last.percent;\n let idle = 0;\n let total = 0;\n for (const cpu of this.readCpus()) {\n const times = cpu.times;\n idle += times.idle;\n total += times.user + times.nice + times.sys + times.idle + times.irq;\n }\n const previous = this.last;\n const deltaTotal = previous ? total - previous.total : 0;\n const percent = previous && deltaTotal > 0 ? toPercent(1 - (idle - previous.idle) / deltaTotal) : null;\n this.last = { idle, total, at: now, percent };\n return percent;\n }\n}\n\n/** \u4E00\u4E2A\u5F85\u6362\u7B97\u7684\u8FDB\u7A0B\u8BFB\u6570\u3002key \u662F\u89D2\u8272\u952E\uFF08db\u3001bds\u3001daemon\u3001sidecar:<id> \u7B49\uFF09\uFF0C\u540C\u4E00\u89D2\u8272\u6362\u4E86 PID \u89C6\u4E3A\u65B0\u8FDB\u7A0B\u3002 */\nexport interface CpuReading {\n key: string;\n pid: number;\n running: boolean;\n cpuSeconds: number | null;\n}\n\n/**\n * \u5404\u8FDB\u7A0B CPU \u5360\u7528\u7387\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Astatus \u8DEF\u7531\u628A BDS\u3001\u6570\u636E\u670D\u52A1\u548C\u5DF2\u767B\u8BB0\u8FDB\u7A0B\u7684\u7D2F\u8BA1 CPU \u79D2\u6570\u4E00\u5E76\u4EA4\u7ED9 measure\uFF0C\u6309\u89D2\u8272\u952E\u53D6\u56DE\u5360\u7528\u7387\u3002\n * \u672C\u8F6E\u6CA1\u51FA\u73B0\u7684\u89D2\u8272\u4F1A\u88AB\u4E22\u5F03\uFF0C\u9644\u5C5E\u8FDB\u7A0B\u53CD\u590D\u542F\u505C\u65F6\u57FA\u7EBF\u8868\u4E0D\u4F1A\u65E0\u9650\u589E\u957F\u3002\n */\nexport class ProcessCpuMeter {\n private last = new Map<string, { pid: number; cpuSeconds: number; at: number; percent: number | null }>();\n /** @param cores \u903B\u8F91\u6838\u6570\uFF0C\u9ED8\u8BA4\u53D6\u672C\u673A\u53EF\u7528\u5E76\u884C\u5EA6\u3002 */\n constructor(\n private cores = Math.max(1, os.availableParallelism()),\n private minWindowMs = CPU_MIN_WINDOW_MS,\n ) {}\n\n measure(rows: CpuReading[], now = Date.now()): Map<string, number | null> {\n const next = new Map<string, { pid: number; cpuSeconds: number; at: number; percent: number | null }>();\n const result = new Map<string, number | null>();\n for (const row of rows) {\n if (!row.running || row.pid <= 0 || row.cpuSeconds === null) {\n result.set(row.key, null);\n continue;\n }\n const previous = this.last.get(row.key);\n const samePid = previous?.pid === row.pid;\n if (previous && samePid && now - previous.at < this.minWindowMs) {\n next.set(row.key, previous);\n result.set(row.key, previous.percent);\n continue;\n }\n const deltaCpu = samePid && previous ? row.cpuSeconds - previous.cpuSeconds : NaN;\n const deltaWall = samePid && previous ? (now - previous.at) / 1000 : 0;\n // \u7D2F\u8BA1\u503C\u56DE\u9000\u8BF4\u660E PID \u88AB\u590D\u7528\u6216\u8BFB\u6570\u5F02\u5E38\uFF0C\u8FD9\u4E00\u8F6E\u53EA\u91CD\u5EFA\u57FA\u7EBF\u3002\n const percent = deltaWall > 0 && deltaCpu >= 0 ? toPercent(deltaCpu / (deltaWall * this.cores)) : null;\n const entry = { pid: row.pid, cpuSeconds: row.cpuSeconds, at: now, percent };\n next.set(row.key, entry);\n result.set(row.key, percent);\n }\n this.last = next;\n return result;\n }\n}\n"],
5
+ "mappings": "AAOA,OAAO,QAAQ;AAGR,MAAM,oBAAoB;AAGjC,SAAS,UAAU,OAAuB;AACxC,SAAO,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,QAAQ,GAAG,CAAC,IAAI,EAAE,IAAI;AACpE;AAMO,MAAM,aAAa;AAAA;AAAA,EAGxB,YACU,WAA+B,GAAG,MAClC,cAAc,mBACtB;AAFQ;AACA;AAAA,EACP;AAAA,EAFO;AAAA,EACA;AAAA,EAJF,OAAmF;AAAA,EAO3F,QAAQ,MAAM,KAAK,IAAI,GAAkB;AACvC,QAAI,KAAK,QAAQ,MAAM,KAAK,KAAK,KAAK,KAAK,YAAa,QAAO,KAAK,KAAK;AACzE,QAAI,OAAO;AACX,QAAI,QAAQ;AACZ,eAAW,OAAO,KAAK,SAAS,GAAG;AACjC,YAAM,QAAQ,IAAI;AAClB,cAAQ,MAAM;AACd,eAAS,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO,MAAM;AAAA,IACpE;AACA,UAAM,WAAW,KAAK;AACtB,UAAM,aAAa,WAAW,QAAQ,SAAS,QAAQ;AACvD,UAAM,UAAU,YAAY,aAAa,IAAI,UAAU,KAAK,OAAO,SAAS,QAAQ,UAAU,IAAI;AAClG,SAAK,OAAO,EAAE,MAAM,OAAO,IAAI,KAAK,QAAQ;AAC5C,WAAO;AAAA,EACT;AACF;AAeO,MAAM,gBAAgB;AAAA;AAAA,EAG3B,YACU,QAAQ,KAAK,IAAI,GAAG,GAAG,qBAAqB,CAAC,GAC7C,cAAc,mBACtB;AAFQ;AACA;AAAA,EACP;AAAA,EAFO;AAAA,EACA;AAAA,EAJF,OAAO,oBAAI,IAAqF;AAAA,EAOxG,QAAQ,MAAoB,MAAM,KAAK,IAAI,GAA+B;AACxE,UAAM,OAAO,oBAAI,IAAqF;AACtG,UAAM,SAAS,oBAAI,IAA2B;AAC9C,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,IAAI,WAAW,IAAI,OAAO,KAAK,IAAI,eAAe,MAAM;AAC3D,eAAO,IAAI,IAAI,KAAK,IAAI;AACxB;AAAA,MACF;AACA,YAAM,WAAW,KAAK,KAAK,IAAI,IAAI,GAAG;AACtC,YAAM,UAAU,UAAU,QAAQ,IAAI;AACtC,UAAI,YAAY,WAAW,MAAM,SAAS,KAAK,KAAK,aAAa;AAC/D,aAAK,IAAI,IAAI,KAAK,QAAQ;AAC1B,eAAO,IAAI,IAAI,KAAK,SAAS,OAAO;AACpC;AAAA,MACF;AACA,YAAM,WAAW,WAAW,WAAW,IAAI,aAAa,SAAS,aAAa;AAC9E,YAAM,YAAY,WAAW,YAAY,MAAM,SAAS,MAAM,MAAO;AAErE,YAAM,UAAU,YAAY,KAAK,YAAY,IAAI,UAAU,YAAY,YAAY,KAAK,MAAM,IAAI;AAClG,YAAM,QAAQ,EAAE,KAAK,IAAI,KAAK,YAAY,IAAI,YAAY,IAAI,KAAK,QAAQ;AAC3E,WAAK,IAAI,IAAI,KAAK,KAAK;AACvB,aAAO,IAAI,IAAI,KAAK,OAAO;AAAA,IAC7B;AACA,SAAK,OAAO;AACZ,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -32,6 +32,14 @@ export interface ResourceSample {
32
32
  * 使用场景:守护进程、QQ 桥、隧道、LLBot、附属进程。旧采样没有该字段;键缺省表示当时没采到。
33
33
  */
34
34
  partsMb?: Record<string, number>;
35
+ /**
36
+ * 整机 CPU 占用率(0–100)与各进程占用率,以整机全部逻辑核为 100%。
37
+ * 使用场景:CPU 曲线。旧采样没有这些字段;null 或键缺省表示当时没采到,不是 0。
38
+ */
39
+ hostCpuPercent?: number | null;
40
+ dbCpuPercent?: number | null;
41
+ bdsCpuPercent?: number | null;
42
+ partsCpuPercent?: Record<string, number>;
35
43
  }
36
44
  export declare class ResourceHistory {
37
45
  private query;
@@ -1 +1 @@
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;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAsBD,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,CAyBnC;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"}
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;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAwCD,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,CA0BnC;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"}
@@ -36,15 +36,25 @@ function parseMetrics(data, now) {
36
36
  const memoryMb = (value) => value === null || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 8388608;
37
37
  const partKey = (key) => /^[a-z0-9][a-z0-9_:-]{0,80}$/i.test(key);
38
38
  const memoryNumber = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 8388608;
39
- function sanitizeParts(value) {
39
+ const percentNumber = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
40
+ function sanitizeParts(value, valid = memoryNumber) {
40
41
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
41
42
  const parts = {};
42
43
  for (const [key, item] of Object.entries(value)) {
43
- if (!partKey(key) || !memoryNumber(item)) continue;
44
+ if (!partKey(key) || !valid(item)) continue;
44
45
  parts[key] = item;
45
46
  }
46
47
  return Object.keys(parts).length ? parts : void 0;
47
48
  }
49
+ function sanitizeCpu(sample) {
50
+ const partsCpuPercent = sanitizeParts(sample.partsCpuPercent, percentNumber);
51
+ return {
52
+ ...percentNumber(sample.hostCpuPercent) ? { hostCpuPercent: sample.hostCpuPercent } : {},
53
+ ...percentNumber(sample.dbCpuPercent) ? { dbCpuPercent: sample.dbCpuPercent } : {},
54
+ ...percentNumber(sample.bdsCpuPercent) ? { bdsCpuPercent: sample.bdsCpuPercent } : {},
55
+ ...partsCpuPercent ? { partsCpuPercent } : {}
56
+ };
57
+ }
48
58
  class ResourceHistory {
49
59
  constructor(query) {
50
60
  this.query = query;
@@ -63,7 +73,8 @@ class ResourceHistory {
63
73
  hostUsedMb: sample.hostUsedMb,
64
74
  dbMb: sample.dbMb,
65
75
  bdsMb: sample.bdsMb,
66
- ...partsMb ? { partsMb } : {}
76
+ ...partsMb ? { partsMb } : {},
77
+ ...sanitizeCpu(sample)
67
78
  };
68
79
  this.query("INSERT OR REPLACE INTO sfmc_resource_metrics (recorded_at, sample) VALUES (?, ?)", [
69
80
  sample.recordedAt,
@@ -174,7 +185,8 @@ function parseResourceRows(rows) {
174
185
  hostUsedMb: sample.hostUsedMb,
175
186
  dbMb: sample.dbMb,
176
187
  bdsMb: sample.bdsMb,
177
- ...partsMb ? { partsMb } : {}
188
+ ...partsMb ? { partsMb } : {},
189
+ ...sanitizeCpu(sample)
178
190
  }];
179
191
  } catch {
180
192
  return [];
@@ -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 { 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 /**\n * \u5176\u4F59\u5DF2\u767B\u8BB0\u8FDB\u7A0B\u7684\u5185\u5B58\uFF08MB\uFF09\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u5B88\u62A4\u8FDB\u7A0B\u3001QQ \u6865\u3001\u96A7\u9053\u3001LLBot\u3001\u9644\u5C5E\u8FDB\u7A0B\u3002\u65E7\u91C7\u6837\u6CA1\u6709\u8BE5\u5B57\u6BB5\uFF1B\u952E\u7F3A\u7701\u8868\u793A\u5F53\u65F6\u6CA1\u91C7\u5230\u3002\n */\n partsMb?: Record<string, number>;\n}\nconst memoryMb = (value: unknown) =>\n value === null || (typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 8_388_608);\n/** \u8FDB\u7A0B\u89D2\u8272\u952E\u3002\u9644\u5C5E\u8FDB\u7A0B\u7528 sidecar:<\u6A21\u5757 id>\u3002 */\nconst partKey = (key: string) => /^[a-z0-9][a-z0-9_:-]{0,80}$/i.test(key);\nconst memoryNumber = (value: unknown) =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 8_388_608;\n\n/**\n * \u4E22\u6389\u4E0D\u5408\u6CD5\u7684\u5206\u9879\uFF0C\u4FDD\u7559\u80FD\u7528\u7684\u6570\u5B57\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u8BFB\u5386\u53F2\u65F6\u65E7\u6570\u636E\u6216\u635F\u574F\u5B57\u6BB5\u4E0D\u80FD\u628A\u6574\u6761\u4E3B\u673A/BDS \u91C7\u6837\u4E22\u6389\u3002\n */\nfunction sanitizeParts(value: unknown): Record<string, number> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const parts: Record<string, number> = {};\n for (const [key, item] of Object.entries(value)) {\n if (!partKey(key) || !memoryNumber(item)) continue;\n parts[key] = item;\n }\n return Object.keys(parts).length ? parts : undefined;\n}\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 const partsMb = sanitizeParts(sample.partsMb);\n if (sample.hostUsedMb === null && sample.dbMb === null && sample.bdsMb === null && !partsMb) return;\n if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return;\n // \u5206\u9879\u91CC\u7684\u574F\u952E\u4E22\u6389\uFF0C\u4E3B\u673A\u548C BDS\u3001\u6570\u636E\u670D\u52A1\u4ECD\u6309\u539F\u6765\u7684\u89C4\u5219\u843D\u5E93\u3002\n sample = {\n recordedAt: sample.recordedAt,\n hostUsedMb: sample.hostUsedMb,\n dbMb: sample.dbMb,\n bdsMb: sample.bdsMb,\n ...(partsMb ? { partsMb } : {}),\n };\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 const partsMb = sanitizeParts(sample.partsMb);\n return [{\n recordedAt: sample.recordedAt,\n hostUsedMb: sample.hostUsedMb,\n dbMb: sample.dbMb,\n bdsMb: sample.bdsMb,\n ...(partsMb ? { partsMb } : {}),\n }];\n } catch {\n return [];\n }\n });\n}\n"],
5
- "mappings": "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",
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 /**\n * \u5176\u4F59\u5DF2\u767B\u8BB0\u8FDB\u7A0B\u7684\u5185\u5B58\uFF08MB\uFF09\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u5B88\u62A4\u8FDB\u7A0B\u3001QQ \u6865\u3001\u96A7\u9053\u3001LLBot\u3001\u9644\u5C5E\u8FDB\u7A0B\u3002\u65E7\u91C7\u6837\u6CA1\u6709\u8BE5\u5B57\u6BB5\uFF1B\u952E\u7F3A\u7701\u8868\u793A\u5F53\u65F6\u6CA1\u91C7\u5230\u3002\n */\n partsMb?: Record<string, number>;\n /**\n * \u6574\u673A CPU \u5360\u7528\u7387\uFF080\u2013100\uFF09\u4E0E\u5404\u8FDB\u7A0B\u5360\u7528\u7387\uFF0C\u4EE5\u6574\u673A\u5168\u90E8\u903B\u8F91\u6838\u4E3A 100%\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1ACPU \u66F2\u7EBF\u3002\u65E7\u91C7\u6837\u6CA1\u6709\u8FD9\u4E9B\u5B57\u6BB5\uFF1Bnull \u6216\u952E\u7F3A\u7701\u8868\u793A\u5F53\u65F6\u6CA1\u91C7\u5230\uFF0C\u4E0D\u662F 0\u3002\n */\n hostCpuPercent?: number | null;\n dbCpuPercent?: number | null;\n bdsCpuPercent?: number | null;\n partsCpuPercent?: Record<string, number>;\n}\nconst memoryMb = (value: unknown) =>\n value === null || (typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 8_388_608);\n/** \u8FDB\u7A0B\u89D2\u8272\u952E\u3002\u9644\u5C5E\u8FDB\u7A0B\u7528 sidecar:<\u6A21\u5757 id>\u3002 */\nconst partKey = (key: string) => /^[a-z0-9][a-z0-9_:-]{0,80}$/i.test(key);\nconst memoryNumber = (value: unknown) =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 8_388_608;\n/** CPU \u5360\u7528\u7387\u7684\u5408\u6CD5\u6570\u503C\uFF1A0\u2013100 \u7684\u6709\u9650\u6570\u3002 */\nconst percentNumber = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 100;\n\n/**\n * \u4E22\u6389\u4E0D\u5408\u6CD5\u7684\u5206\u9879\uFF0C\u4FDD\u7559\u80FD\u7528\u7684\u6570\u5B57\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u8BFB\u5386\u53F2\u65F6\u65E7\u6570\u636E\u6216\u635F\u574F\u5B57\u6BB5\u4E0D\u80FD\u628A\u6574\u6761\u4E3B\u673A/BDS \u91C7\u6837\u4E22\u6389\u3002\n * valid \u9ED8\u8BA4\u6309\u5185\u5B58\u6821\u9A8C\uFF1BCPU \u5206\u9879\u4F20\u5165 percentNumber\u3002\n */\nfunction sanitizeParts(value: unknown, valid: (item: unknown) => boolean = memoryNumber): Record<string, number> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const parts: Record<string, number> = {};\n for (const [key, item] of Object.entries(value)) {\n if (!partKey(key) || !valid(item)) continue;\n parts[key] = item as number;\n }\n return Object.keys(parts).length ? parts : undefined;\n}\n\n/**\n * \u53D6\u51FA\u91C7\u6837\u91CC\u5408\u6CD5\u7684 CPU \u5B57\u6BB5\uFF1B\u4E0D\u5408\u6CD5\u6216\u7F3A\u7701\u7684\u9879\u76F4\u63A5\u7701\u7565\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1AResourceHistory.append \u843D\u5E93\u524D\u4E0E parseResourceRows \u8BFB\u5386\u53F2\u65F6\u5171\u7528\uFF0CCPU \u5B57\u6BB5\u574F\u4E86\u4E0D\u5F71\u54CD\u5185\u5B58\u90E8\u5206\u3002\n */\nfunction sanitizeCpu(sample: ResourceSample): Pick<ResourceSample, \"hostCpuPercent\" | \"dbCpuPercent\" | \"bdsCpuPercent\" | \"partsCpuPercent\"> {\n const partsCpuPercent = sanitizeParts(sample.partsCpuPercent, percentNumber);\n return {\n ...(percentNumber(sample.hostCpuPercent) ? { hostCpuPercent: sample.hostCpuPercent } : {}),\n ...(percentNumber(sample.dbCpuPercent) ? { dbCpuPercent: sample.dbCpuPercent } : {}),\n ...(percentNumber(sample.bdsCpuPercent) ? { bdsCpuPercent: sample.bdsCpuPercent } : {}),\n ...(partsCpuPercent ? { partsCpuPercent } : {}),\n };\n}\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 const partsMb = sanitizeParts(sample.partsMb);\n if (sample.hostUsedMb === null && sample.dbMb === null && sample.bdsMb === null && !partsMb) return;\n if (!memoryMb(sample.hostUsedMb) || !memoryMb(sample.dbMb) || !memoryMb(sample.bdsMb)) return;\n // \u5206\u9879\u91CC\u7684\u574F\u952E\u4E22\u6389\uFF0C\u4E3B\u673A\u548C BDS\u3001\u6570\u636E\u670D\u52A1\u4ECD\u6309\u539F\u6765\u7684\u89C4\u5219\u843D\u5E93\u3002\n sample = {\n recordedAt: sample.recordedAt,\n hostUsedMb: sample.hostUsedMb,\n dbMb: sample.dbMb,\n bdsMb: sample.bdsMb,\n ...(partsMb ? { partsMb } : {}),\n ...sanitizeCpu(sample),\n };\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 const partsMb = sanitizeParts(sample.partsMb);\n return [{\n recordedAt: sample.recordedAt,\n hostUsedMb: sample.hostUsedMb,\n dbMb: sample.dbMb,\n bdsMb: sample.bdsMb,\n ...(partsMb ? { partsMb } : {}),\n ...sanitizeCpu(sample),\n }];\n } catch {\n return [];\n }\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,8 @@ export interface ProcessResources {
3
3
  running: boolean;
4
4
  memoryMb: number | null;
5
5
  cpuSeconds: number | null;
6
+ /** 最近一段时间的 CPU 占用率(整机为 100%)。由 status 路由的 ProcessCpuMeter 补上,采集函数本身不填。 */
7
+ cpuPercent?: number | null;
6
8
  }
7
9
  export declare function collectProcessResources(pid: number, running: boolean): Promise<ProcessResources>;
8
10
  /** 一次系统查询得到的单个进程用量。查不到的 PID 不会出现。 */
@@ -1 +1 @@
1
- {"version":3,"file":"process-resources.d.ts","sourceRoot":"","sources":["../../src/domain/process-resources.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmCtG;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CA+CpG"}
1
+ {"version":3,"file":"process-resources.d.ts","sourceRoot":"","sources":["../../src/domain/process-resources.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmCtG;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CA+CpG"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/domain/process-resources.ts"],
4
- "sourcesContent": ["/** \u53D7\u63A7\u8FDB\u7A0B\u8D44\u6E90\u91C7\u96C6\uFF1A\u4EC5\u5141\u8BB8\u5DF2\u6838\u9A8C\u7684\u6570\u5B57 PID\uFF0C\u5931\u8D25\u4FDD\u7559\u672A\u77E5\u3002 */\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nconst execute = promisify(execFile);\nexport interface ProcessResources {\n pid: number;\n running: boolean;\n memoryMb: number | null;\n cpuSeconds: number | null;\n}\n\nexport async function collectProcessResources(pid: number, running: boolean): Promise<ProcessResources> {\n const result: ProcessResources = { pid, running, memoryMb: null, cpuSeconds: null };\n if (!running || !Number.isSafeInteger(pid) || pid <= 0) return result;\n if (pid === process.pid) {\n const cpu = process.cpuUsage();\n return {\n ...result,\n memoryMb: Math.round(process.memoryUsage().rss / 1048576),\n cpuSeconds: (cpu.user + cpu.system) / 1_000_000,\n };\n }\n try {\n if (process.platform === \"win32\") {\n const script = `$p=Get-Process -Id ${pid} -ErrorAction Stop; @{memoryMb=[Math]::Round($p.WorkingSet64/1MB);cpuSeconds=$p.CPU}|ConvertTo-Json -Compress`;\n const { stdout } = await execute(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n windowsHide: true,\n timeout: 2000,\n });\n const data = JSON.parse(stdout) as { memoryMb: number; cpuSeconds: number | null };\n if (Number.isFinite(data.memoryMb) && data.memoryMb >= 0) result.memoryMb = data.memoryMb;\n if (typeof data.cpuSeconds === \"number\" && Number.isFinite(data.cpuSeconds) && data.cpuSeconds >= 0)\n result.cpuSeconds = data.cpuSeconds;\n } else if (process.platform === \"linux\") {\n const { stdout } = await execute(\"ps\", [\"-p\", String(pid), \"-o\", \"rss=\", \"-o\", \"time=\"], { timeout: 2000 });\n const match = stdout.trim().match(/^(\\d+)\\s+(?:(\\d+)-)?(\\d+):(\\d+):(\\d+)$/);\n if (match) {\n result.memoryMb = Math.round(Number(match[1]) / 1024);\n result.cpuSeconds =\n Number(match[2] ?? 0) * 86400 + Number(match[3]) * 3600 + Number(match[4]) * 60 + Number(match[5]);\n }\n }\n } catch {\n /* \u65E0\u6743\u9650\u3001\u8FDB\u7A0B\u9000\u51FA\u3001\u91C7\u96C6\u8D85\u65F6\u5747\u4E0D\u4F2A\u62A5 0\u3002 */\n }\n return result;\n}\n\n/** \u4E00\u6B21\u7CFB\u7EDF\u67E5\u8BE2\u5F97\u5230\u7684\u5355\u4E2A\u8FDB\u7A0B\u7528\u91CF\u3002\u67E5\u4E0D\u5230\u7684 PID \u4E0D\u4F1A\u51FA\u73B0\u3002 */\nexport interface ProcessUsage {\n memoryMb: number;\n cpuSeconds: number | null;\n}\n\n/**\n * \u4E00\u6B21\u67E5\u8BE2\u591A\u4E2A PID \u7684\u5DE5\u4F5C\u96C6\u548C\u7D2F\u8BA1 CPU\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u5185\u5B58\u9762\u677F\u9664 BDS\u3001\u6570\u636E\u670D\u52A1\u5916\u8FD8\u8981\u770B\u5B88\u62A4\u8FDB\u7A0B\u3001QQ \u6865\u7B49\uFF0C\u907F\u514D\u6BCF\u4E2A\u8FDB\u7A0B\u5404\u8D77\u4E00\u6B21\u7CFB\u7EDF\u547D\u4EE4\u3002\n * \u5F53\u524D\u8FDB\u7A0B\u4ECD\u7528 process.memoryUsage\uFF0C\u4E0E collectProcessResources \u5BF9\u81EA\u8EAB\u7684\u7B97\u6CD5\u4E00\u81F4\u3002\n * \u547D\u4EE4\u5931\u8D25\u65F6\u8FD4\u56DE\u7A7A\u8868\uFF0C\u8C03\u7528\u65B9\u6309\u300C\u8FD9\u6B21\u6CA1\u91C7\u5230\u300D\u5904\u7406\uFF0C\u4E0D\u628A\u7F3A\u5931\u5199\u6210 0\u3002\n */\nexport async function collectProcessResourcesMany(pids: number[]): Promise<Map<number, ProcessUsage>> {\n const unique = [...new Set(pids.filter((pid) => Number.isSafeInteger(pid) && pid > 0))];\n const found = new Map<number, ProcessUsage>();\n if (unique.includes(process.pid)) {\n const cpu = process.cpuUsage();\n found.set(process.pid, {\n memoryMb: Math.round(process.memoryUsage().rss / 1048576),\n cpuSeconds: (cpu.user + cpu.system) / 1_000_000,\n });\n }\n const external = unique.filter((pid) => pid !== process.pid);\n if (!external.length) return found;\n try {\n if (process.platform === \"win32\") {\n const script = [\n \"$ErrorActionPreference = 'SilentlyContinue'\",\n `$ids = @(${external.join(\",\")})`,\n \"$rows = foreach ($id in $ids) {\",\n \" $p = Get-Process -Id $id -ErrorAction SilentlyContinue\",\n \" if ($null -eq $p) { continue }\",\n \" $cpu = $p.CPU\",\n \" [pscustomobject]@{ pid = [int]$p.Id; memoryMb = [int][Math]::Round($p.WorkingSet64/1MB); cpuSeconds = $(if ($null -eq $cpu) { $null } else { [double]$cpu }) }\",\n \"}\",\n \"if ($null -eq $rows) { '[]' } else { ConvertTo-Json -InputObject @($rows) -Compress }\",\n ].join(\"\\n\");\n const { stdout } = await execute(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n windowsHide: true,\n timeout: 2000,\n });\n for (const row of parseUsageRows(JSON.parse(stdout))) found.set(row.pid, row.usage);\n } else if (process.platform === \"linux\") {\n const { stdout } = await execute(\"ps\", [\"-p\", external.join(\",\"), \"-o\", \"pid=\", \"-o\", \"rss=\", \"-o\", \"time=\"], {\n timeout: 2000,\n });\n for (const line of stdout.split(/\\n/)) {\n const match = line.trim().match(/^(\\d+)\\s+(\\d+)\\s+(\\S+)$/);\n if (!match) continue;\n const pid = Number(match[1]);\n const rssKb = Number(match[2]);\n if (!Number.isSafeInteger(pid) || pid <= 0 || !Number.isFinite(rssKb)) continue;\n found.set(pid, { memoryMb: Math.round(rssKb / 1024), cpuSeconds: parseCpuClock(match[3] ?? \"\") });\n }\n }\n } catch {\n /* \u91C7\u96C6\u8D85\u65F6\u6216\u8FDB\u7A0B\u5DF2\u9000\u51FA\u65F6\uFF0C\u5DF2\u6210\u529F\u7684\u81EA\u8EAB\u8BFB\u6570\u4ECD\u4FDD\u7559\uFF0C\u5176\u4F59\u6309\u6CA1\u91C7\u5230\u5904\u7406\u3002 */\n }\n return found;\n}\n\n/** \u628A\u7CFB\u7EDF\u547D\u4EE4\u8FD4\u56DE\u7684\u884C\u6536\u6210\u7528\u91CF\u3002\u975E\u6CD5\u884C\u8DF3\u8FC7\u3002 */\nfunction parseUsageRows(parsed: unknown): Array<{ pid: number; usage: ProcessUsage }> {\n // Windows PowerShell 5 \u4F1A\u628A\u53EA\u6709\u4E00\u884C\u7684\u7ED3\u679C\u6536\u6210\u5BF9\u8C61\uFF0C7 \u5219\u4FDD\u6301\u6570\u7EC4\u3002\n const list = Array.isArray(parsed) ? parsed : parsed && typeof parsed === \"object\" ? [parsed] : [];\n const rows: Array<{ pid: number; usage: ProcessUsage }> = [];\n for (const item of list) {\n if (!item || typeof item !== \"object\") continue;\n const row = item as { pid?: unknown; memoryMb?: unknown; cpuSeconds?: unknown };\n if (typeof row.pid !== \"number\" || !Number.isSafeInteger(row.pid) || row.pid <= 0) continue;\n if (typeof row.memoryMb !== \"number\" || !Number.isFinite(row.memoryMb) || row.memoryMb < 0) continue;\n const cpuSeconds =\n typeof row.cpuSeconds === \"number\" && Number.isFinite(row.cpuSeconds) && row.cpuSeconds >= 0 ? row.cpuSeconds : null;\n rows.push({ pid: row.pid, usage: { memoryMb: row.memoryMb, cpuSeconds } });\n }\n return rows;\n}\n\n/** ps \u7684\u65F6\u95F4\u5217\uFF1Add-hh:mm:ss\u3001hh:mm:ss \u6216 mm:ss\u3002\u89E3\u6790\u4E0D\u4E86\u65F6\u8FD4\u56DE null\u3002 */\nfunction parseCpuClock(text: string): number | null {\n const match = text.trim().match(/^(?:(\\d+)-)?(?:(\\d+):)?(\\d+):(\\d+)$/);\n if (!match) return null;\n const days = Number(match[1] ?? 0);\n const hours = Number(match[2] ?? 0);\n const minutes = Number(match[3]);\n const seconds = Number(match[4]);\n if (![days, hours, minutes, seconds].every((value) => Number.isFinite(value))) return null;\n return days * 86400 + hours * 3600 + minutes * 60 + seconds;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/** \u53D7\u63A7\u8FDB\u7A0B\u8D44\u6E90\u91C7\u96C6\uFF1A\u4EC5\u5141\u8BB8\u5DF2\u6838\u9A8C\u7684\u6570\u5B57 PID\uFF0C\u5931\u8D25\u4FDD\u7559\u672A\u77E5\u3002 */\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nconst execute = promisify(execFile);\nexport interface ProcessResources {\n pid: number;\n running: boolean;\n memoryMb: number | null;\n cpuSeconds: number | null;\n /** \u6700\u8FD1\u4E00\u6BB5\u65F6\u95F4\u7684 CPU \u5360\u7528\u7387\uFF08\u6574\u673A\u4E3A 100%\uFF09\u3002\u7531 status \u8DEF\u7531\u7684 ProcessCpuMeter \u8865\u4E0A\uFF0C\u91C7\u96C6\u51FD\u6570\u672C\u8EAB\u4E0D\u586B\u3002 */\n cpuPercent?: number | null;\n}\n\nexport async function collectProcessResources(pid: number, running: boolean): Promise<ProcessResources> {\n const result: ProcessResources = { pid, running, memoryMb: null, cpuSeconds: null };\n if (!running || !Number.isSafeInteger(pid) || pid <= 0) return result;\n if (pid === process.pid) {\n const cpu = process.cpuUsage();\n return {\n ...result,\n memoryMb: Math.round(process.memoryUsage().rss / 1048576),\n cpuSeconds: (cpu.user + cpu.system) / 1_000_000,\n };\n }\n try {\n if (process.platform === \"win32\") {\n const script = `$p=Get-Process -Id ${pid} -ErrorAction Stop; @{memoryMb=[Math]::Round($p.WorkingSet64/1MB);cpuSeconds=$p.CPU}|ConvertTo-Json -Compress`;\n const { stdout } = await execute(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n windowsHide: true,\n timeout: 2000,\n });\n const data = JSON.parse(stdout) as { memoryMb: number; cpuSeconds: number | null };\n if (Number.isFinite(data.memoryMb) && data.memoryMb >= 0) result.memoryMb = data.memoryMb;\n if (typeof data.cpuSeconds === \"number\" && Number.isFinite(data.cpuSeconds) && data.cpuSeconds >= 0)\n result.cpuSeconds = data.cpuSeconds;\n } else if (process.platform === \"linux\") {\n const { stdout } = await execute(\"ps\", [\"-p\", String(pid), \"-o\", \"rss=\", \"-o\", \"time=\"], { timeout: 2000 });\n const match = stdout.trim().match(/^(\\d+)\\s+(?:(\\d+)-)?(\\d+):(\\d+):(\\d+)$/);\n if (match) {\n result.memoryMb = Math.round(Number(match[1]) / 1024);\n result.cpuSeconds =\n Number(match[2] ?? 0) * 86400 + Number(match[3]) * 3600 + Number(match[4]) * 60 + Number(match[5]);\n }\n }\n } catch {\n /* \u65E0\u6743\u9650\u3001\u8FDB\u7A0B\u9000\u51FA\u3001\u91C7\u96C6\u8D85\u65F6\u5747\u4E0D\u4F2A\u62A5 0\u3002 */\n }\n return result;\n}\n\n/** \u4E00\u6B21\u7CFB\u7EDF\u67E5\u8BE2\u5F97\u5230\u7684\u5355\u4E2A\u8FDB\u7A0B\u7528\u91CF\u3002\u67E5\u4E0D\u5230\u7684 PID \u4E0D\u4F1A\u51FA\u73B0\u3002 */\nexport interface ProcessUsage {\n memoryMb: number;\n cpuSeconds: number | null;\n}\n\n/**\n * \u4E00\u6B21\u67E5\u8BE2\u591A\u4E2A PID \u7684\u5DE5\u4F5C\u96C6\u548C\u7D2F\u8BA1 CPU\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u5185\u5B58\u9762\u677F\u9664 BDS\u3001\u6570\u636E\u670D\u52A1\u5916\u8FD8\u8981\u770B\u5B88\u62A4\u8FDB\u7A0B\u3001QQ \u6865\u7B49\uFF0C\u907F\u514D\u6BCF\u4E2A\u8FDB\u7A0B\u5404\u8D77\u4E00\u6B21\u7CFB\u7EDF\u547D\u4EE4\u3002\n * \u5F53\u524D\u8FDB\u7A0B\u4ECD\u7528 process.memoryUsage\uFF0C\u4E0E collectProcessResources \u5BF9\u81EA\u8EAB\u7684\u7B97\u6CD5\u4E00\u81F4\u3002\n * \u547D\u4EE4\u5931\u8D25\u65F6\u8FD4\u56DE\u7A7A\u8868\uFF0C\u8C03\u7528\u65B9\u6309\u300C\u8FD9\u6B21\u6CA1\u91C7\u5230\u300D\u5904\u7406\uFF0C\u4E0D\u628A\u7F3A\u5931\u5199\u6210 0\u3002\n */\nexport async function collectProcessResourcesMany(pids: number[]): Promise<Map<number, ProcessUsage>> {\n const unique = [...new Set(pids.filter((pid) => Number.isSafeInteger(pid) && pid > 0))];\n const found = new Map<number, ProcessUsage>();\n if (unique.includes(process.pid)) {\n const cpu = process.cpuUsage();\n found.set(process.pid, {\n memoryMb: Math.round(process.memoryUsage().rss / 1048576),\n cpuSeconds: (cpu.user + cpu.system) / 1_000_000,\n });\n }\n const external = unique.filter((pid) => pid !== process.pid);\n if (!external.length) return found;\n try {\n if (process.platform === \"win32\") {\n const script = [\n \"$ErrorActionPreference = 'SilentlyContinue'\",\n `$ids = @(${external.join(\",\")})`,\n \"$rows = foreach ($id in $ids) {\",\n \" $p = Get-Process -Id $id -ErrorAction SilentlyContinue\",\n \" if ($null -eq $p) { continue }\",\n \" $cpu = $p.CPU\",\n \" [pscustomobject]@{ pid = [int]$p.Id; memoryMb = [int][Math]::Round($p.WorkingSet64/1MB); cpuSeconds = $(if ($null -eq $cpu) { $null } else { [double]$cpu }) }\",\n \"}\",\n \"if ($null -eq $rows) { '[]' } else { ConvertTo-Json -InputObject @($rows) -Compress }\",\n ].join(\"\\n\");\n const { stdout } = await execute(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n windowsHide: true,\n timeout: 2000,\n });\n for (const row of parseUsageRows(JSON.parse(stdout))) found.set(row.pid, row.usage);\n } else if (process.platform === \"linux\") {\n const { stdout } = await execute(\"ps\", [\"-p\", external.join(\",\"), \"-o\", \"pid=\", \"-o\", \"rss=\", \"-o\", \"time=\"], {\n timeout: 2000,\n });\n for (const line of stdout.split(/\\n/)) {\n const match = line.trim().match(/^(\\d+)\\s+(\\d+)\\s+(\\S+)$/);\n if (!match) continue;\n const pid = Number(match[1]);\n const rssKb = Number(match[2]);\n if (!Number.isSafeInteger(pid) || pid <= 0 || !Number.isFinite(rssKb)) continue;\n found.set(pid, { memoryMb: Math.round(rssKb / 1024), cpuSeconds: parseCpuClock(match[3] ?? \"\") });\n }\n }\n } catch {\n /* \u91C7\u96C6\u8D85\u65F6\u6216\u8FDB\u7A0B\u5DF2\u9000\u51FA\u65F6\uFF0C\u5DF2\u6210\u529F\u7684\u81EA\u8EAB\u8BFB\u6570\u4ECD\u4FDD\u7559\uFF0C\u5176\u4F59\u6309\u6CA1\u91C7\u5230\u5904\u7406\u3002 */\n }\n return found;\n}\n\n/** \u628A\u7CFB\u7EDF\u547D\u4EE4\u8FD4\u56DE\u7684\u884C\u6536\u6210\u7528\u91CF\u3002\u975E\u6CD5\u884C\u8DF3\u8FC7\u3002 */\nfunction parseUsageRows(parsed: unknown): Array<{ pid: number; usage: ProcessUsage }> {\n // Windows PowerShell 5 \u4F1A\u628A\u53EA\u6709\u4E00\u884C\u7684\u7ED3\u679C\u6536\u6210\u5BF9\u8C61\uFF0C7 \u5219\u4FDD\u6301\u6570\u7EC4\u3002\n const list = Array.isArray(parsed) ? parsed : parsed && typeof parsed === \"object\" ? [parsed] : [];\n const rows: Array<{ pid: number; usage: ProcessUsage }> = [];\n for (const item of list) {\n if (!item || typeof item !== \"object\") continue;\n const row = item as { pid?: unknown; memoryMb?: unknown; cpuSeconds?: unknown };\n if (typeof row.pid !== \"number\" || !Number.isSafeInteger(row.pid) || row.pid <= 0) continue;\n if (typeof row.memoryMb !== \"number\" || !Number.isFinite(row.memoryMb) || row.memoryMb < 0) continue;\n const cpuSeconds =\n typeof row.cpuSeconds === \"number\" && Number.isFinite(row.cpuSeconds) && row.cpuSeconds >= 0 ? row.cpuSeconds : null;\n rows.push({ pid: row.pid, usage: { memoryMb: row.memoryMb, cpuSeconds } });\n }\n return rows;\n}\n\n/** ps \u7684\u65F6\u95F4\u5217\uFF1Add-hh:mm:ss\u3001hh:mm:ss \u6216 mm:ss\u3002\u89E3\u6790\u4E0D\u4E86\u65F6\u8FD4\u56DE null\u3002 */\nfunction parseCpuClock(text: string): number | null {\n const match = text.trim().match(/^(?:(\\d+)-)?(?:(\\d+):)?(\\d+):(\\d+)$/);\n if (!match) return null;\n const days = Number(match[1] ?? 0);\n const hours = Number(match[2] ?? 0);\n const minutes = Number(match[3]);\n const seconds = Number(match[4]);\n if (![days, hours, minutes, seconds].every((value) => Number.isFinite(value))) return null;\n return days * 86400 + hours * 3600 + minutes * 60 + seconds;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -15,6 +15,8 @@ export interface TrackedProcess {
15
15
  running: true;
16
16
  memoryMb: number;
17
17
  cpuSeconds: number | null;
18
+ /** CPU 占用率(整机为 100%)。由 status 路由的 ProcessCpuMeter 补上,这里采集时不填。 */
19
+ cpuPercent?: number | null;
18
20
  }
19
21
  /**
20
22
  * 从运行态 JSON 里取出 pid。
@@ -1 +1 @@
1
- {"version":3,"file":"tracked-processes.d.ts","sourceRoot":"","sources":["../../src/domain/tracked-processes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD;AA6BD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA8BhH"}
1
+ {"version":3,"file":"tracked-processes.d.ts","sourceRoot":"","sources":["../../src/domain/tracked-processes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD;AA6BD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA8BhH"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/domain/tracked-processes.ts"],
4
- "sourcesContent": ["/**\n * tracked-processes.ts \u2014 \u91C7\u96C6\u672C\u5B9E\u4F8B\u5DF2\u767B\u8BB0\u7684\u5176\u5B83 SFMC \u8FDB\u7A0B\u5185\u5B58\u3002\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A\u8FD0\u884C\u6307\u6807\u9664 BDS\u3001\u6570\u636E\u670D\u52A1\u5916\uFF0C\u8FD8\u8981\u5217\u51FA\u5B88\u62A4\u8FDB\u7A0B\u3001QQ \u6865\u3001\u96A7\u9053\u3001LLBot \u548C\u6A21\u5757\u9644\u5C5E\u8FDB\u7A0B\u3002\n * \u53EA\u8BFB\u8FD9\u4E9B\u8FDB\u7A0B\u81EA\u5DF1\u5199\u4E0B\u7684\u8FD0\u884C\u6001\u6587\u4EF6\uFF0C\u4E0D\u6309\u8FDB\u7A0B\u540D\u5168\u673A\u626B\u63CF\uFF0C\u907F\u514D\u628A\u540C\u673A\u53E6\u4E00\u4E2A\u7A0B\u5E8F\u7B97\u8FDB\u6765\u3002\n * \u754C\u9762\u540D\u79F0\u4E0E\u684C\u9762\u7AEF metric-series.ts \u7684 sfmcPartLabel \u4E00\u81F4\u3002\n */\n\nimport { qqRuntimeStatusPath, stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { collectProcessResourcesMany } from \"./process-resources.js\";\n\n/** \u4E00\u6761\u5DF2\u786E\u8BA4\u4ECD\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B\u3002\u5185\u5B58\u91C7\u4E0D\u5230\u7684\u4E0D\u8FD4\u56DE\uFF0C\u907F\u514D\u628A\u8FC7\u671F PID \u663E\u793A\u6210\u300C\u8D44\u6E90\u672A\u77E5\u300D\u3002 */\nexport interface TrackedProcess {\n /** daemon\u3001qq\u3001tunnel\u3001llbot\uFF0C\u6216 sidecar:<\u6A21\u5757 id> */\n key: string;\n /** \u4FA7\u680F\u548C\u66F2\u7EBF\u7528\u7684\u540D\u79F0 */\n label: string;\n pid: number;\n running: true;\n memoryMb: number;\n cpuSeconds: number | null;\n}\n\n/** \u5355\u8FDB\u7A0B\u8FD0\u884C\u6001\u6587\u4EF6\u3002\u8DEF\u5F84\u90FD\u5728 `<\u6839>/.sfmc` \u4E0B\uFF0C\u4E0E\u5199\u5165\u65B9\u4F7F\u7528\u7684\u6587\u4EF6\u540D\u4E00\u81F4\u3002 */\nconst ROLE_FILES: Array<{ key: string; label: string; file: (root: string) => string }> = [\n { key: \"daemon\", label: \"\u5B88\u62A4\u8FDB\u7A0B\", file: (root) => path.join(stateDir(root), \"daemon.json\") },\n { key: \"qq\", label: \"QQ \u6865\", file: (root) => qqRuntimeStatusPath(root) },\n { key: \"tunnel\", label: \"\u96A7\u9053\", file: (root) => path.join(stateDir(root), \"tunnel.runtime.json\") },\n { key: \"llbot\", label: \"LLBot\", file: (root) => path.join(stateDir(root), \"llbot.runtime.json\") },\n];\n\n/**\n * \u4ECE\u8FD0\u884C\u6001 JSON \u91CC\u53D6\u51FA pid\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Adaemon.json\u3001qq.runtime.json \u7B49\u53EA\u501F pid \u5B57\u6BB5\uFF0C\u5176\u4F59\u5185\u5BB9\uFF08\u4F8B\u5982\u9274\u6743 token\uFF09\u4E0D\u8BFB\u53D6\u3001\u4E0D\u8FD4\u56DE\u3002\n */\nexport function readRecordedPid(text: string): number {\n try {\n const pid = (JSON.parse(text) as { pid?: unknown }).pid;\n return typeof pid === \"number\" && Number.isSafeInteger(pid) && pid > 0 ? pid : 0;\n } catch {\n return 0;\n }\n}\n\n/** \u9644\u5C5E\u8FDB\u7A0B\u6E05\u5355\u3002id \u53EA\u63A5\u53D7\u6A21\u5757\u6807\u8BC6\uFF0C\u907F\u514D\u628A\u4EFB\u610F\u5B57\u7B26\u4E32\u5199\u8FDB\u6307\u6807\u952E\u3002 */\nfunction readSidecars(text: string): Array<{ id: string; pid: number }> {\n try {\n const processes = (JSON.parse(text) as { processes?: unknown }).processes;\n if (!Array.isArray(processes)) return [];\n const rows: Array<{ id: string; pid: number }> = [];\n for (const item of processes) {\n if (!item || typeof item !== \"object\") continue;\n const row = item as { id?: unknown; pid?: unknown };\n if (typeof row.id !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(row.id)) continue;\n if (typeof row.pid !== \"number\" || !Number.isSafeInteger(row.pid) || row.pid <= 0) continue;\n rows.push({ id: row.id, pid: row.pid });\n }\n return rows;\n } catch {\n return [];\n }\n}\n\nfunction readText(file: string): string {\n try {\n return fs.readFileSync(file, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * \u91C7\u96C6\u4ECD\u5728\u8FD0\u884C\u7684\u5DF2\u767B\u8BB0\u8FDB\u7A0B\u3002\n * @param root \u5B9E\u4F8B\u6839\u76EE\u5F55\u3002\n * @param skip \u5DF2\u7ECF\u5355\u72EC\u7EDF\u8BA1\u8FC7\u7684 PID\uFF08\u6570\u636E\u670D\u52A1\u3001\u672C\u5B9E\u4F8B BDS\uFF09\uFF0C\u8FD9\u91CC\u4E0D\u518D\u91CD\u590D\u8BA1\u5165\u3002\n */\nexport async function collectTrackedProcesses(root: string, skip: ReadonlySet<number>): Promise<TrackedProcess[]> {\n const wanted: Array<{ key: string; label: string; pid: number }> = [];\n const seen = new Set<number>(skip);\n for (const role of ROLE_FILES) {\n const pid = readRecordedPid(readText(role.file(root)));\n if (!pid || seen.has(pid)) continue;\n seen.add(pid);\n wanted.push({ key: role.key, label: role.label, pid });\n }\n for (const row of readSidecars(readText(path.join(stateDir(root), \"sidecars.runtime.json\")))) {\n if (seen.has(row.pid)) continue;\n seen.add(row.pid);\n wanted.push({ key: `sidecar:${row.id}`, label: row.id, pid: row.pid });\n }\n if (!wanted.length) return [];\n const measured = await collectProcessResourcesMany(wanted.map((row) => row.pid));\n const found: TrackedProcess[] = [];\n for (const row of wanted) {\n const usage = measured.get(row.pid);\n if (!usage) continue;\n found.push({\n key: row.key,\n label: row.label,\n pid: row.pid,\n running: true,\n memoryMb: usage.memoryMb,\n cpuSeconds: usage.cpuSeconds,\n });\n }\n return found;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * tracked-processes.ts \u2014 \u91C7\u96C6\u672C\u5B9E\u4F8B\u5DF2\u767B\u8BB0\u7684\u5176\u5B83 SFMC \u8FDB\u7A0B\u5185\u5B58\u3002\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A\u8FD0\u884C\u6307\u6807\u9664 BDS\u3001\u6570\u636E\u670D\u52A1\u5916\uFF0C\u8FD8\u8981\u5217\u51FA\u5B88\u62A4\u8FDB\u7A0B\u3001QQ \u6865\u3001\u96A7\u9053\u3001LLBot \u548C\u6A21\u5757\u9644\u5C5E\u8FDB\u7A0B\u3002\n * \u53EA\u8BFB\u8FD9\u4E9B\u8FDB\u7A0B\u81EA\u5DF1\u5199\u4E0B\u7684\u8FD0\u884C\u6001\u6587\u4EF6\uFF0C\u4E0D\u6309\u8FDB\u7A0B\u540D\u5168\u673A\u626B\u63CF\uFF0C\u907F\u514D\u628A\u540C\u673A\u53E6\u4E00\u4E2A\u7A0B\u5E8F\u7B97\u8FDB\u6765\u3002\n * \u754C\u9762\u540D\u79F0\u4E0E\u684C\u9762\u7AEF metric-series.ts \u7684 sfmcPartLabel \u4E00\u81F4\u3002\n */\n\nimport { qqRuntimeStatusPath, stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { collectProcessResourcesMany } from \"./process-resources.js\";\n\n/** \u4E00\u6761\u5DF2\u786E\u8BA4\u4ECD\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B\u3002\u5185\u5B58\u91C7\u4E0D\u5230\u7684\u4E0D\u8FD4\u56DE\uFF0C\u907F\u514D\u628A\u8FC7\u671F PID \u663E\u793A\u6210\u300C\u8D44\u6E90\u672A\u77E5\u300D\u3002 */\nexport interface TrackedProcess {\n /** daemon\u3001qq\u3001tunnel\u3001llbot\uFF0C\u6216 sidecar:<\u6A21\u5757 id> */\n key: string;\n /** \u4FA7\u680F\u548C\u66F2\u7EBF\u7528\u7684\u540D\u79F0 */\n label: string;\n pid: number;\n running: true;\n memoryMb: number;\n cpuSeconds: number | null;\n /** CPU \u5360\u7528\u7387\uFF08\u6574\u673A\u4E3A 100%\uFF09\u3002\u7531 status \u8DEF\u7531\u7684 ProcessCpuMeter \u8865\u4E0A\uFF0C\u8FD9\u91CC\u91C7\u96C6\u65F6\u4E0D\u586B\u3002 */\n cpuPercent?: number | null;\n}\n\n/** \u5355\u8FDB\u7A0B\u8FD0\u884C\u6001\u6587\u4EF6\u3002\u8DEF\u5F84\u90FD\u5728 `<\u6839>/.sfmc` \u4E0B\uFF0C\u4E0E\u5199\u5165\u65B9\u4F7F\u7528\u7684\u6587\u4EF6\u540D\u4E00\u81F4\u3002 */\nconst ROLE_FILES: Array<{ key: string; label: string; file: (root: string) => string }> = [\n { key: \"daemon\", label: \"\u5B88\u62A4\u8FDB\u7A0B\", file: (root) => path.join(stateDir(root), \"daemon.json\") },\n { key: \"qq\", label: \"QQ \u6865\", file: (root) => qqRuntimeStatusPath(root) },\n { key: \"tunnel\", label: \"\u96A7\u9053\", file: (root) => path.join(stateDir(root), \"tunnel.runtime.json\") },\n { key: \"llbot\", label: \"LLBot\", file: (root) => path.join(stateDir(root), \"llbot.runtime.json\") },\n];\n\n/**\n * \u4ECE\u8FD0\u884C\u6001 JSON \u91CC\u53D6\u51FA pid\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Adaemon.json\u3001qq.runtime.json \u7B49\u53EA\u501F pid \u5B57\u6BB5\uFF0C\u5176\u4F59\u5185\u5BB9\uFF08\u4F8B\u5982\u9274\u6743 token\uFF09\u4E0D\u8BFB\u53D6\u3001\u4E0D\u8FD4\u56DE\u3002\n */\nexport function readRecordedPid(text: string): number {\n try {\n const pid = (JSON.parse(text) as { pid?: unknown }).pid;\n return typeof pid === \"number\" && Number.isSafeInteger(pid) && pid > 0 ? pid : 0;\n } catch {\n return 0;\n }\n}\n\n/** \u9644\u5C5E\u8FDB\u7A0B\u6E05\u5355\u3002id \u53EA\u63A5\u53D7\u6A21\u5757\u6807\u8BC6\uFF0C\u907F\u514D\u628A\u4EFB\u610F\u5B57\u7B26\u4E32\u5199\u8FDB\u6307\u6807\u952E\u3002 */\nfunction readSidecars(text: string): Array<{ id: string; pid: number }> {\n try {\n const processes = (JSON.parse(text) as { processes?: unknown }).processes;\n if (!Array.isArray(processes)) return [];\n const rows: Array<{ id: string; pid: number }> = [];\n for (const item of processes) {\n if (!item || typeof item !== \"object\") continue;\n const row = item as { id?: unknown; pid?: unknown };\n if (typeof row.id !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(row.id)) continue;\n if (typeof row.pid !== \"number\" || !Number.isSafeInteger(row.pid) || row.pid <= 0) continue;\n rows.push({ id: row.id, pid: row.pid });\n }\n return rows;\n } catch {\n return [];\n }\n}\n\nfunction readText(file: string): string {\n try {\n return fs.readFileSync(file, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * \u91C7\u96C6\u4ECD\u5728\u8FD0\u884C\u7684\u5DF2\u767B\u8BB0\u8FDB\u7A0B\u3002\n * @param root \u5B9E\u4F8B\u6839\u76EE\u5F55\u3002\n * @param skip \u5DF2\u7ECF\u5355\u72EC\u7EDF\u8BA1\u8FC7\u7684 PID\uFF08\u6570\u636E\u670D\u52A1\u3001\u672C\u5B9E\u4F8B BDS\uFF09\uFF0C\u8FD9\u91CC\u4E0D\u518D\u91CD\u590D\u8BA1\u5165\u3002\n */\nexport async function collectTrackedProcesses(root: string, skip: ReadonlySet<number>): Promise<TrackedProcess[]> {\n const wanted: Array<{ key: string; label: string; pid: number }> = [];\n const seen = new Set<number>(skip);\n for (const role of ROLE_FILES) {\n const pid = readRecordedPid(readText(role.file(root)));\n if (!pid || seen.has(pid)) continue;\n seen.add(pid);\n wanted.push({ key: role.key, label: role.label, pid });\n }\n for (const row of readSidecars(readText(path.join(stateDir(root), \"sidecars.runtime.json\")))) {\n if (seen.has(row.pid)) continue;\n seen.add(row.pid);\n wanted.push({ key: `sidecar:${row.id}`, label: row.id, pid: row.pid });\n }\n if (!wanted.length) return [];\n const measured = await collectProcessResourcesMany(wanted.map((row) => row.pid));\n const found: TrackedProcess[] = [];\n for (const row of wanted) {\n const usage = measured.get(row.pid);\n if (!usage) continue;\n found.push({\n key: row.key,\n label: row.label,\n pid: row.pid,\n running: true,\n memoryMb: usage.memoryMb,\n cpuSeconds: usage.cpuSeconds,\n });\n }\n return found;\n}\n"],
5
+ "mappings": "AAQA,SAAS,qBAAqB,gBAAgB;AAC9C,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,mCAAmC;AAiB5C,MAAM,aAAoF;AAAA,EACxF,EAAE,KAAK,UAAU,OAAO,4BAAQ,MAAM,CAAC,SAAS,KAAK,KAAK,SAAS,IAAI,GAAG,aAAa,EAAE;AAAA,EACzF,EAAE,KAAK,MAAM,OAAO,aAAQ,MAAM,CAAC,SAAS,oBAAoB,IAAI,EAAE;AAAA,EACtE,EAAE,KAAK,UAAU,OAAO,gBAAM,MAAM,CAAC,SAAS,KAAK,KAAK,SAAS,IAAI,GAAG,qBAAqB,EAAE;AAAA,EAC/F,EAAE,KAAK,SAAS,OAAO,SAAS,MAAM,CAAC,SAAS,KAAK,KAAK,SAAS,IAAI,GAAG,oBAAoB,EAAE;AAClG;AAMO,SAAS,gBAAgB,MAAsB;AACpD,MAAI;AACF,UAAM,MAAO,KAAK,MAAM,IAAI,EAAwB;AACpD,WAAO,OAAO,QAAQ,YAAY,OAAO,cAAc,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,EACjF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,aAAa,MAAkD;AACtE,MAAI;AACF,UAAM,YAAa,KAAK,MAAM,IAAI,EAA8B;AAChE,QAAI,CAAC,MAAM,QAAQ,SAAS,EAAG,QAAO,CAAC;AACvC,UAAM,OAA2C,CAAC;AAClD,eAAW,QAAQ,WAAW;AAC5B,UAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,YAAM,MAAM;AACZ,UAAI,OAAO,IAAI,OAAO,YAAY,CAAC,mCAAmC,KAAK,IAAI,EAAE,EAAG;AACpF,UAAI,OAAO,IAAI,QAAQ,YAAY,CAAC,OAAO,cAAc,IAAI,GAAG,KAAK,IAAI,OAAO,EAAG;AACnF,WAAK,KAAK,EAAE,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC;AAAA,IACxC;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,SAAS,MAAsB;AACtC,MAAI;AACF,WAAO,GAAG,aAAa,MAAM,MAAM;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,wBAAwB,MAAc,MAAsD;AAChH,QAAM,SAA6D,CAAC;AACpE,QAAM,OAAO,IAAI,IAAY,IAAI;AACjC,aAAW,QAAQ,YAAY;AAC7B,UAAM,MAAM,gBAAgB,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC;AACrD,QAAI,CAAC,OAAO,KAAK,IAAI,GAAG,EAAG;AAC3B,SAAK,IAAI,GAAG;AACZ,WAAO,KAAK,EAAE,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,EACvD;AACA,aAAW,OAAO,aAAa,SAAS,KAAK,KAAK,SAAS,IAAI,GAAG,uBAAuB,CAAC,CAAC,GAAG;AAC5F,QAAI,KAAK,IAAI,IAAI,GAAG,EAAG;AACvB,SAAK,IAAI,IAAI,GAAG;AAChB,WAAO,KAAK,EAAE,KAAK,WAAW,IAAI,EAAE,IAAI,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC;AAAA,EACvE;AACA,MAAI,CAAC,OAAO,OAAQ,QAAO,CAAC;AAC5B,QAAM,WAAW,MAAM,4BAA4B,OAAO,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC;AAC/E,QAAM,QAA0B,CAAC;AACjC,aAAW,OAAO,QAAQ;AACxB,UAAM,QAAQ,SAAS,IAAI,IAAI,GAAG;AAClC,QAAI,CAAC,MAAO;AACZ,UAAM,KAAK;AAAA,MACT,KAAK,IAAI;AAAA,MACT,OAAO,IAAI;AAAA,MACX,KAAK,IAAI;AAAA,MACT,SAAS;AAAA,MACT,UAAU,MAAM;AAAA,MAChB,YAAY,MAAM;AAAA,IACpB,CAAC;AAAA,EACH;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@
8
8
  * POST /api/sfmc/db/tx/step body {txId, step} → {result}
9
9
  * POST /api/sfmc/db/tx/commit body {txId} → TxResponse
10
10
  * POST /api/sfmc/db/tx/rollback body {txId} → {ok}
11
+ * POST /api/sfmc/db/tx/reap body {} → {ok, aborted}(回收本模块遗留会话)
11
12
  * POST /api/sfmc/db/query body {table, opts?} → {rows}
12
13
  * ...
13
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAa,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAoKrB"}
1
+ {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAa,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAyKrB"}
@@ -63,6 +63,11 @@ function createDbRoutes(depsIn) {
63
63
  path: "/api/sfmc/db/tx/rollback",
64
64
  requireTxId: true,
65
65
  run: ({ txId }) => deps.txRunner.rollbackSession(txId, moduleId)
66
+ },
67
+ {
68
+ // 新脚本实例启动时回收本模块旧实例遗留的会话;只作用于鉴权身份自己的会话
69
+ path: "/api/sfmc/db/tx/reap",
70
+ run: () => ({ ok: true, aborted: deps.txRunner.abortModuleSessions(moduleId) })
66
71
  }
67
72
  ];
68
73
  for (const op of sessionOps) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/db-routes.ts"],
4
- "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\nimport { log } from \"../lib/log.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { DefineTableRequest, SchemaRegistry } from \"../schema-registry.js\";\nimport type { TxRequest, TxRunner, TxStep } from \"../tx-runner.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(moduleId, String(body.action), String(body.key));\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(moduleId, String(body.action), String(body.key), body.value);\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/tx/reap body {} \u2192 {ok, aborted}\uFF08\u56DE\u6536\u672C\u6A21\u5757\u9057\u7559\u4F1A\u8BDD\uFF09\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\nimport { log } from \"../lib/log.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { DefineTableRequest, SchemaRegistry } from \"../schema-registry.js\";\nimport type { TxRequest, TxRunner, TxStep } from \"../tx-runner.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n {\n // \u65B0\u811A\u672C\u5B9E\u4F8B\u542F\u52A8\u65F6\u56DE\u6536\u672C\u6A21\u5757\u65E7\u5B9E\u4F8B\u9057\u7559\u7684\u4F1A\u8BDD\uFF1B\u53EA\u4F5C\u7528\u4E8E\u9274\u6743\u8EAB\u4EFD\u81EA\u5DF1\u7684\u4F1A\u8BDD\n path: \"/api/sfmc/db/tx/reap\",\n run: () => ({ ok: true, aborted: deps.txRunner!.abortModuleSessions(moduleId) }),\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(moduleId, String(body.action), String(body.key));\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(moduleId, String(body.action), String(body.key), body.value);\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,6 @@
1
1
  /** GET /api/sfmc/status 与 BDS 实时快照入口。 */
2
2
  import { type SystemStatusSnapshot } from "../domain/system-status.js";
3
+ import { HostCpuMeter, ProcessCpuMeter } from "../domain/cpu-usage.js";
3
4
  import type { QueryFn } from "../lib/sqlite.js";
4
5
  import { type RouteFactory } from "./_shared.js";
5
6
  declare const FRESH_MS = 60000;
@@ -7,7 +8,11 @@ interface Deps {
7
8
  collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;
8
9
  projectRoot?: string;
9
10
  query?: QueryFn;
11
+ /** 主机 CPU 占用率换算器;缺省按本机 os.cpus() 新建一个。 */
12
+ hostCpu?: HostCpuMeter;
13
+ /** 各进程 CPU 占用率换算器;缺省按本机逻辑核数新建一个。 */
14
+ processCpu?: ProcessCpuMeter;
10
15
  }
11
- declare function createStatusRoutes({ collectSystem, projectRoot, query }: Deps): ReturnType<RouteFactory>;
16
+ declare function createStatusRoutes({ collectSystem, projectRoot, query, hostCpu, processCpu }: Deps): ReturnType<RouteFactory>;
12
17
  export { createStatusRoutes, FRESH_MS };
13
18
  //# sourceMappingURL=status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAEzC,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAK5F,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,CA2IjG;AAED,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,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;AAK5F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACvE,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;IAChB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,oCAAoC;IACpC,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED,iBAAS,kBAAkB,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,CAqKtH;AAED,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC"}
@@ -3,18 +3,21 @@ import { PROJECT_ROOT } from "../project-root.js";
3
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 { collectTrackedProcesses } from "../domain/tracked-processes.js";
6
+ import { HostCpuMeter, ProcessCpuMeter } from "../domain/cpu-usage.js";
6
7
  import fs from "node:fs";
7
8
  import pathModule from "node:path";
8
9
  import { stateDir } from "@sfmc-bds/sdk/node/config";
9
10
  import { body, json } from "./_shared.js";
10
11
  const FRESH_MS = 6e4;
11
- function createStatusRoutes({ collectSystem, projectRoot, query }) {
12
+ function createStatusRoutes({ collectSystem, projectRoot, query, hostCpu, processCpu }) {
12
13
  const root = projectRoot ?? PROJECT_ROOT;
13
14
  const collect = collectSystem ?? collectSystemStatus;
14
15
  let live = null;
15
16
  let metrics = null;
16
17
  const history = query ? new MetricsHistory(query) : void 0;
17
18
  const resources = query ? new ResourceHistory(query) : void 0;
19
+ const hostCpuMeter = hostCpu ?? new HostCpuMeter();
20
+ const processCpuMeter = processCpu ?? new ProcessCpuMeter();
18
21
  let lastCapture = 0;
19
22
  const captureResources = async (system) => {
20
23
  if (!system) return null;
@@ -32,17 +35,38 @@ function createStatusRoutes({ collectSystem, projectRoot, query }) {
32
35
  if (processes.db.pid > 0) skip.add(processes.db.pid);
33
36
  if (processes.bds && processes.bds.pid > 0) skip.add(processes.bds.pid);
34
37
  const parts = await collectTrackedProcesses(root, skip);
38
+ const now = Date.now();
39
+ const hostCpuPercent = hostCpuMeter.measure(now);
40
+ const cpu = processCpuMeter.measure(
41
+ [
42
+ { key: "db", pid: processes.db.pid, running: processes.db.running, cpuSeconds: processes.db.cpuSeconds },
43
+ ...processes.bds ? [{ key: "bds", pid: processes.bds.pid, running: processes.bds.running, cpuSeconds: processes.bds.cpuSeconds }] : [],
44
+ ...parts.map((part) => ({ key: part.key, pid: part.pid, running: part.running, cpuSeconds: part.cpuSeconds }))
45
+ ],
46
+ now
47
+ );
48
+ processes.db.cpuPercent = cpu.get("db") ?? null;
49
+ if (processes.bds) processes.bds.cpuPercent = cpu.get("bds") ?? null;
50
+ for (const part of parts) part.cpuPercent = cpu.get(part.key) ?? null;
35
51
  const partsMb = {};
36
- for (const part of parts) partsMb[part.key] = part.memoryMb;
52
+ const partsCpuPercent = {};
53
+ for (const part of parts) {
54
+ partsMb[part.key] = part.memoryMb;
55
+ if (typeof part.cpuPercent === "number") partsCpuPercent[part.key] = part.cpuPercent;
56
+ }
37
57
  resources?.append({
38
- recordedAt: Date.now(),
58
+ recordedAt: now,
39
59
  hostUsedMb: system.host.memory.usedMb,
40
60
  dbMb: processes.db.memoryMb,
41
61
  bdsMb: processes.bds?.memoryMb ?? null,
42
- ...Object.keys(partsMb).length ? { partsMb } : {}
62
+ ...Object.keys(partsMb).length ? { partsMb } : {},
63
+ hostCpuPercent,
64
+ dbCpuPercent: processes.db.cpuPercent,
65
+ bdsCpuPercent: processes.bds?.cpuPercent ?? null,
66
+ ...Object.keys(partsCpuPercent).length ? { partsCpuPercent } : {}
43
67
  });
44
68
  lastCapture = Date.now();
45
- return { ...processes, ...parts.length ? { parts } : {} };
69
+ return { processes: { ...processes, ...parts.length ? { parts } : {} }, hostCpuPercent };
46
70
  };
47
71
  let systemCache;
48
72
  let collecting;
@@ -119,7 +143,8 @@ function createStatusRoutes({ collectSystem, projectRoot, query }) {
119
143
  }
120
144
  const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && bds?.pid === ownedPid;
121
145
  const metricFresh = owned && metrics !== null && bds?.state === "running" && now - metrics.recordedAt <= 6e4 && (bdsAgeMs === null || now - metrics.recordedAt <= bdsAgeMs + 2e3);
122
- const processes = await captureResources(system);
146
+ const captured = await captureResources(system);
147
+ const host = system ? { ...system.host, cpu: { ...system.host.cpu, usedPercent: captured?.hostCpuPercent ?? null } } : null;
123
148
  let span = METRICS_RAW_WINDOW_MS;
124
149
  const rawSpan = params.get("spanMs");
125
150
  if (rawSpan) {
@@ -135,8 +160,8 @@ function createStatusRoutes({ collectSystem, projectRoot, query }) {
135
160
  current: metricFresh ? metrics : null,
136
161
  updatedAt: metrics?.recordedAt ?? null,
137
162
  history: history?.read(now, span) ?? [],
138
- host: system?.host ?? null,
139
- processes,
163
+ host,
164
+ processes: captured?.processes ?? null,
140
165
  resourcesUpdatedAt: system ? Date.now() : null,
141
166
  resourceHistory: resources?.read(now, span) ?? [],
142
167
  note: metricFresh ? void 0 : bds?.state === "stopped" ? "\u670D\u52A1\u5668\u672A\u8FD0\u884C" : "\u7B49\u5F85\u5E73\u53F0\u6307\u6807\u540C\u6B65"
@@ -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 { METRICS_RAW_WINDOW_MS, MetricsHistory, ResourceHistory, clampMetricsSpan, parseMetrics, type MetricsSample } from \"../domain/metrics.js\";\nimport { collectProcessResources } from \"../domain/process-resources.js\";\nimport { collectTrackedProcesses } from \"../domain/tracked-processes.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 const skip = new Set<number>();\n if (processes.db.pid > 0) skip.add(processes.db.pid);\n if (processes.bds && processes.bds.pid > 0) skip.add(processes.bds.pid);\n const parts = await collectTrackedProcesses(root, skip);\n const partsMb: Record<string, number> = {};\n for (const part of parts) partsMb[part.key] = part.memoryMb;\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 ...(Object.keys(partsMb).length ? { partsMb } : {}),\n });\n lastCapture = Date.now();\n return { ...processes, ...(parts.length ? { parts } : {}) };\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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",
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 { collectTrackedProcesses } from \"../domain/tracked-processes.js\";\nimport { HostCpuMeter, ProcessCpuMeter } from \"../domain/cpu-usage.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 /** \u4E3B\u673A CPU \u5360\u7528\u7387\u6362\u7B97\u5668\uFF1B\u7F3A\u7701\u6309\u672C\u673A os.cpus() \u65B0\u5EFA\u4E00\u4E2A\u3002 */\n hostCpu?: HostCpuMeter;\n /** \u5404\u8FDB\u7A0B CPU \u5360\u7528\u7387\u6362\u7B97\u5668\uFF1B\u7F3A\u7701\u6309\u672C\u673A\u903B\u8F91\u6838\u6570\u65B0\u5EFA\u4E00\u4E2A\u3002 */\n processCpu?: ProcessCpuMeter;\n}\n\nfunction createStatusRoutes({ collectSystem, projectRoot, query, hostCpu, processCpu }: 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 const hostCpuMeter = hostCpu ?? new HostCpuMeter();\n const processCpuMeter = processCpu ?? new ProcessCpuMeter();\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 const skip = new Set<number>();\n if (processes.db.pid > 0) skip.add(processes.db.pid);\n if (processes.bds && processes.bds.pid > 0) skip.add(processes.bds.pid);\n const parts = await collectTrackedProcesses(root, skip);\n const now = Date.now();\n // \u7D2F\u8BA1 CPU \u79D2\u6570\u6362\u7B97\u6210\u5360\u7528\u7387\uFF1A\u540C\u4E00\u8F6E\u7684\u8FDB\u7A0B\u4E00\u8D77\u4EA4\u7ED9\u6362\u7B97\u5668\uFF0C\u4E3B\u673A\u5360\u7528\u7387\u5728\u540C\u4E00\u65F6\u523B\u8BFB\u53D6\u3002\n const hostCpuPercent = hostCpuMeter.measure(now);\n const cpu = processCpuMeter.measure(\n [\n { key: \"db\", pid: processes.db.pid, running: processes.db.running, cpuSeconds: processes.db.cpuSeconds },\n ...(processes.bds ? [{ key: \"bds\", pid: processes.bds.pid, running: processes.bds.running, cpuSeconds: processes.bds.cpuSeconds }] : []),\n ...parts.map((part) => ({ key: part.key, pid: part.pid, running: part.running, cpuSeconds: part.cpuSeconds })),\n ],\n now,\n );\n processes.db.cpuPercent = cpu.get(\"db\") ?? null;\n if (processes.bds) processes.bds.cpuPercent = cpu.get(\"bds\") ?? null;\n for (const part of parts) part.cpuPercent = cpu.get(part.key) ?? null;\n const partsMb: Record<string, number> = {};\n const partsCpuPercent: Record<string, number> = {};\n for (const part of parts) {\n partsMb[part.key] = part.memoryMb;\n if (typeof part.cpuPercent === \"number\") partsCpuPercent[part.key] = part.cpuPercent;\n }\n resources?.append({\n recordedAt: now,\n hostUsedMb: system.host.memory.usedMb,\n dbMb: processes.db.memoryMb,\n bdsMb: processes.bds?.memoryMb ?? null,\n ...(Object.keys(partsMb).length ? { partsMb } : {}),\n hostCpuPercent,\n dbCpuPercent: processes.db.cpuPercent,\n bdsCpuPercent: processes.bds?.cpuPercent ?? null,\n ...(Object.keys(partsCpuPercent).length ? { partsCpuPercent } : {}),\n });\n lastCapture = Date.now();\n return { processes: { ...processes, ...(parts.length ? { parts } : {}) }, hostCpuPercent };\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 captured = await captureResources(system);\n // \u4E3B\u673A CPU \u5360\u7528\u7387\u6302\u5728 host.cpu \u4E0A\uFF0C\u4E0E\u6838\u6570\u3001\u578B\u53F7\u653E\u5728\u4E00\u8D77\uFF1B\u8FD8\u6CA1\u6709\u57FA\u7EBF\u65F6\u4E3A null\u3002\n const host = system ? { ...system.host, cpu: { ...system.host.cpu, usedPercent: captured?.hostCpuPercent ?? null } } : null;\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, processes: captured?.processes ?? null, 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": "AAEA,SAAS,2BAAsD;AAC/D,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB,gBAAgB,iBAAiB,kBAAkB,oBAAwC;AAC3H,SAAS,+BAA+B;AACxC,SAAS,+BAA+B;AACxC,SAAS,cAAc,uBAAuB;AAE9C,OAAO,QAAQ;AACf,OAAO,gBAAgB;AACvB,SAAS,gBAAgB;AACzB,SAAS,MAAM,YAA+B;AAE9C,MAAM,WAAW;AAkBjB,SAAS,mBAAmB,EAAE,eAAe,aAAa,OAAO,SAAS,WAAW,GAAmC;AACtH,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,iBAAiB;AACjC,MAAI,OAA4B;AAChC,MAAI,UAAgC;AACpC,QAAM,UAAU,QAAQ,IAAI,eAAe,KAAK,IAAI;AACpD,QAAM,YAAY,QAAQ,IAAI,gBAAgB,KAAK,IAAI;AACvD,QAAM,eAAe,WAAW,IAAI,aAAa;AACjD,QAAM,kBAAkB,cAAc,IAAI,gBAAgB;AAC1D,MAAI,cAAc;AAClB,QAAM,mBAAmB,OAAO,WAAwC;AACtE,QAAI,CAAC,OAAQ,QAAO;AACpB,QAAI,WAAW;AACf,QAAI;AAAE,iBAAW,OAAO,GAAG,aAAa,WAAW,KAAK,SAAS,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,KAAK,CAAC;AAAA,IAAG,QAAQ;AAAA,IAAsB;AACnI,UAAM,QAAQ,OAAO,cAAc,QAAQ,KAAK,WAAW,KAAK,OAAO,IAAI,QAAQ;AACnF,UAAM,YAAY;AAAA,MAChB,IAAI,MAAM,wBAAwB,OAAO,GAAG,KAAK,OAAO,GAAG,OAAO;AAAA,MAClE,KAAK,QAAQ,MAAM,wBAAwB,OAAO,IAAI,KAAK,OAAO,IAAI,OAAO,IAAI;AAAA,IACnF;AACA,UAAM,OAAO,oBAAI,IAAY;AAC7B,QAAI,UAAU,GAAG,MAAM,EAAG,MAAK,IAAI,UAAU,GAAG,GAAG;AACnD,QAAI,UAAU,OAAO,UAAU,IAAI,MAAM,EAAG,MAAK,IAAI,UAAU,IAAI,GAAG;AACtE,UAAM,QAAQ,MAAM,wBAAwB,MAAM,IAAI;AACtD,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,iBAAiB,aAAa,QAAQ,GAAG;AAC/C,UAAM,MAAM,gBAAgB;AAAA,MAC1B;AAAA,QACE,EAAE,KAAK,MAAM,KAAK,UAAU,GAAG,KAAK,SAAS,UAAU,GAAG,SAAS,YAAY,UAAU,GAAG,WAAW;AAAA,QACvG,GAAI,UAAU,MAAM,CAAC,EAAE,KAAK,OAAO,KAAK,UAAU,IAAI,KAAK,SAAS,UAAU,IAAI,SAAS,YAAY,UAAU,IAAI,WAAW,CAAC,IAAI,CAAC;AAAA,QACtI,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS,YAAY,KAAK,WAAW,EAAE;AAAA,MAC/G;AAAA,MACA;AAAA,IACF;AACA,cAAU,GAAG,aAAa,IAAI,IAAI,IAAI,KAAK;AAC3C,QAAI,UAAU,IAAK,WAAU,IAAI,aAAa,IAAI,IAAI,KAAK,KAAK;AAChE,eAAW,QAAQ,MAAO,MAAK,aAAa,IAAI,IAAI,KAAK,GAAG,KAAK;AACjE,UAAM,UAAkC,CAAC;AACzC,UAAM,kBAA0C,CAAC;AACjD,eAAW,QAAQ,OAAO;AACxB,cAAQ,KAAK,GAAG,IAAI,KAAK;AACzB,UAAI,OAAO,KAAK,eAAe,SAAU,iBAAgB,KAAK,GAAG,IAAI,KAAK;AAAA,IAC5E;AACA,eAAW,OAAO;AAAA,MAChB,YAAY;AAAA,MACZ,YAAY,OAAO,KAAK,OAAO;AAAA,MAC/B,MAAM,UAAU,GAAG;AAAA,MACnB,OAAO,UAAU,KAAK,YAAY;AAAA,MAClC,GAAI,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,MACjD;AAAA,MACA,cAAc,UAAU,GAAG;AAAA,MAC3B,eAAe,UAAU,KAAK,cAAc;AAAA,MAC5C,GAAI,OAAO,KAAK,eAAe,EAAE,SAAS,EAAE,gBAAgB,IAAI,CAAC;AAAA,IACnE,CAAC;AACD,kBAAc,KAAK,IAAI;AACvB,WAAO,EAAE,WAAW,EAAE,GAAG,WAAW,GAAI,MAAM,SAAS,EAAE,MAAM,IAAI,CAAC,EAAG,GAAG,eAAe;AAAA,EAC3F;AACA,MAAI;AACJ,MAAI;AACJ,QAAM,YAAY,YAAY;AAC5B,QAAI,eAAe,KAAK,IAAI,IAAI,YAAY,KAAK,IAAM,QAAO,YAAY;AAC1E,mBAAe,QAAQ,IAAI,EAAE,KAAK,UAAQ;AAAE,oBAAc,EAAE,MAAM,IAAI,KAAK,IAAI,EAAE;AAAG,aAAO;AAAA,IAAM,CAAC,EAAE,QAAQ,MAAM;AAAE,mBAAa;AAAA,IAAW,CAAC;AAC7I,WAAO;AAAA,EACT;AACA,MAAI,WAAW;AACb,UAAM,QAAQ,YAAY,MAAM;AAC9B,UAAI,KAAK,IAAI,IAAI,cAAc,KAAQ;AACvC,WAAK,UAAU,EAAE,KAAK,YAAU,iBAAiB,MAAM,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IACjF,GAAG,IAAM;AACT,UAAM,MAAM;AAAA,EACd;AAEA,SAAO,eAAe,OAAO,EAAE,MAAM,QAAQ,QAAQ,KAAK,IAAI,GAAqB;AACjF,QAAI,SAAS,2BAA2B,SAAS,0BAA0B;AACzE,UAAI,WAAW,QAAQ;AACrB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,eAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,YAAM,MAAM,KAAK;AACjB,YAAM,MAAM,KAAK;AACjB,YAAM,aAAa,KAAK;AACxB,UACE,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,SAAS,OACpC,CAAC,IAAI,MAAM,CAAC,SAAS,OAAO,SAAS,YAAY,KAAK,KAAK,EAAE,SAAS,KAAK,KAAK,UAAU,EAAE,KAC5F,OAAO,QAAQ,YAAY,CAAC,OAAO,cAAc,GAAG,KAAK,MAAM,KAC/D,OAAO,eAAe,YAAY,WAAW,SAAS,IACtD;AACA,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,sBAAsB,GAAG,GAAG;AAC/D,eAAO;AAAA,MACT;AACA,UAAI,SAAS,0BAA0B;AACrC,cAAM,SAAS,aAAa,MAAM,KAAK,IAAI,CAAC;AAC5C,YAAI,CAAC,QAAQ;AAAE,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,GAAG,GAAG;AAAG,iBAAO;AAAA,QAAM;AAC1F,iBAAS,OAAO,MAAM;AACtB,kBAAU;AAAA,MACZ;AACA,aAAO;AAAA,QACL,QAAQ,IAAI,IAAI,CAAC,UAAkB,EAAE,MAAM,KAAK,KAAK,EAAE,EAAE;AAAA,QACzD,OAAO,EAAE,KAAK,WAAW;AAAA,QACzB,WAAW,KAAK,IAAI;AAAA,MACtB;AACA,WAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,sBAAsB,SAAS,oBAAqB,QAAO;AACxE,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,QAAI,SAAsC;AAC1C,QAAI;AACF,eAAS,MAAM,UAAU;AAAA,IAC3B,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,QAAQ;AACpB,UAAM,WAAW,OAAO,KAAK,cAAc,WAAW,IAAI,YAAY,MAAO;AAC7E,UAAM,WAAW;AACjB,UAAM,QAAQ,aAAa,QACzB,KAAK,UAAU,aACf,MAAM,SAAS,aAAa,aAC3B,aAAa,QAAQ,MAAM,SAAS,aAAa,WAAW;AAC/D,UAAM,UAAU,QAAQ,WAAW;AAEnC,QAAI,SAAS,qBAAqB;AAEhC,UAAI,WAAW;AACf,UAAI;AAAE,mBAAW,OAAO,GAAG,aAAa,WAAW,KAAK,SAAS,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,KAAK,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAsB;AACnI,YAAM,QAAQ,OAAO,cAAc,QAAQ,KAAK,WAAW,KAAK,KAAK,QAAQ;AAC7E,YAAM,cAAc,SAAS,YAAY,QAAQ,KAAK,UAAU,aAAa,MAAM,QAAQ,cAAc,QACtG,aAAa,QAAQ,MAAM,QAAQ,cAAc,WAAW;AAC/D,YAAM,WAAW,MAAM,iBAAiB,MAAM;AAE9C,YAAM,OAAO,SAAS,EAAE,GAAG,OAAO,MAAM,KAAK,EAAE,GAAG,OAAO,KAAK,KAAK,aAAa,UAAU,kBAAkB,KAAK,EAAE,IAAI;AACvH,UAAI,OAAO;AACX,YAAM,UAAU,OAAO,IAAI,QAAQ;AACnC,UAAI,SAAS;AACX,YAAI;AAAE,iBAAO,iBAAiB,OAAO,OAAO,CAAC;AAAA,QAAG,QAAQ;AACtD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,eAAe,GAAG,GAAG;AACxD,iBAAO;AAAA,QACT;AAAA,MACF;AACA,WAAK,KAAK;AAAA,QAAE,OAAO;AAAA,QAAa,SAAS,cAAc,UAAU;AAAA,QAAM,WAAW,SAAS,cAAc;AAAA,QACvG,SAAS,SAAS,KAAK,KAAK,IAAI,KAAK,CAAC;AAAA,QAAG;AAAA,QAAM,WAAW,UAAU,aAAa;AAAA,QAAM,oBAAoB,SAAS,KAAK,IAAI,IAAI;AAAA,QACjI,iBAAiB,WAAW,KAAK,KAAK,IAAI,KAAK,CAAC;AAAA,QAChD,MAAM,cAAc,SAAY,KAAK,UAAU,YAAY,yCAAW;AAAA,MAAW,CAAC;AACpF,aAAO;AAAA,IACT;AAEA,SAAK,KAAK;AAAA,MACR,QAAQ,SAAS,UAAU,CAAC;AAAA,MAC5B,OAAO,SAAS,SAAS;AAAA,MACzB,WAAW,SAAS,aAAa;AAAA,MACjC,MAAM,QAAQ,QAAQ;AAAA,MACtB,WAAW,SAAS,EAAE,IAAI,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI;AAAA,MACzD,QAAQ;AAAA,MACR,MAAM,QAAQ,SAAY,KAAK,UAAU,YAAY,yCAAW;AAAA,IAClE,CAAC;AACD,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -200,6 +200,16 @@ export declare class TxRunner {
200
200
  rollbackSession(txId: string, moduleId: string): {
201
201
  ok: true;
202
202
  } | TxError;
203
+ /**
204
+ * 中止某模块名下的全部交互式事务会话。
205
+ * 使用场景:BDS 执行 /reload 或重启后,新脚本实例在任何模块初始化之前经
206
+ * POST /api/sfmc/db/tx/reap 调用,回收旧实例来不及提交/回滚而遗留的会话,
207
+ * 避免它们占住全局事务槽直到空闲超时,导致其他模块的读写返回「事务繁忙」。
208
+ *
209
+ * @param moduleId 会话所属模块(由路由按鉴权身份传入,模块只能回收自己的会话)。
210
+ * @returns 被中止的会话数量。
211
+ */
212
+ abortModuleSessions(moduleId: string): number;
203
213
  private abortSession;
204
214
  private acquireTransactionSlot;
205
215
  private releaseTransactionSlot;
@@ -1 +1 @@
1
- {"version":3,"file":"tx-runner.d.ts","sourceRoot":"","sources":["../src/tx-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tx-runner.d.ts","sourceRoot":"","sources":["../src/tx-runner.ts"],"names":[],"mappings":"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"}
package/dist/tx-runner.js CHANGED
@@ -202,6 +202,23 @@ class TxRunner {
202
202
  this.abortSession(txId);
203
203
  return { ok: true };
204
204
  }
205
+ /**
206
+ * 中止某模块名下的全部交互式事务会话。
207
+ * 使用场景:BDS 执行 /reload 或重启后,新脚本实例在任何模块初始化之前经
208
+ * POST /api/sfmc/db/tx/reap 调用,回收旧实例来不及提交/回滚而遗留的会话,
209
+ * 避免它们占住全局事务槽直到空闲超时,导致其他模块的读写返回「事务繁忙」。
210
+ *
211
+ * @param moduleId 会话所属模块(由路由按鉴权身份传入,模块只能回收自己的会话)。
212
+ * @returns 被中止的会话数量。
213
+ */
214
+ abortModuleSessions(moduleId) {
215
+ const stale = [...this.sessions.entries()].filter(([, session]) => session.moduleId === moduleId).map(([txId]) => txId);
216
+ for (const txId of stale) {
217
+ log.warn(`[tx-session ${txId.slice(0, 8)}] reaped module=${moduleId}\uFF08\u811A\u672C\u5B9E\u4F8B\u5DF2\u91CD\u8F7D\uFF09`);
218
+ this.abortSession(txId);
219
+ }
220
+ return stale.length;
221
+ }
205
222
  abortSession(txId) {
206
223
  const session = this.sessions.get(txId);
207
224
  if (!session) return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/tx-runner.ts"],
4
- "sourcesContent": ["/**\n * tx-runner.ts \u2014 \u4E8B\u52A1 RPC \u5904\u7406\u5668\n *\n * \u534F\u8BAE(\u4E24\u79CD):\n * A. \u6279\u91CF: POST /api/sfmc/db/tx body={ steps } \u2192 { ok, results? } | { ok:false, step, error, code }\n * B. \u4EA4\u4E92: begin \u2192 step* \u2192 commit|rollback(SDK db.tx \u7528;\u56DE\u8C03\u5185\u53EF\u8BFB\u56DE query/get/call)\n *\n * \u6D41\u7A0B(\u6279\u91CF):\n * 1. \u6821\u9A8C moduleId in enabled\n * 2. BEGIN IMMEDIATE\n * 3. \u987A\u5E8F\u8DD1 steps \u2014 \u4EFB\u4E00\u629B\u9519 \u2192 ROLLBACK + reply { ok:false, step:<i>, ... }\n * 4. \u5168\u8FC7 \u2192 COMMIT + reply { ok:true, results }\n *\n * step.op:\n * - query { table, opts? }\n * - get { table, id }\n * - insert { table, row }\n * - update { table, id, patch }\n * - delete { table, id, hard }\n * - audit { table, rowId, action, data? } \u2192 \u5E73\u53F0 sfmc__audit \u8868\n * - service { name, input } \u2192 \u6D3E\u53D1;\u5931\u8D25\u629B\u9519\u56DE\u9000\u6574\u4E8B\u52A1\n *\n * \u4E8B\u52A1\u5185 service call:\u4E0D\u6301\u6709 db handle \u7ED9 handler(\u907F\u514D\u5E76\u53D1\u5199) \u2014\n * \u5B9E\u73B0:handler \u8FD4\u56DE result \u5199\u56DE step \u8F93\u51FA;\u82E5 handler \u8FD8\u8981\u5199 db,\u5E94\u8D70\u5176\u5B83\u63A5\u53E3\u5728\u4E8B\u52A1\u5916\u505A\u3002\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\nimport { normalizeOrderBy } from \"./lib/order-by.js\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport { Perm, PermissionDeniedError, assertModulePermission } from \"./permission-gate.js\";\nimport type { SchemaRegistry } from \"./schema-registry.js\";\nimport { DispatchError, type ServiceRegistry } from \"./service-registry.js\";\nimport type { WhereExpr } from \"./where.js\";\nimport { compile } from \"./where.js\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction identOk(t: string): void {\n if (!IDENT.test(t)) throw new Error(`[tx] invalid table name \"${t}\"`);\n}\n\nexport interface TxStepQuery {\n op: \"query\";\n table: string;\n opts?: {\n where?: WhereExpr;\n /** SDK \u7528 field;\u5386\u53F2 col \u4ECD\u63A5\u53D7(normalizeOrderBy) */\n orderBy?:\n | { field?: string; col?: string; dir?: \"asc\" | \"desc\" }\n | Array<{ field?: string; col?: string; dir?: \"asc\" | \"desc\" }>;\n limit?: number;\n offset?: number;\n };\n}\n\nexport interface TxStepGet {\n op: \"get\";\n table: string;\n id: string;\n}\n\nexport interface TxStepInsert {\n op: \"insert\";\n table: string;\n row: Record<string, unknown>;\n}\n\nexport interface TxStepUpdate {\n op: \"update\";\n table: string;\n id: string;\n patch: Record<string, unknown>;\n}\n\nexport interface TxStepDelete {\n op: \"delete\";\n table: string;\n id: string;\n hard: boolean;\n}\n\nexport interface TxStepAudit {\n op: \"audit\";\n table: string;\n rowId: string;\n action: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TxStepService {\n op: \"service\";\n name: string;\n input: Record<string, unknown>;\n}\n\nexport type TxStep = TxStepQuery | TxStepGet | TxStepInsert | TxStepUpdate | TxStepDelete | TxStepAudit | TxStepService;\n\nexport type TxStepResult =\n | { op: \"query\"; rows: Record<string, unknown>[] }\n | { op: \"get\"; row: Record<string, unknown> | null }\n | { op: \"insert\"; row: Record<string, unknown> }\n | { op: \"update\"; row: Record<string, unknown> }\n | { op: \"delete\"; changes: number }\n | { op: \"audit\"; changes: number }\n | { op: \"service\"; result: unknown };\n\nexport interface TxResponse {\n ok: true;\n results: TxStepResult[];\n}\n\nexport type TxError = {\n ok: false;\n step: number;\n error: string;\n code:\n | \"permission_denied\"\n | \"forbidden\"\n | \"no_such_service\"\n | \"not_in_requires\"\n | \"domain_error\"\n | \"transaction_busy\"\n | \"internal\"\n | \"no_such_table\";\n};\n\nexport interface TxRequest {\n moduleId: string;\n steps: TxStep[];\n}\n\n/** \u4E8B\u52A1\u63D0\u4EA4\u6210\u529F\u540E\u7684\u63D2\u5165\u884C\u3002\u804A\u5929\u6D88\u606F\u636E\u6B64\u51B3\u5B9A\u662F\u5426\u8F6C\u53D1\u5230 QQ\u3002 */\nexport interface CommittedInsert {\n table: string;\n row: Record<string, unknown>;\n}\n\ninterface TxSession {\n moduleId: string;\n /** \u5DF2\u6210\u529F\u6267\u884C\u7684\u6B65\u9AA4\uFF0C\u63D0\u4EA4\u540E\u7528\u4E8E afterCommit\uFF0C\u4E0D\u8FDB\u5165\u54CD\u5E94\u4F53\u3002 */\n steps: TxStep[];\n results: TxStepResult[];\n idleTimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TransactionWaiter {\n resolve: (acquired: boolean) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport interface TxRunnerOptions {\n /** \u7B49\u5F85\u5168\u5C40 SQLite \u4E8B\u52A1\u69FD\u7684\u6700\u957F\u65F6\u95F4\u3002 */\n slotWaitTimeoutMs?: number;\n /** \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4E24\u6B21\u8BF7\u6C42\u4E4B\u95F4\u5141\u8BB8\u7A7A\u95F2\u7684\u6700\u957F\u65F6\u95F4\u3002 */\n sessionIdleTimeoutMs?: number;\n}\n\nexport interface TxRunnerDeps {\n db: DatabaseSync;\n query: import(\"./lib/sqlite.js\").QueryFn;\n schema: SchemaRegistry;\n serviceRegistry: ServiceRegistry;\n enabled: Map<string, ModuleManifestV2>;\n}\n\nexport class TxRunner {\n private readonly sessions = new Map<string, TxSession>();\n private transactionBusy = false;\n private readonly transactionWaiters: TransactionWaiter[] = [];\n private readonly slotWaitTimeoutMs: number;\n private readonly sessionIdleTimeoutMs: number;\n /**\n * \u63D0\u4EA4\u6210\u529F\u540E\u7684\u63D2\u5165\u901A\u77E5\u3002\u6E38\u620F\u804A\u5929\u7ECF db.tx \u843D\u5E93\uFF0C\u4E0D\u8D70 /api/sfmc/messages\uFF0C\n * \u56E0\u6B64 MC\u2192QQ \u8981\u5728\u8FD9\u91CC\u6302\u94A9\u3002\u94A9\u5B50\u4E0D\u5F97\u629B\u9519\u5F71\u54CD\u5DF2\u63D0\u4EA4\u7ED3\u679C\u3002\n */\n private afterCommit?: (inserts: CommittedInsert[]) => void;\n\n constructor(\n private readonly deps: TxRunnerDeps,\n options: TxRunnerOptions = {}\n ) {\n this.slotWaitTimeoutMs = options.slotWaitTimeoutMs ?? 2_000;\n this.sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? 15_000;\n }\n\n /**\n * \u6CE8\u518C\u63D0\u4EA4\u6210\u529F\u540E\u7684\u63D2\u5165\u56DE\u8C03\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Adb-server \u542F\u52A8\u540E\u628A\u804A\u5929\u884C\u8F6C\u53D1\u7ED9 QQ\uFF0C\u907F\u514D\u4E8B\u52A1\u5C42\u4F9D\u8D56\u5177\u4F53\u6865\u63A5\u5B9E\u73B0\u3002\n */\n setAfterCommit(hook: (inserts: CommittedInsert[]) => void): void {\n this.afterCommit = hook;\n }\n\n async run(req: TxRequest): Promise<TxResponse | TxError> {\n const { moduleId, steps } = req;\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n // \u6CE8\u610F:\u4E0D\u5728\u6B64\u5904\u505A\u300C\u6574\u4F53 db:write:*\u300D\u65AD\u8A00\u3002\n // \u901A\u914D `db:write:*` \u65E0\u6CD5\u901A\u8FC7 permission-gate \u7684 validPermissionKey \u6821\u9A8C(\u6A21\u5757\u58F0\u660E\u5B83\u4F1A\u542F\u52A8\u5931\u8D25),\n // \u56E0\u6B64\u8BE5\u65AD\u8A00\u4F1A\u8BA9\u6240\u6709\u4E8B\u52A1(\u542B\u53EA\u8BFB\u4E8B\u52A1)\u6052\u88AB\u62D2\u3002\u771F\u6B63\u7684\u8BFB\u5199\u6743\u9650\u7531\u6BCF\u4E2A step \u7684\n // requireTableRead / requireTableWrite \u6309\u5177\u4F53\u8868\u7CBE\u786E gate\u3002\n\n const traceId = randomUUID().slice(0, 8);\n const results: TxStepResult[] = [];\n\n if (!(await this.acquireTransactionSlot())) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\", code: \"transaction_busy\" };\n }\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n for (let i = 0; i < steps.length; i++) {\n const step: TxStep = steps[i]!;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n results.push(r);\n } catch (err) {\n this.rollback();\n this.releaseTransactionSlot();\n const code = this.mapErrorCode(err);\n log.warn(`[tx ${traceId}] step=${i} failed: ${(err as Error).message}`);\n return { ok: false, step: i, error: (err as Error).message, code };\n }\n }\n\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.rollback();\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n this.releaseTransactionSlot();\n this.emitAfterCommit(steps);\n log.info(`[tx ${traceId}] module=${moduleId} ${steps.length} steps OK`);\n return { ok: true, results };\n }\n\n /**\n * \u5F00\u542F\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF08\u534F\u8BAE\u6D41\u7A0B\uFF1Abegin \u2192 step* \u2192 commit | rollback\uFF09\u3002\n * \u4F9B\u5BA2\u6237\u7AEF `db.tx` \u5728\u56DE\u8C03\u5185\u90E8\u5F02\u6B65\u6267\u884C\u5E76\u5B9E\u65F6\u8BFB\u56DE query / get / call \u7684\u670D\u52A1\u7AEF\u6267\u884C\u7ED3\u679C\u3002\n * \u5355\u8FDE\u63A5\u4E92\u65A5\u4FDD\u62A4\uFF1A\u4E0E\u6279\u91CF run() \u5171\u7528\u4E8B\u52A1\u961F\u5217\uFF0C\u5E76\u5728\u63D0\u4EA4/\u56DE\u6EDA\u65F6\u5524\u9192\u4E0B\u4E00\u4E2A\u7B49\u5F85\u8005\u3002\n *\n * @param moduleId \u8BF7\u6C42\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @returns \u6210\u529F\u8FD4\u56DE\u5206\u914D\u7684\u4F1A\u8BDD txId\uFF0C\u5931\u8D25\u8FD4\u56DE\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async beginSession(moduleId: string): Promise<{ ok: true; txId: string } | TxError> {\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n\n // run() \u4E0E\u4EA4\u4E92\u5F0F\u4F1A\u8BDD\u5171\u7528\u540C\u4E00 SQLite \u8FDE\u63A5\uFF0C\u5FC5\u987B\u5171\u7528\u540C\u4E00\u4E2A\u4E8B\u52A1\u69FD\u3002\n if (!(await this.acquireTransactionSlot())) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\", code: \"transaction_busy\" };\n }\n\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const txId = randomUUID();\n this.sessions.set(txId, {\n moduleId,\n steps: [],\n results: [],\n idleTimer: this.createSessionIdleTimer(txId, moduleId),\n });\n log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);\n return { ok: true, txId };\n }\n\n /**\n * \u5728\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\u4E2D\u987A\u5E8F\u6267\u884C\u5355\u4E2A\u6B65\u9AA4\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @param step \u5F85\u6267\u884C\u7684\u64CD\u4F5C\u6B65\u9AA4\u5BF9\u8C61\u3002\n * @returns \u6B65\u9AA4\u6267\u884C\u7ED3\u679C\u6216\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async stepSession(\n txId: string,\n moduleId: string,\n step: TxStep\n ): Promise<{ ok: true; result: TxStepResult } | TxError> {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: session.results.length, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n clearTimeout(session.idleTimer);\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) {\n this.abortSession(txId);\n return { ok: false, step: session.results.length, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n }\n const stepIndex = session.results.length;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n session.steps.push(step);\n session.results.push(r);\n session.idleTimer = this.createSessionIdleTimer(txId, moduleId);\n return { ok: true, result: r };\n } catch (err) {\n this.abortSession(txId);\n const code = this.mapErrorCode(err);\n log.warn(`[tx-session ${txId.slice(0, 8)}] step=${stepIndex} failed: ${(err as Error).message}`);\n return { ok: false, step: stepIndex, error: (err as Error).message, code };\n }\n }\n\n /**\n * \u63D0\u4EA4\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u5C06\u6240\u6709\u6B65\u9AA4\u7684\u4FEE\u6539\u6301\u4E45\u5316\u5E76\u91CA\u653E\u8FDE\u63A5\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u63D0\u4EA4\u7ED3\u679C\u54CD\u5E94\u3002\n */\n commitSession(txId: string, moduleId: string): TxResponse | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n clearTimeout(session.idleTimer);\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.abortSession(txId);\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const results = session.results;\n const steps = session.steps;\n this.sessions.delete(txId);\n this.releaseTransactionSlot();\n this.emitAfterCommit(steps);\n log.info(`[tx-session ${txId.slice(0, 8)}] commit ${results.length} steps OK`);\n return { ok: true, results };\n }\n\n /** \u53EA\u901A\u77E5\u63D2\u5165\u884C\u3002\u56DE\u8C03\u5931\u8D25\u53EA\u8BB0\u65E5\u5FD7\uFF0C\u4E8B\u52A1\u5DF2\u7ECF\u63D0\u4EA4\u3002 */\n private emitAfterCommit(steps: TxStep[]): void {\n const hook = this.afterCommit;\n if (!hook) return;\n const inserts: CommittedInsert[] = [];\n for (const step of steps) {\n if (step.op === \"insert\") inserts.push({ table: step.table, row: step.row });\n }\n if (inserts.length === 0) return;\n try {\n hook(inserts);\n } catch (err) {\n log.warn(`[tx] afterCommit: ${(err as Error).message}`);\n }\n }\n\n /**\n * \u56DE\u6EDA\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u64A4\u9500\u6240\u6709\u672A\u63D0\u4EA4\u7684\u6570\u636E\u5E93\u53D8\u66F4\u3002\n * \u5177\u6709\u5E42\u7B49\u6027\uFF1A\u4F1A\u8BDD\u5DF2\u7ED3\u675F\u6216\u5DF2\u56DE\u6EDA\u65F6\u540C\u6837\u8FD4\u56DE\u6210\u529F\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u56DE\u6EDA\u786E\u8BA4\u7ED3\u679C\u3002\n */\n rollbackSession(txId: string, moduleId: string): { ok: true } | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n // \u5E42\u7B49\uFF1A\u5DF2\u7ED3\u675F\u89C6\u4E3A\u6210\u529F\u56DE\u6EDA\n return { ok: true };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n this.abortSession(txId);\n return { ok: true };\n }\n\n private abortSession(txId: string): void {\n const session = this.sessions.get(txId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n this.sessions.delete(txId);\n this.rollback();\n this.releaseTransactionSlot();\n log.info(`[tx-session ${txId.slice(0, 8)}] aborted`);\n }\n\n private async acquireTransactionSlot(): Promise<boolean> {\n if (!this.transactionBusy) {\n this.transactionBusy = true;\n return true;\n }\n return new Promise<boolean>((resolve) => {\n const waiter: TransactionWaiter = {\n resolve,\n timer: setTimeout(() => {\n const index = this.transactionWaiters.indexOf(waiter);\n if (index >= 0) this.transactionWaiters.splice(index, 1);\n resolve(false);\n }, this.slotWaitTimeoutMs),\n };\n this.transactionWaiters.push(waiter);\n });\n }\n\n private releaseTransactionSlot(): void {\n const next = this.transactionWaiters.shift();\n if (next) {\n clearTimeout(next.timer);\n next.resolve(true);\n return;\n }\n this.transactionBusy = false;\n }\n\n private createSessionIdleTimer(txId: string, moduleId: string): ReturnType<typeof setTimeout> {\n const timer = setTimeout(() => {\n if (!this.sessions.has(txId)) return;\n log.warn(`[tx-session ${txId.slice(0, 8)}] idle timeout module=${moduleId} after ${this.sessionIdleTimeoutMs}ms`);\n this.abortSession(txId);\n }, this.sessionIdleTimeoutMs);\n timer.unref?.();\n return timer;\n }\n\n private mapErrorCode(err: unknown): TxError[\"code\"] {\n return err instanceof PermissionDeniedError\n ? \"permission_denied\"\n : err instanceof DispatchError &&\n (err.code === \"no_such_service\" ||\n err.code === \"not_in_requires\" ||\n err.code === \"forbidden\" ||\n err.code === \"domain_error\")\n ? err.code\n : (err as { code?: string }).code === \"no_such_service\"\n ? \"no_such_service\"\n : (err as { code?: string }).code === \"not_in_requires\"\n ? \"not_in_requires\"\n : (err as { code?: string }).code === \"no_such_table\"\n ? \"no_such_table\"\n : \"internal\";\n }\n\n private rollback(): void {\n try {\n this.deps.db.exec(\"ROLLBACK\");\n } catch {\n // best-effort; fall through\n }\n }\n\n /** \u5355 step \u7531\u5BF9\u5E94\u6743\u9650\u68C0\u67E5 + SQL \u8DEF\u5F84\u6267\u884C */\n private async runOne(moduleId: string, manifest: ModuleManifestV2, step: TxStep): Promise<TxStepResult> {\n switch (step.op) {\n case \"query\":\n return this.doQuery(moduleId, manifest, step);\n case \"get\":\n return this.doGet(moduleId, manifest, step);\n case \"insert\":\n return this.doInsert(moduleId, manifest, step);\n case \"update\":\n return this.doUpdate(moduleId, manifest, step);\n case \"delete\":\n return this.doDelete(moduleId, manifest, step);\n case \"audit\":\n return this.doAudit(moduleId, manifest, step);\n case \"service\":\n return this.doService(moduleId, manifest, step);\n }\n }\n\n private requireTableRead(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbRead(table));\n }\n private requireTableWrite(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbWrite(table));\n }\n private assertTableRegistered(table: string): void {\n if (table.startsWith(\"sfmc_\")) return; // \u5E73\u53F0\u8868 \u2014 \u59CB\u7EC8\u53EF\u8BFB\u5199(\u5E73\u53F0\u5C42)\n if (!this.deps.schema.isRegistered(table)) {\n const err = new Error(`\u8868 \"${table}\" \u672A\u6CE8\u518C\u5230 schema-registry`);\n (err as { code?: string }).code = \"no_such_table\";\n throw err;\n }\n }\n\n private doQuery(_mid: string, mod: ModuleManifestV2, step: TxStepQuery): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = compile(step.opts?.where);\n // LSP:\u4E0E SDK QueryOptions.orderBy({field}|[]) \u5BF9\u9F50,\u517C\u6536\u9057\u7559 {col}\n const orders = normalizeOrderBy(step.opts?.orderBy);\n const limit = typeof step.opts?.limit === \"number\" ? step.opts?.limit : undefined;\n const offset = typeof step.opts?.offset === \"number\" ? step.opts?.offset : undefined;\n\n const whereClause = where.sql;\n const params = [...where.values];\n let extra = \"\";\n if (orders.length > 0) {\n const parts: string[] = [];\n for (const o of orders) {\n if (!IDENT.test(o.col)) throw new Error(`[tx] orderBy bad column \"${o.col}\"`);\n parts.push(`\"${o.col}\" ${o.dir === \"desc\" ? \"DESC\" : \"ASC\"}`);\n }\n extra += ` ORDER BY ${parts.join(\", \")}`;\n }\n if (typeof limit === \"number\") {\n extra += \" LIMIT \" + Math.max(0, Math.floor(limit));\n }\n if (typeof offset === \"number\") {\n extra += \" OFFSET \" + Math.max(0, Math.floor(offset));\n }\n\n const sql = `SELECT * FROM \"${step.table}\" WHERE ${whereClause}${extra}`;\n const rows = this.deps.db.prepare(sql).all(...(params as never[])) as Record<string, unknown>[];\n return { op: \"query\", rows };\n }\n\n /** \u89E3\u6790\u4E3B\u952E WHERE\u3002\u5355\u5217\u76F4\u63A5\u7B49\u503C;\u8054\u5408\u4E3B\u952E\u652F\u6301 `a|b|c`(\u4E0E data-backup \u7EA6\u5B9A\u4E00\u81F4)\u3002 */\n private pkWhere(table: string, id: string | number): { clause: string; values: unknown[] } {\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${table}\")`).all() as Array<{\n name: string;\n pk: number;\n }>;\n const pkCols = cols\n .filter((c) => c.pk > 0)\n .sort((a, b) => a.pk - b.pk)\n .map((c) => c.name);\n if (pkCols.length === 0) {\n throw new Error(`[tx] ${table} \u6CA1\u6709 primary key`);\n }\n if (pkCols.length === 1) {\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n const parts = String(id).split(\"|\");\n if (parts.length === pkCols.length) {\n return {\n clause: pkCols.map((c) => `\"${c}\" = ?`).join(\" AND \"),\n values: parts,\n };\n }\n // \u8054\u5408\u4E3B\u952E\u4F46\u53EA\u4F20\u4E86\u9996\u5217(\u9891\u9053 id \u7B49):\u6309\u7B2C\u4E00\u4E3B\u952E\u5217\u5339\u914D\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n\n private doGet(_mid: string, mod: ModuleManifestV2, step: TxStepGet): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n const row = this.deps.db\n .prepare(`SELECT * FROM \"${step.table}\" WHERE ${where.clause}`)\n .get(...(where.values as never[])) as Record<string, unknown> | undefined;\n return { op: \"get\", row: row ?? null };\n }\n\n private doInsert(_mid: string, mod: ModuleManifestV2, step: TxStepInsert): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.row);\n if (cols.length === 0) throw new Error(\"[tx] insert row \u5FC5\u987B\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const placeholders = cols.map(() => \"?\").join(\",\");\n const quoted = cols.map((c) => `\"${c}\"`).join(\",\");\n const values = cols.map((c) => step.row[c]);\n const sql = `INSERT INTO \"${step.table}\" (${quoted}) VALUES (${placeholders})`;\n try {\n this.deps.db.prepare(sql).run(...(values as never[]));\n } catch (e) {\n throw new Error(`INSERT ${step.table} \u5931\u8D25: ${(e as Error).message}`);\n }\n return { op: \"insert\", row: { ...step.row } };\n }\n\n private doUpdate(_mid: string, mod: ModuleManifestV2, step: TxStepUpdate): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.patch);\n if (cols.length === 0) throw new Error(\"[tx] update patch \u4E0D\u80FD\u4E3A\u7A7A\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const where = this.pkWhere(step.table, step.id);\n const sets = cols.map((c) => `\"${c}\" = ?`).join(\",\");\n const params = [...cols.map((c) => step.patch[c]), ...where.values];\n const sql = `UPDATE \"${step.table}\" SET ${sets} WHERE ${where.clause}`;\n const result = this.deps.db.prepare(sql).run(...(params as never[]));\n if (result.changes === 0) {\n throw new Error(`UPDATE ${step.table} id=${step.id} \u5F71\u54CD 0 \u884C(\u627E\u4E0D\u5230 / \u5DF2\u5220?)`);\n }\n return { op: \"update\", row: { ...step.patch, id: step.id } as unknown as Record<string, unknown> };\n }\n\n private doDelete(_mid: string, mod: ModuleManifestV2, step: TxStepDelete): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n if (step.hard) {\n const sql = `DELETE FROM \"${step.table}\" WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${step.table}\")`).all() as Array<{\n name: string;\n }>;\n const hasDeletedAt = cols.find((c) => c.name === \"_deleted_at\");\n if (!hasDeletedAt) {\n throw new Error(`[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`);\n }\n const sql = `UPDATE \"${step.table}\" SET \"_deleted_at\" = ?, \"_version\" = COALESCE(\"_version\",0)+1 WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(Date.now() as never, ...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n\n private doAudit(_mid: string, mod: ModuleManifestV2, step: TxStepAudit): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const dataJson = step.data ? JSON.stringify(step.data) : null;\n const r = this.deps.db\n .prepare(\n `INSERT INTO sfmc__audit (module_id, table_name, row_id, action, data, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`\n )\n .run(mod.id, step.table, step.rowId, step.action, dataJson, new Date().toISOString());\n return { op: \"audit\", changes: Number(r.changes) };\n }\n\n private async doService(_mid: string, mod: ModuleManifestV2, step: TxStepService): Promise<TxStepResult> {\n // requires / \u81EA\u8C03\u7528\u8C41\u514D\u7684\u552F\u4E00\u6743\u5A01\u5728 ServiceRegistry.dispatch(DRY/LSP);\n // \u6B64\u5904\u53EA\u8865 dispatch \u4E0D\u505A\u7684 service:<name> \u6743\u9650\u95E8(\u4E0E HTTP service-routes \u5BF9\u9F50)\u3002\n assertModulePermission(mod.id, mod.permissions, Perm.service(step.name));\n const result = await this.deps.serviceRegistry.dispatch(this.deps.enabled, mod.id, step.name, step.input, {\n query: this.deps.query,\n db: this.deps.db,\n });\n return { op: \"service\", result: result.result };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * tx-runner.ts \u2014 \u4E8B\u52A1 RPC \u5904\u7406\u5668\n *\n * \u534F\u8BAE(\u4E24\u79CD):\n * A. \u6279\u91CF: POST /api/sfmc/db/tx body={ steps } \u2192 { ok, results? } | { ok:false, step, error, code }\n * B. \u4EA4\u4E92: begin \u2192 step* \u2192 commit|rollback(SDK db.tx \u7528;\u56DE\u8C03\u5185\u53EF\u8BFB\u56DE query/get/call)\n *\n * \u6D41\u7A0B(\u6279\u91CF):\n * 1. \u6821\u9A8C moduleId in enabled\n * 2. BEGIN IMMEDIATE\n * 3. \u987A\u5E8F\u8DD1 steps \u2014 \u4EFB\u4E00\u629B\u9519 \u2192 ROLLBACK + reply { ok:false, step:<i>, ... }\n * 4. \u5168\u8FC7 \u2192 COMMIT + reply { ok:true, results }\n *\n * step.op:\n * - query { table, opts? }\n * - get { table, id }\n * - insert { table, row }\n * - update { table, id, patch }\n * - delete { table, id, hard }\n * - audit { table, rowId, action, data? } \u2192 \u5E73\u53F0 sfmc__audit \u8868\n * - service { name, input } \u2192 \u6D3E\u53D1;\u5931\u8D25\u629B\u9519\u56DE\u9000\u6574\u4E8B\u52A1\n *\n * \u4E8B\u52A1\u5185 service call:\u4E0D\u6301\u6709 db handle \u7ED9 handler(\u907F\u514D\u5E76\u53D1\u5199) \u2014\n * \u5B9E\u73B0:handler \u8FD4\u56DE result \u5199\u56DE step \u8F93\u51FA;\u82E5 handler \u8FD8\u8981\u5199 db,\u5E94\u8D70\u5176\u5B83\u63A5\u53E3\u5728\u4E8B\u52A1\u5916\u505A\u3002\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\nimport { normalizeOrderBy } from \"./lib/order-by.js\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport { Perm, PermissionDeniedError, assertModulePermission } from \"./permission-gate.js\";\nimport type { SchemaRegistry } from \"./schema-registry.js\";\nimport { DispatchError, type ServiceRegistry } from \"./service-registry.js\";\nimport type { WhereExpr } from \"./where.js\";\nimport { compile } from \"./where.js\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction identOk(t: string): void {\n if (!IDENT.test(t)) throw new Error(`[tx] invalid table name \"${t}\"`);\n}\n\nexport interface TxStepQuery {\n op: \"query\";\n table: string;\n opts?: {\n where?: WhereExpr;\n /** SDK \u7528 field;\u5386\u53F2 col \u4ECD\u63A5\u53D7(normalizeOrderBy) */\n orderBy?:\n | { field?: string; col?: string; dir?: \"asc\" | \"desc\" }\n | Array<{ field?: string; col?: string; dir?: \"asc\" | \"desc\" }>;\n limit?: number;\n offset?: number;\n };\n}\n\nexport interface TxStepGet {\n op: \"get\";\n table: string;\n id: string;\n}\n\nexport interface TxStepInsert {\n op: \"insert\";\n table: string;\n row: Record<string, unknown>;\n}\n\nexport interface TxStepUpdate {\n op: \"update\";\n table: string;\n id: string;\n patch: Record<string, unknown>;\n}\n\nexport interface TxStepDelete {\n op: \"delete\";\n table: string;\n id: string;\n hard: boolean;\n}\n\nexport interface TxStepAudit {\n op: \"audit\";\n table: string;\n rowId: string;\n action: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TxStepService {\n op: \"service\";\n name: string;\n input: Record<string, unknown>;\n}\n\nexport type TxStep = TxStepQuery | TxStepGet | TxStepInsert | TxStepUpdate | TxStepDelete | TxStepAudit | TxStepService;\n\nexport type TxStepResult =\n | { op: \"query\"; rows: Record<string, unknown>[] }\n | { op: \"get\"; row: Record<string, unknown> | null }\n | { op: \"insert\"; row: Record<string, unknown> }\n | { op: \"update\"; row: Record<string, unknown> }\n | { op: \"delete\"; changes: number }\n | { op: \"audit\"; changes: number }\n | { op: \"service\"; result: unknown };\n\nexport interface TxResponse {\n ok: true;\n results: TxStepResult[];\n}\n\nexport type TxError = {\n ok: false;\n step: number;\n error: string;\n code:\n | \"permission_denied\"\n | \"forbidden\"\n | \"no_such_service\"\n | \"not_in_requires\"\n | \"domain_error\"\n | \"transaction_busy\"\n | \"internal\"\n | \"no_such_table\";\n};\n\nexport interface TxRequest {\n moduleId: string;\n steps: TxStep[];\n}\n\n/** \u4E8B\u52A1\u63D0\u4EA4\u6210\u529F\u540E\u7684\u63D2\u5165\u884C\u3002\u804A\u5929\u6D88\u606F\u636E\u6B64\u51B3\u5B9A\u662F\u5426\u8F6C\u53D1\u5230 QQ\u3002 */\nexport interface CommittedInsert {\n table: string;\n row: Record<string, unknown>;\n}\n\ninterface TxSession {\n moduleId: string;\n /** \u5DF2\u6210\u529F\u6267\u884C\u7684\u6B65\u9AA4\uFF0C\u63D0\u4EA4\u540E\u7528\u4E8E afterCommit\uFF0C\u4E0D\u8FDB\u5165\u54CD\u5E94\u4F53\u3002 */\n steps: TxStep[];\n results: TxStepResult[];\n idleTimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TransactionWaiter {\n resolve: (acquired: boolean) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport interface TxRunnerOptions {\n /** \u7B49\u5F85\u5168\u5C40 SQLite \u4E8B\u52A1\u69FD\u7684\u6700\u957F\u65F6\u95F4\u3002 */\n slotWaitTimeoutMs?: number;\n /** \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4E24\u6B21\u8BF7\u6C42\u4E4B\u95F4\u5141\u8BB8\u7A7A\u95F2\u7684\u6700\u957F\u65F6\u95F4\u3002 */\n sessionIdleTimeoutMs?: number;\n}\n\nexport interface TxRunnerDeps {\n db: DatabaseSync;\n query: import(\"./lib/sqlite.js\").QueryFn;\n schema: SchemaRegistry;\n serviceRegistry: ServiceRegistry;\n enabled: Map<string, ModuleManifestV2>;\n}\n\nexport class TxRunner {\n private readonly sessions = new Map<string, TxSession>();\n private transactionBusy = false;\n private readonly transactionWaiters: TransactionWaiter[] = [];\n private readonly slotWaitTimeoutMs: number;\n private readonly sessionIdleTimeoutMs: number;\n /**\n * \u63D0\u4EA4\u6210\u529F\u540E\u7684\u63D2\u5165\u901A\u77E5\u3002\u6E38\u620F\u804A\u5929\u7ECF db.tx \u843D\u5E93\uFF0C\u4E0D\u8D70 /api/sfmc/messages\uFF0C\n * \u56E0\u6B64 MC\u2192QQ \u8981\u5728\u8FD9\u91CC\u6302\u94A9\u3002\u94A9\u5B50\u4E0D\u5F97\u629B\u9519\u5F71\u54CD\u5DF2\u63D0\u4EA4\u7ED3\u679C\u3002\n */\n private afterCommit?: (inserts: CommittedInsert[]) => void;\n\n constructor(\n private readonly deps: TxRunnerDeps,\n options: TxRunnerOptions = {}\n ) {\n this.slotWaitTimeoutMs = options.slotWaitTimeoutMs ?? 2_000;\n this.sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? 15_000;\n }\n\n /**\n * \u6CE8\u518C\u63D0\u4EA4\u6210\u529F\u540E\u7684\u63D2\u5165\u56DE\u8C03\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Adb-server \u542F\u52A8\u540E\u628A\u804A\u5929\u884C\u8F6C\u53D1\u7ED9 QQ\uFF0C\u907F\u514D\u4E8B\u52A1\u5C42\u4F9D\u8D56\u5177\u4F53\u6865\u63A5\u5B9E\u73B0\u3002\n */\n setAfterCommit(hook: (inserts: CommittedInsert[]) => void): void {\n this.afterCommit = hook;\n }\n\n async run(req: TxRequest): Promise<TxResponse | TxError> {\n const { moduleId, steps } = req;\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n // \u6CE8\u610F:\u4E0D\u5728\u6B64\u5904\u505A\u300C\u6574\u4F53 db:write:*\u300D\u65AD\u8A00\u3002\n // \u901A\u914D `db:write:*` \u65E0\u6CD5\u901A\u8FC7 permission-gate \u7684 validPermissionKey \u6821\u9A8C(\u6A21\u5757\u58F0\u660E\u5B83\u4F1A\u542F\u52A8\u5931\u8D25),\n // \u56E0\u6B64\u8BE5\u65AD\u8A00\u4F1A\u8BA9\u6240\u6709\u4E8B\u52A1(\u542B\u53EA\u8BFB\u4E8B\u52A1)\u6052\u88AB\u62D2\u3002\u771F\u6B63\u7684\u8BFB\u5199\u6743\u9650\u7531\u6BCF\u4E2A step \u7684\n // requireTableRead / requireTableWrite \u6309\u5177\u4F53\u8868\u7CBE\u786E gate\u3002\n\n const traceId = randomUUID().slice(0, 8);\n const results: TxStepResult[] = [];\n\n if (!(await this.acquireTransactionSlot())) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\", code: \"transaction_busy\" };\n }\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n for (let i = 0; i < steps.length; i++) {\n const step: TxStep = steps[i]!;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n results.push(r);\n } catch (err) {\n this.rollback();\n this.releaseTransactionSlot();\n const code = this.mapErrorCode(err);\n log.warn(`[tx ${traceId}] step=${i} failed: ${(err as Error).message}`);\n return { ok: false, step: i, error: (err as Error).message, code };\n }\n }\n\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.rollback();\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n\n this.releaseTransactionSlot();\n this.emitAfterCommit(steps);\n log.info(`[tx ${traceId}] module=${moduleId} ${steps.length} steps OK`);\n return { ok: true, results };\n }\n\n /**\n * \u5F00\u542F\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF08\u534F\u8BAE\u6D41\u7A0B\uFF1Abegin \u2192 step* \u2192 commit | rollback\uFF09\u3002\n * \u4F9B\u5BA2\u6237\u7AEF `db.tx` \u5728\u56DE\u8C03\u5185\u90E8\u5F02\u6B65\u6267\u884C\u5E76\u5B9E\u65F6\u8BFB\u56DE query / get / call \u7684\u670D\u52A1\u7AEF\u6267\u884C\u7ED3\u679C\u3002\n * \u5355\u8FDE\u63A5\u4E92\u65A5\u4FDD\u62A4\uFF1A\u4E0E\u6279\u91CF run() \u5171\u7528\u4E8B\u52A1\u961F\u5217\uFF0C\u5E76\u5728\u63D0\u4EA4/\u56DE\u6EDA\u65F6\u5524\u9192\u4E0B\u4E00\u4E2A\u7B49\u5F85\u8005\u3002\n *\n * @param moduleId \u8BF7\u6C42\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @returns \u6210\u529F\u8FD4\u56DE\u5206\u914D\u7684\u4F1A\u8BDD txId\uFF0C\u5931\u8D25\u8FD4\u56DE\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async beginSession(moduleId: string): Promise<{ ok: true; txId: string } | TxError> {\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) return { ok: false, step: -1, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n\n // run() \u4E0E\u4EA4\u4E92\u5F0F\u4F1A\u8BDD\u5171\u7528\u540C\u4E00 SQLite \u8FDE\u63A5\uFF0C\u5FC5\u987B\u5171\u7528\u540C\u4E00\u4E2A\u4E8B\u52A1\u69FD\u3002\n if (!(await this.acquireTransactionSlot())) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\", code: \"transaction_busy\" };\n }\n\n try {\n this.deps.db.exec(\"BEGIN IMMEDIATE\");\n } catch (e) {\n this.releaseTransactionSlot();\n return { ok: false, step: -1, error: `BEGIN \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const txId = randomUUID();\n this.sessions.set(txId, {\n moduleId,\n steps: [],\n results: [],\n idleTimer: this.createSessionIdleTimer(txId, moduleId),\n });\n log.info(`[tx-session ${txId.slice(0, 8)}] begin module=${moduleId}`);\n return { ok: true, txId };\n }\n\n /**\n * \u5728\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\u4E2D\u987A\u5E8F\u6267\u884C\u5355\u4E2A\u6B65\u9AA4\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @param step \u5F85\u6267\u884C\u7684\u64CD\u4F5C\u6B65\u9AA4\u5BF9\u8C61\u3002\n * @returns \u6B65\u9AA4\u6267\u884C\u7ED3\u679C\u6216\u9519\u8BEF\u54CD\u5E94\u3002\n */\n async stepSession(\n txId: string,\n moduleId: string,\n step: TxStep\n ): Promise<{ ok: true; result: TxStepResult } | TxError> {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: session.results.length, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n clearTimeout(session.idleTimer);\n const manifest = this.deps.enabled.get(moduleId);\n if (!manifest) {\n this.abortSession(txId);\n return { ok: false, step: session.results.length, error: \"\u6A21\u5757\u672A enabled\", code: \"forbidden\" };\n }\n const stepIndex = session.results.length;\n try {\n const r = await this.runOne(moduleId, manifest, step);\n session.steps.push(step);\n session.results.push(r);\n session.idleTimer = this.createSessionIdleTimer(txId, moduleId);\n return { ok: true, result: r };\n } catch (err) {\n this.abortSession(txId);\n const code = this.mapErrorCode(err);\n log.warn(`[tx-session ${txId.slice(0, 8)}] step=${stepIndex} failed: ${(err as Error).message}`);\n return { ok: false, step: stepIndex, error: (err as Error).message, code };\n }\n }\n\n /**\n * \u63D0\u4EA4\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u5C06\u6240\u6709\u6B65\u9AA4\u7684\u4FEE\u6539\u6301\u4E45\u5316\u5E76\u91CA\u653E\u8FDE\u63A5\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u63D0\u4EA4\u7ED3\u679C\u54CD\u5E94\u3002\n */\n commitSession(txId: string, moduleId: string): TxResponse | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n return { ok: false, step: -1, error: \"\u4E8B\u52A1\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F\", code: \"internal\" };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n clearTimeout(session.idleTimer);\n try {\n this.deps.db.exec(\"COMMIT\");\n } catch (e) {\n this.abortSession(txId);\n return { ok: false, step: -1, error: `COMMIT \u5931\u8D25: ${(e as Error).message}`, code: \"internal\" };\n }\n const results = session.results;\n const steps = session.steps;\n this.sessions.delete(txId);\n this.releaseTransactionSlot();\n this.emitAfterCommit(steps);\n log.info(`[tx-session ${txId.slice(0, 8)}] commit ${results.length} steps OK`);\n return { ok: true, results };\n }\n\n /** \u53EA\u901A\u77E5\u63D2\u5165\u884C\u3002\u56DE\u8C03\u5931\u8D25\u53EA\u8BB0\u65E5\u5FD7\uFF0C\u4E8B\u52A1\u5DF2\u7ECF\u63D0\u4EA4\u3002 */\n private emitAfterCommit(steps: TxStep[]): void {\n const hook = this.afterCommit;\n if (!hook) return;\n const inserts: CommittedInsert[] = [];\n for (const step of steps) {\n if (step.op === \"insert\") inserts.push({ table: step.table, row: step.row });\n }\n if (inserts.length === 0) return;\n try {\n hook(inserts);\n } catch (err) {\n log.warn(`[tx] afterCommit: ${(err as Error).message}`);\n }\n }\n\n /**\n * \u56DE\u6EDA\u5F53\u524D\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\uFF0C\u64A4\u9500\u6240\u6709\u672A\u63D0\u4EA4\u7684\u6570\u636E\u5E93\u53D8\u66F4\u3002\n * \u5177\u6709\u5E42\u7B49\u6027\uFF1A\u4F1A\u8BDD\u5DF2\u7ED3\u675F\u6216\u5DF2\u56DE\u6EDA\u65F6\u540C\u6837\u8FD4\u56DE\u6210\u529F\u3002\n *\n * @param txId \u4F1A\u8BDD\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param moduleId \u8C03\u7528\u6A21\u5757\u6807\u8BC6\u3002\n * @returns \u56DE\u6EDA\u786E\u8BA4\u7ED3\u679C\u3002\n */\n rollbackSession(txId: string, moduleId: string): { ok: true } | TxError {\n const session = this.sessions.get(txId);\n if (!session) {\n // \u5E42\u7B49\uFF1A\u5DF2\u7ED3\u675F\u89C6\u4E3A\u6210\u529F\u56DE\u6EDA\n return { ok: true };\n }\n if (session.moduleId !== moduleId) {\n return { ok: false, step: -1, error: \"moduleId \u4E0E\u4F1A\u8BDD\u4E0D\u7B26\", code: \"forbidden\" };\n }\n this.abortSession(txId);\n return { ok: true };\n }\n\n /**\n * \u4E2D\u6B62\u67D0\u6A21\u5757\u540D\u4E0B\u7684\u5168\u90E8\u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1ABDS \u6267\u884C /reload \u6216\u91CD\u542F\u540E\uFF0C\u65B0\u811A\u672C\u5B9E\u4F8B\u5728\u4EFB\u4F55\u6A21\u5757\u521D\u59CB\u5316\u4E4B\u524D\u7ECF\n * POST /api/sfmc/db/tx/reap \u8C03\u7528\uFF0C\u56DE\u6536\u65E7\u5B9E\u4F8B\u6765\u4E0D\u53CA\u63D0\u4EA4/\u56DE\u6EDA\u800C\u9057\u7559\u7684\u4F1A\u8BDD\uFF0C\n * \u907F\u514D\u5B83\u4EEC\u5360\u4F4F\u5168\u5C40\u4E8B\u52A1\u69FD\u76F4\u5230\u7A7A\u95F2\u8D85\u65F6\uFF0C\u5BFC\u81F4\u5176\u4ED6\u6A21\u5757\u7684\u8BFB\u5199\u8FD4\u56DE\u300C\u4E8B\u52A1\u7E41\u5FD9\u300D\u3002\n *\n * @param moduleId \u4F1A\u8BDD\u6240\u5C5E\u6A21\u5757\uFF08\u7531\u8DEF\u7531\u6309\u9274\u6743\u8EAB\u4EFD\u4F20\u5165\uFF0C\u6A21\u5757\u53EA\u80FD\u56DE\u6536\u81EA\u5DF1\u7684\u4F1A\u8BDD\uFF09\u3002\n * @returns \u88AB\u4E2D\u6B62\u7684\u4F1A\u8BDD\u6570\u91CF\u3002\n */\n abortModuleSessions(moduleId: string): number {\n const stale = [...this.sessions.entries()]\n .filter(([, session]) => session.moduleId === moduleId)\n .map(([txId]) => txId);\n for (const txId of stale) {\n log.warn(`[tx-session ${txId.slice(0, 8)}] reaped module=${moduleId}\uFF08\u811A\u672C\u5B9E\u4F8B\u5DF2\u91CD\u8F7D\uFF09`);\n this.abortSession(txId);\n }\n return stale.length;\n }\n\n private abortSession(txId: string): void {\n const session = this.sessions.get(txId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n this.sessions.delete(txId);\n this.rollback();\n this.releaseTransactionSlot();\n log.info(`[tx-session ${txId.slice(0, 8)}] aborted`);\n }\n\n private async acquireTransactionSlot(): Promise<boolean> {\n if (!this.transactionBusy) {\n this.transactionBusy = true;\n return true;\n }\n return new Promise<boolean>((resolve) => {\n const waiter: TransactionWaiter = {\n resolve,\n timer: setTimeout(() => {\n const index = this.transactionWaiters.indexOf(waiter);\n if (index >= 0) this.transactionWaiters.splice(index, 1);\n resolve(false);\n }, this.slotWaitTimeoutMs),\n };\n this.transactionWaiters.push(waiter);\n });\n }\n\n private releaseTransactionSlot(): void {\n const next = this.transactionWaiters.shift();\n if (next) {\n clearTimeout(next.timer);\n next.resolve(true);\n return;\n }\n this.transactionBusy = false;\n }\n\n private createSessionIdleTimer(txId: string, moduleId: string): ReturnType<typeof setTimeout> {\n const timer = setTimeout(() => {\n if (!this.sessions.has(txId)) return;\n log.warn(`[tx-session ${txId.slice(0, 8)}] idle timeout module=${moduleId} after ${this.sessionIdleTimeoutMs}ms`);\n this.abortSession(txId);\n }, this.sessionIdleTimeoutMs);\n timer.unref?.();\n return timer;\n }\n\n private mapErrorCode(err: unknown): TxError[\"code\"] {\n return err instanceof PermissionDeniedError\n ? \"permission_denied\"\n : err instanceof DispatchError &&\n (err.code === \"no_such_service\" ||\n err.code === \"not_in_requires\" ||\n err.code === \"forbidden\" ||\n err.code === \"domain_error\")\n ? err.code\n : (err as { code?: string }).code === \"no_such_service\"\n ? \"no_such_service\"\n : (err as { code?: string }).code === \"not_in_requires\"\n ? \"not_in_requires\"\n : (err as { code?: string }).code === \"no_such_table\"\n ? \"no_such_table\"\n : \"internal\";\n }\n\n private rollback(): void {\n try {\n this.deps.db.exec(\"ROLLBACK\");\n } catch {\n // best-effort; fall through\n }\n }\n\n /** \u5355 step \u7531\u5BF9\u5E94\u6743\u9650\u68C0\u67E5 + SQL \u8DEF\u5F84\u6267\u884C */\n private async runOne(moduleId: string, manifest: ModuleManifestV2, step: TxStep): Promise<TxStepResult> {\n switch (step.op) {\n case \"query\":\n return this.doQuery(moduleId, manifest, step);\n case \"get\":\n return this.doGet(moduleId, manifest, step);\n case \"insert\":\n return this.doInsert(moduleId, manifest, step);\n case \"update\":\n return this.doUpdate(moduleId, manifest, step);\n case \"delete\":\n return this.doDelete(moduleId, manifest, step);\n case \"audit\":\n return this.doAudit(moduleId, manifest, step);\n case \"service\":\n return this.doService(moduleId, manifest, step);\n }\n }\n\n private requireTableRead(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbRead(table));\n }\n private requireTableWrite(mod: ModuleManifestV2, table: string): void {\n identOk(table);\n this.assertTableRegistered(table);\n assertModulePermission(mod.id, mod.permissions, Perm.dbWrite(table));\n }\n private assertTableRegistered(table: string): void {\n if (table.startsWith(\"sfmc_\")) return; // \u5E73\u53F0\u8868 \u2014 \u59CB\u7EC8\u53EF\u8BFB\u5199(\u5E73\u53F0\u5C42)\n if (!this.deps.schema.isRegistered(table)) {\n const err = new Error(`\u8868 \"${table}\" \u672A\u6CE8\u518C\u5230 schema-registry`);\n (err as { code?: string }).code = \"no_such_table\";\n throw err;\n }\n }\n\n private doQuery(_mid: string, mod: ModuleManifestV2, step: TxStepQuery): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = compile(step.opts?.where);\n // LSP:\u4E0E SDK QueryOptions.orderBy({field}|[]) \u5BF9\u9F50,\u517C\u6536\u9057\u7559 {col}\n const orders = normalizeOrderBy(step.opts?.orderBy);\n const limit = typeof step.opts?.limit === \"number\" ? step.opts?.limit : undefined;\n const offset = typeof step.opts?.offset === \"number\" ? step.opts?.offset : undefined;\n\n const whereClause = where.sql;\n const params = [...where.values];\n let extra = \"\";\n if (orders.length > 0) {\n const parts: string[] = [];\n for (const o of orders) {\n if (!IDENT.test(o.col)) throw new Error(`[tx] orderBy bad column \"${o.col}\"`);\n parts.push(`\"${o.col}\" ${o.dir === \"desc\" ? \"DESC\" : \"ASC\"}`);\n }\n extra += ` ORDER BY ${parts.join(\", \")}`;\n }\n if (typeof limit === \"number\") {\n extra += \" LIMIT \" + Math.max(0, Math.floor(limit));\n }\n if (typeof offset === \"number\") {\n extra += \" OFFSET \" + Math.max(0, Math.floor(offset));\n }\n\n const sql = `SELECT * FROM \"${step.table}\" WHERE ${whereClause}${extra}`;\n const rows = this.deps.db.prepare(sql).all(...(params as never[])) as Record<string, unknown>[];\n return { op: \"query\", rows };\n }\n\n /** \u89E3\u6790\u4E3B\u952E WHERE\u3002\u5355\u5217\u76F4\u63A5\u7B49\u503C;\u8054\u5408\u4E3B\u952E\u652F\u6301 `a|b|c`(\u4E0E data-backup \u7EA6\u5B9A\u4E00\u81F4)\u3002 */\n private pkWhere(table: string, id: string | number): { clause: string; values: unknown[] } {\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${table}\")`).all() as Array<{\n name: string;\n pk: number;\n }>;\n const pkCols = cols\n .filter((c) => c.pk > 0)\n .sort((a, b) => a.pk - b.pk)\n .map((c) => c.name);\n if (pkCols.length === 0) {\n throw new Error(`[tx] ${table} \u6CA1\u6709 primary key`);\n }\n if (pkCols.length === 1) {\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n const parts = String(id).split(\"|\");\n if (parts.length === pkCols.length) {\n return {\n clause: pkCols.map((c) => `\"${c}\" = ?`).join(\" AND \"),\n values: parts,\n };\n }\n // \u8054\u5408\u4E3B\u952E\u4F46\u53EA\u4F20\u4E86\u9996\u5217(\u9891\u9053 id \u7B49):\u6309\u7B2C\u4E00\u4E3B\u952E\u5217\u5339\u914D\n return { clause: `\"${pkCols[0]}\" = ?`, values: [id] };\n }\n\n private doGet(_mid: string, mod: ModuleManifestV2, step: TxStepGet): TxStepResult {\n this.requireTableRead(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n const row = this.deps.db\n .prepare(`SELECT * FROM \"${step.table}\" WHERE ${where.clause}`)\n .get(...(where.values as never[])) as Record<string, unknown> | undefined;\n return { op: \"get\", row: row ?? null };\n }\n\n private doInsert(_mid: string, mod: ModuleManifestV2, step: TxStepInsert): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.row);\n if (cols.length === 0) throw new Error(\"[tx] insert row \u5FC5\u987B\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const placeholders = cols.map(() => \"?\").join(\",\");\n const quoted = cols.map((c) => `\"${c}\"`).join(\",\");\n const values = cols.map((c) => step.row[c]);\n const sql = `INSERT INTO \"${step.table}\" (${quoted}) VALUES (${placeholders})`;\n try {\n this.deps.db.prepare(sql).run(...(values as never[]));\n } catch (e) {\n throw new Error(`INSERT ${step.table} \u5931\u8D25: ${(e as Error).message}`);\n }\n return { op: \"insert\", row: { ...step.row } };\n }\n\n private doUpdate(_mid: string, mod: ModuleManifestV2, step: TxStepUpdate): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const cols = Object.keys(step.patch);\n if (cols.length === 0) throw new Error(\"[tx] update patch \u4E0D\u80FD\u4E3A\u7A7A\");\n for (const c of cols) {\n if (!IDENT.test(c)) throw new Error(`[tx] invalid column name \"${c}\"`);\n }\n const where = this.pkWhere(step.table, step.id);\n const sets = cols.map((c) => `\"${c}\" = ?`).join(\",\");\n const params = [...cols.map((c) => step.patch[c]), ...where.values];\n const sql = `UPDATE \"${step.table}\" SET ${sets} WHERE ${where.clause}`;\n const result = this.deps.db.prepare(sql).run(...(params as never[]));\n if (result.changes === 0) {\n throw new Error(`UPDATE ${step.table} id=${step.id} \u5F71\u54CD 0 \u884C(\u627E\u4E0D\u5230 / \u5DF2\u5220?)`);\n }\n return { op: \"update\", row: { ...step.patch, id: step.id } as unknown as Record<string, unknown> };\n }\n\n private doDelete(_mid: string, mod: ModuleManifestV2, step: TxStepDelete): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const where = this.pkWhere(step.table, step.id);\n if (step.hard) {\n const sql = `DELETE FROM \"${step.table}\" WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n const cols = this.deps.db.prepare(`PRAGMA table_info(\"${step.table}\")`).all() as Array<{\n name: string;\n }>;\n const hasDeletedAt = cols.find((c) => c.name === \"_deleted_at\");\n if (!hasDeletedAt) {\n throw new Error(`[tx] ${step.table} \u6CA1\u542F\u7528 softDelete,\u5220\u4E0D\u6389;\u8BF7 hard=true \u6216 schema \u8BBE softDelete=true`);\n }\n const sql = `UPDATE \"${step.table}\" SET \"_deleted_at\" = ?, \"_version\" = COALESCE(\"_version\",0)+1 WHERE ${where.clause}`;\n const r = this.deps.db.prepare(sql).run(Date.now() as never, ...(where.values as never[]));\n return { op: \"delete\", changes: Number(r.changes) };\n }\n\n private doAudit(_mid: string, mod: ModuleManifestV2, step: TxStepAudit): TxStepResult {\n this.requireTableWrite(mod, step.table);\n const dataJson = step.data ? JSON.stringify(step.data) : null;\n const r = this.deps.db\n .prepare(\n `INSERT INTO sfmc__audit (module_id, table_name, row_id, action, data, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`\n )\n .run(mod.id, step.table, step.rowId, step.action, dataJson, new Date().toISOString());\n return { op: \"audit\", changes: Number(r.changes) };\n }\n\n private async doService(_mid: string, mod: ModuleManifestV2, step: TxStepService): Promise<TxStepResult> {\n // requires / \u81EA\u8C03\u7528\u8C41\u514D\u7684\u552F\u4E00\u6743\u5A01\u5728 ServiceRegistry.dispatch(DRY/LSP);\n // \u6B64\u5904\u53EA\u8865 dispatch \u4E0D\u505A\u7684 service:<name> \u6743\u9650\u95E8(\u4E0E HTTP service-routes \u5BF9\u9F50)\u3002\n assertModulePermission(mod.id, mod.permissions, Perm.service(step.name));\n const result = await this.deps.serviceRegistry.dispatch(this.deps.enabled, mod.id, step.name, step.input, {\n query: this.deps.query,\n db: this.deps.db,\n });\n return { op: \"service\", result: result.result };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["sql", "r"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfmc-bds/db-server",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
5
5
  "author": "Shiroha7z",
6
6
  "license": "AGPL-3.0-only",
@@ -60,11 +60,11 @@
60
60
  ],
61
61
  "dependencies": {
62
62
  "sql-template-strings": "^2.2.2",
63
- "@sfmc-bds/sdk": "0.7.0",
64
- "@sfmc-bds/bds-tools": "0.3.1"
63
+ "@sfmc-bds/bds-tools": "0.3.3",
64
+ "@sfmc-bds/sdk": "0.7.2"
65
65
  },
66
66
  "devDependencies": {
67
- "@sfmc-bds/tools": "0.2.12"
67
+ "@sfmc-bds/tools": "0.2.14"
68
68
  },
69
69
  "publishConfig": {
70
70
  "access": "public",