import type { Context } from "hono"; import { Hono } from "hono"; import type { AuthEnv } from "../../http/auth.ts"; import { ApiException } from "../../http/errors.ts"; import type { ModuleApp } from "../types.ts"; // 进程内转调其它模块的 HTTP 端点(库存叠加 GET /inventory、下载 POST /acquisition-plans): // 走对方真实的路由(scope 校验 / 入参校验 / 审计 / 业务逻辑都不复制),调用者身份沿用本次请求已通过鉴权的 principal。 // 所有模块路由注册完后第一次调用时才组装(route() 按调用时刻复制路由表)。 interface Origin { principal: AuthEnv["Variables"]["principal"]; actor: AuthEnv["Variables"]["actor"]; session: AuthEnv["Variables"]["session"]; requestId: string | undefined; } export class InternalDispatcher { private inner: Hono | null = null; private readonly origins = new WeakMap(); private readonly errors = new WeakMap(); constructor(private readonly api: ModuleApp) {} private app(): Hono { if (this.inner) return this.inner; const inner = new Hono(); inner.use("*", async (c, next) => { const o = this.origins.get(c.req.raw); if (!o) throw new ApiException("unauthorized", "内部调用缺少调用者"); c.set("principal", o.principal); c.set("actor", o.actor); c.set("session", o.session); if (o.requestId) c.set("requestId" as never, o.requestId as never); await next(); }); inner.route("/", this.api); // 原样抛回原始错误(ApiException 由外层 app.onError 转信封) inner.onError((err, c) => { this.errors.set(c.req.raw, err); return c.body(null, 500); }); inner.notFound((c) => { this.errors.set(c.req.raw, new ApiException("internal", `内部端点不存在:${c.req.method} ${c.req.path}`)); return c.body(null, 404); }); this.inner = inner; return inner; } async call(from: Context, method: "GET" | "POST", path: string, body?: unknown): Promise { const req = new Request(`http://internal${path}`, { method, signal: from.req.raw.signal, ...(body !== undefined ? { headers: { "content-type": "application/json" }, body: JSON.stringify(body) } : {}), }); this.origins.set(req, { principal: from.get("principal"), actor: from.get("actor"), session: from.get("session"), requestId: from.get("requestId" as never) as string | undefined, }); const res = await this.app().fetch(req); const err = this.errors.get(req); if (err) throw err; const json = (await res.json()) as { ok: boolean; data?: T; error?: { code: string; message: string } }; if (!json.ok) { throw new ApiException( (json.error?.code ?? "internal") as never, json.error?.message ?? `内部调用失败(${res.status})`, ); } return json.data as T; } }