Schema Migrations
For a practical guide on evolving schemas across deployments, see Evolving Schemas in Production.
When Do You Need Schema Management?
Section titled “When Do You Need Schema Management?”As your application evolves, your graph schema changes:
- Adding features: New node types, new properties, new relationships
- Refactoring: Renaming types, changing property formats
- Deploying safely: Ensuring schema changes don’t break running applications
Without schema management, you’d face:
- No way to know if the database matches your code
- Silent failures when property names change
- Manual migration scripts for every deployment
TypeGraph’s schema management:
- Stores the schema in the database alongside your data
- Detects changes between your code and the stored schema
- Auto-migrates safe changes (adding types, optional properties)
- Blocks breaking changes until you handle them explicitly
How It Works
Section titled “How It Works”TypeGraph stores your graph schema in the database, enabling version tracking, safe migrations, and runtime introspection.
When you create a store with createStoreWithSchema(), TypeGraph:
- Serializes your graph definition to JSON
- Compares it with the stored schema (if any)
- Returns the result so you can act on it
Schema Lifecycle
Section titled “Schema Lifecycle”When you create a store, TypeGraph can automatically manage schema versions:
import { createStoreWithSchema } from "@nicia-ai/typegraph";
const [store, result] = await createStoreWithSchema(graph, backend);
switch (result.status) { case "initialized": console.log(`Schema initialized at version ${result.version}`); break; case "unchanged": console.log(`Schema unchanged at version ${result.version}`); break; case "migrated": console.log(`Migrated from v${result.fromVersion} to v${result.toVersion}`); break; case "pending": console.log(`Safe changes pending at version ${result.version}`); break; case "breaking": console.log("Breaking changes detected:", result.actions); break;}Basic vs Managed Store
Section titled “Basic vs Managed Store”TypeGraph provides two ways to create a store:
Basic Store (No Schema Management)
Section titled “Basic Store (No Schema Management)”Use createStore() when you manage schema versions yourself:
import { createStore } from "@nicia-ai/typegraph";
const store = createStore(graph, backend);// No schema versioning - you handle migrations manuallyManaged Store (Automatic Schema Management)
Section titled “Managed Store (Automatic Schema Management)”Use createStoreWithSchema() for automatic version tracking:
import { createStoreWithSchema } from "@nicia-ai/typegraph";
const [store, result] = await createStoreWithSchema(graph, backend, { autoMigrate: true, // Auto-apply safe changes (default: true) throwOnBreaking: true, // Throw on breaking changes (default: true) onBeforeMigrate: (context) => { console.log(`Migrating ${context.graphId} from v${context.fromVersion} to v${context.toVersion}`); }, onAfterMigrate: (context) => { console.log(`Migration complete: v${context.toVersion}`); },});Schema Validation Results
Section titled “Schema Validation Results”The validation result indicates what happened during store initialization:
| Status | Meaning |
|---|---|
initialized | First run - schema version 1 was created |
unchanged | Schema matches stored version - no changes |
migrated | Safe changes auto-applied, new version created |
pending | Safe changes detected but autoMigrate is false |
breaking | Breaking changes detected, action required |
Safe vs Breaking Changes
Section titled “Safe vs Breaking Changes”Safe Changes (Auto-Migrated)
Section titled “Safe Changes (Auto-Migrated)”These changes are backwards compatible and can be auto-migrated:
- Adding new node types
- Adding new edge types
- Adding optional properties with defaults
- Adding new ontology relations
Breaking Changes (Require Manual Action)
Section titled “Breaking Changes (Require Manual Action)”These changes require manual migration:
- Removing node or edge types
- Renaming node or edge types
- Changing property types
- Removing properties
- Changing cardinality constraints to be more restrictive
Handling Breaking Changes
Section titled “Handling Breaking Changes”When breaking changes are detected:
const [store, result] = await createStoreWithSchema(graph, backend, { throwOnBreaking: false, // Don't throw, inspect instead});
if (result.status === "breaking") { console.log("Breaking changes detected:"); console.log("Summary:", result.diff.summary); console.log("Required actions:"); for (const action of result.actions) { console.log(` - ${action}`); }
// Option 1: Fix your schema to be backwards compatible
// Option 2: Force migration (data loss possible!) // import { migrateSchema } from "@nicia-ai/typegraph/schema"; // await migrateSchema(backend, graph, currentVersion);}Schema Introspection
Section titled “Schema Introspection”Query the stored schema at runtime:
import { getActiveSchema, isSchemaInitialized, getSchemaChanges } from "@nicia-ai/typegraph/schema";
// Check if schema existsconst initialized = await isSchemaInitialized(backend, "my_graph");
// Get the current schemaconst schema = await getActiveSchema(backend, "my_graph");if (schema) { console.log("Graph ID:", schema.graphId); console.log("Version:", schema.version); console.log("Nodes:", Object.keys(schema.nodes)); console.log("Edges:", Object.keys(schema.edges));}
// Preview changes without applyingconst diff = await getSchemaChanges(backend, graph);if (diff?.hasChanges) { console.log("Pending changes:", diff.summary); console.log("Is backwards compatible:", !diff.hasBreakingChanges);}Manual Migration
Section titled “Manual Migration”For full control over migrations:
import { initializeSchema, migrateSchema, rollbackSchema, ensureSchema,} from "@nicia-ai/typegraph/schema";
// Initialize schema (first run only)const row = await initializeSchema(backend, graph);console.log("Created version:", row.version);
// Migrate to new versionconst newVersion = await migrateSchema(backend, graph, currentVersion);console.log("Migrated to version:", newVersion);
// Rollback to a previous versionawait rollbackSchema(backend, "my_graph", 1);console.log("Rolled back to version 1");
// Or use ensureSchema for automatic handlingconst result = await ensureSchema(backend, graph, { autoMigrate: true, throwOnBreaking: true,});Schema Serialization
Section titled “Schema Serialization”Schemas are stored as JSON documents with computed hashes for fast comparison:
import { serializeSchema, computeSchemaHash } from "@nicia-ai/typegraph/schema";
// Serialize a graph definitionconst serialized = serializeSchema(graph, 1);
// Compute hash for comparisonconst hash = computeSchemaHash(serialized);The serialized schema includes:
- Graph ID and version
- All node types with their Zod schemas (as JSON Schema)
- All edge types with endpoints and constraints
- Complete ontology relations
- Uniqueness constraints and delete behaviors
Version History
Section titled “Version History”TypeGraph maintains a history of all schema versions:
typegraph_schema_versions├── version 1 (initial)├── version 2 (added User node)├── version 3 (added email property) ← active└── ...Only one version is marked as “active” at a time. Previous versions are preserved for auditing and potential rollback.
Best Practices
Section titled “Best Practices”1. Use Managed Stores in Production
Section titled “1. Use Managed Stores in Production”// Production: Use schema managementconst [store, result] = await createStoreWithSchema(graph, backend);
// Development: Basic store is fine for rapid iterationconst store = createStore(graph, backend);2. Check Migration Status on Startup
Section titled “2. Check Migration Status on Startup”async function initializeApp() { const [store, result] = await createStoreWithSchema(graph, backend);
if (result.status === "breaking") { console.error("Database schema incompatible with application!"); console.error("Run migrations before deploying this version."); process.exit(1); }
if (result.status === "migrated") { console.log(`Schema auto-migrated to v${result.toVersion}`); }
return store;}3. Preview Changes Before Deployment
Section titled “3. Preview Changes Before Deployment”import { getSchemaChanges } from "@nicia-ai/typegraph/schema";
// In your CI/CD pipeline or migration scriptconst diff = await getSchemaChanges(backend, graph);
if (diff?.hasChanges) { console.log("Schema changes detected:"); console.log(diff.summary);
if (!diff.isBackwardsCompatible) { console.error("Breaking changes require manual migration!"); process.exit(1); }}4. Add Properties with Defaults
Section titled “4. Add Properties with Defaults”When adding new properties, always provide defaults to ensure backwards compatibility:
// Good: Optional with defaultconst User = defineNode("User", { schema: z.object({ name: z.string(), // New property with default - safe migration status: z.enum(["active", "inactive"]).default("active"), }),});
// Bad: Required without default - breaking changeconst User = defineNode("User", { schema: z.object({ name: z.string(), status: z.enum(["active", "inactive"]), // No default! }),});