/** * config-loader.ts — 配置加载器 * * 负责加载并合并三层配置文件(default → env → local), * 执行 Fail Fast 校验,然后 deepFreeze 返回只读配置对象。 * * 合并规则(含 CJS interop 支持): * - 普通标量(port、host):覆盖(后者优先) * - 普通对象(logger、cors):深度合并(只覆盖写了的子字段) * - middlewares[] 数组:按 name patch 合并(匹配则浅合并,未匹配则追加) * * 配置文件结构: * src/config/ * ├── default.ts — 基准配置(必须存在) * ├── development.ts — 开发环境覆盖(可选) * ├── production.ts — 生产环境覆盖(可选) * └── local.ts — 本地覆盖(最高优先级,可选,不提交 git) * * @module lib/config-loader * @see 05-config.md §5(配置合并规则) * @see IMPLEMENTATION-PLAN.md 任务 1.1 */ import type { VextConfig, VextMiddlewareConfig } from "../types/app.js"; import { type BootstrapCommand } from "./bootstrap-config.js"; /** * 中间件声明的两种形式: * - 字符串:中间件名称(如 'auth') * - 对象:带配置的中间件声明(如 { name: 'auth', options: { ... } }) */ type MiddlewareDecl = string | (VextMiddlewareConfig & { enabled?: boolean; }); export interface LoadConfigMetadata { providerPatch?: Record; } export interface LoadConfigOptions { rootDir?: string; command?: BootstrapCommand; isBuilt?: boolean; env?: NodeJS.ProcessEnv; meta?: LoadConfigMetadata; } /** * 在 configDir 下查找指定名称的配置文件 * * 按 EXTENSIONS 顺序尝试,返回第一个存在的文件路径。 * * @param configDir 配置目录绝对路径 * @param name 文件名(不含扩展名),如 'default'、'production'、'local' * @returns 完整文件路径,或 null(不存在) */ declare function resolveConfigFile(configDir: string, name: string): string | null; /** * 深度合并两个对象(target 上叠加 source) * * 合并策略: * - 普通对象:递归合并(只覆盖 source 中有的字段,其余继承 target) * - 数组:整体覆盖(非 middlewares,middlewares 走专用 patch) * - 标量 / null / undefined:source 覆盖 target * * 注意:middlewares 字段不在此函数中处理,由 patchMiddlewares 单独处理。 * * @param target 基准对象 * @param source 覆盖对象 * @returns 合并后的新对象(不修改 target 和 source) */ declare function deepMerge>(target: T, source: Partial): T; /** * middlewares 专用 patch 合并 * * 按 name 匹配: * 1. 以 base 数组为基准(保持原始顺序) * 2. override 中每一项按 name 在 base 中查找 * 3. 匹配到 → 浅合并该项(options / enabled 等字段覆盖) * 4. 未匹配到 → 追加到数组末尾 * * @param base 基准数组(来自 default.ts 或上一层合并结果) * @param override 覆盖数组(来自 env.ts 或 local.ts) * @returns 合并后的新数组 */ declare function patchMiddlewares(base: MiddlewareDecl[], override: MiddlewareDecl[]): MiddlewareDecl[]; declare function applyConfigLayer(base: Record, override: Record): Record; declare function applyCliOverrides(merged: Record, env: NodeJS.ProcessEnv): Record; /** * 递归深冻结对象 * * Object.freeze 只冻结顶层属性(浅冻结),嵌套对象仍可被修改。 * deepFreeze 递归冻结所有层级,确保 config 完全只读。 * * 跳过规则: * - null / 非对象 / 已冻结对象 → 避免无意义递归 * - Date / RegExp / Buffer / Map / Set 等非纯对象 → * 冻结这些对象会破坏其内部状态(如 Date.setTime() 失效) * - 跳过非纯对象的检查(Q23:不要冻结非纯对象) * * @param obj 待冻结对象 * @returns 冻结后的对象(同一引用) */ declare function deepFreeze(obj: T): T; /** * 配置校验(启动时 Fail Fast) * * 在应用启动时立即校验配置合法性,发现问题立即抛出错误终止启动, * 避免运行时出现难以追踪的配置问题。 * * @param config 合并后的配置对象 * @throws Error 配置不合法时抛出描述性错误 */ declare function validateConfig(config: Record): void; /** * 加载并合并配置文件 * * 合并顺序:default → {NODE_ENV} → local * 合并完成后执行 Fail Fast 校验,通过后 deepFreeze 返回只读对象。 * * @param configDir 配置目录绝对路径(通常为 path.join(projectRoot, 'src/config')) * @returns 合并、校验、冻结后的 VextConfig * @throws Error 配置文件缺失或配置不合法时抛出 * * @example * ```typescript * import { loadConfig } from './config-loader.js' * import path from 'node:path' * * const config = await loadConfig(path.join(process.cwd(), 'src/config')) * // config 已 deepFreeze,运行时不可修改 * ``` */ export declare function loadRawConfig(configDir: string, options?: LoadConfigOptions): Promise>; export declare function finalizeConfig(rawConfig: Record): VextConfig; export declare function loadConfig(configDir: string, options?: LoadConfigOptions): Promise; export { deepMerge as _deepMerge, deepFreeze as _deepFreeze, patchMiddlewares as _patchMiddlewares, applyCliOverrides as _applyCliOverrides, applyConfigLayer as _applyConfigLayer, validateConfig as _validateConfig, resolveConfigFile as _resolveConfigFile, };