@selvajs/supabase-provider 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -0
  3. package/dist/auth/SupabaseAuthProvider.d.ts +81 -0
  4. package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -0
  5. package/dist/auth/SupabaseAuthProvider.js +383 -0
  6. package/dist/auth/SupabaseAuthProvider.js.map +1 -0
  7. package/dist/auth/__tests__/conformance.test.d.ts +2 -0
  8. package/dist/auth/__tests__/conformance.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/conformance.test.js +76 -0
  10. package/dist/auth/__tests__/conformance.test.js.map +1 -0
  11. package/dist/auth/index.d.ts +3 -0
  12. package/dist/auth/index.d.ts.map +1 -0
  13. package/dist/auth/index.js +2 -0
  14. package/dist/auth/index.js.map +1 -0
  15. package/dist/data/SupabaseAuditQuery.d.ts +16 -0
  16. package/dist/data/SupabaseAuditQuery.d.ts.map +1 -0
  17. package/dist/data/SupabaseAuditQuery.js +83 -0
  18. package/dist/data/SupabaseAuditQuery.js.map +1 -0
  19. package/dist/data/SupabaseComputeServerStore.d.ts +26 -0
  20. package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -0
  21. package/dist/data/SupabaseComputeServerStore.js +227 -0
  22. package/dist/data/SupabaseComputeServerStore.js.map +1 -0
  23. package/dist/data/SupabaseDataProvider.d.ts +55 -0
  24. package/dist/data/SupabaseDataProvider.d.ts.map +1 -0
  25. package/dist/data/SupabaseDataProvider.js +114 -0
  26. package/dist/data/SupabaseDataProvider.js.map +1 -0
  27. package/dist/data/SupabaseDefinitionStore.d.ts +39 -0
  28. package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -0
  29. package/dist/data/SupabaseDefinitionStore.js +419 -0
  30. package/dist/data/SupabaseDefinitionStore.js.map +1 -0
  31. package/dist/data/SupabaseEventSink.d.ts +17 -0
  32. package/dist/data/SupabaseEventSink.d.ts.map +1 -0
  33. package/dist/data/SupabaseEventSink.js +31 -0
  34. package/dist/data/SupabaseEventSink.js.map +1 -0
  35. package/dist/data/SupabaseInviteStore.d.ts +22 -0
  36. package/dist/data/SupabaseInviteStore.d.ts.map +1 -0
  37. package/dist/data/SupabaseInviteStore.js +159 -0
  38. package/dist/data/SupabaseInviteStore.js.map +1 -0
  39. package/dist/data/SupabaseOrgStore.d.ts +33 -0
  40. package/dist/data/SupabaseOrgStore.d.ts.map +1 -0
  41. package/dist/data/SupabaseOrgStore.js +455 -0
  42. package/dist/data/SupabaseOrgStore.js.map +1 -0
  43. package/dist/data/SupabaseProjectStore.d.ts +29 -0
  44. package/dist/data/SupabaseProjectStore.d.ts.map +1 -0
  45. package/dist/data/SupabaseProjectStore.js +328 -0
  46. package/dist/data/SupabaseProjectStore.js.map +1 -0
  47. package/dist/data/SupabaseShareLinkStore.d.ts +26 -0
  48. package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -0
  49. package/dist/data/SupabaseShareLinkStore.js +150 -0
  50. package/dist/data/SupabaseShareLinkStore.js.map +1 -0
  51. package/dist/data/__tests__/compute-server-conformance.test.d.ts +2 -0
  52. package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +1 -0
  53. package/dist/data/__tests__/compute-server-conformance.test.js +40 -0
  54. package/dist/data/__tests__/compute-server-conformance.test.js.map +1 -0
  55. package/dist/data/__tests__/definition-conformance.test.d.ts +2 -0
  56. package/dist/data/__tests__/definition-conformance.test.d.ts.map +1 -0
  57. package/dist/data/__tests__/definition-conformance.test.js +85 -0
  58. package/dist/data/__tests__/definition-conformance.test.js.map +1 -0
  59. package/dist/data/__tests__/event-sink-conformance.test.d.ts +2 -0
  60. package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +1 -0
  61. package/dist/data/__tests__/event-sink-conformance.test.js +27 -0
  62. package/dist/data/__tests__/event-sink-conformance.test.js.map +1 -0
  63. package/dist/data/__tests__/invite-conformance.test.d.ts +2 -0
  64. package/dist/data/__tests__/invite-conformance.test.d.ts.map +1 -0
  65. package/dist/data/__tests__/invite-conformance.test.js +45 -0
  66. package/dist/data/__tests__/invite-conformance.test.js.map +1 -0
  67. package/dist/data/__tests__/org-conformance.test.d.ts +2 -0
  68. package/dist/data/__tests__/org-conformance.test.d.ts.map +1 -0
  69. package/dist/data/__tests__/org-conformance.test.js +29 -0
  70. package/dist/data/__tests__/org-conformance.test.js.map +1 -0
  71. package/dist/data/__tests__/project-conformance.test.d.ts +2 -0
  72. package/dist/data/__tests__/project-conformance.test.d.ts.map +1 -0
  73. package/dist/data/__tests__/project-conformance.test.js +57 -0
  74. package/dist/data/__tests__/project-conformance.test.js.map +1 -0
  75. package/dist/data/__tests__/share-link-conformance.test.d.ts +2 -0
  76. package/dist/data/__tests__/share-link-conformance.test.d.ts.map +1 -0
  77. package/dist/data/__tests__/share-link-conformance.test.js +88 -0
  78. package/dist/data/__tests__/share-link-conformance.test.js.map +1 -0
  79. package/dist/data/__tests__/test-helpers.d.ts +53 -0
  80. package/dist/data/__tests__/test-helpers.d.ts.map +1 -0
  81. package/dist/data/__tests__/test-helpers.js +175 -0
  82. package/dist/data/__tests__/test-helpers.js.map +1 -0
  83. package/dist/data/client.d.ts +44 -0
  84. package/dist/data/client.d.ts.map +1 -0
  85. package/dist/data/client.js +40 -0
  86. package/dist/data/client.js.map +1 -0
  87. package/dist/data/index.d.ts +12 -0
  88. package/dist/data/index.d.ts.map +1 -0
  89. package/dist/data/index.js +11 -0
  90. package/dist/data/index.js.map +1 -0
  91. package/dist/data/pagination.d.ts +30 -0
  92. package/dist/data/pagination.d.ts.map +1 -0
  93. package/dist/data/pagination.js +53 -0
  94. package/dist/data/pagination.js.map +1 -0
  95. package/dist/index.d.ts +9 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +6 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/permissions/SupabasePlatformPermissionStore.d.ts +25 -0
  100. package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -0
  101. package/dist/permissions/SupabasePlatformPermissionStore.js +153 -0
  102. package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -0
  103. package/dist/permissions/__tests__/conformance.test.d.ts +2 -0
  104. package/dist/permissions/__tests__/conformance.test.d.ts.map +1 -0
  105. package/dist/permissions/__tests__/conformance.test.js +27 -0
  106. package/dist/permissions/__tests__/conformance.test.js.map +1 -0
  107. package/dist/permissions/index.d.ts +2 -0
  108. package/dist/permissions/index.d.ts.map +1 -0
  109. package/dist/permissions/index.js +2 -0
  110. package/dist/permissions/index.js.map +1 -0
  111. package/dist/storage/SupabaseStorageProvider.d.ts +67 -0
  112. package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -0
  113. package/dist/storage/SupabaseStorageProvider.js +136 -0
  114. package/dist/storage/SupabaseStorageProvider.js.map +1 -0
  115. package/dist/storage/__tests__/conformance.test.d.ts +2 -0
  116. package/dist/storage/__tests__/conformance.test.d.ts.map +1 -0
  117. package/dist/storage/__tests__/conformance.test.js +149 -0
  118. package/dist/storage/__tests__/conformance.test.js.map +1 -0
  119. package/dist/storage/index.d.ts +3 -0
  120. package/dist/storage/index.d.ts.map +1 -0
  121. package/dist/storage/index.js +2 -0
  122. package/dist/storage/index.js.map +1 -0
  123. package/dist/userProfile/SupabaseUserProfileProvider.d.ts +16 -0
  124. package/dist/userProfile/SupabaseUserProfileProvider.d.ts.map +1 -0
  125. package/dist/userProfile/SupabaseUserProfileProvider.js +148 -0
  126. package/dist/userProfile/SupabaseUserProfileProvider.js.map +1 -0
  127. package/dist/userProfile/__tests__/conformance.test.d.ts +2 -0
  128. package/dist/userProfile/__tests__/conformance.test.d.ts.map +1 -0
  129. package/dist/userProfile/__tests__/conformance.test.js +38 -0
  130. package/dist/userProfile/__tests__/conformance.test.js.map +1 -0
  131. package/dist/userProfile/index.d.ts +2 -0
  132. package/dist/userProfile/index.d.ts.map +1 -0
  133. package/dist/userProfile/index.js +2 -0
  134. package/dist/userProfile/index.js.map +1 -0
  135. package/package.json +75 -0
  136. package/src/auth/SupabaseAuthProvider.ts +464 -0
  137. package/src/auth/__tests__/conformance.test.ts +78 -0
  138. package/src/auth/index.ts +2 -0
  139. package/src/data/SupabaseAuditQuery.ts +103 -0
  140. package/src/data/SupabaseComputeServerStore.ts +262 -0
  141. package/src/data/SupabaseDataProvider.ts +125 -0
  142. package/src/data/SupabaseDefinitionStore.ts +504 -0
  143. package/src/data/SupabaseEventSink.ts +30 -0
  144. package/src/data/SupabaseInviteStore.ts +183 -0
  145. package/src/data/SupabaseOrgStore.ts +547 -0
  146. package/src/data/SupabaseProjectStore.ts +414 -0
  147. package/src/data/SupabaseShareLinkStore.ts +191 -0
  148. package/src/data/__tests__/compute-server-conformance.test.ts +40 -0
  149. package/src/data/__tests__/definition-conformance.test.ts +89 -0
  150. package/src/data/__tests__/event-sink-conformance.test.ts +32 -0
  151. package/src/data/__tests__/invite-conformance.test.ts +46 -0
  152. package/src/data/__tests__/org-conformance.test.ts +30 -0
  153. package/src/data/__tests__/project-conformance.test.ts +57 -0
  154. package/src/data/__tests__/share-link-conformance.test.ts +87 -0
  155. package/src/data/__tests__/test-helpers.ts +186 -0
  156. package/src/data/client.ts +86 -0
  157. package/src/data/index.ts +11 -0
  158. package/src/data/pagination.ts +68 -0
  159. package/src/index.ts +23 -0
  160. package/src/permissions/SupabasePlatformPermissionStore.ts +161 -0
  161. package/src/permissions/__tests__/conformance.test.ts +28 -0
  162. package/src/permissions/index.ts +1 -0
  163. package/src/storage/SupabaseStorageProvider.ts +177 -0
  164. package/src/storage/__tests__/conformance.test.ts +152 -0
  165. package/src/storage/index.ts +2 -0
  166. package/src/userProfile/SupabaseUserProfileProvider.ts +177 -0
  167. package/src/userProfile/__tests__/conformance.test.ts +39 -0
  168. package/src/userProfile/index.ts +1 -0
  169. package/supabase/.branches/_current_branch +1 -0
  170. package/supabase/.temp/cli-latest +1 -0
  171. package/supabase/config.toml +68 -0
  172. package/supabase/migrations/0001_initial.sql +1084 -0
  173. package/supabase/seed.sql +13 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Selva FelixBrunold VektorNode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # @selvajs/supabase-provider
2
+
3
+ Supabase (Auth + Postgres + Storage) implementation of the `@selvajs/platform` interfaces.
4
+
5
+ Runs the Selva backend against **either** a managed Supabase project (production) or a local Supabase CLI stack in Docker (development). Same code — different `SUPABASE_URL` and keys.
6
+
7
+ ---
8
+
9
+ ## Table of contents
10
+
11
+ - [Quick start](#quick-start)
12
+ - [Environment variables](#environment-variables)
13
+ - [Wiring into `selva.config.ts`](#wiring-into-selvaconfigts)
14
+ - [Applying the schema](#applying-the-schema)
15
+ - [Development — local Supabase stack](#development--local-supabase-stack)
16
+ - [Production — hosted Supabase project](#production--hosted-supabase-project)
17
+ - [Running the conformance tests](#running-the-conformance-tests)
18
+ - [Architecture notes](#architecture-notes)
19
+
20
+ ---
21
+
22
+ ## Quick start
23
+
24
+ 1. Install the package in the workspace (already listed in `pnpm-workspace.yaml`).
25
+ 2. Provision Supabase — either local (`npx supabase start`) or hosted (supabase.com).
26
+ 3. Apply the migrations from `packages/providers/supabase/supabase/migrations/` (the local CLI does this automatically on `db reset`).
27
+ 4. Copy the three env vars into your selva app `.env` (see below).
28
+ 5. Edit [`selva.config.ts`](../../../selva.config.ts) at the repo root to swap the local provider for the Supabase provider.
29
+ 6. `pnpm dev` — the selva app now reads and writes from Supabase.
30
+
31
+ ---
32
+
33
+ ## Environment variables
34
+
35
+ All env vars are documented in [`packages/selva/.env.example`](../../selva/.env.example) — copy that file to `.env` and edit it. The Supabase provider needs `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and `SUPABASE_SERVICE_ROLE_KEY`; the optional bucket / private-URL / signup overrides are also listed there.
36
+
37
+ `SELVA_HMAC_KEY` is not used for sessions by the Supabase provider — those are Supabase JWTs. It's still consulted as the fallback secret for share-link / invite token hashing if `SHARE_LINK_SECRET` / `INVITE_TOKEN_SECRET` are unset. `SELVA_AT_REST_KEY` is local-provider-only and is ignored. Both are harmless if left set.
38
+
39
+ Rhino.Compute URL + API key are configured in `/admin/compute` and persisted in the `compute_config` table — unchanged by the provider choice.
40
+
41
+ ### Finding the keys
42
+
43
+ **Local (`npx supabase status`):**
44
+
45
+ ```
46
+ ╭──────────────────────────────────────────────────╮
47
+ │ 🔑 Authentication Keys │
48
+ ├─────────────┬────────────────────────────────────┤
49
+ │ Publishable │ sb_publishable_... │ ← SUPABASE_ANON_KEY
50
+ │ Secret │ sb_secret_... │ ← SUPABASE_SERVICE_ROLE_KEY
51
+ ╰─────────────┴────────────────────────────────────╯
52
+ ```
53
+
54
+ **Hosted:** Supabase Dashboard → **Project Settings** → **API**.
55
+
56
+ > **The S3-compat keys under the "Storage (S3)" section are NOT what you want.** Those are for external S3 tooling (rclone, etc.), not for `@supabase/supabase-js`.
57
+
58
+ ---
59
+
60
+ ## Wiring into `selva.config.ts`
61
+
62
+ The repo's [`selva.config.ts`](../../selva.config.ts) is the single DI point — it picks which provider backs every interface. Switch between providers by changing imports:
63
+
64
+ ```ts
65
+ import { defineConfig } from '@selvajs/platform';
66
+ import * as supa from '@selvajs/supabase-provider';
67
+
68
+ export default defineConfig((env) => ({
69
+ tenancy: 'single' as const,
70
+ flags: {
71
+ ALLOW_CROSS_ORG_PUBLIC: false,
72
+ ALLOW_ORG_COMPUTE_OVERRIDE: false,
73
+ ALLOW_ORG_CREATION: false
74
+ },
75
+ auth: supa.SupabaseAuthProvider.fromEnv(env),
76
+ data: supa.SupabaseDataProvider.fromEnv(env),
77
+ storage: supa.SupabaseStorageProvider.fromEnv(env)
78
+ }));
79
+ ```
80
+
81
+ To switch back to the local provider, swap the import and the three provider lines. See the commented example in [`selva.config.ts`](../../selva.config.ts).
82
+
83
+ ---
84
+
85
+ ## Applying the schema
86
+
87
+ The `supabase/migrations/` directory holds a single bootstrap file that installs the entire schema in one pass:
88
+
89
+ | File | What it installs |
90
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | `0001_initial.sql` | Everything: `user_profiles` (+ auto-seed trigger), `orgs` / `org_members` / `projects` / `project_members` (+ RLS helpers), `definitions` / `definition_versions` (+ deletion-protection FKs and the atomic `increment_run_count` RPC), `invites` (+ `get_invite_by_token` RPC), `compute_servers` (+ per-org/instance defaults, spec §3 BYO compute), `share_links` (+ `try_increment_share_link_solve_count` RPC, spec §7), and the `selva-public` / `selva-private` storage policies. |
92
+
93
+ Future schema changes go in numbered files (`0002_…`, `0003_…`).
94
+
95
+ The `supabase/seed.sql` file creates the two storage buckets (`selva-public`, `selva-private`).
96
+
97
+ **Local:** `npx supabase db reset` applies everything on a fresh DB.
98
+
99
+ **Hosted:** link the CLI to your project, then push:
100
+
101
+ ```bash
102
+ cd packages/providers/supabase
103
+
104
+ # One-time link. Reads the project-ref from Supabase Dashboard → Project Settings → General.
105
+ npx supabase link --project-ref <your-project-ref>
106
+
107
+ # Review what will be applied.
108
+ npx supabase db diff
109
+
110
+ # Apply migrations + seed.
111
+ npx supabase db push
112
+ ```
113
+
114
+ Alternatively you can copy each `.sql` file into Supabase Dashboard → **SQL Editor** and run them in order. The CLI path is strongly preferred — it's idempotent and version-controlled.
115
+
116
+ ---
117
+
118
+ ## Development — local Supabase stack
119
+
120
+ Prerequisite: **Docker Desktop running.** First run pulls ~1 GB of images.
121
+
122
+ ```bash
123
+ cd packages/providers/supabase
124
+ npx supabase start
125
+ ```
126
+
127
+ This spins up Postgres (54322), GoTrue/Auth (54321), Storage, Studio (54323), and Mailpit (54324 — fake SMTP inbox for auth emails). Migrations and the bucket seed apply automatically.
128
+
129
+ Copy the printed **Publishable** and **Secret** keys into your `.env` at the selva app or repo root:
130
+
131
+ ```bash
132
+ # selva app .env
133
+ SUPABASE_URL=http://127.0.0.1:54321
134
+ SUPABASE_ANON_KEY=sb_publishable_...
135
+ SUPABASE_SERVICE_ROLE_KEY=sb_secret_...
136
+ ```
137
+
138
+ Rhino.Compute server URL + API key are registered in `/admin/compute` after first boot.
139
+
140
+ Switch `selva.config.ts` to Supabase (snippet above), then:
141
+
142
+ ```bash
143
+ pnpm dev
144
+ ```
145
+
146
+ **Useful commands:**
147
+
148
+ ```bash
149
+ npx supabase status # show URL + keys again
150
+ npx supabase db reset # wipe DB + re-run migrations + seed
151
+ npx supabase stop # stop containers (keeps volumes)
152
+ npx supabase stop --no-backup # stop + wipe volumes
153
+ ```
154
+
155
+ Studio is a full admin UI at `http://127.0.0.1:54323`. Use it to inspect tables, rows, and RLS policies while developing.
156
+
157
+ ---
158
+
159
+ ## Production — hosted Supabase project
160
+
161
+ 1. Create a project on [supabase.com](https://supabase.com). Choose a region close to your selva app deployment.
162
+ 2. Dashboard → **Project Settings** → **API**. Copy the **Project URL**, **Publishable** key, and **Secret** key.
163
+ 3. Apply migrations via the CLI:
164
+ ```bash
165
+ cd packages/providers/supabase
166
+ npx supabase link --project-ref <your-project-ref>
167
+ npx supabase db push
168
+ ```
169
+ 4. Create the two storage buckets by running `supabase/seed.sql` in the Dashboard → **SQL Editor** (the CLI's `db push` doesn't run the seed on a hosted project — it's dev-only).
170
+ 5. Set the env vars on your selva app host (Vercel, Fly.io, Docker, etc.):
171
+ ```
172
+ SUPABASE_URL=https://<project-ref>.supabase.co
173
+ SUPABASE_ANON_KEY=sb_publishable_...
174
+ SUPABASE_SERVICE_ROLE_KEY=sb_secret_...
175
+ ```
176
+ After deploying, register your Rhino.Compute server URL (+ optional API key) via `/admin/compute`.
177
+ 6. Deploy the selva app with `selva.config.ts` wired to `@selvajs/supabase-provider`.
178
+ 7. Bootstrap the first user:
179
+ - Open `/setup` once — creates the first admin with `instance_admin`.
180
+ - Or manually: Dashboard → **Authentication** → **Add user**, then in the SQL Editor: `UPDATE public.user_profiles SET platform_permissions = ARRAY['instance_admin']::text[] WHERE user_id = '<uuid>';`
181
+
182
+ ### Operational checklist
183
+
184
+ - **Backups.** Supabase handles daily backups on paid plans; verify restore works before relying on it.
185
+ - **Rotate keys** if the Secret key is ever logged or committed. Dashboard → **Project Settings** → **API** → **Reset service_role key**.
186
+ - **Row-level security is on for every table.** Disabling it on any table silently breaks the tenant boundary.
187
+ - **Email sender.** Dashboard → **Authentication** → **Email templates** — configure an SMTP provider before going live, otherwise password-reset emails route through Supabase's rate-limited dev sender.
188
+
189
+ ---
190
+
191
+ ## Running the conformance tests
192
+
193
+ The package ships a full conformance suite that runs against a **live** Supabase stack. It proves the provider implements every interface the same way the local provider does.
194
+
195
+ ```bash
196
+ cd packages/providers/supabase
197
+
198
+ # 1. Make sure the stack is running.
199
+ npx supabase start
200
+
201
+ # 2. Create .env.test with the same three vars.
202
+ # (This is separate from your app's .env — it's gitignored and used only by tests.)
203
+ cat > .env.test <<EOF
204
+ SUPABASE_URL=http://127.0.0.1:54321
205
+ SUPABASE_ANON_KEY=<publishable key>
206
+ SUPABASE_SERVICE_ROLE_KEY=<secret key>
207
+ EOF
208
+
209
+ # 3. Run.
210
+ pnpm test
211
+ ```
212
+
213
+ When `.env.test` is missing the suite skips with a single explanatory test.
214
+
215
+ The suite wipes every table and every auth user between tests — **do not point it at a production project.**
216
+
217
+ ---
218
+
219
+ ## Architecture notes
220
+
221
+ ### Storage
222
+
223
+ Two buckets:
224
+
225
+ - **`selva-public`** (public) — covers, archives. `getPublicUrl` returns the direct CDN URL.
226
+ - **`selva-private`** (private) — `.gh` / `.ghx` files. `getPublicUrl` returns `/api/files/{path}` which the selva app's route handler must proxy after an auth check.
227
+
228
+ Images are transcoded to WebP (1200px cap, quality 85) via the shared `transcodeImageIfNeeded` helper from `@selvajs/platform/storage`. Same bytes out of both providers.
229
+
230
+ ### Auth
231
+
232
+ `SupabaseAuthProvider.verifyLogin` wraps `supabase.auth.signInWithPassword`, returning the JWT directly as `sessionToken`. `verifyToken` calls `supabase.auth.getUser(token)` — GoTrue validates the signature for us. Platform permissions merge in from `user_profiles.platform_permissions` (auto-created by the signup trigger).
233
+
234
+ MFA methods on `IPasswordAuth` are currently undefined — MFA is deferred. If a user enrolls a factor via the Supabase dashboard, `signInWithPassword` returns an AAL1 session and routes that gate on AAL2 would fail; no route does today.
235
+
236
+ ### Data + RLS
237
+
238
+ Every store goes through `ClientBundle.forRequest(ctx)`:
239
+
240
+ - `ctx.system` → service-role client (bypasses RLS)
241
+ - `ctx.adapterContext.sessionToken` → anon client with `Authorization: Bearer <jwt>` (RLS enforces per-user visibility)
242
+ - neither → service-role fallback (admin paths)
243
+
244
+ Helper SQL functions (`is_instance_admin`, `is_org_member`, `visible_project`, `has_org_permission`) are `SECURITY DEFINER` so they evaluate without looping through RLS.
245
+
246
+ ### User profile
247
+
248
+ `user_profiles` is 1:1 with `auth.users`. The `handle_new_auth_user` trigger inserts a profile row on every signup so the provider never has to fence creation. `starDefinition` / `unstarDefinition` / `recordRun` do read-modify-write — OK for the expected scale; a SECURITY DEFINER RPC would close the race if concurrent starring ever matters.
249
+
250
+ ### Atomic improvements over the local provider
251
+
252
+ - **`incrementRunCount`** uses a SQL function (`UPDATE … SET run_count = run_count + 1`) — atomic. The local provider does read-modify-write and can lose bumps under concurrent solves.
253
+
254
+ ### Findings
255
+
256
+ Every abstraction-pressure point hit during implementation is logged in [FINDINGS.md](./FINDINGS.md). Short version: six items, none requiring platform contract changes beyond the Phase 0 set.
257
+
258
+ ---
259
+
260
+ ## Status
261
+
262
+ Every interface from `@selvajs/platform` is implemented and exercised by the shared conformance suites against a live local Supabase stack:
263
+
264
+ - `SupabaseAuthProvider` (incl. MFA-ready `LoginResult`)
265
+ - `SupabaseStorageProvider`
266
+ - `SupabaseOrgStore`, `SupabaseProjectStore`, `SupabaseDefinitionStore` (atomic `incrementRunCount`), `SupabaseInviteStore`, `SupabaseShareLinkStore`, `SupabaseComputeServerStore`
267
+ - `SupabaseUserProfileProvider`
268
+ - `SupabasePlatformPermissionStore`
269
+ - `SupabaseDataProvider` — composition of the data stores
270
+
271
+ Run them with `pnpm test` (see [Running the conformance tests](#running-the-conformance-tests)).
@@ -0,0 +1,81 @@
1
+ import type { IAuthProvider, IEmailLinkAuth, IOAuthAuth, IPasswordAuth, AuthUser, UserManagementResult, ListOptions, Page } from '@selvajs/platform';
2
+ /**
3
+ * Auth backed by Supabase Auth (GoTrue).
4
+ *
5
+ * Identity + session lifecycle are fully delegated:
6
+ * - `verifyLogin` wraps `auth.signInWithPassword` — returns the access_token
7
+ * straight from Supabase as the session token.
8
+ * - `verifyToken` calls `auth.getUser(token)` against an anon client scoped
9
+ * to that bearer; GoTrue validates the JWT for us.
10
+ * - User management uses `auth.admin.*` with the service-role client.
11
+ *
12
+ * Identity-only — platform permissions live on `IPlatformPermissionStore`
13
+ * (`SupabasePlatformPermissionStore` reads `user_profiles.platform_permissions`)
14
+ * and profile state on `IUserProfileStore`.
15
+ */
16
+ export interface SupabaseAuthProviderConfig {
17
+ supabaseUrl: string;
18
+ anonKey: string;
19
+ serviceRoleKey: string;
20
+ /**
21
+ * Whether self-service signup is allowed via `passwordAuth.registerUser`.
22
+ * Default false — production deployments start with invite-only.
23
+ */
24
+ enableSelfSignup?: boolean;
25
+ /**
26
+ * OAuth providers enabled in the Supabase dashboard (lowercased:
27
+ * "google", "github", …). Surfaced via `oauth.listProviders()` so the
28
+ * driving layer doesn't read provider-specific env vars itself. Defaults
29
+ * to an empty list.
30
+ */
31
+ oauthProviders?: readonly string[];
32
+ /**
33
+ * Allow `emailLink.sendMagicLink` to create new users on first request.
34
+ * Default true — matches Supabase's `signInWithOtp` default and gives
35
+ * fresh installs a working signup path. Set false for invite-only
36
+ * deployments; `sendMagicLink` for an unknown email returns
37
+ * `{ ok: false, reason: 'signup_disabled' }`.
38
+ */
39
+ allowEmailLinkSignup?: boolean;
40
+ }
41
+ export declare class SupabaseAuthProvider implements IAuthProvider {
42
+ readonly name = "Supabase Auth";
43
+ readonly passwordAuth: IPasswordAuth;
44
+ readonly oauth: IOAuthAuth;
45
+ readonly emailLink: IEmailLinkAuth;
46
+ private readonly admin;
47
+ private readonly anon;
48
+ private readonly anonKey;
49
+ private readonly supabaseUrl;
50
+ constructor(config: SupabaseAuthProviderConfig);
51
+ static fromEnv(env: Record<string, string | undefined>): SupabaseAuthProvider;
52
+ verifyToken(token: string): Promise<AuthUser | null>;
53
+ getUser(id: string): Promise<AuthUser | null>;
54
+ listUsers(opts?: ListOptions): Promise<Page<AuthUser> | null>;
55
+ createUser(email: string): Promise<AuthUser>;
56
+ deleteUser(id: string): Promise<UserManagementResult>;
57
+ disableUser(id: string): Promise<UserManagementResult>;
58
+ touchLastLogin(id: string): Promise<void>;
59
+ /**
60
+ * Sign in via GoTrue. Returns the access token string and the hydrated
61
+ * user. Called by `SupabasePasswordAuth.verifyLogin` — kept on the
62
+ * provider class so it has access to the hydrate helper without plumbing.
63
+ */
64
+ private signIn;
65
+ /**
66
+ * Map a GoTrue `User` to our identity-only `AuthUser`. Platform permissions
67
+ * live on `IPlatformPermissionStore` and profile fields (displayName,
68
+ * starred, recentRuns) on `IUserProfileStore` — neither is in AuthUser.
69
+ */
70
+ private hydrate;
71
+ /**
72
+ * Expose the anon URL + key so other code (e.g. the same process's
73
+ * storage provider or per-request client factories) can build user-scoped
74
+ * clients without requiring a second config read.
75
+ */
76
+ getAnonClientConfig(): {
77
+ supabaseUrl: string;
78
+ anonKey: string;
79
+ };
80
+ }
81
+ //# sourceMappingURL=SupabaseAuthProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SupabaseAuthProvider.d.ts","sourceRoot":"","sources":["../../src/auth/SupabaseAuthProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EACb,cAAc,EACd,UAAU,EACV,aAAa,EACb,QAAQ,EAER,oBAAoB,EACpB,WAAW,EACX,IAAI,EACJ,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,0BAA0B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,qBAAa,oBAAqB,YAAW,aAAa;IACzD,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,EAAE,0BAA0B;IA4B9C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,oBAAoB;IAuBvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAWpD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAM7C,SAAS,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAkB7D,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAa5C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAcrD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAoBtD,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB/C;;;;OAIG;YACW,MAAM;IAUpB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAWf;;;;OAIG;IACH,mBAAmB,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAG/D"}