ServerMetadata
Migration Engine
DDL execution library for tenant databases with safe patterns.
The migration engine is a utility layer in lib/migration/ that abstracts raw SQL into a typed TypeScript API using TenantPrisma.
Core API
createModel(org, spec, prisma)
Creates a table with the specified fields:
await createModel("acme-corp", [
{ name: "id", type: "String" },
{ name: "name", type: "String", nullable: false },
{ name: "amount", type: "Float", default: 0 },
], tenantPrisma);
// Executes: CREATE TABLE "acme-corp"."invoice" (...)addFieldToModel(org, table, field, prisma)
Adds a column to existing table:
await addFieldToModel("acme-corp", "invoice", {
name: "status",
type: "String",
nullable: true,
default: "draft",
}, tenantPrisma);
// Executes: ALTER TABLE "acme-corp"."invoice" ADD COLUMN status VARCHAR(100)dropFieldFromModel(org, table, column, prisma)
Drops a column:
await dropFieldFromModel("acme-corp", "invoice", "old_field", tenantPrisma);
// Executes: ALTER TABLE "acme-corp"."invoice" DROP COLUMN old_fieldFieldSpec Interface
interface FieldSpec {
name: string;
type: "String" | "Int" | "Float" | "Boolean" | "DateTime" | "Text" | "Json";
nullable?: boolean;
unique?: boolean;
default?: any;
references?: {
schema: string;
table: string;
column: string;
onDelete: "CASCADE" | "SET NULL" | "RESTRICT";
};
}Safe DDL Patterns
1. Idempotency
The engine assumes metadata is consistent. It does not auto-re-sync if DB and metadata drift.
2. Transactional Safety
PostgreSQL DDL is transactional. If a hook fails, the metadata transaction rolls back.
3. Retry Logic
Uses withDbRetry to handle transient connection issues:
await withDbRetry(
() => tenantPrisma.$executeRaw`ALTER TABLE...`,
{ maxAttempts: 3, delayMs: 1000 }
);4. Connection Handling
Requires TenantPrisma instance from CRUD Engine which resolves connection by orgId:
// Provided by CRUD context
const tenantPrisma = context.prisma; // Already scoped to tenantLogging
All DDL operations are logged with structured logs:
{
"message": "CREATE TABLE",
"org": "acme-corp",
"table": "invoice",
"fields": ["id", "name", "amount"]
}