import { Elysia } from 'elysia'; import type { ModuleDeps } from './types.js'; import { getModuleRequestUser } from './module-user.js'; import { createRequestAudit } from '../business-audit.js'; /** * 创建模块根 Elysia 实例(统一 name / prefix / seed)。 * * 自动把平台网关校验过的登录用户 derive 到 Elysia context 里,handler 直接 * 解构 `user` 即可用(等价于其他框架里的 `request.user`),不需要每个 * handler 手动调用 `getModuleRequestUser(request)`。能走到模块自己的 API 的 * 请求已经在网关层(apps/api dispatch 前)校验过登录态,这里的 user 正常 * 情况下不为 null;仅本地绕过网关直连时才可能是 null,建议仍按 null 兜底。 * * 子路由拆到独立文件时,用 {@link ModuleApp} 类型接收/传递已经创建好的根 * 实例,直接在上面链 `.get()/.post()`(而不是各自 `new Elysia()` 建一个独立 * 实例再 `.use()` 拼起来): * ```ts * function sprintsRoutes(app: ModuleApp, deps: ModuleDeps) { * return app.get('/sprints', ({ user }) => success({ ... })) * } * export default (deps: ModuleDeps) => sprintsRoutes(createModuleApp(deps), deps) * ``` * 这样写既保留了 user 的类型推断,也避免了多份独立实例各自带 `prefix`/ * `name` 造成的路径重复前缀或插件去重冲突。 */ export function createModuleApp(deps: ModuleDeps) { const auditSink = deps.auditSink ?? { async write(): Promise { throw new Error('当前平台未提供业务审计能力,请升级 Stago API'); }, }; return new Elysia({ name: `module:${deps.moduleName}`, seed: { version: deps.version }, prefix: deps.routeBase, }).derive(({ request }) => { const user = getModuleRequestUser(request); return { user, audit: createRequestAudit(auditSink, user, request) }; }); } /** {@link createModuleApp} 返回值类型,供拆分到独立文件的子路由函数做参数类型标注 */ export type ModuleApp = ReturnType;