import type { Component } from './world'; type ComponentConstructor = abstract new (...args: any[]) => Component; type MigrationFn = (state: Record) => Record; interface ComponentMeta { ctor: ComponentConstructor; version: number; } interface MigrationEntry { toVersion: number; fn: MigrationFn; } const registry = new Map(); const reverseRegistry = new Map(); /** migrations[name][fromVersion] → { toVersion, fn } */ const migrations = new Map>(); function register(name: string, ctor: ComponentConstructor, version: number): void { registry.set(name, { ctor, version }); reverseRegistry.set(ctor, name); } export function getComponentMeta(name: string): ComponentMeta | undefined { return registry.get(name); } export function getComponentName(ctor: Function): string | undefined { return reverseRegistry.get(ctor as ComponentConstructor); } /** * Register a migration that upgrades component state from `fromVersion` to `toVersion`. * Migrations are chained automatically: registering 0→1 and 1→2 handles saves at version 0. */ export function registerMigration( name: string, fromVersion: number, toVersion: number, fn: MigrationFn, ): void { if (!migrations.has(name)) migrations.set(name, new Map()); migrations.get(name)!.set(fromVersion, { toVersion, fn }); } /** * Apply all registered migrations to bring `state` from `fromVersion` up to the current * registered version. Returns the migrated state (may be the same object if no migrations ran). */ export function migrateState( name: string, state: Record, fromVersion: number, ): Record { const meta = registry.get(name); if (!meta) return state; const chain = migrations.get(name); if (!chain) return state; let current = fromVersion; let s = state; while (current < meta.version) { const entry = chain.get(current); if (!entry) throw new Error( `[registry] No migration for '${name}' from version ${current} to ${meta.version}. ` + `Register one with registerMigration('${name}', ${current}, ...).` ); s = entry.fn(s); current = entry.toVersion; } return s; } interface ComponentOptions { name?: string; version?: number; } type ComponentDecorator = (target: ComponentConstructor, ctx: ClassDecoratorContext) => void; export function component(target: ComponentConstructor, ctx: ClassDecoratorContext): void; export function component(name: string): ComponentDecorator; export function component(options: ComponentOptions): ComponentDecorator; export function component( nameOrTargetOrOptions: string | ComponentConstructor | ComponentOptions, ctx?: ClassDecoratorContext, ): void | ComponentDecorator { if (typeof nameOrTargetOrOptions === 'string') { const name = nameOrTargetOrOptions; return (target: ComponentConstructor) => register(name, target, 0); } if (typeof nameOrTargetOrOptions === 'object') { const { name, version = 0 } = nameOrTargetOrOptions; return (target: ComponentConstructor, ctx: ClassDecoratorContext) => register(name ?? String(ctx.name), target, version); } // Used as bare @component register(String(ctx!.name), nameOrTargetOrOptions, 0); }