Skip to content

Docs V2: Supabase integration and supabase-js guide - #39

Merged
coderdan merged 31 commits into
v2from
cip-3328-supabase
Jul 30, 2026
Merged

Docs V2: Supabase integration and supabase-js guide#39
coderdan merged 31 commits into
v2from
cip-3328-supabase

Conversation

@coderdan

@coderdan coderdan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the complete Supabase integration journey to the V2 docs and aligns it with the Stack 1.0, EQL 3.0.4, and Auth 0.42 release APIs.

The section covers database setup and operations, migrating live plaintext columns without downtime, day-to-day supabase-js usage, Supabase Auth federation, identity-bound encryption, and Edge Functions. It also adds the canonical encryptedSupabase query-builder reference.

Supabase pages

  • /integrations/supabase — overview of EQL installation, encrypted domain types, grants, indexes, RLS composition, connections, dashboard behavior, and the CipherStash dashboard integration.
  • /integrations/supabase/quickstart — converts populated plaintext columns using nullable encrypted mirrors, dual-writes, a production deploy gate, resumable backfill, functional indexes, read cutover, soak, and guarded plaintext removal.
  • /integrations/supabase/supabase-js — creates the encrypted client and covers optional typed schemas, CRUD, filters, ordering, pagination, responses, errors, RLS, audit metadata, and lock contexts.
  • /integrations/supabase/auth — federates the current Supabase Auth session with OidcFederationStrategy and optionally binds encryption to JWT claims.
  • /integrations/supabase/edge-functions — runs encryption in Deno through the wasm-inline entry and caches CipherStash tokens across invocations.
  • /reference/stack/supabase — canonical wrapper reference: connect-time introspection, domain capabilities, the complete builder surface, response/error shapes, and deferred execution.

Release alignment

  • Uses encryptedSupabase as the primary EQL v3 factory. encryptedSupabaseV3 remains documented as a deprecated, type-identical alias.
  • Pins the Supabase section to Stack 1.0, EQL 3.0.4, and Auth 0.42 behavior.
  • Documents direct Postgres introspection through DATABASE_URL while application queries continue through PostgREST and preserve the wrapped client's Auth/RLS context.
  • Documents supported encrypted equality, range, and OPE-backed ordering operations.
  • Fails closed around a PostgREST limitation introduced in EQL 3.0.2 and retained in EQL 3.0.4: encrypted free-text and encrypted-JSON query operands require casts PostgREST cannot express. The docs route those queries to Drizzle, Prisma Next, or scoped SQL/RPC.
  • Corrects current behavior for OidcFederationStrategy.create(), plain-object lock contexts, unsupported encrypted ordering domains, and .csv().

Related integration work

  • Adds Drizzle and Prisma Next integration pages and updates the integrations landing page to route readers by database/query layer.
  • Adds shared FAQ components and Supabase-specific FAQ content.
  • Preserves and validates the supporting identity-aware encryption, migration, schema-design, audit, and CTS links introduced by the section.
  • Clears the repository's existing Biome failures by removing unsafe generator assertions, formatting the CLI fixture, and separating Mermaid rendering from the hook-using code-block component.

Validation

  • bun run lint
  • MDX generation and TypeScript checks
  • bun run validate-content
  • bun run validate-links
  • bun run validate-mermaid
  • git diff --check

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
public-docs Ready Ready Preview Jul 30, 2026 7:21am

Request Review

@coderdan
coderdan marked this pull request as ready for review July 2, 2026 12:56
@coderdan
coderdan force-pushed the cip-3328-supabase branch from 9683c86 to fd918a6 Compare July 6, 2026 13:50
coderdan added a commit that referenced this pull request Jul 8, 2026
- Remove em-dashes across the overview (AI "tell")
- Backtick `base` in "EQL base types"
- Convert the text_search note to a concise <Callout>
- Collapse the RLS/stack/KMS/performance/security sections into one
  linked "Built for the Supabase stack" summary
@coderdan

coderdan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Pushed two commits: a v2 sync merge, and a rework of the Supabase pages onto encryptedSupabaseV3 + the EQL 3.0.0 domains.

Everything below was verified against the adapter source on feat/eql-v3-text-search-schema (stack #588 merged into it, not into main) and against the EQL v3 domain SQL. The PR bodies have drifted from the code in three places, so I did not use them:

  • stack #588's body shows .like("email", "%@example.com") working. A later commit on the same branch, feat(stack)!: replace like/ilike with contains on the v3 supabase surface, makes .like() / .ilike() throw on encrypted columns.
  • EQL #389's body says text_search keeps [hm, ob, bf]. Its generated schema says [hm, op, bf].
  • main's encryptedSupabaseV3 is a sync ({encryptionClient, supabaseClient}) with a two-argument from(). That is the pre-#588 shape, because #588's base is #535, which is still open.

What changed

  • await encryptedSupabaseV3(url, key, options?) or (client, options?). Async, because it introspects at connect time and derives each column's config from its EQL domain. .from() takes only a table name; no encryptedTable to keep in sync.
  • Introspection needs a direct Postgres connection (DATABASE_URL or options.databaseUrl), which is a different credential from the anon key. Added to the quickstart env block, since it is easy to miss.
  • schemas is optional: compile-time types plus startup verification. Imported from @cipherstash/stack/eql/v3 (there is no @cipherstash/stack/schema/v3 export).
  • select('*') works on v3, expanded from the introspected column list. It throws on v2.
  • Column types are the prefixed domains, public.eql_v3_text_eq, not eql_v3.text_eq. Domains live in public; functions and operators live in eql_v3.
  • Supabase runs the EQL installer as a non-superuser, so it cannot create the block-ORE operator class and disables the ORE-backed domains, which then raise feature_not_supported on first write. Called out on the pages that offer the choice.
  • The query builder's .withLockContext() takes a LockContext instance; the core client's takes a plain { identityClaim } object. The auth page was passing the core shape to the builder. (Our own validate-content guard caught this, which is a nice result for chore(ci): gate the build on deprecated and non-existent API in docs #61.)

Two things worth a second opinion

.contains() may be unusable for substring search. text_search always builds its match index with include_original: true and exposes no tuning. The query term therefore carries the whole query string as one of its tokens, and containment requires every query token to be present in the stored set, so contains("email", "alice") finds nothing on a stored alice@example.com. The adapter's own note in types.ts says as much: "include_original: true is therefore always in force, so a substring contains matches nothing on either." I documented that behaviour in a warn callout, but if it is a bug rather than the intent, the callout should become a bug report.

edge-functions.mdx still uses the v2 schema builders, because @cipherstash/stack/wasm-inline exports encryptedTable / encryptedColumn from @/schema and does not export the v3 types. I left the page alone; it already carries a "being confirmed" callout. Worth deciding whether the edge build should re-export the v3 surface.

Depends on

  • The v3 domain registry re-prefix (in progress) — until it lands, encryptedSupabaseV3 rejects every EQL 3.0.0 table, because DOMAIN_REGISTRY is keyed on the old unprefixed names.
  • stack #609, for .in() on encrypted columns and .not(col, 'in', […]), both of which the filter table documents as working.
  • docs(eql): update the reference to the EQL 3.0.0 shapes #67, which moves the EQL reference to these same domain names.

Checks

bun run build passes. validate-content, validate-links, validate-mermaid pass.

@coderdan

coderdan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Correction to my note above on contains. My explanation was wrong; the conclusion happened to be right, for a different reason. Pushed 6a16f79 to fix the callouts.

You're right about the storage side. include_original means the stored value's bloom carries its trigrams and the whole value as one extra token, so a query matches on the exact original term or on any of its trigrams. That is the intended widening.

What I missed is which payload the wrapper puts on the query side. It sends a full storage payload as the operand, not a narrow query term, and the source says why:

PostgREST has no syntax to cast a filter VALUE, and an uncast literal is ambiguous between the _query and jsonb @>/= overloads (42725). […] Independently, protect-ffi 0.28 throws EQL_V3_QUERY_UNSUPPORTED for any v3 scalar encryptQuery, so a narrowed term cannot be produced today.

Confirmed at query-builder-v3.ts:356, which calls encryptionClient.encrypt(...) rather than encryptQuery(...).

So the needle gets tokenized as if it were a stored value: trigrams("alice") plus include_original("alice"). Containment requires every needle bit to be present in the haystack. The trigrams are there, but the whole-needle token "alice" is not among the haystack's tokens, which are trigrams("alice@example.com") plus "alice@example.com". So containment fails.

It follows that .contains() matches only when the needle equals the stored value, or when the needle is exactly three characters, where its include_original token is one of the haystack's trigrams.

The class doc reaches the same conclusion and calls it a defect:

KNOWN BROKEN for real substrings, and not fixable from this file. […] v3 Drizzle's contains has the same defect for the same reason. Tracked in EQL; do not paper over it here.

The docs now describe it as a known defect with that precise rule, rather than as intended behaviour. I could not find the tracking issue in encrypt-query-language (searched open and closed), so it may be in Linear.

@coderdan

Copy link
Copy Markdown
Contributor Author

Updated to the shipped Stack 1.0-rc.0 family and EQL 3.0 defaults (commit 9dc5abd). Verified against the published 1.0.0-rc.0 tarballs of @cipherstash/stack, @cipherstash/stack-supabase, @cipherstash/stack-drizzle, and stash.

The adapters split into their own packages. @cipherstash/stack 1.0 removed the ./supabase, ./drizzle, and ./eql/v3/drizzle subpath exports. Migrated:

  • @cipherstash/stack/supabase@cipherstash/stack-supabase
  • @cipherstash/stack/eql/v3/drizzle@cipherstash/stack-drizzle/v3

@cipherstash/stack/eql/v3 (encryptedTable, types) and @cipherstash/stack/v3 (EncryptionV3) are unchanged and stay. Install commands now include the split packages, and the Supabase reference gained a package-install note.

EQL v3 is now the CLI default. stash eql install and stash init both default to v3 (DEFAULT_EQL_VERSION = 3), so the explicit --eql-version 3 is dropped everywhere. The get-started quickstart's separate "force v3 over the v2 install" step is gone — init installs v3 directly, exactly as that step's own callout predicted. The Drizzle-migration restriction (stack#613) still holds and stays documented.

agent-skills: the bundled skill set changed with 1.0 — stash-secrets is gone (secrets removed from the SDK) and stash-supply-chain-security is new. Repointed the Drizzle/Supabase skills at their split packages.

Heads-up, not from these edits: bun run build is currently red at the TypeDoc generator, which clones the latest @cipherstash/stack source; that source needs a protect-ffi with the renamed ProtectError/ProtectErrorCode exports. Reproduces with my content edits stashed, so it is pre-existing and independent — but it will red CI here until the generator is pinned/patched (relates to the "repoint TypeDoc generator" launch task).

@coderdan

Copy link
Copy Markdown
Contributor Author

Fixed the failing Vercel build (79c726e). It was the TypeDoc SDK-reference generator choking on the Stack 1.0 monorepo restructure, in three ways:

  1. Stale entry pointssecrets/, drizzle/, and supabase/ no longer exist under packages/stack/src (secrets removed; the two adapters became their own packages), so TypeDoc had nothing to read. Kept the seven modules @cipherstash/stack still ships.
  2. Cross-package type errors — the stack monorepo is a pnpm workspace but the generator installs it with bun, which leaves e.g. @cipherstash/protect-ffi's ProtectError unresolved for the isolated typecheck (0.29.0 does export it). Set TypeDoc skipErrorChecking: true — it emits accurate signatures from the source AST regardless.
  3. Multi-version generation — the module layout diverged across majors, and one entryPoints config can't document both 0.18 and 1.0. Now generates only the latest major; older-major reference lives on the previous docs site. Also resets the working tree before each tag checkout, since a prior tag's bun install rewrites the tracked package.json.

Verified locally: generate-docs, validate-links, and a full bun run build all pass.

This is a shared generator fix. #43 and #54 will hit the same red build until they get it — either after this merges to v2 and they rebase, or I can cherry-pick 79c726e onto them now. Say the word.

coderdan added 26 commits July 30, 2026 17:13
Add a shared, product-wide FAQ component (src/components/faq) that renders
an accordion and emits FAQPage JSON-LD. Generic answers (privacy, scaling,
KMS, RLS-vs-encryption, free tier) live once in faq/shared and are composed
with Supabase-specific questions inline on the overview. De-dupe the
performance/privacy prose now covered by the FAQ.
Reuse the shared privacy/KMS/free-tier FAQ entries and add DynamoDB-specific
questions (equality-only querying, keeping AWS SDK control, adoption).
Omits the Postgres-oriented scaling/RLS entries, which do not apply.
…ains

The Supabase pages documented `encryptedSupabase`, the EQL v2 dialect.
Stack 1.0 ships `encryptedSupabaseV3` alongside it, and it is a different
shape. Verified against the adapter source on the release branch, not
against the PR descriptions, which have drifted.

- `await encryptedSupabaseV3(url, key, options?)` or `(client, options?)`.
  It is async because it introspects the database at connect time, reads
  the EQL domain of every column, and derives the encryption config from
  it. There is no schema to declare and no `encryptedTable` to keep in
  sync, so `.from()` takes only a table name.
- Introspection needs a direct Postgres connection: `DATABASE_URL`, or
  `options.databaseUrl`. Documented in the quickstart env block.
- `schemas` is optional, and buys compile-time types plus startup
  verification. Import from `@cipherstash/stack/eql/v3`.
- `select('*')` works, expanded from the introspected column list. On v2
  it throws.
- `.like()` / `.ilike()` throw on encrypted columns. Free-text search is
  `.contains()`. With the default match options the query term carries
  the whole query string as a token, so a substring matches nothing;
  called out where it will bite.
- Column types are the EQL 3.0.0 prefixed domains: `public.eql_v3_text_eq`,
  not `eql_v3.text_eq`. The domains live in `public`; only the functions
  and operators live in `eql_v3`.
- Supabase runs the installer as a non-superuser, so the ORE-backed
  domains are disabled and raise on first write. Say so on the pages that
  offer the choice, and point at `_ord` / `text_search`.
- The query builder's `.withLockContext()` takes a `LockContext`
  instance, unlike the core client's, which takes a plain object. The
  auth page passed the wrong one.

The v2 wrapper is unchanged and still documented, at the foot of the
reference page.
The operand is a storage payload, not a query term: PostgREST cannot cast
a filter value to the narrow _query domain, and protect-ffi 0.28 rejects
encryptQuery for the v3 scalars. So the needle's bloom carries the whole
needle as an include_original token, which the haystack's bloom cannot
contain unless the two are equal, or the needle is exactly one trigram
long.

Says it is a defect to be fixed in EQL rather than intended behaviour.
Drizzle and the Supabase wrapper share the defect but not the cause. The
common blocker is that protect-ffi has no v3 scalar encryptQuery, so no
client can build a narrow operand. Only Supabase is additionally blocked
by PostgREST's inability to cast a filter value to the query domain;
Drizzle emits its own SQL and can cast once a narrow term exists.
Not a protect-ffi gap: substring containment works at the ffi layer when
include_original is off, which is the schema default. stack's shared
match defaults force it on, inherited from the v2 builder, where QueryMode
dropped the original token. Link the tracking issue.
Docs do not link GitHub issues. The callout had also accumulated two
competing explanations across revisions; keep the one that is true.
…w link

- Remove the `.contains()` substring callout from the reference page and
  the quickstart. Known defects belong in the issue tracker, not in the
  docs; the fix is imminent.
- Remove the pre-publish redirect that sent /integrations/supabase to the
  legacy /stack/cipherstash/supabase page. It was placed on main so the
  future URL would resolve before v2 shipped, and its own comment says to
  remove it once v2 owns the URL. This branch is where v2 owns it, so the
  section's Overview nav item was landing on the old IA.
- List Supabase first under Integrations.
The screenshot no longer reflects the product, and there is no
replacement yet. It also illustrated adding EQL columns through the
Table Editor, which Fundamentals already says is unreliable: the
type picker does not surface custom domains. The copy now sends
readers to SQL, which is the dependable path and what a migration
needs anyway.

Also drops the last two "Supabase Studio" mentions. It is the
Supabase Dashboard.
- Install EQL with `npx stash eql install --eql-version 3` rather than
  curling the release SQL into the SQL Editor or a migration file. The
  CLI detects Supabase from DATABASE_URL and installs directly.
- Say that v3 is direct-install only: --migration, --drizzle, --latest
  and --migrations-dir are all rejected alongside --eql-version 3, so a
  v3 install does not survive `supabase db reset`. That retires the
  "commit it as your earliest migration" path, which only works for v2.
- Auth: promote the server-side-only warning from a blockquote to a warn
  callout, and lead with the rule rather than the token-refresh detail.
- Edge Functions: drop the "being confirmed" callout about the wrapper.
The page was an unwritten placeholder. Removes it, the "Next.js" card on
the Supabase ORMs page, its two IA.md entries, and `nextjs` from every
`pairsWith` facet.

Also corrects `prisma-next` to `prisma` in the Supabase facets: the page
is at /integrations/prisma, so the old value named nothing.
…on index

Drizzle, on EQL v3. Concrete column types from
`@cipherstash/stack/eql/v3/drizzle`, `extractEncryptionSchemaV3` +
`EncryptionV3`, and the async capability-checked operators. Notes that
`--eql-version 3` cannot emit a Drizzle migration, and that free-text
search is `ops.contains` rather than `like`.

Prisma, on EQL v2. The integration is `@cipherstash/prisma-next`, which
targets Prisma Next rather than classic `@prisma/client`, and whose
migrations create `eql_v2_encrypted` columns. Both facts are stated up
front rather than left for a reader to discover. Classic Prisma users are
pointed at the SDK.

The index routes by how the application talks to the database, since that
is what actually decides the integration.

Also teaches validate-content-api about per-file exemptions: the Prisma
page must name `eql_v2_encrypted` to be correct, and the v3-only rules
would otherwise reject it. Verified the rule still fires elsewhere. Fixes
that rule's own fix hint, which suggested the pre-3.0.0 `public.text_eq`.
The integration is `@cipherstash/prisma-next`, for the Prisma Next
framework, not classic `@prisma/client`. Titling it "Prisma" invited
exactly the confusion the page then had to spend a callout undoing.

Moves the slug to /integrations/prisma-next, which is what IA.md already
expected, and repoints the two inbound cards, the `pairsWith` facets, and
the validate-content exemption path.

Ticks the three integrations entries in IA.md, noting that Prisma Next is
EQL v2 today and should be revisited when v3 support lands.
Stack 1.0 split the Drizzle and Supabase adapters into their own packages
and the stash CLI made EQL v3 the default. Verified against the published
1.0.0-rc.0 tarballs.

Import moves (the old subpath exports are removed from @cipherstash/stack):
- @cipherstash/stack/supabase        → @cipherstash/stack-supabase
- @cipherstash/stack/eql/v3/drizzle  → @cipherstash/stack-drizzle/v3

Install commands add the split packages; the Supabase reference gains a
package-install note.

CLI: `stash eql install` and `stash init` now target EQL v3 by default,
so the explicit `--eql-version 3` is dropped everywhere. The get-started
quickstart's separate "force v3 over the v2 install" step is removed —
init installs v3 directly, exactly as that step's own callout predicted.
The Drizzle-migration restriction (#613) still holds and stays documented.

agent-skills: the bundled skill set changed. `stash-secrets` is gone
(secrets removed from the SDK) and `stash-supply-chain-security` is new.
Repointed the Drizzle/Supabase skills at their split packages and noted
the Drizzle skill now covers the v3 dialect.

verifiedAgainst bumped to 1.0.0-rc.0 on the pages re-checked against it.

Note: `bun run build` is currently red at the TypeDoc generator, which
clones the latest @cipherstash/stack source; that source needs a
protect-ffi with the renamed ProtectError export. Pre-existing and
unrelated to these content edits (reproduces with them stashed).
Stack renamed the encrypted free-text method in
cipherstash/stack#617 (`feat(stack-drizzle,stack-supabase)!: rename encrypted
contains() -> matches()`), landed 2026-07-13 — one day before this branch was
last updated, so it was missed.

The rename is not cosmetic. `contains()` now RAISES on an encrypted text
column, because containment promises exactness that bloom-filter matching
cannot deliver:

  contains() is native (exact) containment and does not apply to encrypted
  column "email". Use matches() for encrypted free-text search.

Every free-text example on these pages taught the spelling that now raises.
`contains()` is still correct for encrypted JSON (sub-document containment)
and plaintext arrays, so the filter table now lists both, plus the
selectorEq/selectorNe pair added alongside them.

Also rewrites "There is no `.like()`", which was wrong in the other direction.
`like`/`ilike` on an encrypted column do not throw — they are a compatibility
shim that reduces the pattern to a needle and delegates to `matches()`. The
result is approximate (case-insensitive, one-sided, anchoring lost) and only
an unapproximable pattern — an internal `%`, or any `_` — raises. Plaintext
columns keep real SQL LIKE. The section now says that, and the anchor is
renamed to match, with the inbound link updated.

Verified against @cipherstash/stack@1.0.0-rc.4 (packages/stack-supabase):
encryptedSupabaseV3's four overloads are async and take (client | url+key) plus
options, and databaseUrl/config/schemas are all real, so the surrounding
examples were already correct. The v2 paragraph checks out too — encryptedSupabase
is synchronous, takes { encryptionClient, supabaseClient }, and needs .from(table,
schema).

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
… adapter

Correcting my own earlier commit. Renaming contains() -> matches() fixed the
spelling but documented an operation that does not run: on EQL 3.0.2+ the
supabase-js wrapper raises for every encrypted free-text and encrypted-JSON
term, whatever the spelling.

  [supabase v3]: matches() on encrypted column "email" is unavailable with EQL
  3.0.2+: the SQL operator requires an eql_v3.query_* cast that PostgREST
  cannot express. Use the Drizzle or Prisma Next adapter, or a scoped SQL/RPC
  function.

The docs pin EQL 3.0.4, so this is the behaviour every reader gets. The guard
fires for the freeTextSearch and searchableJson query types via any spelling —
matches(), contains() on a json column, selectorEq()/selectorNe(), and the same
terms reached through not(), or(), or a raw filter() — and fails closed on an
unknown EQL version. It does NOT touch equality, ordering, or range, so the
rest of the page stands.

Scoped to the supabase-js query path, which is what the limit is about. The
Auth and Edge Functions pages are unaffected: this is PostgREST's inability to
express an operand cast, not a limit on CipherStash with Supabase.

- The wrapper reference leads the free-text section with the constraint and the
  three workarounds, and marks the affected rows in the filter table.
- The quickstart drops its free-text example rather than showing a call that
  raises, and its callout now says which operations do work.
- fundamentals' column-type table is accurate as written (these ARE the
  column's capabilities) but a reader could pick text_match or json and only
  then discover they cannot query it here, so it points at the constraint.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
…s remove_v2

Two Vercel toolbar comments on the preview.

edge-functions: generate the CS_* values with `stash env` rather than having the
reader assemble the file by hand. Additive — the explicit variable listing
stays, since it is what documents which four the SDK actually reads. `--write`
is a boolean (verified against stash@0.17.1), so the example redirects to the
env file that `supabase functions serve --env-file` then consumes.

fundamentals: rewrite the install callout against the stack `remove_v2` branch.
What was out of date:

- It justified the v3 restrictions as flags that "exist for the v2 install
  paths". With v2 removed there are no v2 install paths, and the install is
  always v3, so the framing goes.
- It listed `--drizzle` alongside the other rejected flags. `--drizzle` has had
  its own v3 answer since stack#691: `stash eql migration --drizzle` generates a
  v3 install migration, cs_migrations tracking schema included, for Drizzle
  projects that want the install in their migration history. The install command
  still rejects the flag, but now points there rather than at `--eql-version 2`.
- Also drops "`--eql-version 2` opts back to v2" from the prose above it.

`--migration`, `--migrations-dir`, and `--latest` remain rejected, and there is
still no Supabase migration variant — one v3 SQL artifact targets every
platform — so the `supabase db reset` consequence stands and is kept.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
…stead

`remove_v2` makes `encryptedSupabase` the v3 wrapper, so a section documenting
it as "the EQL v2 dialect" would describe the opposite of what it names. The v2
dialect it described will never have existed for a reader of the live site,
since these docs ship after that branch.

Replaced with a short note on the naming, because the alias is the thing a
reader actually needs: `encryptedSupabase` and `encryptedSupabaseV3` are the
same function, either import works, and the V3 spelling is a deprecated alias
kept for existing imports now that the suffix is redundant. Same for the type
names. Also drops the "pick the dialect by function" framing — after the merge
there is only one dialect.

The examples on this page still say `encryptedSupabaseV3`, deliberately. That
name is correct both today and after the merge, whereas `encryptedSupabase`
means the OLD synchronous v2 wrapper on the currently published SDK
(1.0.0-rc.4) — switching the examples now would document a different function
with a different signature. They should be renamed once remove_v2 ships, along
with this page's `verifiedAgainst`.

Also fixes one cell in choose-your-stack, which is outside this PR's scope but
factually wrong in the same way: it advertised `.like()` as something that
"keeps working" on Supabase. It delegates to `matches()`, which raises on EQL
3.0.2+ through PostgREST. Swapped for `.order()`, which does.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
… Data layer away

Four structural changes to the Integrations section:

- The /integrations landing page leaves the sidebar (`"!index"` in
  meta.json, which drops it as both a child item and the folder link).
  It stays reachable from the docs index, Get started, and the v2
  redirect map — it just isn't a nav entry.

- The landing page becomes a plain list by category: Database (Supabase,
  Prisma Postgres) and ORM (Supabase-js, Prisma Next, Drizzle). The
  decision content it used to duplicate — the Postgres platform table,
  the ORM table, the identity-provider table — already lives on
  /get-started/choose-your-stack, which it now points at instead.

- Supabase Overview and Fundamentals merge into one Overview page. The
  overlap was substantial: both explained EQL column types, both covered
  RLS, and both explained why the Table Editor can't create encrypted
  columns. Fundamentals' section anchors are preserved on the merged
  page, so #installing-eql, #row-level-security,
  #working-in-the-dashboard and #connecting-your-project-to-cipherstash
  keep resolving; inbound links across auth, quickstart and the wrapper
  reference are repointed.

- Data layer folds into the Quickstart as "Using an ORM instead",
  condensed from 71 lines to ~20: what the direct-connection path is,
  which Supabase connection string to use, and the fact that a
  privileged direct connection bypasses RLS.

Verified against the rendered output rather than the source: the built
sidebar has no bare /integrations link, and every anchor the merge moved
resolves to a real id on the merged page.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
`content/stack/reference/drizzle.mdx` points at `/integrations/drizzle`,
which is a page this branch creates. #81 (the link-validator fix, off v2)
removes that link, because on v2 alone it is a genuine 404 — the target
does not exist there yet.

That set up a silent failure. This branch did not touch the line, so
rebasing onto a v2 containing #81 would have taken #81's removal without
a conflict, dropping the cross-link even though the page it points at had
just landed.

Rewording the sentence here makes the branch an explicit editor of that
line, so the two changes conflict and the merge has to make a decision
instead of quietly picking one. The rewrite is also more accurate on its
own terms: this page documents the older `@cipherstash/stack/drizzle`
entry point, which the previous phrasing left implicit.

The redirect itself needed no change — `/stack/reference/drizzle` →
`/integrations/drizzle` has been in v2-redirects.mjs all along. What this
branch supplies is the destination, so the redirect resolves here where
it did not on v2.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
The quickstart went from an empty project to an encrypted table, which is
the easy case and not the one readers arrive with. Reframe it around a
`patients` table that already holds live plaintext data, and walk the
deployment ladder from the stash-deployment skill: add nullable
`*_encrypted` mirror columns for `name` and `date_of_birth`, dual-write
everywhere, deploy, backfill the historical rows, build the extractor
indexes, then cut reads over.

Stops short of dropping the plaintext columns. That is a separate,
irreversible deploy, so it gets a follow-up section covering the soak,
`stash encrypt drop`, and why the generated migration re-checks coverage
at apply time — with sequencing deferred to the deployment guide.

Carries over the failure modes that lose data silently: nullable-mandatory
mirrors, the `_encrypted` naming convention `backfill` resolves by default,
dual-writing on every path, running the backfill under the app's `CS_*`
credentials, and needing those credentials at build time.

Keeps a callout for the greenfield path, and updates the overview's
descriptions of the quickstart plus its migration FAQ answer to match.
@coderdan
coderdan force-pushed the cip-3328-supabase branch from 5338fc2 to 59f8f57 Compare July 30, 2026 07:17
@coderdan
coderdan merged commit 4f7f5a4 into v2 Jul 30, 2026
3 checks passed
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