feat(server): ServerModule skeleton with shared ServerContext, worker runs engine + outbox relay (B6)
modules/{auth-settings,sources-identity,acquisition-organize,agent} placeholders listed in
SERVER_MODULES; ServerModule registers routes (under /api/v1, authenticated), tasks,
outbox routes, capabilities and background loops. bootstrap() builds ServerContext (db,
config, logger, events, engine, outbox writer, secrets placeholder, settings reader,
capability registry), fills unregistered tasks with stubs and rejects duplicate task or
outbox ownership. worker starts the OpenWorkflow worker, OutboxRelay and module start().
--role migrate also migrates the engine schema; api/worker connect without migrating.
tasks: TaskRegistry.has, migrateEngineSchema.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ef3998767c
commit
9858b8494d
23 files changed
+829
-22
No files matched your search
@@ -1,16 +1,35 @@
|
||||
import { EventType } from "@umt/contracts";
|
||||
import { OutboxRelay } from "@umt/tasks";
|
||||
import type { Logger } from "pino";
|
||||
import type { EventBus } from "../http/sse.ts";
|
||||
import { collectOutboxRoutes, tasksLogSink } from "../context.ts";
|
||||
import type { EventBusPort } from "../http/sse.ts";
|
||||
import type { ServerContext, ServerModule } from "../modules/types.ts";
|
||||
|
||||
export interface WorkerDeps {
|
||||
logger: Logger;
|
||||
heartbeatMs: number;
|
||||
/** role=all 时与 API 共享同一总线,心跳可在 SSE 中看到 */
|
||||
bus?: EventBus;
|
||||
bus?: EventBusPort;
|
||||
/** 业务装配;省略时只跑心跳(测试用) */
|
||||
runtime?: {
|
||||
ctx: ServerContext;
|
||||
modules: readonly ServerModule[];
|
||||
/** 设置 outbox 中继实例到 ctx.outbox.relay */
|
||||
setRelay?: (relay: OutboxRelay) => void;
|
||||
outboxIntervalMs?: number;
|
||||
concurrency?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/** worker 占位:只打印心跳。持久任务引擎在 W1(packages/tasks)接入 */
|
||||
export function startWorker({ logger, heartbeatMs, bus }: WorkerDeps) {
|
||||
export interface RunningWorker {
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* worker 角色:心跳 + 任务引擎 worker(执行全部已注册任务)+ OutboxRelay(按模块的 outboxRoutes 路由)
|
||||
* + 各模块的后台循环(start)。stop 逆序停止;引擎连接本身由进程退出时关闭。
|
||||
*/
|
||||
export async function startWorker({ logger, heartbeatMs, bus, runtime }: WorkerDeps): Promise<RunningWorker> {
|
||||
const log = logger.child({ component: "worker" });
|
||||
let n = 0;
|
||||
const beat = () => {
|
||||
@@ -18,12 +37,41 @@ export function startWorker({ logger, heartbeatMs, bus }: WorkerDeps) {
|
||||
log.debug({ n }, "worker heartbeat");
|
||||
bus?.publish(EventType.heartbeat, { role: "worker", n });
|
||||
};
|
||||
log.info({ heartbeatMs }, "worker started");
|
||||
|
||||
const started: ServerModule[] = [];
|
||||
let relay: OutboxRelay | null = null;
|
||||
if (runtime) {
|
||||
const { ctx, modules } = runtime;
|
||||
await ctx.engine.startWorker({ ...(runtime.concurrency ? { concurrency: runtime.concurrency } : {}) });
|
||||
relay = new OutboxRelay({
|
||||
db: ctx.db,
|
||||
engine: ctx.engine,
|
||||
routes: collectOutboxRoutes(modules, ctx),
|
||||
logSink: tasksLogSink(logger),
|
||||
...(runtime.outboxIntervalMs ? { intervalMs: runtime.outboxIntervalMs } : {}),
|
||||
});
|
||||
runtime.setRelay?.(relay);
|
||||
relay.start();
|
||||
for (const m of modules) {
|
||||
await m.start?.(ctx);
|
||||
started.push(m);
|
||||
}
|
||||
}
|
||||
|
||||
log.info({ heartbeatMs, modules: started.map((m) => m.name) }, "worker started");
|
||||
beat();
|
||||
const timer = setInterval(beat, heartbeatMs);
|
||||
return {
|
||||
async stop() {
|
||||
clearInterval(timer);
|
||||
for (const m of started.reverse()) {
|
||||
try {
|
||||
await m.stop?.();
|
||||
} catch (err) {
|
||||
log.error({ err, module: m.name }, "模块停止失败");
|
||||
}
|
||||
}
|
||||
await relay?.stop();
|
||||
log.info("worker stopped");
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user