@tonerow/zod-migrate
v0.1.0
Published
Intelligent schema migration tool for Zod with automatic version detection
Downloads
2
Maintainers
Readme
@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
structuredCloneto 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 zodBasic 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: "[email protected]" })
);
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: "[email protected]" }
// Already up-to-date data is preserved
const v2Data = { name: "Jane Smith", age: 25, email: "[email protected]" };
const preserved = v3Manager.migrate(v2Data);
// Result: { name: ["Jane", "Smith"], age: 25, email: "[email protected]" }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
- Version Detection: Works backward through schema versions using
safeParse() - Selective Migration: Only applies migrations from the detected version forward
- Validation: Final result is validated against the latest schema
- 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
