Skip to content

feat(appkit): add appkit db introspect - #670

Draft
ditadi wants to merge 1 commit into
stack/database-dev-flow/03-db-migratefrom
stack/database-dev-flow/04-db-introspect
Draft

ditadi wants to merge 1 commit into
stack/database-dev-flow/03-db-migratefrom
stack/database-dev-flow/04-db-introspect

Conversation

@ditadi

@ditadi ditadi commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #666.

Summary

Adds appkit db introspect, for apps whose tables already exist in Lakebase.

It reads a Postgres schema (app by default, or --schema-name) from pg_catalog and writes config/database/schema.ts from it, keeping the previous file as schema.ts.bak. It also writes the migration that brings history up to the database, and records it as applied on the branch it read:

Recording is only true on the branch introspect read, so it runs only on the developer's dev-… or session-… branch. On any other branch it refuses before connecting and points to appkit db init (#672), which creates the dev branch from the project's default branch and introspects it when there is no schema.ts.

Details

  • It never reads the current schema.ts, so a missing, empty or broken file can be regenerated. If the schema has no tables introspect can write, it refuses to replace an existing schema.ts with an empty one.
  • What the schema builder cannot express (composite keys, unsupported types or defaults, some foreign keys) is left out with an // appkit: skipped … comment and reported. A skipped NOT NULL column without a default is a loud warning, since inserts through the API would fail.
  • What schema.ts declares differently from the database (varchar without a length, timestamp precision, serial keys, partitioned tables, CHECK constraints, indexes, DEFERRABLE) is listed. The branch keeps it as it is. A database the migrations create gets what schema.ts declares.
  • What introspect does not read at all is named in a warning: views, materialized views, foreign tables, standalone sequences, triggers, policies, row-level security, inheritance, functions, domains, range and composite types, and enums no column uses. Objects an extension owns are left out of it.
  • With history, the diff starts from the latest committed snapshot, so the branch must record exactly the committed migrations. It refuses when the branch records migrations the checkout lacks, skipped one, or has not applied one. Migrations an interrupted introspect wrote but did not record are recorded by the next run, once a fresh render matches them.
  • It refuses a migration that drops something schema.ts left out and the branch still has: a table, column, default, constraint or enum type. The check matches the exact statement drizzle-kit writes for each skipped item, including the unqualified form drizzle-kit uses for the public schema. A drop of something the branch no longer has is written, with the breaking-change warning from feat(appkit): add appkit db migrate #666.
  • Recording takes the migration lock and reads the journal again. It records nothing if the journal changed in the meantime or the lock wait runs out.
  • Catalog names in comments and terminal lines have line breaks, control characters and bidi controls escaped. String literals in schema.ts also escape U+2028, U+2029 and bidi controls. Constraint names are cut at 63 bytes, as Postgres does.
  • If anything fails after schema.ts is written (it does not load, drizzle-kit is missing, a rename is ambiguous, recording fails), schema.ts, its backup, database.d.ts and the migrations are put back. The backup is only rewritten when the content changes, so a rerun keeps the original.
  • The report's recorded lists every tag recorded as applied.

Tests

  • Rendering is checked against a golden file, and rendered sources are loaded through the plugin's loader and diffed by the real drizzle-kit.
  • A round trip with hostile names (line terminators, quotes, backslashes, backticks, bidi controls) in tables, columns, foreign keys, enums and defaults loads and diffs cleanly. Removing the escaping or the bracket access makes it fail.
  • runIntrospect tests cover the branch refusal, each drift case, recovery from an interrupted run, a refused drop for each kind of skipped item (also in public), a drop that is written, the unread warning, an empty or fully skipped schema, and rollback when recording or the types write fails.
  • New Postgres tests run in CI against the Postgres service from feat(appkit): apply committed database migrations on deploy start #654:
    • catalog.pg.test.ts reads a real schema with partitions, identity and generated columns, checks, indexes, enums, and every kind of object introspect does not read.
    • apply.pg.test.ts covers recording: exactly the pending migrations without running them, refusal on drift, a missing tag failing before connecting, and the lock wait limit.

`appkit db introspect` reads an existing Postgres schema (default: app) from
pg_catalog and writes config/database/schema.ts from it, keeping the previous
file as schema.ts.bak. It also writes the migration that brings history up to
the database, 0000_baseline first and NNNN_introspect after, and records it
as applied. That is only true on the branch it read, so it runs only on the
developer's dev or session branch, which `appkit db init` creates. The
deployed app adopts 0000_baseline on start when its tables already exist.

What the schema builder cannot express is skipped and reported, and what it
declares differently from the database (a timestamp precision, an index) is
listed. Objects it does not read (views, triggers, policies, functions,
standalone sequences) are named in a warning, since a database the
migrations create will not have them.

It refuses when the branch's journal and the checkout's migrations differ,
and it refuses a migration that drops something schema.ts left out, since
the deployed app runs that migration on start. Recording takes the
migration lock and checks the journal again, and a run interrupted before
recording is finished by the next one. If anything fails after schema.ts is
written, schema.ts, its backup, database.d.ts and the migrations are put
back.

Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi
ditadi force-pushed the stack/database-dev-flow/04-db-introspect branch from a2522e5 to c3e1172 Compare October 19, 2026 15:02

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant