/** * L2-D: Config version migration framework. Pure functions, decoupled * from disk/CLI — caller passes a parsed JSON object and gets back the * up-to-date `Config` shape (or a structured error explaining why * migration failed). * * Migrations are registered as `{ from, to, migrate }` triples and run * sequentially. Each migration is independently testable. Adding a new * version means appending one migration; existing user configs are * upgraded in place at load time. */ export interface MigrationContext { /** * Original on-disk version of the input. Migrations may use this to * decide between in-place patches and rewrites. */ fromVersion: number; /** * Set when the migration writes back to disk. Callers persist the * migrated config when this is true so the user doesn't see the same * migration banner on every boot. */ shouldPersist: boolean; } export interface ConfigMigration { /** Version of the input this migration accepts. */ from: number; /** Version of the output it produces. */ to: number; /** Pure transform — no I/O. */ migrate(input: Record, ctx: MigrationContext): Record; /** Optional human-readable description for migration logs / banners. */ describe?: string | undefined; } export interface MigrationResult { /** Final config (still typed as `unknown`-keyed — caller validates). */ config: Record; /** Ordered list of `from→to` versions that ran. */ applied: string[]; /** True when at least one migration produced changes worth persisting. */ shouldPersist: boolean; } export declare class ConfigMigrationError extends Error { readonly fromVersion: number; readonly targetVersion: number; readonly missingStep: number | null; constructor(opts: { message: string; fromVersion: number; targetVersion: number; missingStep: number | null; }); } /** * Run registered migrations until the input reaches `targetVersion`. * * Resolution rules: * 1. If `input.version === targetVersion`, no migrations run; `shouldPersist` * is false. * 2. Otherwise walk the migration chain from `input.version` upward, * picking the migration whose `from` matches the current version. * 3. Stop when `current.version === targetVersion`. * 4. If no migration matches at some point, throw `ConfigMigrationError` * with the missing step recorded for diagnostics. * * Migrations may be downward (e.g. for staged rollouts), but `targetVersion` * must be reachable strictly via the registered chain — there's no implicit * "skip" or transitive resolution. */ export declare function runConfigMigrations(input: Record, targetVersion: number, migrations: readonly ConfigMigration[]): MigrationResult; /** * Default empty migration registry. Real migrations are appended as new * Config versions are introduced. Example (when v2 lands): * * export const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [ * { * from: 1, to: 2, describe: 'rename `apiKey` → `auth.apiKey`', * migrate(cfg) { * const apiKey = cfg.apiKey; * delete cfg.apiKey; * return { ...cfg, auth: { ...(cfg.auth ?? {}), apiKey } }; * }, * }, * ]; */ export declare const DEFAULT_CONFIG_MIGRATIONS: readonly ConfigMigration[]; //# sourceMappingURL=config-migration.d.ts.map