Selegic CRM Docs
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_field

FieldSpec 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 tenant

Logging

All DDL operations are logged with structured logs:

{
  "message": "CREATE TABLE",
  "org": "acme-corp",
  "table": "invoice",
  "fields": ["id", "name", "amount"]
}

On this page