# `sync-queue/version-migration.ts` ## Purpose Forward-migrates raw `SyncQueueEntry` records loaded from persistent storage (IndexedDB / localStorage) to the current schema version. Entries created by older versions of `@suaveplan/pwa` linger in browser storage indefinitely, so the queue must upgrade them on every load before they cross the `SyncQueueEntrySchema` validator. The migration registry is deterministic and idempotent: an entry already at the current version passes through unchanged. ## Features - `DEFAULT_MIGRATORS` — ordered list of `VersionMigrator` records shipped with the package, currently covering `v1 → v2` (the leap that added an explicit `schemaVersion` field). - `migrateEntry(raw, migrators?)` — single-entry upgrade. - `migrateBatch(raws, migrators?)` — convenience for bulk loads. - `asSyncQueueEntry(record)` — type narrowing for callers that have just validated a record against `SyncQueueEntrySchema`. - Throws when no migrator is registered for a `fromVersion`, so corrupt versions surface as errors rather than silently propagating. ## Usage: Basic ```ts import { migrateEntry } from "@suaveplan/pwa"; const upgraded = migrateEntry({ id: "old", url: "/api/x", method: "POST", createdAt: 0, }); // upgraded.schemaVersion === 2 ``` ## Usage: Advanced Supply an extra migrator for a custom future schema bump: ```ts import { DEFAULT_MIGRATORS, migrateBatch, type VersionMigrator } from "@suaveplan/pwa"; const v2ToV3: VersionMigrator = { fromVersion: 2, upgrade: (e) => ({ ...e, schemaVersion: 3, tenant: e.tenant ?? "default" }), }; const upgraded = migrateBatch(rawRows, [...DEFAULT_MIGRATORS, v2ToV3]); ``` ## API Reference - `VersionMigrator` — `{ fromVersion, upgrade(entry) }`. - `DEFAULT_MIGRATORS` — frozen array shipped with the package. - `migrateEntry(raw, migrators?): Record`. - `migrateBatch(raws, migrators?): Record[]`. - `asSyncQueueEntry(record): SyncQueueEntry` — narrowing cast helper. ## Implementation Notes The function operates on `Record` rather than `SyncQueueEntry` because pre-current-version records do not satisfy the current type. The caller is expected to feed the output through `SyncQueueEntrySchema.parse(...)` before trusting field types. The loop terminates because every migrator must increment `schemaVersion` by at least one (enforced via the throw on missing migrator when a record's version is `>= SYNC_QUEUE_SCHEMA_VERSION` we don't enter the loop at all). Idempotency derives from the same property: a record already at the current version skips the loop body entirely.