Schema Sync
How schema synchronization works -- from .laizy files to database, with safety checks and migration plans.
Schema Sync
Schema synchronization is the process that keeps your local .laizy schema files in sync with the database. It compares what you have defined locally against what exists remotely, generates a migration plan with safety classifications, and executes the changes after confirmation.
The Sync Lifecycle
When you run laizy sync, the system follows a five-stage pipeline:
Parse → Diff → Plan → Enhance → Execute1. Parse
The local schema file (default: laizy/schema.laizy) is parsed into an abstract syntax tree (AST). Each model block becomes a ModelDefinition with typed fields and constraints:
interface ModelDefinition {
type: 'model';
name: string;
fields: Array<{
name: string;
fieldType: 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime';
constraints: {
required?: boolean;
unique?: boolean;
default?: string | number | boolean;
maxLength?: number;
minLength?: number;
};
}>;
}If the schema has syntax errors, parsing fails with line numbers pointing to the problem:
Schema parsing failed
Error: Line 5: Expected ":" after field name2. Diff
The compareSchemas() function compares the local AST against the current remote state (fetched from the API via getSchemaState()). It produces a SchemaDiff:
interface SchemaDiff {
toCreate: ModelDefinition[]; // New models to add
toUpdate: ModelUpdate[]; // Existing models to modify
toDelete: RemoteContentModel[]; // Models to remove
}For updated models, the differ computes field-level changes:
interface FieldChange {
type: 'added' | 'removed' | 'modified';
field: FieldDefinition;
impact: 'safe' | 'warning' | 'destructive';
migrationStrategy?: {
type: 'auto_fill' | 'manual' | 'none';
defaultValue?: any;
description: string;
};
}3. Plan
The generateMigrationPlan() function converts the diff into an ordered list of operations:
interface MigrationPlan {
operations: MigrationOperation[];
safetyReport: SafetyReport;
requiresConfirmation: boolean;
}
interface MigrationOperation {
type: 'create_model' | 'update_model' | 'delete_model';
modelName: string;
impact: 'safe' | 'warning' | 'destructive';
description: string;
contentCount?: number;
}4. Enhance
The SchemaSyncOrchestrator enhances the migration plan with content impact data. For each affected model, it queries the API for the number of existing content entries and annotates the operations:
// Before enhancement:
// "Create new content model "BlogPost" with 4 fields"
// After enhancement:
// "Create new content model "BlogPost" with 4 fields (no existing content)"
// "Delete content model "OldPage" and all associated content (3 content entries affected)"5. Execute
If the plan is not a dry run, the orchestrator sends the migration plan to the server for execution. The server processes each operation in order and returns a result:
interface MigrationResult {
success: boolean;
operationsCompleted: number;
totalOperations: number;
errors?: string[];
}Impact Classification
Every operation in the migration plan is classified into one of three impact levels:
Safe
No existing data is affected. Creating new models and adding optional fields are always safe.
| Change | Impact |
|---|---|
| Create a new model | Safe |
| Add an optional field | Safe |
| Add a field with a default value | Safe |
| Make a required field optional | Safe |
| Change constraints (unique, maxLength) | Safe |
Warning
Existing data may need updates but will not be lost. The CLI shows a warning and asks for confirmation.
| Change | Impact |
|---|---|
| Add a required field (no default) | Warning -- auto-fills with type default |
Change Int to Float | Warning -- lossless conversion |
Change Float to Int | Warning -- may lose decimal precision |
Change from String to another type | Warning -- depends on content |
| Make an optional field required | Warning -- null values need defaults |
Destructive
Data will be permanently lost. The CLI shows an explicit warning and requires confirmation.
| Change | Impact |
|---|---|
| Delete a model | Destructive -- all content entries removed |
| Remove a field | Destructive -- field data deleted |
Change Boolean to non-Boolean | Destructive -- incompatible conversion |
Change DateTime to non-DateTime | Destructive -- incompatible conversion |
Auto-Fill Defaults
When a required field is added to a model that already has content, the system auto-fills existing entries with a type-appropriate default value:
| Field Type | Auto-Fill Default |
|---|---|
String | "" (empty string) |
Int | 0 |
Float | 0.0 |
Boolean | false |
DateTime | null |
If the field has a default constraint in the schema, that value is used instead.
Safety Report
Every migration plan includes a safety report summarizing the risk:
interface SafetyReport {
safeOperations: number;
warningOperations: number;
destructiveOperations: number;
warnings: string[]; // Human-readable warning messages
blockers: string[]; // Currently unused, reserved for future hard blockers
}The plan requires confirmation if warningOperations > 0 or destructiveOperations > 0. Example output:
Migration Plan:
Operation Model Impact Description
create_model Product safe Create new content model "Product" with 3 fields (no existing content)
update_model BlogPost warning Update model "BlogPost" (2 field changes) (15 entries affected)
delete_model OldPage destructive Delete content model "OldPage" and all associated content (3 entries affected)
DESTRUCTIVE: Deleting model "OldPage" will permanently remove all content data (3 content entries will be affected)
? Are you absolutely sure you want to continue? This cannot be undone. (y/N)Dry Run Mode
Preview the full migration plan without executing anything:
pnpm laizy sync --dry-runDry run performs the entire pipeline (parse, diff, plan, enhance) but stops before execution. Use this to review changes before committing.
Migration Plan:
Operation Model Impact Description
create_model Product safe Create new content model "Product" with 4 fields
Dry run complete - no changes madeForce Mode
Skip all confirmation prompts and execute immediately:
pnpm laizy sync --forceForce mode bypasses all safety confirmations including destructive changes. Only use this in CI/CD pipelines after verifying the plan with --dry-run.
Using the Orchestrator Programmatically
The SchemaSyncOrchestrator can be used directly in your code for custom sync workflows:
import { SchemaSyncOrchestrator } from '@/lib/schema/sync/schema-sync-orchestrator';
import { ManagementClient } from '@/lib/management-client';
const client = new ManagementClient({
baseUrl: 'https://laizycms.com',
apiToken: process.env.LAIZY_API_TOKEN!,
projectId: 'your-project-id',
});
const orchestrator = new SchemaSyncOrchestrator(client);
// Get the diff without executing
const diff = await orchestrator.getDiff(localModels);
// Generate a plan with content impact
const plan = await orchestrator.planMigration(localModels);
// Execute the full sync
const result = await orchestrator.syncSchemas(localModels, {
projectId: 'your-project-id',
dryRun: false,
force: true,
});
console.log(result.message);
// "Migration completed successfully. 3/3 operations executed."There are also convenience functions for common operations:
import {
syncSchemas,
getSchemaDiff,
planSchemaMigration,
} from '@/lib/schema/sync/schema-sync-orchestrator';
// One-liner sync
const result = await syncSchemas(client, localModels, {
projectId: 'your-project-id',
dryRun: false,
force: true,
});
// One-liner diff
const diff = await getSchemaDiff(client, localModels);
// One-liner plan
const plan = await planSchemaMigration(client, localModels);Error Handling
Parse errors
Schema syntax errors stop the pipeline immediately:
Schema parsing failed
Error: Line 12: Unexpected token "{"Authentication errors
If the API token is invalid or expired:
Analysis failed
Error: unauthorized
This might be an authentication issue.
Try running laizy init to update your credentials.Execution errors
If an operation fails during migration, the result includes error details. Operations that completed before the failure are not rolled back:
const result = await orchestrator.syncSchemas(localModels, options);
if (!result.migrationResult?.success) {
console.error('Migration had errors:', result.migrationResult?.errors);
console.log(`Completed: ${result.migrationResult?.operationsCompleted}/${result.migrationResult?.totalOperations}`);
}Confirmation Flow
When a migration plan requires confirmation (any warning or destructive operations), the behavior depends on the mode:
| Mode | Behavior |
|---|---|
| Interactive (default) | Shows plan, asks for confirmation before executing |
--dry-run | Shows plan, stops without executing |
--force | Shows plan, executes immediately without asking |
Programmatic (skipConfirmation: true) | Returns the plan without executing, lets caller decide |
Next Steps
- Content Models API -- The underlying API endpoints used by the orchestrator
- laizy sync Command -- CLI command reference
- Field Types -- Type conversion rules and migration behavior