Files
UHD_Media_Tool/apps/server/src/hooks/index.ts
T
rootandClaude Opus 5.5 1db689d9a7 feat: 生命周期广播事件与链式钩子设施(ADR-006)
- 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
2026-10-06 19:27:02 +08:00

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);
}