- contracts:补齐生命周期广播事件(订阅、下载、整理结束、刮削、字幕、作品关注 / 元数据 / 季集结构、站点可达性) 的类型与 payload schema,LIFECYCLE_EVENTS 清单与 lifecycleKey 幂等键;HOOK_POINTS 链式钩子挂点; FEATURES 功能清单与 SLOT_POSITIONS 前端插槽位置 - outbox 中继支持一条事件路由给多个订阅方(其余订阅方用 <键>#<任务名>);生命周期事件允许多模块订阅, 没有订阅方时也被中继认领并镜像到 SSE - 服务端 HookRegistry:按注册顺序执行、rewrite / intercept 两类、单钩子超时与异常隔离、无注册零开销; ServerModule 增加 manifest 与 registerHooks,bootstrap 装配 ctx.hooks - ADR-006 与 server README「如何写一个功能模块」 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
import type { HookPoint } from "@umt/contracts";
|
|
import type { ServerContext } from "../modules/types.ts";
|
|
import type { HookContext, HookValue, InterceptPoint, RewritePoint } from "./points.ts";
|
|
import type { HookRejection } from "./registry.ts";
|
|
|
|
export type * from "./points.ts";
|
|
export {
|
|
DEFAULT_HOOK_TIMEOUT_MS,
|
|
type HookOptions,
|
|
type HookRegistrar,
|
|
HookRegistry,
|
|
type HookRejection,
|
|
} from "./registry.ts";
|
|
|
|
// 业务节点调用钩子的入口。ctx.hooks 由 bootstrap 装配;测试里手搭的上下文没有它,等同于没有注册。
|
|
|
|
/** 这个挂点有没有钩子(上下文构造代价大时先问一下) */
|
|
export function hasHook(ctx: Pick<ServerContext, "hooks">, point: HookPoint): boolean {
|
|
return ctx.hooks?.has(point) ?? false;
|
|
}
|
|
|
|
/** 改写:没有钩子时原样返回同一个值;accept 判定单个钩子的结果能不能用(不能用的跳过) */
|
|
export async function rewriteHook<P extends RewritePoint>(
|
|
ctx: Pick<ServerContext, "hooks">,
|
|
point: P,
|
|
value: HookValue<P>,
|
|
context: HookContext<P> | (() => HookContext<P>),
|
|
accept?: (v: HookValue<P>) => boolean,
|
|
): Promise<HookValue<P>> {
|
|
const h = ctx.hooks;
|
|
if (!h?.has(point)) return value;
|
|
return h.runRewrite(point, value, typeof context === "function" ? context() : context, accept);
|
|
}
|
|
|
|
/** 拦截:返回第一个拒绝,没有钩子或都放行时返回 null */
|
|
export async function interceptHook<P extends InterceptPoint>(
|
|
ctx: Pick<ServerContext, "hooks">,
|
|
point: P,
|
|
context: HookContext<P> | (() => HookContext<P>),
|
|
): Promise<HookRejection | null> {
|
|
const h = ctx.hooks;
|
|
if (!h?.has(point)) return null;
|
|
return h.runIntercept(point, typeof context === "function" ? context() : context);
|
|
}
|