encryptedSupabaseV3 cannot be constructed without a direct Postgres connection. That rules out Cloudflare Workers, Supabase Edge Functions, and the browser — environments where the Supabase client is otherwise perfectly usable.
This is a v3 limitation that exists today, not a consequence of any planned work. Filing it separately because it was briefly mistaken for an EQL v2 removal blocker (#707) and is not one.
What happens
packages/stack-supabase/src/introspect.ts:167-171
encryptedSupabaseV3 introspects the database over a direct Postgres connection, but the optional peer dependency 'pg' is not installed. Install it (npm install pg). This also means encryptedSupabaseV3 cannot run in a Worker or the browser — use encryptedSupabase (EQL v2) there.
Construction also hard-requires options.databaseUrl or DATABASE_URL (index.ts:198-203), which is awkward even on Node — the caller already has an authenticated Supabase client and now has to supply a second, more privileged credential.
Why v3 needs it and v2 doesn't
Not an oversight — it falls out of the v3 design.
- v2 (
encryptedSupabase): the caller declares the schema in code. No database connection needed at construction.
- v3 (
encryptedSupabaseV3): the SDK detects encrypted columns from their Postgres domain types and derives each column's encryption config from the domain name. public.eql_v3_text_search is the config. That is the headline v3 ergonomic — "callers no longer pass a schema to from()" — and it requires reading information_schema.domain_name.
PostgREST can't supply it: a domain column's udt_name is just jsonb, so domain_name is the only discriminator, and it isn't exposed over the REST API.
Note that passing schemas does not avoid introspection. It still runs, and is used for verification (verifyDeclaredSchemas) plus the unmodelled-column guard, then merged (index.ts:212-224).
The unmodelled-column guard matters
Introspection returns two things: the tables, and unmodelled — columns carrying EQL v3 domains this SDK version does not know about. synthesizeTables silently drops those, so without the guard an encrypted column would be read and written as plaintext. Any fix that skips introspection has to account for this; it is not merely a convenience check.
Possible directions, cheapest first
- Verify the claim. The error message says "a Worker or the browser" as one case, but they differ: the browser genuinely cannot open a TCP socket, while Cloudflare Workers (
nodejs_compat) and Supabase Edge Functions (Deno, which has raw TCP and node compat) plausibly can run pg today. If so, a good part of this is a stale error message plus a documentation line rather than a design change. Worth an hour before scoping anything else.
- Introspect over the client you already have. Ship a Postgres function in the EQL v3 bundle and call it via
supabase.rpc('cipherstash_introspect'). Plain HTTP, no pg, no TCP, works anywhere supabase-js works — which is the only environment that matters, since the wrapper is useless without it. Keeps both the verification pass and the unmodelled guard, and drops the DATABASE_URL requirement entirely. Cost: the function has to be installed, so it becomes part of the bundle.
- Declared-schemas-only mode. Skip introspection when
schemas is supplied. Cheapest to build, but forfeits both the drift check and the unmodelled guard — the second being a silent-plaintext hazard. Reasonable as a documented opt-in escape hatch, not as the primary answer.
A build-time introspection snapshot was also considered and is not recommended: it trades a runtime dependency for a staleness hazard that reintroduces the same silent-leak risk by another route.
Priority
Depends entirely on whether anyone runs the Supabase wrapper in an Edge Function or the browser. If nobody does, this is a documented limitation rather than a defect. Worth answering before investing in (2).
Related: #707 (EQL v2 removal — this is not a prerequisite for it), #638 (delegating proxy facade), #601 (raw-pg / no-ORM guidance).
🤖 Generated with Claude Code
encryptedSupabaseV3cannot be constructed without a direct Postgres connection. That rules out Cloudflare Workers, Supabase Edge Functions, and the browser — environments where the Supabase client is otherwise perfectly usable.This is a v3 limitation that exists today, not a consequence of any planned work. Filing it separately because it was briefly mistaken for an EQL v2 removal blocker (#707) and is not one.
What happens
Construction also hard-requires
options.databaseUrlorDATABASE_URL(index.ts:198-203), which is awkward even on Node — the caller already has an authenticated Supabase client and now has to supply a second, more privileged credential.Why v3 needs it and v2 doesn't
Not an oversight — it falls out of the v3 design.
encryptedSupabase): the caller declares the schema in code. No database connection needed at construction.encryptedSupabaseV3): the SDK detects encrypted columns from their Postgres domain types and derives each column's encryption config from the domain name.public.eql_v3_text_searchis the config. That is the headline v3 ergonomic — "callers no longer pass a schema tofrom()" — and it requires readinginformation_schema.domain_name.PostgREST can't supply it: a domain column's
udt_nameis justjsonb, sodomain_nameis the only discriminator, and it isn't exposed over the REST API.Note that passing
schemasdoes not avoid introspection. It still runs, and is used for verification (verifyDeclaredSchemas) plus the unmodelled-column guard, then merged (index.ts:212-224).The unmodelled-column guard matters
Introspection returns two things: the tables, and
unmodelled— columns carrying EQL v3 domains this SDK version does not know about.synthesizeTablessilently drops those, so without the guard an encrypted column would be read and written as plaintext. Any fix that skips introspection has to account for this; it is not merely a convenience check.Possible directions, cheapest first
nodejs_compat) and Supabase Edge Functions (Deno, which has raw TCP and node compat) plausibly can runpgtoday. If so, a good part of this is a stale error message plus a documentation line rather than a design change. Worth an hour before scoping anything else.supabase.rpc('cipherstash_introspect'). Plain HTTP, nopg, no TCP, works anywheresupabase-jsworks — which is the only environment that matters, since the wrapper is useless without it. Keeps both the verification pass and the unmodelled guard, and drops theDATABASE_URLrequirement entirely. Cost: the function has to be installed, so it becomes part of the bundle.schemasis supplied. Cheapest to build, but forfeits both the drift check and the unmodelled guard — the second being a silent-plaintext hazard. Reasonable as a documented opt-in escape hatch, not as the primary answer.A build-time introspection snapshot was also considered and is not recommended: it trades a runtime dependency for a staleness hazard that reintroduces the same silent-leak risk by another route.
Priority
Depends entirely on whether anyone runs the Supabase wrapper in an Edge Function or the browser. If nobody does, this is a documented limitation rather than a defect. Worth answering before investing in (2).
Related: #707 (EQL v2 removal — this is not a prerequisite for it), #638 (delegating proxy facade), #601 (raw-pg / no-ORM guidance).
🤖 Generated with Claude Code