Files
skills/docs/foundation/data/pwa/src/sync-queue/version-migration.md
2026-05-29 08:20:56 +01:00

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 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

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.