You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Record Supabase walking-skeleton strategy and lane sequencing
Document the walking-skeleton delivery approach for the Supabase
integration: a single runnable examples/supabase app, stood up early by
extension-supabase M1 and grown one feature at a time, with a
"wire your feature into the running example" definition-of-done clause on
every constituent. Capture the two-lane test strategy (hermetic PGlite +
bootstrapSupabaseShim; real-Supabase acceptance) and the current lane
sequencing. Strip the stale May-18 launch dates throughout.
- umbrella: add C13/C14 decisions, walking-skeleton + running-order
sections, refresh the status table
- extension-supabase: M1 stands up the skeleton + authors the shim; M3
reframed to finalize the grown example; example path corrected to
examples/supabase; two resolved open questions
- cross-contract-refs / postgres-rls / runtime-target-layer /
explicit-namespace-dsl: add walking-skeleton DoD hooks
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
Per the umbrella's walking-skeleton strategy (decisions [C13/C14](../supabase-integration/decisions.md); [README](../supabase-integration/README.md) §"Walking skeleton"), this project's definition of done includes wiring its feature into the running `examples/supabase` app:
103
+
104
+
-[ ] Add the `Profile.userId → auth.User.id` cross-contract FK (with `onDelete: 'cascade'`) to the `examples/supabase` app contract; confirm the planner emits qualified `REFERENCES "auth"."users"("id")`.
105
+
-[ ] Cover it in the example's hermetic test lane (PGlite + `bootstrapSupabaseShim`): migration creates the FK; a cascade delete from `auth.users` removes the dependent `public.profile` row.
106
+
100
107
## Risks and mitigations
101
108
102
109
-**Risk:** the colon-prefix tokenizer change in PSL is a backwards-incompatible lexer change. Any existing code that used `:` inside a type position (unlikely, but possible in malformed contracts) breaks.
Copy file name to clipboardExpand all lines: projects/explicit-namespace-dsl/plan.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,7 +39,7 @@ Single PR; dispatches are logical execution order for one implementer (or one re
39
39
-**Outcome:** Supabase-shaped fixture: `namespace public { model Profile … }` plus extension `auth``users`; emit contract; integration test queries `db.sql.auth.users` and `db.sql.public.profile` (and ORM equivalents) against PGlite; AC1–AC4 satisfied.
40
40
-**Builds on:** D2 runtime resolution.
41
41
-**Hands to:** D4 close-out.
42
-
-**Focus:** Authoring + emit + query in one test; keep example minimal if extension-supabase will own the polished demo.
42
+
-**Focus:** Authoring + emit + query in one test. **Wire this into the `examples/supabase` walking skeleton** (decisions [C13/C14](../supabase-integration/decisions.md)) rather than a throwaway fixture — add the `auth.users`-alongside-`public.users` explicit-accessor query to the running example, tested via PGlite + `bootstrapSupabaseShim`. `extension-supabase` finalizes the polished demo on top.
-[ ] No regressions on default-namespace demo queries (AC3).
57
+
-[ ] The `examples/supabase` walking skeleton exercises the explicit `auth.users` / `public.users` accessor (cross-cutting walking-skeleton DoD; [README](../supabase-integration/README.md) §"Walking skeleton").
57
58
-[ ] PR linked from [TML-2550](https://linear.app/prisma-company/issue/TML-2550); operator notified that [TML-2503](https://linear.app/prisma-company/issue/TML-2503) explicit-accessor prerequisite is cleared.
Copy file name to clipboardExpand all lines: projects/extension-supabase/plan.md
+16-14Lines changed: 16 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
## Summary
4
4
5
-
The project ships in four PRs sequenced contract → runtime → example → polish. M1 scaffolds the `@prisma-next/extension-supabase` package: subpath exports, hand-authored `contract.json`, `contract.d.ts`, branded `/contract` handles, the `/pack` descriptor. M2 builds the runtime facade: `SupabaseRuntime extends PostgresRuntime`, JWT validation (sync via `jwtSecret`, async warmup via `jwksUrl`), `SupabaseDb` with `asUser` / `asAnon` / `asServiceRole`, `RoleBoundDb` extending `Db` with `transaction(...)`, the `SET LOCAL` + implicit-transaction plumbing wired through `withRawConnection`. M3 ships the canonical example app at `packages/3-extensions/supabase/examples/basic/` plus its integration tests against PGlite-with-Supabase-schema. M4 closes out documentation, the launch-blocking real-Supabase-project acceptance test, and the umbrella decisions log.
5
+
The project ships in four PRs sequenced contract → runtime → example → polish, and is the home of the **walking skeleton** — the runnable `examples/supabase` app that every other constituent wires into as it lands (strategy + growth table in the [umbrella README](../supabase-integration/README.md) §"Walking skeleton"; decisions [C13/C14](../supabase-integration/decisions.md)). M1 scaffolds the `@prisma-next/extension-supabase` package (subpath exports, hand-authored `contract.json`, `contract.d.ts`, branded `/contract` handles, the `/pack` descriptor) **and stands up the skeleton** running on the stock `@prisma-next/postgres/runtime`; the Supabase `/runtime` subpath is deferred to M2, so M1 is unblocked by the foundation + control-policy alone. M2 builds the runtime facade: `SupabaseRuntime extends PostgresRuntime`, JWT validation (sync via `jwtSecret`, async warmup via `jwksUrl`), `SupabaseDb` with `asUser` / `asAnon` / `asServiceRole`, `RoleBoundDb` extending `Db` with `transaction(...)`, the `SET LOCAL` + implicit-transaction plumbing wired through `withRawConnection`. M3 finalizes the (by-then incrementally-grown) `examples/supabase` app and adds the live-query RLS-enforcement e2e that depends on M2's role binding, running on the hermetic PGlite + Supabase-shim lane. M4 closes out documentation, the real-Supabase acceptance lane, and the umbrella decisions log.
**Goal:** the package exists with the subpath exports, the shipped contract is in place, and an app contract can import `AuthUser` and `roles` and reference them. No runtime yet.
30
+
**Goal:** the package exists with the subpath exports, the shipped contract is in place, an app contract can import `AuthUser` and `roles` and reference them, and the `examples/supabase` walking skeleton runs end-to-end against `public.*` on the stock Postgres runtime. No Supabase runtime yet (`/runtime` is M2).
31
31
32
32
**Tasks:**
33
33
34
-
-[ ] Scaffold the package at `packages/3-extensions/supabase/`. `package.json` with `exports` field declaring `/pack`, `/contract`, `/runtime` subpaths. `tsconfig.json` matching the existing extension packages (`cipherstash`, `pgvector`).
34
+
-[ ] Scaffold the package at `packages/3-extensions/supabase/`. `package.json` with `exports` field declaring `/pack`, `/contract`, `/runtime` subpaths. `tsconfig.json` matching the existing extension packages (`cipherstash`, `pgvector`). The `/runtime` subpath may be a stub in M1 (filled in by M2); `/pack` + `/contract` are complete.
35
35
-[ ] Hand-author `src/contract/contract.json`. Models: `AuthUser`, `AuthIdentity`, `StorageBucket`, `StorageObject` (the minimum set the example app needs). Roles: `anon`, `authenticated`, `service_role`. `defaultControl: 'external'`. Namespaces: `auth`, `storage`, `realtime`, `extensions`.
36
36
-[ ] Generate `src/contract/contract.d.ts` via the standard emitter pipeline. The contract type is consumed by the runtime facade's generics in M2.
37
37
-[ ] Hand-author `src/contract/index.ts` exporting branded `ModelHandle<'supabase', …>` instances for each model and `roles: { anon, authenticated, serviceRole }` as `RoleRef<'supabase'>` values. Each handle exposes `.refs.<column>` accessors typed from the `contract.d.ts` model shape.
@@ -43,8 +43,10 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
43
43
- The contract lowers correctly when the app's `defineContract` declares `extensionPacks: [supabasePack]`.
44
44
-[ ]`pnpm lint:deps` passes. `/pack` does not transitively import runtime; `/contract` does not transitively import SDK/runtime.
45
45
-[ ] Bundle-size measurement: `/pack` under 5 KB gzip; `/contract` under 10 KB gzip.
46
+
-[ ]**Stand up the walking skeleton** at `examples/supabase` (top-level, alongside the other example apps). Minimal runnable app: `prisma-next.config.ts` wiring `extensionPacks: [supabasePack]`; an app contract with a `Profile` model in `public`; a `db.ts` that builds a db via the **stock `@prisma-next/postgres/runtime`** factory (not the Supabase `/runtime` — that's M2); one handler that runs a basic `public.*` query. Gate it out of the default test matrix until green (or mark its integration test `.skip` / `todo`) so an intentionally-WIP example never reds CI.
47
+
-[ ]**Author `bootstrapSupabaseShim(client)`** (mirroring [`test/integration/test/postgres-bootstrap.ts`](../../test/integration/test/postgres-bootstrap.ts)) for the hermetic PGlite lane: seeds the `anon`/`authenticated`/`service_role`/`authenticator` roles, the `auth` schema + `auth.users`, and `auth.uid()`/`auth.jwt()`/`auth.role()` SQL functions reading the session GUCs. This is the shared fixture every later constituent's `examples/supabase` test uses (decision [C14](../supabase-integration/decisions.md)).
46
48
47
-
**Validation:** AC1, AC2, AC3 verified through end-to-end authoring smoke tests. No runtime yet; AC4 onwards land in M2.
49
+
**Validation:** AC1, AC2, AC3 verified through end-to-end authoring smoke tests. The skeleton boots and runs a `public.*` query on the stock Postgres runtime against the shim-bootstrapped PGlite database. No Supabase runtime yet; AC4 onwards land in M2.
48
50
49
51
### M2 — Runtime facade (`SupabaseRuntime`, role binding, SET LOCAL)
50
52
@@ -67,21 +69,21 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
67
69
-`db.asUser(badJwt)` throws synchronously; no connection acquired.
68
70
-`db.asUser(goodJwt)` returns a `RoleBoundDb`.
69
71
-`SupabaseDb` doesn't expose `.sql.from(...)` at the top level (type-level assertion via a `// @ts-expect-error` test).
70
-
-[ ] Integration tests against PGlite-with-Supabase-schema (a hand-crafted minimal schema mirroring `auth.users` shape):
72
+
-[ ] Integration tests against PGlite seeded with `bootstrapSupabaseShim(client)` (from M1):
71
73
- Statement sequence on `asUser(jwt).sql.from(...).execute()` is `BEGIN; SET LOCAL role; SET LOCAL request.jwt.claims; <query>; COMMIT;`. Verified by hooking the connection and recording statements.
72
74
- User middleware sees only the logical query (the `BEGIN` / `SET LOCAL` / `COMMIT` statements are invisible). Verified by a logging middleware in the test fixture.
73
75
-`asUser(jwt).transaction(async (tx) => { tx.sql..., tx.sql... })` issues one BEGIN + one SET LOCAL + two queries + one COMMIT.
74
76
75
77
**Validation:** AC4, AC5, AC6, AC7 verified.
76
78
77
-
### M3 — Example app + RLS-enforcement integration tests
79
+
### M3 — Finalize the example app + live-query RLS-enforcement e2e
78
80
79
-
**Goal:** the canonical demo runs end-to-end in CI. Every framework primitive is exercised against a real database; RLS enforcement is observable.
81
+
**Goal:** the `examples/supabase` walking skeleton — grown incrementally across the constituent lands — is finalized as the canonical demo, ungated in CI, and the RLS-enforcement e2e that depends on M2's automatic role binding is added. By this point the example already exercises the cross-contract FK, RLS policies, and namespace queries (each wired in by its own constituent's DoD); M3 assembles the full handler flow and proves enforcement through the runtime rather than by hand.
80
82
81
83
**Tasks:**
82
84
83
-
-[ ]Scaffold the example app at `packages/3-extensions/supabase/examples/basic/`. App contract (with `Profile`model and cross-contract FK to `AuthUser`, RLS policies), `prisma-next.config.ts`, `db.ts` factory, handler module exposing `listProfiles` / `createProfile` / `updateProfile` / `adminListProfiles`.
84
-
-[ ]Integration tests against PGlite-with-Supabase-schema:
85
+
-[ ]Finalize the example app at `examples/supabase` (stood up in M1, grown by the constituents). Complete the app contract (`Profile`with cross-contract FK to `AuthUser` + RLS policies), `db.ts` switched to the Supabase `/runtime` factory (`supabase({...})`), and the handler module exposing `listProfiles` / `createProfile` / `updateProfile` / `adminListProfiles`. Ungate it from the default test matrix.
86
+
-[ ]Live-query RLS-enforcement integration tests against PGlite seeded with`bootstrapSupabaseShim(client)` — these are the tests that need M2's automatic role binding (policy correctness via manual `SET ROLE` was already proven in the `postgres-rls` project):
85
87
- Migration: `prisma-next push` against the test database creates `public.profile` with FK to `auth.users.id`; creates the RLS policies; enables RLS on the table.
86
88
- RLS enforcement: a query through `asAnon()` returns rows whose policy permits anon read; returns zero rows where it doesn't.
87
89
- RLS enforcement: a query through `asUser(jwtForUserA)` can update User A's row but not User B's row (zero rows updated).
@@ -120,8 +122,8 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
120
122
121
123
## Risks and mitigations
122
124
123
-
-**Risk:**the May 18th launch date hinges on five upstream projects all landing. Any slip cascades.
124
-
-**Mitigation:** the four sibling projects + control-policy are independent. The umbrella tracker (in `projects/supabase-integration/README.md`) lists each constituent's status. The implementer of this project watches upstream progress weekly. If a slip is foreseeable >1 week out, scope-cut decisions surface to the team: either delay the launch, or carve a smaller v0.1 (e.g. drop the example app from launch; ship the package without it; backfill the example post-launch).
125
+
-**Risk:** launch hinges on five upstream projects all landing. Any slip cascades.
126
+
-**Mitigation:** the four sibling projects + control-policy are independent. The umbrella tracker (in `projects/supabase-integration/README.md`) lists each constituent's status. The implementer of this project watches upstream progress. If a slip is foreseeable, scope-cut decisions surface to the team: either delay the launch, or carve a smaller v0.1 (e.g. drop the example app from launch; ship the package without it; backfill the example post-launch).
125
127
-**Risk:** JWT validation has subtle bugs (audience checking, clock-skew tolerance, algorithm confusion). A bad JWT validator is a security hole.
126
128
-**Mitigation:** use `jose` (or another mature library) rather than hand-rolling validation. Set `algorithms` strictly to `['HS256', 'RS256']` (or whatever Supabase actually uses) to prevent algorithm confusion. Test the validation path explicitly against every documented failure mode.
127
129
-**Risk:** PGlite-with-Supabase-schema diverges from real Supabase behaviour in ways that hide bugs.
0 commit comments