Skip to content
View as .md

Supabase

Schema, migrations, and verify scripts in packages/supabase/. There is no local DB — the project is linked straight to the remote (prod) via packages/supabase/.temp/project-ref, so every push and write hits production. Migrations are dated SQL files at packages/supabase/migrations/YYYYMMDDHHMMSS_slug.sql.

Apply migrations with bun run push-migrations from the repo root — it filters to @arbe/supabase (so the cwd is right; running supabase db push from the repo root hits the separate edge-functions supabase/ dir and fails the migration-history check) and regenerates database.types.ts afterward. Don’t reach for the raw bunx supabase db push unless you need a flag the wrapper lacks.

Terminal window
bun run push-migrations # apply new migrations to prod + regen types (run from repo root)
cd packages/supabase # for the lower-level commands below
bunx supabase db push --linked --dry-run # preview only
bunx supabase migration list --linked # diff local vs remote history
bun run --filter '@arbe/supabase' update-types # regenerate database.types.ts on its own
cd packages/ # workdir auto-resolves here, NOT packages/supabase/
bunx supabase db query --linked "select tablename from pg_tables where schemaname='public'" -o table # ad-hoc read
bunx supabase db query --linked -f supabase/tests/verify-orphan-house-cleanup.sql
bunx supabase db query --linked -f supabase/tests/verify-cascade-delete-atomicity.sql

Extensions: pg_cron is enabled. Workflow schedules own the wf:<id> jobs — a trigger syncs them from workflows.schedule, so manage them through the column, never cron.schedule directly (see workflows). Inspect with select * from cron.job, firing history in cron.job_run_details.

Verify scripts (packages/supabase/tests/*.sql) are begin; do $$ ... $$; rollback; blocks — DB untouched, asserts raise on first failure. Empty rows + no 400 = passed (the Management API swallows raise notice). A failure surfaces as unexpected status 400: ... ERROR: P0004: FAIL: <message>. psql "$POOLER_URL" -f … also works since scripts are pure SQL.

Gotchas worth remembering:

  • supabase db query is not psql — it routes through the Management API, which rejects \set, \echo, or any \-prefixed line with syntax error at or near "\". Keep verify scripts portable: no meta-commands; any end-of-run banner goes as raise notice inside a DO block (and accept that the API swallows it).
  • -f is mandatory for files. A bare positional like bunx supabase db query --linked ./tests/foo.sql is interpreted as inline SQL and fails with syntax error at or near ".".
  • Workdir auto-detection lands on packages/, not packages/supabase/. Paths passed to -f are resolved relative to that workdir, so from anywhere inside the supabase package you still write supabase/tests/foo.sql.
  • Rewriting a function needs a new migration. To change cleanup_orphaned_houses or any create or replace function, add a new dated migration that re-issues the statement — don’t edit historical migration files (e.g. 20260417000000_retire_stranded_bots.sql layered on top of earlier RLS work).
  • Connection pooler vs direct. supabase db push uses the pooler URL (port 5432, pooler.supabase.com). Direct db.<project>.supabase.co:5432 works too but isn’t what the CLI advertises.
  • Never edit database.types.ts by hand — it’s a generated artifact. Manual edits drift from the remote schema and silently break adapters that trust the type.
  • Cannot find project ref? --linked reads the ref only from packages/supabase/.temp/project-ref (gitignored, not committed) — not from config.toml project_id nor $SUPABASE_PROJECT_REF. On a fresh clone, relink once from inside the package: cd packages/supabase && bunx supabase link --project-ref gxlrglyxsrldjjrpkdsy. And run query/migration commands from inside packages/ — the repo-root supabase/ is a separate dir (edge functions) whose --linked lookup fails the same way.

Code: packages/supabase/migrations/, packages/supabase/tests/, packages/supabase/database.types.ts.
See system/storage, system/permissions.