Skip to content

Schema Migrations

lostcause edited this page Aug 24, 2026 · 1 revision

Schema migrations

MigrationRegistry runs contiguous, validated, retry-safe application-level record migrations over a PolyGraph: a version-to-version transform applied over every persisted node (and optionally edge), in batches, with dry runs and resumable progress.

import { MigrationRegistry } from '@0xx0lostcause0xx0/polypack'

const registry = new MigrationRegistry()

registry.register({
  from: 1,
  to: 2,
  migrateNode(node) {
    if (node.type !== 'document') return
    return { ...node, data: { ...node.data, status: node.data.status ?? 'draft' } }
  },
})

const report = await registry.run(graph, 1, 2, {
  batchSize: 500,
  onProgress: (progress) => console.log(progress.processed, '/', progress.total),
})

Registering migrations

register({ from, to, migrateNode, migrateEdge? })from/to must be integers with to > from. migrateNode(node) returns a transformed node, or undefined/void to leave it unchanged; it must not change id or type — doing so throws MigrationError. migrateEdge(edge) is the same for edges and must not change id, source, target, or type. Only one migration may be registered per from version.

Running migrations

run(graph, from, to, options?) walks the contiguous chain of registered migrations from from to to — a gap in the chain throws MigrationError. Each step:

  1. Flushes the graph, then reads the complete persisted node and edge sets (sorted by id for deterministic resume).
  2. Applies migrations in batches (options.batchSize, unbounded by default), committing each batch through graph.transaction so a batch is atomic.
  3. Reports progress via options.onProgress after each batch: { from, to, processed, total, migrated, lastProcessed }.

Options:

  • dryRun — run the full transform and report what would change, without writing anything.
  • batchSize — positive integer; smaller batches mean smaller transactions and more frequent progress callbacks.
  • signal — an AbortSignal; checked between batches, throws MigrationError on abort.
  • resumeAfter: { nodeId?, edgeId? } — resume a previously interrupted run after a specific node/edge id. An id not found in the current persisted set throws MigrationError rather than silently restarting from the beginning.

The returned MigrationReport extends the progress shape with dryRun: boolean.


Back to Home.

Clone this wiki locally