Skip to content

feat(appkit): add appkit db init, clean and seed - #672

Draft
ditadi wants to merge 1 commit into
stack/database-dev-flow/04-db-introspectfrom
stack/database-dev-flow/05-db-init
Draft

ditadi wants to merge 1 commit into
stack/database-dev-flow/04-db-introspectfrom
stack/database-dev-flow/05-db-init

Conversation

@ditadi

@ditadi ditadi commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #670.

Summary

Adds appkit db init, appkit db clean and appkit db seed, which give a checkout its own Lakebase branch for local development. init asks nothing, so an agent or a script can run it as is.

appkit db init:

  • uses your dev-<you>-<hash> branch (no expiry), or with --session a session-<hash>-<id> branch for this checkout (7 days, or --ttl 1h), forked from the project's default branch;
  • makes sure the branch has a read-write endpoint that suspends after 5 idle minutes, and a Postgres role for you with DATABRICKS_SUPERUSER;
  • points .env at the branch;
  • hands you the app schema when the fork copied it owned by another Lakebase role, such as the app's service principal;
  • installs drizzle-kit when the app lacks it;
  • starts the schema: apply the migrations (the default with schema.ts), introspect (the default without), clean and then apply, or leave it.

It refuses the project's default branch, protected branches and any branch databricks.yml deploys to, before changing anything. Those belong to the deployed app, which applies its own migrations on start (#654).

Details

  • Without --session or --dev, init keeps the session branch .env points at, and otherwise uses your dev branch. The session hash comes from your user name, so a session id copied from someone else's .env is refused.
  • A branch name databricks.yml deploys to is refused before any API call. An existing branch that became the default or protected is refused after one read.
  • A deleted dev branch is an error that says how to restore or delete it, since forking again would lose its data. An expired session branch gets a fresh session id.
  • At the project's branch limit, init stops before creating anything and lists your branches with their expiry and the databricks postgres delete-branch command. If none are yours, it says to ask a project owner.
  • A 403 on branch creation names the CAN MANAGE requirement. A 409 reads the branch again and reuses it, or rotates the session id when it is gone. The session id is written to .env before the branch is created, so a rerun after a failure reuses it.
  • The hand-off covers only the role that owns the app schema. init says what it does before deleting that role's copy on this branch with reassign_owned_to. When the delete fails, it lists the objects and privileges that block it, per database, from pg_shdepend. The default branch keeps its roles.
  • .env is refused when it is a symbolic link, before any API call. It is written to a temporary file with mode 0600 and renamed into place. Comments, order, other keys, export and CRLF stay. A multi-line quoted value is one entry. A missing .env gets only the keys init writes, never .env.example's placeholders. When .env names neither DATABRICKS_CONFIG_PROFILE nor DATABRICKS_HOST, init adds the one it authenticated with.
  • drizzle-kit is installed only for the migrate and introspect starts, pinned to the version the tooling supports, with the package manager whose lockfile is nearest. A failed install says what to run.
  • clean drops the app schema, migration journal included, in one transaction. public cannot be dropped, so there it drops the relations, types, functions, procedures, aggregates and window functions you own, and leaves extension members alone. Objects outside the schema that depend on it are dropped with it and listed. On a TTY the confirmation lists them first. Without a TTY it needs --yes.
  • clean --delete-branch deletes this checkout's session branch, for the end of a CI job. It refuses your dev branch.
  • init --schema clean asks on a TTY and needs --yes without one. A cancelled confirmation exits 1.
  • seed runs config/database/seed.sql in one transaction, with the app schema first on the search_path.
  • clean and seed run only on your dev or session branch, and check that before connecting.
  • The CLI loads the tooling through the checked contract and prints each step as it runs. The report says when a session branch expires and what the schema start did.
  • The db commands stay hidden until the PR that adds them to databricks apps init.

Tests

  • runInit against an in-memory Lakebase covers:
    • new and reused dev branches, a session branch with its TTL, --ttl, and the session id kept on rerun;
    • a deleted dev branch, an expired session branch, a 403 and a 409;
    • the branch limit, with and without branches of yours;
    • a deploy-target branch refused with no API call, and a default or protected one refused after one read, with .env unchanged;
    • a symlinked .env refused with no API call;
    • the endpoint's suspend timeout, a missing role, and a role without superuser;
    • the hand-off, an owner the role API does not manage, and a failed delete with and without blockers;
    • each schema start, the drizzle-kit install and a failed install;
    • the .env contents with the profile or host, and project resolution.
  • env-file tests cover the symlink refusal, a failed write leaving the old file and no temporary file, multi-line values and an unclosed quote.
  • CLI tests cover the flags, --session against --dev, no prompts, the clean confirmation with its dependents, --delete-branch, exit code 1 on cancel and the output.
  • New Postgres tests run in CI against the Postgres service from feat(appkit): apply committed database migrations on deploy start #654, and were also run against a local Postgres 14:
    • clean.pg.test.ts checks that an app schema clean reports and drops only what CASCADE takes outside it, and that a public clean drops a role's own objects and keeps extension members and other roles' objects.
    • hand-off.pg.test.ts checks that the blockers list other databases' objects, a database the role owns, and grants.
  • Not yet run against a live Lakebase project.

`appkit db init` gives a checkout its own Lakebase branch, forked from the
project's default branch: your dev branch, or with --session a branch for
this checkout that expires after 7 days (or --ttl). It asks nothing. It
refuses the default branch, protected branches and branches databricks.yml
deploys to before any change, and when the project is at its branch limit it
lists your branches to delete.

It makes sure the branch has a read-write endpoint that suspends after 5 idle
minutes and a Postgres role for you, points .env at it with an atomic 0600
write, hands you the app schema when the fork copied it owned by another
Lakebase role, installs drizzle-kit when the app lacks it, then starts the
schema by migrating, introspecting or cleaning.

`appkit db clean` drops the app schema (only your objects in public) and
lists what outside it went along; --delete-branch deletes this checkout's
session branch instead. `appkit db seed` runs config/database/seed.sql in one
transaction with the app schema on the search_path. Both run only on a branch
created for you or this checkout.

Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi
ditadi force-pushed the stack/database-dev-flow/05-db-init branch from 960b211 to c56de21 Compare October 19, 2026 20:35

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