Skip to content

Migrations

You never write a migration file. You add a new schema version, register it, and Outer diffs it against the previous one.

Register versions

Pass each schema version to .schema() on the builder, in order:

new Outer({ db: pglite() })
  .schema(v1_0)
  .schema(v1_1) // each call adds a migration step and updates the DB type
  .build();

Each call also advances the type of context.db to InferDB<T>, so procedures registered after a .schema() call see the new columns.

Apply migrations

await server.migrator.migrateToLatest();

Outer diffs consecutive schema versions with a custom SchemaMigrationProvider. Each schema("x.y.z") call becomes one Kysely migration keyed by its version string.

  • Up creates new tables, adds new columns with their indexes, and drops removed columns.
  • Down reverses all three.

Run this on startup when you self-host. On serverless, run it from a deploy-time script instead — see Deployment.

Zero-pad version segments past 9

Kysely applies migrations in lexicographic order of their version-string keys, which disagrees with numeric order once a segment reaches double digits: "1.10.0" sorts before "1.2.0".

migrateToLatest() detects the mismatch up front and returns an error telling you to zero-pad the segments, for example "1.02.00". It never applies migrations out of order silently.

Changed columns are refused, not ignored

Editing a column in place — its type, nullability, default, uniqueness, primary key, foreign key, or index — produces no add and no drop, so the diff has nothing to emit.

Rather than migrate to nothing and leave your schema and database disagreeing, migrateToLatest() returns an error naming each offending table and column:

Schema version "2.0.0" changes existing columns, which Outer cannot migrate automatically:
  thing
    name: became nullable

Adding and dropping columns is supported. Altering one is not. Either revert the edit, or write the ALTER yourself with context.db and keep the schema in sync.

Renames destroy data

A rename reads to the diff as a drop plus an add, which it performs happily — and that destroys the old column's data. Copy the data across yourself before removing the old name.

Next steps

  • Resources — generate CRUD over the migrated tables
  • Deployment — when and where to run migrations