import * as elysia from 'elysia'; import { Elysia } from 'elysia'; import { z } from 'zod'; import React from 'react'; import { Pool } from 'pg'; /** * 平台在把请求转发给模块自己的 API(dist/api.js)之前,会先校验登录态, * 校验通过后把用户信息塞进这个内部请求头再转发。未登录的请求已经在到达 * 这里之前被平台拦截为 401 了。 * * 这是 apps/api(编码/写头,见 _utils/module-auth.ts)与本包的 * createModuleApp(解码/读头,见 create-module-app.ts)之间的内部协议, * 模块作者不需要关心:`createModuleApp()` 已经自动把解析结果 derive 到 * Elysia context 里,handler 直接解构 `user` 即可,等价于其他框架的 * `request.user`。因此本文件里只有 `MODULE_USER_HEADER`/`ModuleRequestUser` * 对外导出(供 apps/api 编码时复用同一份契约),`getModuleRequestUser` 本身 * 不对外导出,只在包内被 create-module-app.ts 调用。 */ declare const MODULE_USER_HEADER = "x-stago-user"; /** 网关透传给模块 API 的用户;字段与 live.employees 一一对应:id / phone / name */ interface ModuleRequestUser { id: number; phone: string; name: string | null; roles: string[]; } /** 将用户 payload 编码为 MODULE_USER_HEADER 值(与 apps/api attachModuleUserHeader 同源) */ declare function encodeModuleRequestUser(user: ModuleRequestUser): string; /** 克隆请求并写入 MODULE_USER_HEADER(Request 不可变) */ declare function attachModuleRequestUserHeader(request: Request, user: ModuleRequestUser): Request; type AuditJsonValue = null | boolean | number | string | AuditJsonValue[] | { [key: string]: AuditJsonValue; }; interface BusinessAuditEvent { action?: string; summary: string; target: { type: string; id: string | number; name?: string; }; changes?: Record; metadata?: Record; } interface BusinessAuditRecord extends BusinessAuditEvent { action?: string; actorId: number; requestId: string; sourceIp?: string; userAgent?: string; } interface BusinessAuditSink { write(record: BusinessAuditRecord): Promise<{ id: string; }>; } interface RequestAudit { record(event: BusinessAuditEvent): Promise<{ id: string; }>; } declare class AuditValidationError extends Error { readonly code = "AUDIT_VALIDATION_ERROR"; constructor(message: string); } declare class AuditIdentityError extends Error { readonly code = "AUDIT_IDENTITY_ERROR"; constructor(); } declare function normalizeBusinessAuditEvent(event: BusinessAuditEvent): BusinessAuditEvent; declare function createRequestAudit(sink: BusinessAuditSink, user: ModuleRequestUser | null, request: Request): RequestAudit; /** 模块可用的 scoped 数据库客户端 */ interface ModuleDb { query(sql: string, params?: unknown[]): Promise<{ rows: unknown[]; }>; /** * 在单一连接上执行事务。回调内必须使用 `tx.query`,不要再用外层 `db.query`。 * 不支持嵌套。默认 statement_timeout 30s;`options.timeoutMs` 可覆盖但钳制在 * 1s–120s,非法值(≤0/NaN/Infinity)回落默认,无法关闭。事务空转由 * idle_in_transaction_session_timeout(=上限)兜底断开。 */ transaction(fn: (tx: ModuleDb) => Promise, options?: { timeoutMs?: number; }): Promise; } /** 模块日志接口 */ interface ModuleLogger { info(message: string, data?: unknown): void; warn(message: string, data?: unknown): void; error(message: string, data?: unknown): void; } /** * 平台注入给模块的依赖(由 @stago/api 在 load 时构造) */ interface ModuleDeps { moduleName: string; version: string; /** module.json `api.prefix`,如 `/insight` */ mountPrefix: string; /** 完整 API 路由前缀,如 `/insight/api` */ routeBase: string; db: ModuleDb; log: ModuleLogger; /** 平台提供的业务审计持久化端;模块通过请求上下文中的 audit.record() 调用。 */ auditSink?: BusinessAuditSink; /** * 平台注入的模块专属配置(来自 stago.module_env / 本地 createDevModuleDeps)。 * 业务密钥只应从这里读取,不要读 process.env 中的平台机密。 */ env: Readonly>; } /** 模块 API 入口:export default (deps) => new Elysia(...) * BasePath 显式声明为 string(而非默认的字面量 ''),因为模块通常会用 * deps.routeBase(动态 string)作为 prefix,返回类型的字面量 prefix 会被推断为 string。 */ type ModulePluginFactory = (deps: ModuleDeps) => elysia.Elysia; /** 标准成功响应体(Elysia handler 直接 return) */ declare function success(data: T, message?: string): { success: true; data: T; message: string; } | { success: true; data: T; message?: undefined; }; /** 标准错误响应体;配合 set.status 使用 */ declare function fail(code: string, message: string): { success: false; error: { code: string; message: string; }; }; /** * 创建模块根 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` 造成的路径重复前缀或插件去重冲突。 */ declare function createModuleApp(deps: ModuleDeps): Elysia; }>; /** {@link createModuleApp} 返回值类型,供拆分到独立文件的子路由函数做参数类型标注 */ type ModuleApp = ReturnType; /** 模块内嵌套菜单项(支持任意层级 children) */ type MenuItemConfigInput = { key: string; title: string; icon?: string; path?: string; children?: MenuItemConfigInput[]; }; declare const MenuItemSchema: z.ZodType; /** * Menu configuration schema */ declare const MenuSchema: z.ZodObject<{ title: z.ZodString; icon: z.ZodDefault; level: z.ZodDefault; parent: z.ZodDefault; order: z.ZodDefault; children: z.ZodOptional, "many">>; }, "strict", z.ZodTypeAny, { title: string; icon: string; level: number; parent: string; order: number; children?: MenuItemConfigInput[] | undefined; }, { title: string; icon?: string | undefined; children?: MenuItemConfigInput[] | undefined; level?: number | undefined; parent?: string | undefined; order?: number | undefined; }>; /** * Federation configuration schema */ declare const FederationSchema: z.ZodObject<{ exposes: z.ZodArray; remotes: z.ZodDefault>>; shared: z.ZodDefault, z.ZodRecord; requiredVersion: z.ZodOptional; eager: z.ZodDefault; }, "strict", z.ZodTypeAny, { singleton: boolean; eager: boolean; requiredVersion?: string | undefined; }, { singleton?: boolean | undefined; requiredVersion?: string | undefined; eager?: boolean | undefined; }>>]>>; }, "strict", z.ZodTypeAny, { exposes: string[]; remotes: Record; shared: string[] | Record; }, { exposes: string[]; remotes?: Record | undefined; shared?: string[] | Record | undefined; }>; /** * API configuration schema */ declare const ApiSchema: z.ZodObject<{ routes: z.ZodOptional, "many">; handler: z.ZodOptional; }, "strict", z.ZodTypeAny, { methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; handler?: string | undefined; }, { methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; handler?: string | undefined; }>>>; prefix: z.ZodDefault; handlers: z.ZodOptional; }, "strict", z.ZodTypeAny, { prefix: string; routes?: Record | undefined; handlers?: string | undefined; }, { prefix?: string | undefined; routes?: Record | undefined; handlers?: string | undefined; }>; /** * Data binding configuration schema */ declare const DataBindingSchema: z.ZodObject<{ table: z.ZodString; schema: z.ZodOptional; required: z.ZodDefault; unique: z.ZodDefault; label: z.ZodOptional; }, "strict", z.ZodTypeAny, { type: "string" | "number" | "boolean" | "date" | "json"; required: boolean; unique: boolean; label?: string | undefined; }, { type: "string" | "number" | "boolean" | "date" | "json"; required?: boolean | undefined; unique?: boolean | undefined; label?: string | undefined; }>>>; permissions: z.ZodDefault, "many">>; }, "strict", z.ZodTypeAny, { table: string; permissions: ("read" | "write" | "admin")[]; schema?: Record | undefined; }, { table: string; schema?: Record | undefined; permissions?: ("read" | "write" | "admin")[] | undefined; }>; /** * Module kind schema. * - `app`: 前后端一体模块(默认)。必须提供 `menu` 与 `federation`,由底座以 * Module Federation 远程加载前端。 * - `api-only`: 仅托管后端 API 的模块。无前端、无菜单、无 federation,前端由 * 业务方独立部署与访问,不经过云台页面。必须提供 `api.prefix`。 */ declare const KindSchema: z.ZodEnum<["app", "api-only"]>; /** * Full module configuration schema * * `menu` 与 `federation` 在基础对象层声明为可选,再由 `superRefine` 按 `kind` * 条件强制:`app` 必填两者,`api-only` 必填 `api`。不用 `discriminatedUnion` * 是因为省略 `kind` 时需要回退到默认 `app` 以保持向后兼容,而判别联合在缺省 * 判别字段时无法命中任何变体。 */ declare const ModuleConfigSchema: z.ZodEffects>; description: z.ZodDefault>; author: z.ZodDefault>; menu: z.ZodOptional; level: z.ZodDefault; parent: z.ZodDefault; order: z.ZodDefault; children: z.ZodOptional, "many">>; }, "strict", z.ZodTypeAny, { title: string; icon: string; level: number; parent: string; order: number; children?: MenuItemConfigInput[] | undefined; }, { title: string; icon?: string | undefined; children?: MenuItemConfigInput[] | undefined; level?: number | undefined; parent?: string | undefined; order?: number | undefined; }>>; federation: z.ZodOptional; remotes: z.ZodDefault>>; shared: z.ZodDefault, z.ZodRecord; requiredVersion: z.ZodOptional; eager: z.ZodDefault; }, "strict", z.ZodTypeAny, { singleton: boolean; eager: boolean; requiredVersion?: string | undefined; }, { singleton?: boolean | undefined; requiredVersion?: string | undefined; eager?: boolean | undefined; }>>]>>; }, "strict", z.ZodTypeAny, { exposes: string[]; remotes: Record; shared: string[] | Record; }, { exposes: string[]; remotes?: Record | undefined; shared?: string[] | Record | undefined; }>>; api: z.ZodOptional, "many">; handler: z.ZodOptional; }, "strict", z.ZodTypeAny, { methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; handler?: string | undefined; }, { methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; handler?: string | undefined; }>>>; prefix: z.ZodDefault; handlers: z.ZodOptional; }, "strict", z.ZodTypeAny, { prefix: string; routes?: Record | undefined; handlers?: string | undefined; }, { prefix?: string | undefined; routes?: Record | undefined; handlers?: string | undefined; }>>; client: z.ZodOptional>; dataBinding: z.ZodOptional; required: z.ZodDefault; unique: z.ZodDefault; label: z.ZodOptional; }, "strict", z.ZodTypeAny, { type: "string" | "number" | "boolean" | "date" | "json"; required: boolean; unique: boolean; label?: string | undefined; }, { type: "string" | "number" | "boolean" | "date" | "json"; required?: boolean | undefined; unique?: boolean | undefined; label?: string | undefined; }>>>; permissions: z.ZodDefault, "many">>; }, "strict", z.ZodTypeAny, { table: string; permissions: ("read" | "write" | "admin")[]; schema?: Record | undefined; }, { table: string; schema?: Record | undefined; permissions?: ("read" | "write" | "admin")[] | undefined; }>>; }, "strict", z.ZodTypeAny, { name: string; version: string; kind: "app" | "api-only"; description: string; author: string; menu?: { title: string; icon: string; level: number; parent: string; order: number; children?: MenuItemConfigInput[] | undefined; } | undefined; federation?: { exposes: string[]; remotes: Record; shared: string[] | Record; } | undefined; api?: { prefix: string; routes?: Record | undefined; handlers?: string | undefined; } | undefined; client?: { logtoAppId: string; } | undefined; dataBinding?: { table: string; permissions: ("read" | "write" | "admin")[]; schema?: Record | undefined; } | undefined; }, { name: string; version: string; kind?: "app" | "api-only" | undefined; description?: string | undefined; author?: string | undefined; menu?: { title: string; icon?: string | undefined; children?: MenuItemConfigInput[] | undefined; level?: number | undefined; parent?: string | undefined; order?: number | undefined; } | undefined; federation?: { exposes: string[]; remotes?: Record | undefined; shared?: string[] | Record | undefined; } | undefined; api?: { prefix?: string | undefined; routes?: Record | undefined; handlers?: string | undefined; } | undefined; client?: { logtoAppId: string; } | undefined; dataBinding?: { table: string; schema?: Record | undefined; permissions?: ("read" | "write" | "admin")[] | undefined; } | undefined; }>, { name: string; version: string; kind: "app" | "api-only"; description: string; author: string; menu?: { title: string; icon: string; level: number; parent: string; order: number; children?: MenuItemConfigInput[] | undefined; } | undefined; federation?: { exposes: string[]; remotes: Record; shared: string[] | Record; } | undefined; api?: { prefix: string; routes?: Record | undefined; handlers?: string | undefined; } | undefined; client?: { logtoAppId: string; } | undefined; dataBinding?: { table: string; permissions: ("read" | "write" | "admin")[]; schema?: Record | undefined; } | undefined; }, { name: string; version: string; kind?: "app" | "api-only" | undefined; description?: string | undefined; author?: string | undefined; menu?: { title: string; icon?: string | undefined; children?: MenuItemConfigInput[] | undefined; level?: number | undefined; parent?: string | undefined; order?: number | undefined; } | undefined; federation?: { exposes: string[]; remotes?: Record | undefined; shared?: string[] | Record | undefined; } | undefined; api?: { prefix?: string | undefined; routes?: Record | undefined; handlers?: string | undefined; } | undefined; client?: { logtoAppId: string; } | undefined; dataBinding?: { table: string; schema?: Record | undefined; permissions?: ("read" | "write" | "admin")[] | undefined; } | undefined; }>; type ModuleKind = z.infer; type ModuleConfig = z.input; type ValidatedModuleConfig = z.output; type MenuConfig = z.input; type ValidatedMenuConfig = z.output; type MenuItemConfig = z.input; type ValidatedMenuItemConfig = z.output; type FederationConfig = z.input; type ValidatedFederationConfig = z.output; type ApiConfig = z.input; type ValidatedApiConfig = z.output; type DataBindingConfig = z.input; type ValidatedDataBindingConfig = z.output; /** * Validate module configuration * * @param config - Raw module configuration object * @returns ModuleConfig if valid, null if invalid * * @example * ```typescript * import { validateModuleConfig } from '@stago/sdk' * * const config = { * name: '@demo/sales-dashboard', * version: '1.0.0', * menu: { title: 'Sales Dashboard' }, * federation: { exposes: ['./export-app'] } * } * * const result = validateModuleConfig(config) * if (result) { * console.log('Valid config:', result) * } else { * console.error('Invalid configuration') * } * ``` */ declare function validateModuleConfig(config: unknown): ValidatedModuleConfig | null; /** * Menu item for children configuration */ interface ModuleMenuItem { /** Menu item key (unique identifier) */ key: string; /** Menu item title */ title: string; /** Menu item icon (Ant Design icon name) */ icon?: string; /** Module internal route path (required on leaf items) */ path?: string; /** Nested sub-menu items (Host sidebar grouping) */ children?: ModuleMenuItem[]; } /** * Menu configuration for module */ interface ModuleMenu { /** Menu title */ title: string; /** Menu icon (Ant Design icon name) */ icon?: string; /** Menu order (sort order) */ order?: number; /** * 在底座侧栏的挂载层级(写入发布物 module.json,供注册服务使用)。 * - `1`:顶级模块项(默认) * - `2`:整模块挂在 `parent` 指定的分组下 * 与 `children`(模块内子页面,如 /、/list)含义不同,勿混用。 */ level?: number; /** * 当 `level` 为 2 时:父分组显示名(与顶级模块的 `title` 无继承关系)。 */ parent?: string; /** Sub-menu items */ children?: ModuleMenuItem[]; } /** * Shared dependency configuration */ interface SharedDependency { /** Singleton - share single instance */ singleton?: boolean; /** Required version range */ requiredVersion?: string; /** Eager load */ eager?: boolean; } /** * API configuration for module */ interface ModuleApi { /** API prefix (e.g., '/tasks') */ prefix?: string; /** API handlers directory (e.g., './src/api') */ handlers?: string; } /** * 前端 client 配置(api-only 模块) */ interface ModuleClient { /** 该模块前端在 logto 注册的 Application App ID */ logtoAppId: string; } /** * Stago Module Configuration * Simplified configuration for stago.config.ts */ interface StagoModuleConfig { /** Module name in @scope/name format */ name: string; /** Semantic version - optional, auto-detected from package.json during build */ version?: string; /** Module description */ description?: string; /** Author name */ author?: string; /** * 模块类型: * - `app`(默认):前后端一体模块,需提供 `menu`,由底座 MF 加载前端 * - `api-only`:仅托管后端 API,前端独立部署,不进底座页面,需提供 `api.prefix` */ kind?: ModuleKind; /** Menu configuration (required when `kind="app"`,api-only 时省略) */ menu?: ModuleMenu; /** API configuration (api-only 时必填) */ api?: ModuleApi; /** 前端 client 配置(api-only 时必填) */ client?: ModuleClient; /** Additional shared dependencies (optional) */ shared?: Record; } /** * Define Module Configuration * * @description Main function to define module in stago.config.ts. * Simplifies configuration - SDK handles Module Federation internals. * * `kind="app"`(默认)需提供 `menu`,构建产物经 Module Federation 由底座加载前端; * `kind="api-only"` 仅托管后端 API,需提供 `api.prefix`,前端由业务方独立部署, * 不出现在底座菜单与页面中。 * * @param config - Module configuration * @returns Validated configuration * * @example api-only 模块(前端独立部署) * ```typescript * // stago.config.ts * import { defineModule } from '@stago/sdk' * * export default defineModule({ * name: '@demo/report-api', * version: '1.0.0', * kind: 'api-only', * api: { * prefix: '/reports', * handlers: './src/api' * } * }) * ``` */ declare function defineModule(config: StagoModuleConfig): StagoModuleConfig; /** * Create a minimal valid module configuration */ declare function createMinimalModuleConfig(name: string, version: string, title: string): ModuleConfig; /** * Check if a name is valid @scope/name format */ declare function isValidModuleName(name: string): boolean; /** * Check if a version is valid semver format */ declare function isValidModuleVersion(version: string): boolean; /** * Shared HTTP client types + request helpers for Host and standalone dev. * Keep body/query/timeout/response parsing in one place so implementations cannot drift. */ type HttpResponseKind = 'json' | 'text' | 'blob' | 'response'; type HttpQueryValue = string | number | boolean | undefined | null | Array; type HttpQuery = Record; interface HttpOptions { /** Extra request headers (wins over auto Content-Type) */ headers?: Record; /** Timeout in ms */ timeout?: number; /** Skip authentication (Host may omit Authorization / credentials) */ skipAuth?: boolean; /** Query string params; `undefined` / `null` values are skipped */ query?: HttpQuery; /** * Response parsing mode. Default `json`. * - `json` / `text` / `blob`: throw {@link HttpError} when `!response.ok` * - `response`: return the raw {@link Response} (caller checks status / headers) */ responseKind?: HttpResponseKind; /** AbortSignal; combined with `timeout` when both are set */ signal?: AbortSignal; /** * Override Content-Type for JSON/string bodies. * Ignored for FormData / Blob / URLSearchParams / ArrayBuffer (browser sets boundary). */ contentType?: string; } interface HttpClient { get: (url: string, options?: HttpOptions) => Promise; post: (url: string, body?: unknown, options?: HttpOptions) => Promise; put: (url: string, body?: unknown, options?: HttpOptions) => Promise; patch: (url: string, body?: unknown, options?: HttpOptions) => Promise; delete: (url: string, options?: HttpOptions) => Promise; } declare class HttpError extends Error { readonly status: number; readonly statusText: string; readonly response: Response; constructor(message: string, response: Response); } /** * User information passed from Host to module. * 字段与 live.employees 一一对应:id / phone / name。 */ interface ModuleUser { /** 员工 id(live.employees.id) */ id: number; /** 手机号(live.employees.phone) */ phone: string; /** 姓名(live.employees.name) */ name: string; /** User role */ role: 'admin' | 'user'; /** User permissions list */ permissions: string[]; } /** * Theme configuration and controls */ interface ModuleTheme { /** Current theme mode */ mode: 'light' | 'dark'; /** Primary color (hex) */ primaryColor: string; /** Toggle theme mode */ toggle: () => void; } /** * Router service for navigation */ interface ModuleRouter { /** Current path within module */ currentPath: string; /** Dynamic route parameters (e.g., { id: '123' } from /detail/:id) */ params: Record; /** Query string parameters */ query: URLSearchParams; /** Navigate to a new path within module */ navigate: (path: string) => void; } /** * Notification service for user feedback */ interface ModuleNotifications { /** Show success notification */ success: (message: string, description?: string) => void; /** Show error notification */ error: (message: string, description?: string) => void; /** Show warning notification */ warning: (message: string, description?: string) => void; /** Show info notification */ info: (message: string, description?: string) => void; } /** * Global state for cross-module communication * Simple state store - modules can share data through this */ interface ModuleGlobalState { /** Get value by key */ get: (key: string) => T | undefined; /** Set value by key */ set: (key: string, value: T) => void; /** Subscribe to key changes, returns unsubscribe function */ subscribe: (key: string, callback: (value: unknown) => void) => () => void; /** Clear all state for current module namespace */ clear: () => void; } /** * Storage service with module namespace isolation */ interface ModuleStorage { /** Get item */ get: (key: string) => T | null; /** Set item */ set: (key: string, value: T) => void; /** Remove item */ remove: (key: string) => void; /** Clear all items for this module */ clear: () => void; } /** * Platform configuration */ interface AppConfig { /** Platform API base URL */ apiBaseUrl: string; /** Module base URL */ moduleBaseUrl: string; /** Platform version */ version: string; /** Environment: development | production */ environment: 'development' | 'production'; } /** * Platform services */ interface PlatformServices { /** HTTP client with auto-authentication */ http: HttpClient; /** Module-isolated storage */ storage: ModuleStorage; /** Platform configuration */ config: AppConfig; } /** * Complete Module Context * Provided by Host via ModuleProvider */ interface ModuleContext { /** User information */ user: ModuleUser; /** Theme configuration */ theme: ModuleTheme; /** Router service */ router: ModuleRouter; /** Notification service */ notifications: ModuleNotifications; /** Global state (cross-module communication) */ globalState: ModuleGlobalState; /** Platform services */ platform: PlatformServices; } /** * Get module context * Must be used within ModuleProvider */ declare function useModule(): ModuleContext; /** * ModuleProvider component * Host wraps module with this provider */ declare function ModuleProvider({ children, value, publishToGlobal, }: { children: React.ReactNode; value: ModuleContext; /** 多 Tab keep-alive 时仅激活实例应写入全局,避免隐藏实例覆盖路径 */ publishToGlobal?: boolean; }): React.ReactElement; /** * 是否在底座注入的 `ModuleProvider` 中运行(即作为远程模块嵌在宿主里)。 * 为 `true` 时,宿主侧栏通常已根据 `menu.children` 负责子页导航,模块内应不要再画一层侧栏/菜单,只渲染业务内容区即可。 * 本地独立 `vite` 预览且未包 `ModuleProvider` 时为 `false`,可保留模块自带导航便于开发。 * * 必须在 React 组件或 Hook 中调用(内部使用 `useContext`)。 */ declare function useIsInModuleContext(): boolean; /** * 获取宿主传入的当前子路径(用于将模块内 Router 与 Host 菜单点击保持同步)。 * * @param fallback 未在宿主上下文时的兜底路径,默认 '/' * @returns 当前模块子路径(如 '/dashboard') */ declare function useHostCurrentPath(fallback?: string): string; /** * Get user information only */ declare function useUser(): ModuleUser; /** * Get theme information and controls */ declare function useTheme(): ModuleTheme; /** * Get router service */ declare function useRouter(): ModuleRouter; /** * Get notification service */ declare function useNotifications(): ModuleNotifications; /** * Get global state for cross-module communication */ declare function useGlobalState(): ModuleGlobalState; /** * Subscribe to a global state key * Returns current value and setter */ declare function useGlobalStateValue(key: string, defaultValue?: T): [T | undefined, (value: T) => void]; /** * Get module storage */ declare function useStorage(): ModuleStorage; /** * Get platform config */ declare function useConfig(): AppConfig; interface ModuleRequestInit { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; body?: unknown; query?: HttpOptions['query']; headers?: Record; timeout?: number; signal?: AbortSignal; skipAuth?: boolean; contentType?: string; /** * Default `json`: unwrap `ModuleApiResult{ success, data, error }`. * Non-json kinds return the raw parsed body (Blob / text / Response). */ responseKind?: HttpResponseKind; } /** * Module-facing API client — prefer this over `platform.http` / raw fetch. * * Pass business paths only (e.g. `/sprints`); the Host prepends `config.apiBaseUrl`. * JSON responses are unwrapped from `ModuleApiResult{ success, data, error }`. * Use `responseKind: 'blob' | 'response' | 'text'` for uploads/downloads (no unwrap). */ declare function useModuleRequest(): { request: (path: string, init?: ModuleRequestInit) => Promise; }; /** * Module API path conventions shared by Host, dev, and remote modules. * * - module.json `api.prefix` = mount prefix (e.g. `/insight`) * - defineApiRouter optional handler segment under mount prefix (default empty) * - Host injects full `apiBaseUrl` = mount prefix (e.g. `/insight`) * - Module code calls `request('/sprints')`; platform.http prepends apiBaseUrl */ /** Default handler segment under module mount prefix (empty = no extra /api segment) */ declare const MODULE_HANDLER_API_PREFIX = ""; interface ModuleApiResult { success: boolean; data?: T; error?: { code?: string; message?: string; }; } /** Normalize business path to leading slash (e.g. `sprints` → `/sprints`). */ declare function normalizeModuleApiPath(path: string): string; /** * Join apiBaseUrl with a business path (for direct fetch or debugging). * Platform http clients should prepend apiBaseUrl internally; modules normally * pass only the normalized path to http.get/post. */ declare function joinModuleApiPath(apiBaseUrl: string, path: string): string; /** * Resolve the full API base URL injected into ModuleContext.config.apiBaseUrl. * * @param mountPrefix - module.json `api.prefix` (e.g. `/insight`), empty in standalone dev */ declare function resolveModuleApiBaseUrl(mountPrefix?: string): string; /** * Merge module mount prefix with handler prefix for server route registration. * Avoids duplicate segments when both are `/api` (→ `/api`, not `/api/api`). */ declare function resolveModuleRouteBase(modulePrefix: string, handlerPrefix: string): string; /** * 模块 MF 前端资源 baseUrl(page_url)规范化 * - 相对路径:/module-assets/... 或开发代理 * - 绝对路径:https://cdn.../modules/.../dist/ */ declare function normalizeModulePageUrl(url: string): string; type ModuleDbClientQuery = (sql: string, params?: unknown[]) => Promise<{ rows: unknown[]; }>; interface ModuleDbTxConnection { query: ModuleDbClientQuery; rawQuery: ModuleDbClientQuery; } interface CreateModuleDbOptions { query: ModuleDbClientQuery; withClient: (fn: (conn: ModuleDbTxConnection) => Promise) => Promise; /** 默认 30_000;非法值(≤0/NaN/Infinity)回落默认,无"关闭"语义 */ defaultTimeoutMs?: number; /** 模块可请求的 statement_timeout 上限,默认 120_000;同时用作事务空转超时 */ maxTimeoutMs?: number; } declare function createModuleDb(options: CreateModuleDbOptions): ModuleDb; /** * 校验模块业务 SQL 是否允许执行:拦事务控制语句、SET、DDL * (事务级 tmp_ 临时表除外)。dev 与生产共用,规则或文案变更两边自动同步。 */ declare function assertModuleSqlAllowed(sql: string, moduleName: string): void; /** 模块侧栏菜单树 — 路径规范化与校验辅助(Host / SDK 共用) */ interface MenuTreeNodeLike { key: string; path?: string; module?: string; version?: string; children?: MenuTreeNodeLike[]; } /** 规范化模块内 path(?path= 查询参数) */ declare function normalizeModulePath(path: string | undefined | null): string; /** 从 Host 菜单 key 推导模块内 path(兼容未显式配置 path 的旧数据) */ declare function modulePathFromMenuKey(moduleName: string, menuKey: string): string | null; /** 解析菜单项对应的模块内导航 path */ declare function resolveModuleMenuPath(moduleName: string, item: Pick): string; /** * 在菜单树中按模块名 + 模块内 path 查找选中项,并返回需展开的祖先 key。 */ declare function findModuleMenuSelection(nodes: MenuTreeNodeLike[], moduleName: string, moduleSubPath: string, ancestorKeys?: string[]): { selectedKey: string; openKeys: string[]; } | null; /** 按 Host pathname 在管理类菜单(带静态 path)中查找选中项 */ declare function findStaticMenuSelection(nodes: MenuTreeNodeLike[], pathname: string, ancestorKeys?: string[]): { selectedKey: string; openKeys: string[]; } | null; type DbQueryFn = (sql: string, params?: unknown[]) => Promise<{ rows: unknown[]; }>; type DbQueryExecutor = (sql: string, params?: unknown[]) => Promise<{ rows: unknown[]; } | unknown[]>; /** 结构化 SQL 日志事件(平台 logger 消费) */ interface SqlLogEvent { sql: string; /** 参数内联后的可执行 SQL(开发日志 / 平台 logger 优先展示) */ runnableSql?: string; params?: unknown[]; durationMs: number; rowCount: number; tag?: string; } interface LoggedDbQueryOptions { /** * 是否打印 SQL * - 未设置:开发环境默认 true,生产环境默认 false * - DB_LOG_SQL / STAGO_LOG_SQL:true/false/1/0 显式控制 */ logSql?: boolean; /** 日志前缀,如 @ops/catindex@0.3.14 */ tag?: string; /** 结构化 SQL 日志(生产推荐) */ logEvent?: (event: SqlLogEvent) => void; /** 纯文本 SQL 日志(开发兜底) */ log?: (message: string) => void; } /** 是否启用 SQL 日志(DB_LOG_SQL / STAGO_LOG_SQL) */ declare function isSqlLogEnabled(): boolean; /** 将 pg 占位符参数格式化为 SQL 字面量 */ declare function formatSqlParam(value: unknown): string; /** 将 $1,$2,... 替换为字面量,生成可在 psql / dbhub 直接执行的 SQL */ declare function formatRunnableSql(sql: string, params?: unknown[]): string; /** * 包装任意 db 执行函数,按环境变量打印 SQL、参数、耗时与行数。 */ declare function createLoggedDbQuery(execute: DbQueryExecutor, options?: LoggedDbQueryOptions): DbQueryFn; /** 包装 pg Pool.query */ declare function createLoggedPoolQuery(pool: Pool, options?: LoggedDbQueryOptions): DbQueryFn; /** * 模块 SQL 来源注释:在驱动执行前注入首部注释,便于阿里云慢 SQL 定位来源。 * 安全校验须先对原始 SQL 执行,再调用本工具。 */ type SqlAction = 'select' | 'insert' | 'update' | 'delete' | 'merge' | 'create' | 'analyze' | 'explain' | 'call' | 'copy' | 'other'; interface ModuleSqlCommentContext { /** 模块完整名,如 `@ops/output-retention-monitor`(注释里写短名) */ moduleName: string; /** * 当前登录人员工 id;缺省时注释写 `user: unknown`。 * 生产/本地由请求 ALS 注入,也可显式传入(便于单测)。 */ userId?: number | null; startTime: Date; } /** * 识别 SQL 主操作类型。CTE 越过 WITH 定义后看主语句。 * 无法可靠识别时返回 `other`,不抛错。 */ declare function detectSqlAction(sql: string): SqlAction; /** `@ops/foo` → `foo`;已是短名则原样返回 */ declare function shortModuleName(moduleName: string): string; /** 解析连接池使用的数据库账号;缺失时返回 unknown(不阻断查询) */ declare function resolveDatabaseUser(user?: string | null): string; /** * 将瞬时时间格式化为东八区 ISO(毫秒 + `+08:00`)。 * 例:UTC `2026-08-31T08:20:31.123Z` → `2026-08-31T16:20:31.123+08:00` */ declare function formatBeijingIso(date: Date): string; /** 登录人 id:正整数;否则 unknown */ declare function formatCommentUserId(userId?: number | null): string; /** * 在 SQL 首部注入来源注释。不改写占位符与正文。 * 协议:`/* module: <短名>, user: <登录人id>, start_time: *\/` */ declare function annotateModuleSql(sql: string, context: ModuleSqlCommentContext): string; /** * 平台换票所用的 Logto API Resource Indicator 约定值。 * * 对应 access_token 的 `aud`;平台 API 验签、平台 Web / api-only 业务前端的 * `LogtoConfig.resources` 与 `getAccessToken(resource)` 须与此字符串完全一致 *(含尾斜杠)。稳定标识符,不必真实可达。 * * 平台侧可用环境变量 `LOGTO_API_RESOURCE` 覆盖;业务前端直接 import 本常量即可, * 无需各应用单独配置。 * * @see docs/auth-token-exchange-proposal.md */ declare const STAGO_LOGTO_API_RESOURCE = "https://stago.platform/api"; export { type ApiConfig, type AppConfig, AuditIdentityError, type AuditJsonValue, AuditValidationError, type BusinessAuditEvent, type BusinessAuditRecord, type BusinessAuditSink, type CreateModuleDbOptions, type DataBindingConfig, type DbQueryExecutor, type DbQueryFn, type FederationConfig, type HttpClient, HttpError, type HttpOptions, type HttpQuery, type HttpQueryValue, type HttpResponseKind, type LoggedDbQueryOptions, MODULE_HANDLER_API_PREFIX, MODULE_USER_HEADER, type MenuConfig, type MenuItemConfig, type MenuTreeNodeLike, type ModuleApi, type ModuleApiResult, type ModuleApp, type ModuleClient, type ModuleConfig, ModuleConfigSchema, type ModuleContext, type ModuleDb, type ModuleDbClientQuery, type ModuleDbTxConnection, type ModuleDeps, type ModuleGlobalState, type ModuleLogger, type ModuleMenu, type ModuleMenuItem, type ModuleNotifications, type ModulePluginFactory, ModuleProvider, type ModuleRequestInit, type ModuleRequestUser, type ModuleRouter, type ModuleSqlCommentContext, type ModuleStorage, type ModuleTheme, type ModuleUser, type PlatformServices, type RequestAudit, STAGO_LOGTO_API_RESOURCE, type SharedDependency, type SqlAction, type SqlLogEvent, type StagoModuleConfig, type ValidatedApiConfig, type ValidatedDataBindingConfig, type ValidatedFederationConfig, type ValidatedMenuConfig, type ValidatedMenuItemConfig, type ValidatedModuleConfig, annotateModuleSql, assertModuleSqlAllowed, attachModuleRequestUserHeader, createLoggedDbQuery, createLoggedPoolQuery, createMinimalModuleConfig, createModuleApp, createModuleDb, createRequestAudit, defineModule, detectSqlAction, encodeModuleRequestUser, fail, findModuleMenuSelection, findStaticMenuSelection, formatBeijingIso, formatCommentUserId, formatRunnableSql, formatSqlParam, isSqlLogEnabled, isValidModuleName, isValidModuleVersion, joinModuleApiPath, modulePathFromMenuKey, normalizeBusinessAuditEvent, normalizeModuleApiPath, normalizeModulePageUrl, normalizeModulePath, resolveDatabaseUser, resolveModuleApiBaseUrl, resolveModuleMenuPath, resolveModuleRouteBase, shortModuleName, success, useConfig, useGlobalState, useGlobalStateValue, useHostCurrentPath, useIsInModuleContext, useModule, useModuleRequest, useNotifications, useRouter, useStorage, useTheme, useUser, validateModuleConfig };