2.6 KiB
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 ofVersionMigratorrecords shipped with the package, currently coveringv1 → v2(the leap that added an explicitschemaVersionfield).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 againstSyncQueueEntrySchema.- Throws when no migrator is registered for a
fromVersion, so corrupt versions surface as errors rather than silently propagating.
Usage: Basic
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:
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<string, unknown>.migrateBatch(raws, migrators?): Record<string, unknown>[].asSyncQueueEntry(record): SyncQueueEntry— narrowing cast helper.
Implementation Notes
The function operates on Record<string, unknown> 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.