Skip to content

TML-2503: Supabase-internal namespaces via a secondary db.supabase root (slice D)#845

Merged
wmadden merged 2 commits into
mainfrom
tml-2503-extension-supabase-slice-d
Jul 1, 2026
Merged

TML-2503: Supabase-internal namespaces via a secondary db.supabase root (slice D)#845
wmadden merged 2 commits into
mainfrom
tml-2503-extension-supabase-slice-d

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-2503. Slice D of extension-supabase (slice A merged in #839). Slice contract: projects/extension-supabase/slices/d-service-role-internal-namespaces/spec.md; design in decision C15.

At a glance

const admin = db.asServiceRole();
admin.sql.public.profile…                   // primary root (app contract) — unchanged
admin.supabase.sql.auth.users.select({}) // secondary root (extension contract)
admin.supabase.orm.auth.AuthUser.find({})

db.asAnon().supabase                         // ✗ not on the type — service_role only

Decision

Admin access to Supabase-internal tables (auth.*, storage.*) is exposed as a separate secondary root db.asServiceRole().supabasenot by merging the extension contract into the app contract (a first attempt did that; rejected in review). The .supabase facet is the extension contract's own intact ExecutionContext + a second SupabaseRuntimeImpl bound to it, sharing the app runtime's driver/pool + the service_role session, with marker-verification off (the external extension contract owns no app-space marker). asServiceRole().sql/.orm stay app-contract-only; asUser/asAnon are unchanged (no .supabase).

Why a separate root, not a merge

The runtime is contract-bound by storageHash: SqlFamilyAdapter.validatePlan asserts plan.meta.storageHash === context.contract.storage.storageHash. A plan built against the extension contract carries the extension's hash and can't run on the app runtime — so the two roots are genuinely separate (own context + own runtime), sharing only the driver/pool. Merging two Contracts also breaks codec-registry uniqueness and the marker check. Two intact contexts/runtimes sharing one pool is the ADR 230 pattern.

Behavior changes & evidence

  • db.asServiceRole().supabase.{sql,orm} query the extension contract; asServiceRole().sql/.orm are app-only; asUser/asAnon have no .supabase. Impl: packages/3-extensions/supabase/src/runtime/supabase.ts (SupabaseInternalDb, ServiceRoleDb, buildExtensionContract, extContext/extRuntime). Evidence: examples/supabase/test/explicit-namespace-query.integration.test.ts (reads auth.users via .supabase.sql and .orm; SQL targets "auth"."users"; current_setting('role') = 'service_role') + service-role-namespaces.test-d.ts (type-level: secondary root carries auth/storage; primary root + asAnon/asUser do not).

Reviewer notes

  • Independent opus review: APPROVE-WITH-NITS — confirmed one shared pool (no double-close), service_role binding intact on the ext path, marker-verify correctly scoped (ext off, app on), no auth.* leak to anon/user, per-contract validatePlan. The one acted nit (doc comment explaining SupabaseInternalDb omits transaction) is applied.
  • This single commit replaces an earlier merge approach (force-pushed away) — the diff is the net secondary-root design.
  • ext-contract-type.ts is a no-as type alias naming the extension Contract distinctly from the framework Contract (keeps lint:casts delta 0).
  • v1 limitation (documented): no single transaction spanning the app root and .supabase (separate runtimes, unpinned connections). The principled fix — a Runtime bound to the aggregate contract — is recorded as future direction in C15 + deferred.md.

Testing performed

pnpm exec turbo run typecheck test lint --filter @prisma-next/example-supabase --filter @prisma-next/extension-supabase — green (example suite 9 tests, incl. the .supabase sql+orm reads). pnpm lint:casts delta 0; pnpm lint:deps clean. CI re-running on push.

Skill update

n/a — the only user-facing surface change (the asServiceRole().supabase admin root) is documented in decision C15 + overview.md; no CLI/config/error-code change.

Checklist

  • All commits are signed off (git commit -s).
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated (integration + type-level).
  • PR title is Linear-prefixed.
  • Skill update filled in.

Summary by CodeRabbit

  • New Features
    • Added an additive db.asServiceRole().supabase admin root that enables service-role access to Supabase internal namespaces (e.g., auth, storage) via .sql and .orm, while keeping the primary asServiceRole() surface restricted to public.
  • Tests
    • Added integration coverage for internal-namespace reads, emitted SQL targeting, and role-scoped execution for both app and internal surfaces.
    • Added compile-time typing checks for service_role, anon, and user/JWT namespace visibility.
  • Documentation
    • Updated upgrade instructions to describe the new secondary .supabase surface and related exported types.

@wmadden-electric wmadden-electric requested a review from a team as a code owner June 17, 2026 09:20
@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@845

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@845

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@845

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@845

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@845

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@845

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@845

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@845

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@845

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@845

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@845

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@845

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@845

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@845

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@845

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@845

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@845

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@845

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@845

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@845

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@845

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@845

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@845

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@845

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@845

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@845

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@845

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@845

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@845

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@845

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@845

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@845

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@845

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@845

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@845

prisma-next

npm i https://pkg.pr.new/prisma-next@845

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@845

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@845

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@845

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@845

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@845

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@845

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@845

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@845

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@845

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@845

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@845

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@845

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@845

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@845

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@845

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@845

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@845

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@845

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@845

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@845

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@845

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@845

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@845

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@845

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@845

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@845

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@845

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@845

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@845

commit: 41d1248

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.35 KB (0%)
postgres / emit 147.55 KB (0%)
mongo / no-emit 79.86 KB (0%)
mongo / emit 72.68 KB (0%)
cf-worker / no-emit 188.11 KB (0%)
cf-worker / emit 173.57 KB (0%)

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 378dccaa-5520-4030-9949-08cd038b1b9a

📥 Commits

Reviewing files that changed from the base of the PR and between 31fbe37 and 41d1248.

⛔ Files ignored due to path filters (5)
  • projects/extension-supabase/plan.md is excluded by !projects/**
  • projects/extension-supabase/slices/d-service-role-internal-namespaces/spec.md is excluded by !projects/**
  • projects/supabase-integration/decisions.md is excluded by !projects/**
  • projects/supabase-integration/deferred.md is excluded by !projects/**
  • projects/supabase-integration/overview.md is excluded by !projects/**
📒 Files selected for processing (7)
  • examples/supabase/test/explicit-namespace-query.integration.test.ts
  • examples/supabase/test/service-role-namespaces.test-d.ts
  • packages/3-extensions/supabase/src/exports/runtime.ts
  • packages/3-extensions/supabase/src/runtime/ext-contract-type.ts
  • packages/3-extensions/supabase/src/runtime/supabase.ts
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
✅ Files skipped from review due to trivial changes (2)
  • skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/3-extensions/supabase/src/exports/runtime.ts
  • packages/3-extensions/supabase/src/runtime/ext-contract-type.ts
  • examples/supabase/test/service-role-namespaces.test-d.ts
  • examples/supabase/test/explicit-namespace-query.integration.test.ts
  • packages/3-extensions/supabase/src/runtime/supabase.ts

📝 Walkthrough

Walkthrough

asServiceRole() now returns a service-role wrapper with a separate .supabase root for internal Supabase namespaces. The runtime, exports, tests, and upgrade notes were updated to reflect the new service-role and app-only namespace surfaces.

Changes

Service-role extension namespace surface

Layer / File(s) Summary
Service-role API types and re-exports
packages/3-extensions/supabase/src/runtime/ext-contract-type.ts, packages/3-extensions/supabase/src/runtime/supabase.ts, packages/3-extensions/supabase/src/exports/runtime.ts
Defines SupabaseExtensionContract, adds SupabaseInternalDb and ServiceRoleDb, updates SupabaseDb.asServiceRole() to return the service-role wrapper with .supabase, and re-exports the new runtime types.
Supabase internal runtime wiring
packages/3-extensions/supabase/src/runtime/supabase.ts
Adds extension-contract deserialization, refactors role-bound DB construction around explicit context/runtime inputs, creates the extension-bound .supabase runtime with marker verification disabled, and returns it from asServiceRole().
Compile-time namespace surface tests
examples/supabase/test/service-role-namespaces.test-d.ts
Asserts that asServiceRole().supabase exposes auth and storage, while asServiceRole(), asAnon(), and asUser() remain app-only and type as RoleBoundDb<Contract> where expected.
Runtime namespace query coverage
examples/supabase/test/explicit-namespace-query.integration.test.ts
Sets up a dev Supabase database, seeds auth.users, records emitted SQL, and verifies the secondary root can read internal namespaces while the primary service-role root still reads app-contract public.profile.
Upgrade notes
skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md, skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
Adds TML-2503 notes describing the additive service-role namespace surface and the new example tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: wmadden

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding a secondary Supabase-internal root for service-role access.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2503-extension-supabase-slice-d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@wmadden-electric wmadden-electric force-pushed the tml-2503-extension-supabase-slice-d branch from a93eb6e to 5a4e610 Compare June 29, 2026 16:27

@wmadden wmadden left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the wrong approach. Expose the supabase project as a secondary root

Comment thread examples/supabase/test/explicit-namespace-query.integration.test.ts
Comment thread packages/3-extensions/supabase/src/exports/runtime.ts Outdated
Comment thread packages/3-extensions/supabase/src/runtime/ext-contract-type.ts Outdated
Comment thread packages/3-extensions/supabase/src/runtime/supabase.ts Outdated
Comment thread packages/3-extensions/supabase/src/runtime/supabase.ts Outdated
@wmadden-electric wmadden-electric force-pushed the tml-2503-extension-supabase-slice-d branch from 5a4e610 to 31fbe37 Compare June 29, 2026 17:08
@wmadden-electric wmadden-electric changed the title TML-2503: service_role admin access to Supabase-internal namespaces (slice D) TML-2503: Supabase-internal namespaces via a secondary db.supabase root (slice D) Jun 29, 2026
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Reworked to the secondary-root design (force-pushed as a single clean commit; the merge-design history is gone).

db.asServiceRole().supabase.{sql,orm} now exposes the Supabase extension contract as its own intact root — its own ExecutionContext + a second SupabaseRuntimeImpl bound to it, sharing the app runtime's driver/pool + the service_role session, with marker-verification off. No contract merge. asServiceRole().sql/.orm stay app-contract-only; asUser/asAnon are unchanged (no .supabase). The merge scaffolding (WithExtensionNamespaces, buildServiceRoleContract) is deleted.

Why a separate root and not a merge: the runtime is contract-bound by storageHash (SqlFamilyAdapter.validatePlan), so an extension-contract plan can't run on the app runtime — the two roots are genuinely separate, sharing only the pool. Merging also breaks codec-registry uniqueness + the marker check.

The four inline threads are addressed and resolved. Independent review confirmed: one shared pool, service_role binding intact, marker-verify scoped (ext off / app on), no auth.* leak to anon/user, per-contract validatePlan. Design recorded in decision C15; the principled future (a runtime bound to the aggregate contract) is captured in deferred.md.

Ready for re-review.

@wmadden wmadden enabled auto-merge July 1, 2026 09:18
wmadden-electric and others added 2 commits July 1, 2026 14:22
…db.supabase root

Admin reads of auth.*/storage.* are a SEPARATE secondary root on the
service_role-bound db: db.asServiceRole().supabase.{sql,orm}. The .supabase
facet is the extension contract's own intact ExecutionContext + a second
SupabaseRuntimeImpl bound to it, sharing the app runtime's driver/pool +
service_role session, with marker-verification off. The two contracts are NOT
merged (a merge breaks the storageHash-bound validatePlan, codec-registry
uniqueness, and the marker check). asServiceRole().sql/.orm stay app-only;
asUser/asAnon are unchanged (no .supabase).

- runtime: SupabaseInternalDb + ServiceRoleDb<TContract>, buildExtensionContract,
  extContext/extRuntime; asServiceRole() gains .supabase. ext-contract-type names
  the extension Contract without a bare `as` (lint:casts delta 0).
- tests: integration reads auth.users via .supabase.sql + .orm (SQL targets
  "auth"."users"; current_setting(role)=service_role); type test proves the
  secondary root carries auth/storage while the primary root + asAnon/asUser do not.
- docs: decision C15 (secondary root, the rejected merge, the aggregate-contract
  future), deferred.md (aggregate-contract runtime), slice spec, plan.md, overview.md.

Reviewed (opus): APPROVE-WITH-NITS; nits applied (transaction-omission doc comment).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
check:upgrade-coverage requires a per-PR declaration when a PR touches examples/
or packages/3-extensions/. Slice D is purely additive (new db.asServiceRole().supabase
admin root; the removed WithExtensionNamespaces was never released), so both touches
are incidental — append an incidental note to the user- and extension-author-upgrade
0.14->0.15 instructions (changes arrays unchanged: user changes: [], ext-author block).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
@wmadden-electric wmadden-electric force-pushed the tml-2503-extension-supabase-slice-d branch from 138ced9 to 41d1248 Compare July 1, 2026 12:24
@wmadden wmadden added this pull request to the merge queue Jul 1, 2026
Merged via the queue into main with commit 3a34e7c Jul 1, 2026
21 checks passed
@wmadden wmadden deleted the tml-2503-extension-supabase-slice-d branch July 1, 2026 12:49
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.

2 participants