Skip to content

Intelligent schema migration tool for Zod with automatic version detection

Notifications You must be signed in to change notification settings

tone-row/zod-migrate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@tonerow/zod-migrate

Intelligent schema migration tool for Zod with automatic version detection and type safety.

Features

  • 🧠 Smart Version Detection - Automatically detects data version using schema matching
  • 🔄 Selective Migration - Only applies necessary transformations from detected version forward
  • 🔒 Type Safety - Full TypeScript support with strongly typed outputs
  • 🛡️ Immutable - Uses structuredClone to preserve original data
  • Zero Dependencies - Only requires Zod as peer dependency
  • 🎯 Preserves Existing Data - Won't overwrite fields that already exist

Installation

npm install @tonerow/zod-migrate zod
# or
bun add @tonerow/zod-migrate zod

Basic Usage

import { z } from "zod";
import { createEntity } from "@tonerow/zod-migrate";

// Start with your initial schema
const userManager = createEntity(
  "user",
  z.object({
    name: z.string(),
    age: z.number(),
  })
);

// Add migrations as your schema evolves
const v2Manager = userManager.addMigration(
  // Schema transformation
  (schema) => schema.extend({ email: z.string().email() }),
  // Data transformation
  (data) => ({ ...data, email: "default@example.com" })
);

const v3Manager = v2Manager.addMigration(
  (schema) => schema.extend({ name: z.array(z.string()) }),
  (data) => ({ ...data, name: data.name.split(" ") })
);

// Migrate any version to the latest
const v1Data = { name: "John Doe", age: 30 };
const latestData = v3Manager.migrate(v1Data);
// Result: { name: ["John", "Doe"], age: 30, email: "default@example.com" }

// Already up-to-date data is preserved
const v2Data = { name: "Jane Smith", age: 25, email: "jane@custom.com" };
const preserved = v3Manager.migrate(v2Data);
// Result: { name: ["Jane", "Smith"], age: 25, email: "jane@custom.com" }

API

createEntity(name: string, schema: ZodType)

Creates a new migration manager with the initial schema.

const manager = createEntity("user", z.object({ id: z.string() }));

.addMigration(schemaTransform, dataTransform)

Adds a new migration step.

const v2Manager = manager.addMigration(
  // Transform the schema
  (schema) => schema.extend({ newField: z.string() }),
  // Transform the data
  (data) => ({ ...data, newField: "default" })
);

.migrate(data: any)

Migrates data from any version to the latest schema.

const result = manager.migrate(oldData);
// TypeScript knows result matches the latest schema type

.schema

Access the latest schema for validation or type inference.

type LatestUser = z.infer<typeof manager.schema>;

How It Works

  1. Version Detection: Works backward through schema versions using safeParse()
  2. Selective Migration: Only applies migrations from the detected version forward
  3. Validation: Final result is validated against the latest schema
  4. Type Safety: Output is strongly typed to match the latest schema

Error Handling

Invalid data that can't be migrated will throw Zod validation errors:

try {
  manager.migrate({ invalidField: "wrong type" });
} catch (error) {
  // ZodError with detailed validation information
}

Advanced Examples

Complex Field Transformations

const userManager = createEntity("user", z.object({
  fullName: z.string(),
  age: z.number()
}))
.addMigration(
  // Split fullName into firstName/lastName
  (schema) => schema
    .omit({ fullName: true })
    .extend({
      firstName: z.string(),
      lastName: z.string()
    }),
  (data) => {
    const [firstName, lastName] = data.fullName.split(" ");
    return { ...data, firstName, lastName };
  }
)
.addMigration(
  // Add computed field
  (schema) => schema.extend({
    displayName: z.string(),
    isAdult: z.boolean()
  }),
  (data) => ({
    ...data,
    displayName: `${data.firstName} ${data.lastName}`,
    isAdult: data.age >= 18
  })
);

Conditional Migrations

const productManager = createEntity("product", z.object({
  name: z.string(),
  price: z.number()
}))
.addMigration(
  (schema) => schema.extend({ currency: z.string() }),
  (data) => ({
    ...data,
    // Only add currency if not present
    currency: data.currency || "USD"
  })
);

License

MIT

About

Intelligent schema migration tool for Zod with automatic version detection

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published