- roughly 30 schemas;
- about 8,300 schema objects;
- hundreds of Row Level Security policies.
Speed: the whole tree, no database, in under two seconds
Extracting and planning the entire declarative tree (8,271 modeled objects) runs on the parser alone — no Docker, no shadow database, no introspection:schemas.exclude, role/control-plane bootstrap files under _bootstrap are skipped as inventory, and the remaining normalize-fidelity warnings preserve source SQL without changing object identity. None are engine errors.
Head-to-head on real schema (bounded slice)
A 282-object slice of the real tree (three schemas: identity, calculators, messaging — 73 RLS policies among them) was diffed against itself plus a small additive change, supaschema versus the default Supabase CLI engine, one iteration, both applied to a throwaway Postgres and re-applied:
The Supabase engine emitted an unguarded
CREATE TABLE public.…(…) (no IF NOT EXISTS) and an unguarded CREATE INDEX, so the second apply fails with relation "…" already exists. supaschema’s output is guarded by construction (IF NOT EXISTS, catalog-checked DO blocks), so a crashed or retried deploy simply runs the file again.
Reproduce against any declarative tree:
Security: the miss that speed hides
Speed is the visible pain. RLS correctness is the bigger risk. On a multi-tenant platform, a policy’sUSING predicate is the tenant boundary.
Changing USING (true) to USING (tenant_id = current_tenant()) can close an isolation hole. Every Supabase CLI diff engine measured here silently drops that policy-body change because it diffs policies by name, not by body.
See the accuracy results. supaschema scores F1 1.000; the compared engines score 0.982-0.999 on the same missed-policy fixture.
A slow diff costs time.
An unreplayable diff costs a deploy.
A silently dropped policy change can ship a tenant-isolation bug.

