Pull and import
okm pull, import drizzle, and ext.
pull
okm pull reads one database and writes one table file per table, plus the schema file. It does not write the selected namespaces, so a protected target is allowed. It creates a scratch schema to verify functions and views, then drops it. An existing file is OKM1855 and nothing is written. --out <directory> is the other place to write. The default namespace is the target's current schema. --schema <name> selects others and may be repeated. A column, constraint, or index whose name or column is a reserved word (order, Order) is listed under not pulled yet and is not written. That index is recorded in .okm/pull.json, and push does not drop it. The other tables and columns are still written. A foreign key to a table outside the selected schemas is listed under not pulled yet and is not written. --schema billing alone still writes .references("users") for a key that points at public.users, so the key is rebound onto billing.users. okm build then reports OKM1022 when the column types differ; when they match, the key points at the wrong table. Repeating --schema for both namespaces writes nothing: OKM1023 when both schemas contain the same table name, or OKM1021 when the foreign key's target is not a table in the one namespace schema() can hold.
Export bindings and file stems are unique when folded to lowercase, so Tasks and tasks become Tasks / tasks_2 and do not collide on a case-insensitive volume or in row types. A Windows-reserved stem (con, aux, nul, com1–com9, lpt1–lpt9, any case) gets a _ prefix. The SQL name stays in sqlName when the binding differs. A name that is an okmodel import (t, table, columns, sql, index, schema, extension, fn, trigger, view, materializedView) gets Name on the end so the file can load. A column named __proto__ is written as a computed key. Single-column foreign keys name the exported table binding, not the raw SQL table name. Relative imports keep the .ts extension so Node type stripping and bunx okm can load the schema. When tsc typechecks that output, set allowImportingTsExtensions with noEmit, or set rewriteRelativeImportExtensions so emit rewrites them to .js. --import-ext js writes .js specifiers. --import-ext none writes none. The default is ts. When oxfmt is missing, the files stay unformatted, a notice is printed, and the report still returns.
--casing snake or --casing camel is opt-in. The default keeps each database name as the TypeScript key. With the flag, the key is the other spelling and schema({ casing }) is written. .sqlName() is added where that key does not spell the database name, and each of those names is listed in the report.
Tables, columns, keys, uniques, checks, single-column foreign keys, indexes, identity sequences, enums, domains, extensions, functions, triggers, views, and materialized views are written. An integer generated always as identity column that is the only primary key is t.integerIdentity(). A bigint one stays t.identity(). .okm/pull.json is written at the project root, including when --out writes the target named default or the only configured target. A --out pull of any other --target, or one that passes --schema, leaves an existing record in place. That holds when --out is the project schema directory. okm push refuses OKM1897 when the plan would drop a column or constraint listed under not pulled yet. okm check omits those objects, so an ok report does not list the drops. A pull with no --out replaces .okm/pull.json, including pull --target other when several targets are configured. Push of the original target is then OKM1898 when that replacement no longer lists the drop and the table has a row. A view is SQL text with its columns. Views and BEGIN ATOMIC bodies take dependencies from pg_depend. A plpgsql or SQL-string body gets dependsOn candidates from a lexical match. The report marks each function verified (the scratch replay created it with check_function_bodies on), candidate-only (plpgsql: creation does not prove the body), or unverified (creation failed). A comment above the declaration says the same thing. Policies, enable and force row level security, roles, grants, aggregates, procedures, event triggers, and rules are printed under not pulled yet with a count, and so is anything table() cannot say. Enable and force are one line each, with the alter table statement, when a table has a policy. okm check after migrate baseline does not plan to drop that security when the schema declares no policy. A column type with no builder is that list, with the table and the column. Nothing is dropped without a line. A column the database holds as NOT NULL … NOT VALID (PostgreSQL 18) is written .nullable() with a comment and listed under not validated, because rows with NULL may exist.
bunx okm pullbunx okm pull --schema public --out ./adoptedimport drizzle
okm import drizzle <path> reads a Drizzle schema and writes one table file per table, plus the schema file. It imports that path, so the file runs. The drizzle-orm package is the one installed in the project. okmodel does not depend on it. A missing package is OKM1856: install drizzle-orm in the project. An existing file is OKM1855 and nothing is written. The command does not connect.
The report starts with namespace <name>. When the name is not public, set namespace to it. schema() still compiles public. File stems follow the same lowercase-folded uniqueness and Windows-reserved rules as pull. Composite primary-key columns are not null even when Drizzle omitted .notNull() on the column. An index with no name keeps drizzle-kit's {table}_{columns}_index. A name that is already {table}_{columns}_idx is left as index(...).
Casing is the --drizzle-casing snake_case|camelCase|none flag, then a snakeCase or camelCase builder, then casing on drizzle() and drizzle.config.*, then none. none reports each keyAsName column. An empty SQL name is reported and not written. See From Drizzle.
Composite foreign keys, relations(), and custom column types are printed under not imported with the table and the reason. A serial column is written as integer not null. The sequence serial would create is listed, not written.
bunx okm import drizzle ./src/db/schema.ts --out ./adoptedext
okm ext list and okm ext check connect to the selected target as the read-only check class. A protected target is allowed. A pooler host is not refused.
list prints a tab-separated table. The header is name, default, installed, schema.
check compares extensions declared on the schema with what is installed. No declaration is no extensions. A missing install is OKM1811: Extension <name> is not installed on the server. A version the declaration rejects is OKM1812: Extension <name> needs <declared> or higher. The server has <installed>. Install a version the declaration accepts, or change the pin. An exact pin omits "or higher". A match prints name, declared, installed. An omitted pin prints the extension name. A pin prints that version or floor. Declarations are on Extensions.
scaffold <name> reads pg_depend, pg_type, pg_proc, pg_operator, and pg_settings for an extension that is already installed. A missing install is OKM1811. The text is a starting extension() definition the project owns. --out <file> writes that text. An existing file is invalid and nothing is written. Without --out the text is printed.
test <file> loads that module and checks each exported extension(). Every declared type, operator, function, and index method must exist with that signature. A mismatch is invalid and the message lists each one. A requires version or Postgres floor the server does not meet is OKM1812. A match prints ok <name>.
Another name is Unknown command ext <name>.
bunx okm ext check