Migrate an Existing Schema
One command rewrites tables you already wrote with Drizzle ORM onto the slim packages and leaves every query alone.
npx mion drizzle-migrate src/db/schema.ts
Point it at a file or a whole directory. --check shows what it would change without writing anything, and --out-dir converted/ writes the result somewhere else (for a single directory only), leaving your sources untouched.
Migrated Tables
Your table keeps its name. A second binding, with a $table suffix, holds the slim declaration:
// before
import {pgTable, uuid, varchar} from 'drizzle-orm/pg-core';
const users = pgTable('users', {id: uuid().primaryKey(), name: varchar({length: 50}).notNull()});
// after
import {pgTable, uuid, varchar} from '@mionjs/drizzle-orm-pg-core';
import {toDrizzle} from '@mionjs/drizzle-orm-pg-core/drizzle';
const users$table = pgTable('users', {id: uuid({primaryKey: true}), name: varchar({length: 50, notNull: true})});
const users = toDrizzle(users$table);
users still holds the real Drizzle table, so every query, operator and config reader works with no edits:
await db.select().from(users).where(eq(users.id, someId));
getTableConfig(users);
Your models and routes take their types from users$table. A view gets $view, an enum $enum, and so on for schemas, sequences, roles and policies.
Import Changes
Column builders, tables, views, indexes and constraints move to the slim packages. Operators, config readers and everything on the query side stay on drizzle-orm:
import {alias, except, getTableConfig} from 'drizzle-orm/pg-core';
import {foreignKey, index, integer, text} from '@mionjs/drizzle-orm-pg-core';
Two SQL Templates
sql lives on both sides: Drizzle's builds queries, and the slim one records a default or a check. Both are imported, the slim one as rtSql:
import {sql} from 'drizzle-orm';
import {sql as rtSql} from '@mionjs/drizzle-orm';
const docs$table = pgTable('docs', {at: timestamp({default: [rtSql`now()`]})});
await db.execute(sql`select 1`);
Code It Leaves Alone
The command reports what it cannot place and leaves it as written, so the file still runs:
schema.ts: drizzle-migrate-query-builder-view error [newYorkers:42]: a view built from a query builder stays on drizzle:
its columns come from drizzle's select typing. Declare the columns explicitly to migrate it.
| Name | Stays with Drizzle |
|---|---|
drizzle-migrate-query-builder-view | A view built from a query builder |
drizzle-migrate-unfoldable-column | A table with a column chain that has no one-object spelling: the same modifier twice (.array().array()), or a reference to a table that stays with Drizzle |
Converting to Pure Types
mion convert --to type works on the migrated file too. It rewrites each table as a pure type plus a one-line bridge:
npx mion convert --to type src/db/schema.ts
// after drizzle-migrate
const users$table = pgTable('users', {id: uuid('id', {primaryKey: true}), name: varchar('user_name', {length: 50})});
const users = toDrizzle(users$table);
// after convert --to type
type Users$table = PgTable<'users', {
id: Uuid<{primaryKey: true}>;
name: Varchar<{length: 50}>;
}, [], {name: 'user_name'}>;
const users$table = tableFromType<Users$table>();
const users = toDrizzle(users$table);
It works with the imports you already have (no namespace import required) and on tables declared inside a function or a test, not only at the top of a file. --to builders goes back the other way, and a file in type form converted both ways ends up exactly as it started.
Some tables have no type spelling, and the command says which and why rather than guessing: a table built by a table creator, a table declared on a schema, $type on a column, or a column name that is not a literal. Those tables stay as written and keep working, and the other tables in the file still convert. --report report.json writes the whole list to one file.
References to Later Tables
A column that points at another table keeps working wherever that table is declared. When the target sits further down the file, the bridge takes a function instead of the value, like Drizzle's own .references(() => cities.id):
const children$table = tableFromType<ChildrenTable>({tables: {cities: () => cities$table}});
Checking the Result
Read the diff, then run your type checker and your tests. Your original names still hold the real Drizzle tables, so there is very little left to check.