Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

schema-evolution

CI

A versioned data migrator. Register {from, to, migrate} transforms and walk any document — by its own schemaVersion field — forward to the latest version (or a specific target), with optional downgrade support and batch migration.

This fixes the classic bug in naive migrator implementations: running every registered transform on every document regardless of its starting version (so a v2 document gets the v1→v2 transform re-applied on top of already-migrated data). Here, each transform only fires when the document's current version actually matches that transform's from.

Install

npm install @ferrow/schema-evolution

Quickstart

import { SchemaMigrator } from "schema-evolution";

interface UserDoc {
  schemaVersion: number;
  firstName?: string;
  lastName?: string;
  fullName?: string;
}

const migrator = new SchemaMigrator<UserDoc>();

migrator.register({
  from: 1,
  to: 2,
  migrate: (doc) => ({ ...doc, fullName: `${doc.firstName} ${doc.lastName}` }),
  revert: (doc) => {
    const [firstName, ...rest] = doc.fullName!.split(" ");
    return { schemaVersion: doc.schemaVersion, firstName, lastName: rest.join(" ") };
  },
});

const v1doc: UserDoc = { schemaVersion: 1, firstName: "Ada", lastName: "Lovelace" };
const latest = migrator.migrate(v1doc); // { schemaVersion: 2, fullName: "Ada Lovelace", ... }

API

new SchemaMigrator<T extends SchemaDoc>()

SchemaDoc is any object with a numeric schemaVersion field.

  • register(transform: Transform<T>): this Transform: { from: number, to: number, migrate(doc): doc, revert?(doc): doc }. Forward transforms must step exactly +1 (to === from + 1) — this keeps gap detection well-defined. Throws SchemaMonotonicityError on a bad step, or a plain Error on a duplicate from.
  • migrate(doc: T, options?: { toVersion?: number }): T Walks the chain from doc.schemaVersion to toVersion (default: latestVersion). Forward if toVersion is higher, backward (via revert) if lower. Throws SchemaGapError if a required step isn't registered, or a plain Error if a downgrade step has no revert.
  • migrateBatch(docs: T[], options?): { migrated: T[], errors: {index, doc, error}[] } Migrates each document independently; a failure on one doc doesn't stop the rest — failures are collected in errors instead of thrown.
  • latestVersion: number — highest version reachable by the registered chain.

Design notes

Requiring each forward transform to step by exactly +1 is a deliberate constraint: it means "is there a path from version N to version M" is always answerable by walking N, N+1, N+2, ... and checking each step exists, which is what makes gap detection precise instead of heuristic. The old unconditional-run bug is why migrate() re-checks the document's current version before every single step rather than just running through the registered transform list — a v2 document handed to a registry that also has a v1→v2 transform must never see that transform applied to it.


Sponsored by Ferrow


Part of the ferrow-toolkit collection · Sponsored by Ferrow

About

Versioned data migrator: register {from, to, migrate} transforms, walk any document to the latest schema version (or a target), support downgrade via revert(), batch helper, and gap/monotonicity validation at registration.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages