@sfmc-bds/db-server 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domain/cpu-usage.d.ts +43 -0
- package/dist/domain/cpu-usage.d.ts.map +1 -0
- package/dist/domain/cpu-usage.js +71 -0
- package/dist/domain/cpu-usage.js.map +7 -0
- package/dist/domain/metrics.d.ts +8 -0
- package/dist/domain/metrics.d.ts.map +1 -1
- package/dist/domain/metrics.js +16 -4
- package/dist/domain/metrics.js.map +2 -2
- package/dist/domain/process-resources.d.ts +2 -0
- package/dist/domain/process-resources.d.ts.map +1 -1
- package/dist/domain/process-resources.js.map +2 -2
- package/dist/domain/scoreboard.d.ts +122 -0
- package/dist/domain/scoreboard.d.ts.map +1 -0
- package/dist/domain/scoreboard.js +134 -0
- package/dist/domain/scoreboard.js.map +7 -0
- package/dist/domain/tracked-processes.d.ts +2 -0
- package/dist/domain/tracked-processes.d.ts.map +1 -1
- package/dist/domain/tracked-processes.js.map +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +2 -2
- package/dist/routes/db-routes.d.ts +1 -0
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js +5 -0
- package/dist/routes/db-routes.js.map +2 -2
- package/dist/routes/scoreboard.d.ts +20 -0
- package/dist/routes/scoreboard.d.ts.map +1 -0
- package/dist/routes/scoreboard.js +30 -0
- package/dist/routes/scoreboard.js.map +7 -0
- package/dist/routes/status.d.ts +6 -1
- package/dist/routes/status.d.ts.map +1 -1
- package/dist/routes/status.js +33 -8
- package/dist/routes/status.js.map +2 -2
- package/dist/tx-runner.d.ts +10 -0
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +17 -0
- package/dist/tx-runner.js.map +2 -2
- package/package.json +9 -4
|
@@ -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": "
|
|
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
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routes/scoreboard.ts — 世界计分板快照
|
|
3
|
+
*
|
|
4
|
+
* 路由列表:
|
|
5
|
+
* POST /api/sfmc/scoreboard/live — 游戏内平台脚本上报快照 / 哈希 / 空闲探询(走旧 token 鉴权)
|
|
6
|
+
* GET /api/sfmc/scoreboard — CLI 的 scoreboard.read 读取快照,并让脚本在关注期内持续同步
|
|
7
|
+
*
|
|
8
|
+
* 规则见 domain/scoreboard.ts。
|
|
9
|
+
*/
|
|
10
|
+
import { ScoreboardStore } from "../domain/scoreboard.js";
|
|
11
|
+
import { type RouteHandler } from "./_shared.js";
|
|
12
|
+
interface Deps {
|
|
13
|
+
/** 测试可注入独立实例;缺省每个路由实例各持有一个 */
|
|
14
|
+
store?: ScoreboardStore;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
}
|
|
17
|
+
/** 创建计分板路由。使用场景:index.ts 平台路由链。 */
|
|
18
|
+
export declare function createScoreboardRoutes({ store, now }?: Deps): RouteHandler;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=scoreboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoreboard.d.ts","sourceRoot":"","sources":["../../src/routes/scoreboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAyB,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAE7D,UAAU,IAAI;IACZ,8BAA8B;IAC9B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,mCAAmC;AACnC,wBAAgB,sBAAsB,CAAC,EAAE,KAA6B,EAAE,GAAc,EAAE,GAAE,IAAS,GAAG,YAAY,CAuBjH"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseScoreboardReport, ScoreboardStore } from "../domain/scoreboard.js";
|
|
2
|
+
import { body, json } from "./_shared.js";
|
|
3
|
+
function createScoreboardRoutes({ store = new ScoreboardStore(), now = Date.now } = {}) {
|
|
4
|
+
return async function handle({ path, method, req, res }) {
|
|
5
|
+
if (path === "/api/sfmc/scoreboard/live") {
|
|
6
|
+
if (method !== "POST") {
|
|
7
|
+
json(res, { success: false, error: "not_found" }, 404);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
const report = parseScoreboardReport(await body(req), now());
|
|
11
|
+
if (!report) {
|
|
12
|
+
json(res, { success: false, error: "invalid_scoreboard_report" }, 400);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
json(res, { success: true, ...store.report(report, now()) });
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (path !== "/api/sfmc/scoreboard") return false;
|
|
19
|
+
if (method !== "GET") {
|
|
20
|
+
json(res, { success: false, error: "not_found" }, 404);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
json(res, { success: true, ...store.read(now()) });
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
createScoreboardRoutes
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=scoreboard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/scoreboard.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * routes/scoreboard.ts \u2014 \u4E16\u754C\u8BA1\u5206\u677F\u5FEB\u7167\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * POST /api/sfmc/scoreboard/live \u2014 \u6E38\u620F\u5185\u5E73\u53F0\u811A\u672C\u4E0A\u62A5\u5FEB\u7167 / \u54C8\u5E0C / \u7A7A\u95F2\u63A2\u8BE2\uFF08\u8D70\u65E7 token \u9274\u6743\uFF09\n * GET /api/sfmc/scoreboard \u2014 CLI \u7684 scoreboard.read \u8BFB\u53D6\u5FEB\u7167\uFF0C\u5E76\u8BA9\u811A\u672C\u5728\u5173\u6CE8\u671F\u5185\u6301\u7EED\u540C\u6B65\n *\n * \u89C4\u5219\u89C1 domain/scoreboard.ts\u3002\n */\n\nimport { parseScoreboardReport, ScoreboardStore } from \"../domain/scoreboard.js\";\nimport { body, json, type RouteHandler } from \"./_shared.js\";\n\ninterface Deps {\n /** \u6D4B\u8BD5\u53EF\u6CE8\u5165\u72EC\u7ACB\u5B9E\u4F8B\uFF1B\u7F3A\u7701\u6BCF\u4E2A\u8DEF\u7531\u5B9E\u4F8B\u5404\u6301\u6709\u4E00\u4E2A */\n store?: ScoreboardStore;\n now?: () => number;\n}\n\n/** \u521B\u5EFA\u8BA1\u5206\u677F\u8DEF\u7531\u3002\u4F7F\u7528\u573A\u666F\uFF1Aindex.ts \u5E73\u53F0\u8DEF\u7531\u94FE\u3002 */\nexport function createScoreboardRoutes({ store = new ScoreboardStore(), now = Date.now }: Deps = {}): RouteHandler {\n return async function handle({ path, method, req, res }): Promise<boolean> {\n if (path === \"/api/sfmc/scoreboard/live\") {\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n const report = parseScoreboardReport(await body(req), now());\n if (!report) {\n json(res, { success: false, error: \"invalid_scoreboard_report\" }, 400);\n return true;\n }\n json(res, { success: true, ...store.report(report, now()) });\n return true;\n }\n if (path !== \"/api/sfmc/scoreboard\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, { success: true, ...store.read(now()) });\n return true;\n };\n}\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,uBAAuB,uBAAuB;AACvD,SAAS,MAAM,YAA+B;AASvC,SAAS,uBAAuB,EAAE,QAAQ,IAAI,gBAAgB,GAAG,MAAM,KAAK,IAAI,IAAU,CAAC,GAAiB;AACjH,SAAO,eAAe,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,GAAqB;AACzE,QAAI,SAAS,6BAA6B;AACxC,UAAI,WAAW,QAAQ;AACrB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,eAAO;AAAA,MACT;AACA,YAAM,SAAS,sBAAsB,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC;AAC3D,UAAI,CAAC,QAAQ;AACX,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,4BAA4B,GAAG,GAAG;AACrE,eAAO;AAAA,MACT;AACA,WAAK,KAAK,EAAE,SAAS,MAAM,GAAG,MAAM,OAAO,QAAQ,IAAI,CAAC,EAAE,CAAC;AAC3D,aAAO;AAAA,IACT;AACA,QAAI,SAAS,uBAAwB,QAAO;AAC5C,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AACA,SAAK,KAAK,EAAE,SAAS,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;AACjD,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/routes/status.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/routes/status.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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": "AAEA,SAAS,2BAAsD;AAC/D,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB,gBAAgB,iBAAiB,kBAAkB,oBAAwC;AAC3H,SAAS,+BAA+B;AACxC,SAAS,+BAA+B;
|
|
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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/tx-runner.d.ts
CHANGED
|
@@ -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;
|
package/dist/tx-runner.d.ts.map
CHANGED
|
@@ -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;
|