migratobj
v0.2.0
Published
Type-safe JavaScript object migration across versions
Downloads
24
Readme
migratobj
Type-safe migration of JavaScript objects across versions.
Why
When you persist structured objects — in localStorage, IndexedDB, a config file, or a database — the schema evolves over time. Reading old data with new code silently breaks your application. You need a migration chain that transforms each stored object to the current shape before use. Without a library this logic is ad-hoc, error-prone, and loses all static type information: TypeScript can't verify that your migration functions are complete or correctly typed.
Install
npm install migratobj
pnpm add migratobj
yarn add migratobjRequirements
TypeScript 5.x is required — the implementation uses const type parameters (introduced in TS 5.0). No runtime dependencies.
Usage
1. Define your types
type V1 = { version: 'v1'; name: string };
type V2 = { version: 'v2'; name: string; age: number };
type V3 = { version: 'v3'; fullName: string; age: number };
type TypeMap = { v1: V1; v2: V2; v3: V3 };2. Create the migrator
import { defineMigrator } from 'migratobj';
const { migrate } = defineMigrator<TypeMap>()({
versions: ['v1', 'v2', 'v3'] as const, // oldest → newest; `as const` is required
getVersion: (obj) => obj.version, // reads the version discriminant
migrations: {
'v1->v2': (obj) => ({ ...obj, version: 'v2' as const, age: 0 }),
'v2->v3': (obj) => ({ version: 'v3' as const, fullName: obj.name, age: obj.age }),
},
});3. Call migrate
const v1: V1 = { version: 'v1', name: 'Alice' };
const latest = migrate(v1);
// ^? V3 — always the latest version, not a unionIf the object is already at the latest version, migrate is a no-op.
API
defineMigrator<TypeMap>()(config)
TypeScript cannot partially infer generics, so the type map is supplied in the first call and config (where inference happens) in the second.
Config properties:
| Property | Description |
|---|---|
| versions | readonly [string, ...string[]] — ordered tuple of all version strings, oldest first. Must be as const. |
| getVersion | (obj: AnyVersion) => VersionString — reads the version discriminant. Must return a string that exists in versions. |
| migrations | Object with one key per consecutive step ("vA->vB"). TypeScript enforces completeness and disallows non-consecutive keys. |
Returns { migrate }.
migrate(obj)
- Accepts any version of the object (union type).
- Returns the latest version (exact type, not a union).
- Throws
Error: Unknown version: "<v>"ifgetVersionreturns an unrecognised string.
Type Safety
- Exhaustive: TypeScript errors if any consecutive step is missing from
migrations. - No skipping: TypeScript errors if
migrationscontains a non-consecutive key (e.g."v1->v3"). - Typed functions: Each migration fn receives the exact source type and must return the exact target type.
- Typed return:
migrate()return type is always the latest version type, never a union.
Module Formats
- Ships ESM (
dist/index.js) and CJS (dist/index.cjs) with packageexportsconditions. - TypeScript declarations included (
dist/index.d.ts). - No runtime dependencies.
sideEffects: false— safe for tree-shaking.- < 1kB minified + gzipped (ESM build is 620B unminified).
License
MIT
