better-supabase 0.4.0 → 0.5.1
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.
- package/README.md +135 -16
- package/dist/actor-DaJjqD5R.d.ts +43 -0
- package/dist/channel-FQW-CnGR.d.ts +70 -0
- package/dist/cli/bin.js +1 -1
- package/dist/cli/codemod-DqV7612m.js +388 -0
- package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
- package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
- package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
- package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
- package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
- package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
- package/dist/cli/{doctor-BOym3mbL.js → doctor-C_A-KEAz.js} +533 -50
- package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
- package/dist/cli/gen-CIdSQZad.js +2 -0
- package/dist/cli/{gen-Ccn_p90L.js → gen-CjElD1Vw.js} +321 -88
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{init-ClnxRYBF.js → init-DMa6M7hV.js} +2 -2
- package/dist/cli/{introspect-DxqfkVIv.js → introspect-BKlaIu_M.js} +2 -2
- package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
- package/dist/cli/read-sets-VQcPgxU9.js +436 -0
- package/dist/cli/{run-CP20LKk_.js → run-Bo_NLT5n.js} +16 -12
- package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
- package/dist/cli/sql-CVx-1O8G.js +373 -0
- package/dist/cli/{version-5mW_8sY1.js → version-BCtlgDQx.js} +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +3 -10
- package/dist/clone-BfgmLU90.js +21 -0
- package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -4
- package/dist/config/index.js +4 -4
- package/dist/{config-C1CYcm6o.d.ts → config-Bv7gvfzC.d.ts} +185 -11
- package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
- package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
- package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
- package/dist/edge/index.d.ts +16 -5
- package/dist/edge/index.js +10 -7
- package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-BiJZ4t49.d.ts} +1 -1
- package/dist/env/index.d.ts +2 -2
- package/dist/env/index.js +34 -1
- package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.js +65 -12
- package/dist/events-aIO7vsIk.d.ts +230 -0
- package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
- package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
- package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
- package/dist/hono/index.d.ts +17 -6
- package/dist/hono/index.js +40 -5
- package/dist/impersonation-BgSHAYLf.js +133 -0
- package/dist/{resolve-BAqQcwMW.d.ts → impersonation-BmePsuxz.d.ts} +36 -6
- package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
- package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
- package/dist/{index-Cf-D2jk1.d.ts → index-CWMDdEr4.d.ts} +2 -2
- package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
- package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
- package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
- package/dist/{index-BBdvo-5d.d.ts → index-Dp2VDwog.d.ts} +49 -6
- package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
- package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
- package/dist/index.d.ts +30 -13
- package/dist/index.js +10 -9
- package/dist/jobs/index.d.ts +147 -101
- package/dist/jobs/index.js +744 -237
- package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
- package/dist/kit-events-CTFDfSwb.js +60 -0
- package/dist/kit-transport-B82k1djG.d.ts +33 -0
- package/dist/kit-transport-DJlHQUBU.js +55 -0
- package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
- package/dist/list/index.d.ts +1 -1
- package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
- package/dist/mcp/index.d.ts +25 -8
- package/dist/mcp/index.js +23 -14
- package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-Cu96oJdw.d.ts} +2 -16
- package/dist/next/index.d.ts +31 -10
- package/dist/next/index.js +136 -27
- package/dist/notifications/index.d.ts +162 -0
- package/dist/notifications/index.js +264 -0
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +2 -2
- package/dist/orgs/index.d.ts +132 -0
- package/dist/orgs/index.js +245 -0
- package/dist/orpc/index.d.ts +28 -8
- package/dist/orpc/index.js +16 -3
- package/dist/otel/index.d.ts +21 -2
- package/dist/otel/index.js +50 -9
- package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
- package/dist/permdock-sql-DgOfeixi.js +51 -0
- package/dist/plugin-DwARMuQp.js +59 -0
- package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
- package/dist/plugins/actor/index.d.ts +5 -2
- package/dist/plugins/actor/index.js +22 -8
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +15 -10
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +14 -5
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/tenant/index.js +38 -20
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +4 -3
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +26 -6
- package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
- package/dist/postgres/index.d.ts +5 -5
- package/dist/postgres/index.js +20 -11
- package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
- package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
- package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -3
- package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
- package/dist/queue-BYUCjr59.d.ts +252 -0
- package/dist/react/index.d.ts +8 -7
- package/dist/react/index.js +4 -3
- package/dist/react/server.d.ts +14 -3
- package/dist/react/server.js +10 -2
- package/dist/react/session.d.ts +7 -1
- package/dist/react/session.js +9 -1
- package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
- package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
- package/dist/realtime/index.d.ts +3 -3
- package/dist/realtime/index.js +5 -4
- package/dist/{refresh-BwYkyNOQ.d.ts → refresh-CFPX0Qft.d.ts} +1 -1
- package/dist/{resource-BJcaIfxi.d.ts → resource-DNRFP7KR.d.ts} +5 -5
- package/dist/{resource-My_y-Syh.js → resource-mqfJW4YF.js} +22 -7
- package/dist/respond-BR1fJWOB.d.ts +377 -0
- package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
- package/dist/server/index.d.ts +15 -12
- package/dist/server/index.js +11 -17
- package/dist/{server-BA75B0mT.js → server-B1mZ7CgQ.js} +259 -37
- package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
- package/dist/signers-Cc6r98SW.d.ts +162 -0
- package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
- package/dist/sql/index.d.ts +283 -11
- package/dist/sql/index.js +8705 -775
- package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +1 -2
- package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
- package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
- package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
- package/dist/storage/index.d.ts +4 -4
- package/dist/storage/index.js +155 -26
- package/dist/support-CEPwlw2G.d.ts +89 -0
- package/dist/support-view-B8SlToQX.d.ts +17 -0
- package/dist/support-view-g4YLBeqX.js +18 -0
- package/dist/support-wHnK6zp-.js +293 -0
- package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
- package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
- package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
- package/dist/testing/index.d.ts +132 -10
- package/dist/testing/index.js +476 -8
- package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
- package/dist/user-change-CHFEUro1.js +49 -0
- package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
- package/dist/{version-Bmf7_FzM.js → version-DOYTYwHP.js} +1 -1
- package/dist/{mfa-byfjruR9.js → view-Bn6HNKUe.js} +97 -98
- package/dist/{view-B64o81wp.d.ts → view-CbgObAeb.d.ts} +22 -31
- package/dist/webhooks/index.d.ts +135 -4
- package/dist/webhooks/index.js +588 -2
- package/dist/wire-D2dT86vi.js +35 -0
- package/package.json +27 -19
- package/schemas/config-v1.json +131 -1
- package/schemas/doctor-report-v1.json +11 -0
- package/skills/better-supabase/SKILL.md +9 -6
- package/skills/better-supabase/references/plugins.md +6 -3
- package/skills/better-supabase/references/troubleshooting.md +1 -0
- package/skills/better-supabase-api/SKILL.md +9 -2
- package/skills/better-supabase-api/references/adapters.md +11 -8
- package/skills/better-supabase-auth/SKILL.md +18 -7
- package/skills/better-supabase-auth/references/permdock.md +78 -11
- package/dist/cli/gen-D5cLCULx.js +0 -2
- package/dist/cli/sql-D4VbqmXi.js +0 -199
- package/dist/events-B21UOhvP.d.ts +0 -84
- package/dist/executor-B5IoUxLE.d.ts +0 -195
- package/dist/invalidate-CgxmRamI.js +0 -24
- package/dist/permdock-sql-DImJULY-.d.ts +0 -17
- package/dist/plugin-BqR6wKMB.js +0 -23
- package/dist/respond-DgIbdrt8.d.ts +0 -194
- package/dist/view-BrJI2LKp.js +0 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-supabase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Strongly typed Supabase: generated schema, typed repositories, plugins, auth glue, framework integrations and the better-supabase CLI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -106,6 +106,14 @@
|
|
|
106
106
|
"types": "./dist/jobs/index.d.ts",
|
|
107
107
|
"default": "./dist/jobs/index.js"
|
|
108
108
|
},
|
|
109
|
+
"./orgs": {
|
|
110
|
+
"types": "./dist/orgs/index.d.ts",
|
|
111
|
+
"default": "./dist/orgs/index.js"
|
|
112
|
+
},
|
|
113
|
+
"./notifications": {
|
|
114
|
+
"types": "./dist/notifications/index.d.ts",
|
|
115
|
+
"default": "./dist/notifications/index.js"
|
|
116
|
+
},
|
|
109
117
|
"./env": {
|
|
110
118
|
"types": "./dist/env/index.d.ts",
|
|
111
119
|
"default": "./dist/env/index.js"
|
|
@@ -182,12 +190,12 @@
|
|
|
182
190
|
"provenance": true
|
|
183
191
|
},
|
|
184
192
|
"dependencies": {
|
|
185
|
-
"@standard-schema/spec": "1.1.0",
|
|
186
|
-
"@supabase/middleware": "1.0.0",
|
|
187
|
-
"@supabase/postgrest-js": "2.
|
|
188
|
-
"@supabase/postgrest-typegen": "0.
|
|
189
|
-
"@supabase/server": "1.9.0",
|
|
190
|
-
"@supabase/ssr": "0.12.7"
|
|
193
|
+
"@standard-schema/spec": "^1.1.0",
|
|
194
|
+
"@supabase/middleware": "^1.0.0",
|
|
195
|
+
"@supabase/postgrest-js": "^2.116.0",
|
|
196
|
+
"@supabase/postgrest-typegen": "0.4.0",
|
|
197
|
+
"@supabase/server": "^1.9.0",
|
|
198
|
+
"@supabase/ssr": "^0.12.7"
|
|
191
199
|
},
|
|
192
200
|
"devDependencies": {
|
|
193
201
|
"@arethetypeswrong/cli": "0.18.5",
|
|
@@ -196,13 +204,13 @@
|
|
|
196
204
|
"@clack/prompts": "1.8.1",
|
|
197
205
|
"@effect/platform-node": "4.0.0",
|
|
198
206
|
"@opentelemetry/api": "1.9.1",
|
|
199
|
-
"@orpc/server": "2.0.0-beta.
|
|
207
|
+
"@orpc/server": "2.0.0-beta.41",
|
|
200
208
|
"@supabase/config": "0.11.1",
|
|
201
209
|
"@supabase/supabase-js": "2.117.2",
|
|
202
210
|
"@t3-oss/env-core": "0.13.11",
|
|
203
|
-
"@tanstack/query-core": "5.104.
|
|
204
|
-
"@tanstack/react-query": "5.104.
|
|
205
|
-
"@types/node": "26.6.
|
|
211
|
+
"@tanstack/query-core": "5.104.1",
|
|
212
|
+
"@tanstack/react-query": "5.104.1",
|
|
213
|
+
"@types/node": "26.6.4",
|
|
206
214
|
"@types/pg": "8.23.1",
|
|
207
215
|
"@types/react": "19.3.0",
|
|
208
216
|
"@types/react-dom": "19.3.0",
|
|
@@ -220,7 +228,7 @@
|
|
|
220
228
|
"oxfmt": "0.71.0",
|
|
221
229
|
"oxlint": "1.86.0",
|
|
222
230
|
"pg": "8.23.1",
|
|
223
|
-
"publint": "0.3.
|
|
231
|
+
"publint": "0.3.25",
|
|
224
232
|
"react": "19.3.0",
|
|
225
233
|
"react-dom": "19.3.0",
|
|
226
234
|
"smol-toml": "1.9.0",
|
|
@@ -233,15 +241,15 @@
|
|
|
233
241
|
"zod": "4.6.5"
|
|
234
242
|
},
|
|
235
243
|
"peerDependencies": {
|
|
236
|
-
"@opentelemetry/api": ">=1.9",
|
|
237
|
-
"@orpc/server": ">=2.0.0-
|
|
244
|
+
"@opentelemetry/api": ">=1.9 <2",
|
|
245
|
+
"@orpc/server": ">=2.0.0-beta.40 <3",
|
|
238
246
|
"@supabase/config": ">=0.9.0 <0.12",
|
|
239
247
|
"@supabase/supabase-js": "^2.116.0",
|
|
240
|
-
"@tanstack/query-core": "^5",
|
|
241
|
-
"hono": ">=4",
|
|
242
|
-
"next": ">=16",
|
|
243
|
-
"oxfmt": ">=0.66",
|
|
244
|
-
"pg": ">=8",
|
|
248
|
+
"@tanstack/query-core": "^5.62.0",
|
|
249
|
+
"hono": ">=4 <5",
|
|
250
|
+
"next": ">=16 <17",
|
|
251
|
+
"oxfmt": ">=0.66.0 <1",
|
|
252
|
+
"pg": ">=8.15 <9",
|
|
245
253
|
"react": "^19",
|
|
246
254
|
"temporal-polyfill": ">=1"
|
|
247
255
|
},
|
package/schemas/config-v1.json
CHANGED
|
@@ -292,7 +292,10 @@
|
|
|
292
292
|
"type": "object",
|
|
293
293
|
"additionalProperties": false,
|
|
294
294
|
"properties": {
|
|
295
|
-
"scope": {
|
|
295
|
+
"scope": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"description": "The PermDock scope tenants are. Defaults to the manifest's root scope, the rls.scopes entry without within."
|
|
298
|
+
}
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
301
|
]
|
|
@@ -346,6 +349,11 @@
|
|
|
346
349
|
"description": "Tables that broadcast change signals (`table` or `schema.table`). Install with `better-supabase sql add realtime-tables`.",
|
|
347
350
|
"type": "array",
|
|
348
351
|
"items": { "type": "string" }
|
|
352
|
+
},
|
|
353
|
+
"global": {
|
|
354
|
+
"description": "Tables in `tables` without a tenant column, which broadcast on one topic for every signed-in user.",
|
|
355
|
+
"type": "array",
|
|
356
|
+
"items": { "type": "string" }
|
|
349
357
|
}
|
|
350
358
|
}
|
|
351
359
|
},
|
|
@@ -359,6 +367,14 @@
|
|
|
359
367
|
"kit": { "type": "array", "items": { "type": "string" } }
|
|
360
368
|
}
|
|
361
369
|
},
|
|
370
|
+
"kits": {
|
|
371
|
+
"type": "object",
|
|
372
|
+
"description": "How each SQL kit module maps onto the database, keyed by module name.",
|
|
373
|
+
"properties": {
|
|
374
|
+
"access": { "$ref": "#/$defs/accessKit" }
|
|
375
|
+
},
|
|
376
|
+
"additionalProperties": { "$ref": "#/$defs/kitModule" }
|
|
377
|
+
},
|
|
362
378
|
"seed": {
|
|
363
379
|
"type": "object",
|
|
364
380
|
"additionalProperties": false,
|
|
@@ -394,6 +410,120 @@
|
|
|
394
410
|
}
|
|
395
411
|
},
|
|
396
412
|
"$defs": {
|
|
413
|
+
"stringRecord": {
|
|
414
|
+
"type": "object",
|
|
415
|
+
"additionalProperties": { "type": "string" }
|
|
416
|
+
},
|
|
417
|
+
"kitModuleProperties": {
|
|
418
|
+
"mode": { "enum": ["managed", "adopt", "custom"], "default": "managed" },
|
|
419
|
+
"schema": { "type": "string", "default": "better_supabase" },
|
|
420
|
+
"tables": {
|
|
421
|
+
"type": "object",
|
|
422
|
+
"additionalProperties": { "type": ["string", "null"] }
|
|
423
|
+
},
|
|
424
|
+
"columns": {
|
|
425
|
+
"type": "object",
|
|
426
|
+
"additionalProperties": {
|
|
427
|
+
"type": "object",
|
|
428
|
+
"additionalProperties": { "type": ["string", "null"] }
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
"idType": { "type": "string" },
|
|
432
|
+
"triggerPrefix": { "type": "string", "default": "bs_" },
|
|
433
|
+
"permissions": { "$ref": "#/$defs/stringRecord" },
|
|
434
|
+
"options": { "type": "object" }
|
|
435
|
+
},
|
|
436
|
+
"kitModule": {
|
|
437
|
+
"type": "object",
|
|
438
|
+
"additionalProperties": false,
|
|
439
|
+
"properties": {
|
|
440
|
+
"mode": { "$ref": "#/$defs/kitModuleProperties/mode" },
|
|
441
|
+
"schema": { "$ref": "#/$defs/kitModuleProperties/schema" },
|
|
442
|
+
"tables": { "$ref": "#/$defs/kitModuleProperties/tables" },
|
|
443
|
+
"columns": { "$ref": "#/$defs/kitModuleProperties/columns" },
|
|
444
|
+
"idType": { "$ref": "#/$defs/kitModuleProperties/idType" },
|
|
445
|
+
"triggerPrefix": {
|
|
446
|
+
"$ref": "#/$defs/kitModuleProperties/triggerPrefix"
|
|
447
|
+
},
|
|
448
|
+
"permissions": { "$ref": "#/$defs/kitModuleProperties/permissions" },
|
|
449
|
+
"options": { "$ref": "#/$defs/kitModuleProperties/options" }
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
"accessKit": {
|
|
453
|
+
"type": "object",
|
|
454
|
+
"additionalProperties": false,
|
|
455
|
+
"properties": {
|
|
456
|
+
"mode": { "$ref": "#/$defs/kitModuleProperties/mode" },
|
|
457
|
+
"schema": { "$ref": "#/$defs/kitModuleProperties/schema" },
|
|
458
|
+
"tables": { "$ref": "#/$defs/kitModuleProperties/tables" },
|
|
459
|
+
"columns": { "$ref": "#/$defs/kitModuleProperties/columns" },
|
|
460
|
+
"idType": { "$ref": "#/$defs/kitModuleProperties/idType" },
|
|
461
|
+
"triggerPrefix": {
|
|
462
|
+
"$ref": "#/$defs/kitModuleProperties/triggerPrefix"
|
|
463
|
+
},
|
|
464
|
+
"permissions": { "$ref": "#/$defs/kitModuleProperties/permissions" },
|
|
465
|
+
"options": { "$ref": "#/$defs/kitModuleProperties/options" },
|
|
466
|
+
"model": {
|
|
467
|
+
"enum": ["roles", "catalog", "permdock", "custom"],
|
|
468
|
+
"default": "roles"
|
|
469
|
+
},
|
|
470
|
+
"roles": {
|
|
471
|
+
"type": "object",
|
|
472
|
+
"additionalProperties": {
|
|
473
|
+
"type": "array",
|
|
474
|
+
"items": { "type": "string" }
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
"functions": {
|
|
478
|
+
"type": "object",
|
|
479
|
+
"additionalProperties": false,
|
|
480
|
+
"properties": {
|
|
481
|
+
"can": { "type": "string" },
|
|
482
|
+
"tenantIdsWith": { "type": "string" },
|
|
483
|
+
"isPlatform": { "type": "string" },
|
|
484
|
+
"canUser": { "type": "string" },
|
|
485
|
+
"canAssign": { "type": "string" },
|
|
486
|
+
"permissionClaims": { "type": "string" }
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"platformClaim": {
|
|
490
|
+
"type": "string",
|
|
491
|
+
"default": "platform_permissions"
|
|
492
|
+
},
|
|
493
|
+
"disabled": {
|
|
494
|
+
"type": "object",
|
|
495
|
+
"additionalProperties": false,
|
|
496
|
+
"properties": {
|
|
497
|
+
"tenant": { "type": "string" },
|
|
498
|
+
"tenantKey": { "type": "string" },
|
|
499
|
+
"user": { "type": "string" },
|
|
500
|
+
"userKey": { "type": "string" }
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
"activeTenant": {
|
|
504
|
+
"oneOf": [
|
|
505
|
+
{ "enum": ["claim", "resolver"] },
|
|
506
|
+
{
|
|
507
|
+
"type": "object",
|
|
508
|
+
"additionalProperties": false,
|
|
509
|
+
"required": ["profileColumn"],
|
|
510
|
+
"properties": {
|
|
511
|
+
"profileColumn": { "type": "string" },
|
|
512
|
+
"key": { "type": "string" }
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
"permdock": {
|
|
518
|
+
"type": "object",
|
|
519
|
+
"additionalProperties": false,
|
|
520
|
+
"properties": {
|
|
521
|
+
"schema": { "type": "string" },
|
|
522
|
+
"scope": { "type": "string" }
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
},
|
|
397
527
|
"casing": { "enum": ["snake", "camel"] },
|
|
398
528
|
"privileges": {
|
|
399
529
|
"type": "array",
|
|
@@ -73,6 +73,14 @@
|
|
|
73
73
|
"BS304",
|
|
74
74
|
"BS305",
|
|
75
75
|
"BS306",
|
|
76
|
+
"BS307",
|
|
77
|
+
"BS308",
|
|
78
|
+
"BS309",
|
|
79
|
+
"BS310",
|
|
80
|
+
"BS311",
|
|
81
|
+
"BS312",
|
|
82
|
+
"BS313",
|
|
83
|
+
"BS314",
|
|
76
84
|
"BS401",
|
|
77
85
|
"BS402",
|
|
78
86
|
"BS403",
|
|
@@ -81,6 +89,9 @@
|
|
|
81
89
|
"BS406",
|
|
82
90
|
"BS407",
|
|
83
91
|
"BS408",
|
|
92
|
+
"BS409",
|
|
93
|
+
"BS410",
|
|
94
|
+
"BS411",
|
|
84
95
|
"BS501",
|
|
85
96
|
"BS502"
|
|
86
97
|
]
|
|
@@ -53,16 +53,19 @@ stay hidden.
|
|
|
53
53
|
|
|
54
54
|
## Workflow: after upgrading better-supabase
|
|
55
55
|
|
|
56
|
-
1.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
2. Run `pnpm better-supabase
|
|
56
|
+
1. Run `pnpm better-supabase codemod <version> --dry-run` for each minor
|
|
57
|
+
version you crossed (`codemod` without a name lists them), then without
|
|
58
|
+
`--dry-run`, and fix the lines it lists for review.
|
|
59
|
+
2. Run `pnpm better-supabase sql upgrade`, so the kit modules get the
|
|
60
|
+
release's SQL and any forward steps land in a migration, then write a
|
|
61
|
+
migration from the changed files.
|
|
62
|
+
3. Run `pnpm better-supabase gen` and commit the result. Since 0.3, a
|
|
60
63
|
composite foreign key such as `(customer_id, organization_id)` is named
|
|
61
64
|
after the remaining column (`customer`); set `tables.<name>.relations` in
|
|
62
65
|
the config to keep an old relation name.
|
|
63
|
-
|
|
66
|
+
4. Run `pnpm better-supabase skills install --check` and reinstall the skills
|
|
64
67
|
when it reports them stale.
|
|
65
|
-
|
|
68
|
+
5. Read the release's breaking changes in
|
|
66
69
|
https://github.com/ScaleDockHQ/better-supabase/blob/main/CHANGELOG.md
|
|
67
70
|
(guides live under https://bettersupabase.com/docs/migration). For 0.3: time values are
|
|
68
71
|
`Temporal` (see below), `--db-url` is gone, `doctor --format json` is
|
|
@@ -53,9 +53,12 @@ filled it.
|
|
|
53
53
|
RLS still decides what a user can read and write. `tenant()` makes queries
|
|
54
54
|
explicit and fills the column; it doesn't replace the policy.
|
|
55
55
|
|
|
56
|
-
The tenant comes from the
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
The tenant comes from `context.tenant`, which the server's `tenant` resolver
|
|
57
|
+
sets per request (the default `kits.access.activeTenant: 'resolver'`), then
|
|
58
|
+
the verified `tenant_id` claim (renamed with `claims.tenant` in
|
|
59
|
+
`better-supabase.config.ts`), then `app_metadata.tenant_id`. A resolver may
|
|
60
|
+
read the URL, but `current_tenant_id()` only returns a tenant the caller is a
|
|
61
|
+
member of. Never trust a tenant from `user_metadata` or a request body. When the project
|
|
59
62
|
has a `permdock.config.ts`, PermDock's hook writes that claim and the
|
|
60
63
|
memberships: don't run `sql add tenant`, and follow the PermDock reference of
|
|
61
64
|
the `better-supabase-auth` skill
|
|
@@ -16,6 +16,7 @@ Branch on `result.error.kind`, not on the message.
|
|
|
16
16
|
| `foreign_key` | Referenced row missing, or still referenced on delete | Create the parent first, or delete children |
|
|
17
17
|
| `not_null`, `check`, `exclusion` | A constraint rejected the row | Validate input earlier with the `validation()` plugin |
|
|
18
18
|
| `invalid_input` | Postgres couldn't parse a value (bad uuid, enum) | Validate before the call |
|
|
19
|
+
| `invalid_value` | A stored value the app type can't hold (`infinity` in a Temporal timestamp) | Store a real timestamp or `null`, or read the column without the codec |
|
|
19
20
|
| `validation` | A Standard Schema rejected the input; see `issues` | Show the issues to the user |
|
|
20
21
|
| `invalid_request` | The query can't run over PostgREST (for example a relation filter on `update`) | Read the keys first, or use a database function or `better-supabase/postgres` |
|
|
21
22
|
| `multiple_rows` | `findUnique` or a single-row write matched more than one row | Filter by a unique key |
|
|
@@ -88,11 +88,18 @@ for lists that clients read page by page: the list takes `after` instead of
|
|
|
88
88
|
|
|
89
89
|
## Background work (`better-supabase/jobs`, needs `sql add jobs idempotency webhook-inbox`)
|
|
90
90
|
|
|
91
|
-
- `createJobs(postgres.admin, { queue_name: zodSchema })` runs on Supabase Queues (pgmq): `enqueue` in the request, then `work` or `drain` in a worker. The handler throws to retry. `schedule(name, cron, queue, payload)` uses pg_cron. Queue names are lowercase letters, digits and underscores. With a service-role Supabase client instead of SQL, it uses the `pgmq_public` RPCs (no dedupe or schedules).
|
|
91
|
+
- `createJobs(postgres.admin, { queue_name: zodSchema })` runs on Supabase Queues (pgmq): `enqueue` in the request, then `work` or `drain` in a worker. The handler throws to retry. `schedule(name, cron, queue, payload, { timeZone })` uses pg_cron, or with `kits.jobs.options.scheduler: "drain"` a `jobs.drainRoute({ secret: process.env.CRON_SECRET, handlers })` route that Vercel Cron calls; `kits.jobs.options.backend: "table"` runs without pgmq. Queue names are lowercase letters, digits and underscores. With a service-role Supabase client instead of SQL, it uses the `pgmq_public` RPCs (no dedupe or schedules).
|
|
92
92
|
- `createIdempotency(postgres.admin).handle(request, handler)` for POST endpoints that clients retry.
|
|
93
93
|
- `createInbox(postgres.admin, { source, secrets }).receive(request)` for webhooks; `process(handler)` later.
|
|
94
94
|
- Times are `Temporal.Instant`: `EnqueueOptions.runAt`, `Job.enqueuedAt`, `Job.visibleUntil`, `InboxMessage.receivedAt` and webhook timestamps.
|
|
95
|
-
- Schedule cleanup with pg_cron at a quiet hour: `better_supabase.purge_job_archive('<queue>')`, `purge_webhooks()`, `purge_audit_log()` and `
|
|
95
|
+
- Schedule cleanup with pg_cron at a quiet hour: `better_supabase.purge_job_archive('<queue>')`, `purge_webhooks()`, `purge_audit_log()`, `purge_idempotency_keys()`, `purge_rate_limits()`, `purge_outbox()`, `purge_notifications()` and `purge_webhook_deliveries()`, for the modules you installed. Only `service_role` can execute them.
|
|
96
|
+
- Audit a table with `better_supabase.audit('public.t', redact => '{secret}', event_prefix => 't')`; record non-row events with `better_supabase.audit_event(event_type, ...)` and an `idempotency_key`. For per-tenant retention, write an `audit_retention(tenant)` SQL function or call `purgeAuditLog(sql, { retention })` from `better-supabase/jobs`.
|
|
97
|
+
- For a "view as user" support mode, add the `support-sessions` SQL module, pass `support: supportSessions({ store: sqlSupportStore(postgres) })` to `createServer`, call `bs.startSupport({ targetUserId, reason })` and `bs.stopSupport()` from server actions, and show a banner with `useSupportSession()`. Sessions are read-only by default; grant admins the `support.start` platform permission.
|
|
98
|
+
- For teams, add the `organizations` and `invitations` SQL modules and call them through `createOrgs({ transport: sqlTransport(postgres.asUser(claims)), events: betterSupabase.events, onInvite })` from `better-supabase/orgs`. Send the invitation email in `onInvite`, show `previewInvitation(token)` on the accept page, and switch with `orgs.switch(id)`, refreshing the session when it returns `refresh: true`. Errors carry codes such as `ORG_SLUG_TAKEN` in `hint`. Existing tables work with `mode: 'adopt'`.
|
|
99
|
+
- For user profiles, add the `profiles` SQL module: sign-up creates the row from auth metadata with a unique username and mirrors the email; users update only the `updatable` columns. Adopt an existing table with `mode: 'adopt'` and `syncTrigger: false` when your own trigger creates profiles. Use `avatarBucket()` and `orgLogoBucket()` from `better-supabase/storage` for uploads; `policy: { access: { read, write } }` checks any bucket through the access contract.
|
|
100
|
+
- For events other systems must see, add the `outbox` SQL module and write events with `emit_event(type, payload, subject, tenant, key)` inside the same transaction as the change (the kit modules emit theirs automatically). Relay them with `createOutbox(postgres.admin, { source }).relayRoute({ secret, consumers: { name: httpSink(url) } })` from `better-supabase/jobs` after `outbox.register(name, { types })`. Delivery is at least once; receivers deduplicate on the CloudEvent `id`.
|
|
101
|
+
- For in-app notifications, add the `notifications` SQL module and send with `createNotifications({ transport: sqlTransport(postgres.asUser(claims)), types })` from `better-supabase/notifications`, where `types` maps each notification type to a Standard Schema for its data. Pass a `key` so retries don't notify twice. Show them with `useNotifications({ topic, load })` from `better-supabase/react`; the topic defaults to `notifications:{userId}`. For email or push, pass `channels` and call `notifications.deliver()` from a cron route with a service connection.
|
|
102
|
+
- For webhooks to customers' endpoints, add the `webhooks-out` SQL module and publish with `createWebhooks({ transport: sqlTransport(postgres.admin) }).publish({ type, data, tenant, id })` from `better-supabase/webhooks`. Pass an `id` so retries don't send twice. Send with `webhooks.deliverRoute({ secret: process.env.CRON_SECRET })` from a cron route; deliveries are signed with Standard Webhooks and only go to public HTTPS URLs by default.
|
|
96
103
|
|
|
97
104
|
Done when a failing job is retried and then archived after `maxAttempts`,
|
|
98
105
|
a replayed webhook or POST doesn't run twice, and each kit table has a purge
|
|
@@ -15,6 +15,7 @@ export const betterSupabase = defineSupabase(schema);
|
|
|
15
15
|
## Next.js
|
|
16
16
|
|
|
17
17
|
```ts title="src/lib/supabase/server.ts"
|
|
18
|
+
import "server-only";
|
|
18
19
|
import { createNext } from "better-supabase/next";
|
|
19
20
|
|
|
20
21
|
import { betterSupabase } from "./index";
|
|
@@ -44,15 +45,15 @@ export const GET = bs.route(
|
|
|
44
45
|
## Hono
|
|
45
46
|
|
|
46
47
|
```ts title="src/server.ts"
|
|
47
|
-
import {
|
|
48
|
-
import { Hono } from "hono";
|
|
48
|
+
import { createHono } from "better-supabase/hono";
|
|
49
49
|
|
|
50
|
-
import {
|
|
50
|
+
import { betterSupabase } from "./lib/supabase";
|
|
51
51
|
|
|
52
52
|
const bs = createHono(betterSupabase);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// bs.app() is new Hono<typeof bs.Env>() with bs.onError installed.
|
|
55
|
+
const app = bs
|
|
56
|
+
.app()
|
|
56
57
|
.use("/api/*", bs.middleware())
|
|
57
58
|
.get("/api/me", (c) => c.json({ kind: c.var.auth.kind }))
|
|
58
59
|
.route(
|
|
@@ -106,7 +107,7 @@ import { betterSupabase } from "../_shared/supabase.ts";
|
|
|
106
107
|
const bs = createMcp(betterSupabase, {
|
|
107
108
|
name: "crm",
|
|
108
109
|
version: "0.1.0",
|
|
109
|
-
|
|
110
|
+
advertisedScopes: ["openid", "crm.read"],
|
|
110
111
|
allowedOrigins: ["https://claude.ai"],
|
|
111
112
|
allowedHosts: ["crm.example.com", "localhost"],
|
|
112
113
|
resourceDocumentation: "https://crm.example.com/docs/mcp",
|
|
@@ -126,6 +127,8 @@ const bs = createMcp(betterSupabase, {
|
|
|
126
127
|
Deno.serve(bs.fetch);
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
Tools run as the calling user, so RLS applies to every tool call.
|
|
130
|
-
|
|
130
|
+
Tools run as the calling user, so RLS applies to every tool call.
|
|
131
|
+
`advertisedScopes` only advertises scopes; `requiredScopes` refuses delegated
|
|
132
|
+
tokens that lack one, and `authorize` refuses a single call. The session comes
|
|
133
|
+
from the `Authorization` header only, never from a cookie. A
|
|
131
134
|
`delegation` that is unset means the user's own token, which no scope limits.
|
|
@@ -61,16 +61,22 @@ A token from the Supabase OAuth server carries `client_id` and `scope`; a
|
|
|
61
61
|
token exchanged for an agent carries an RFC 8693 `act` chain. The session
|
|
62
62
|
names who acts for the user:
|
|
63
63
|
|
|
64
|
-
| Token
|
|
65
|
-
|
|
|
66
|
-
| The user's own
|
|
67
|
-
| `client_id: "c1"`, `scope: "openid crm.read"`
|
|
68
|
-
| `act: { sub: "agent", act: { sub: "mcp-42" } }`
|
|
64
|
+
| Token | `session.actor` | `session.delegation` |
|
|
65
|
+
| ------------------------------------------------ | ---------------------------------------------- | -------------------- |
|
|
66
|
+
| The user's own | not set | not set |
|
|
67
|
+
| `client_id: "c1"`, `scope: "openid crm.read"` | `{ id: "c1", kind: "oauth-client" }` | `{ scopes: [...] }` |
|
|
68
|
+
| `act: { sub: "agent", act: { sub: "mcp-42" } }` | `{ id: "agent", chain, ... }` | `{ scopes, chain }` |
|
|
69
|
+
| `act: { kind: "support", sub, session_id, ... }` | `{ kind: "support", id, sessionId, readOnly }` | not set |
|
|
70
|
+
| `act: { kind: "impersonation", sub, reason }` | `{ kind: "impersonation", id, reason }` | not set |
|
|
69
71
|
|
|
70
72
|
- Limit what delegated tokens may do with the `scopes` guard option on
|
|
71
73
|
`bs.route`, `bs.action`, `bs.handler` and `bs.middleware`. A missing
|
|
72
|
-
scope answers 403 with an `insufficient_scope` challenge.
|
|
73
|
-
|
|
74
|
+
scope answers 403 with an `insufficient_scope` challenge. Only an
|
|
75
|
+
`oauth-client` actor is limited: the user's own token, a support session
|
|
76
|
+
and an impersonated session are not.
|
|
77
|
+
- Switch on `session.actor.kind` with a `never` default: it is
|
|
78
|
+
`oauth-client`, `support` or `impersonation`. An `act` with another `kind`
|
|
79
|
+
resolves to `invalid` with reason `actor`.
|
|
74
80
|
- In MCP servers, refuse calls in `authorize` by reading
|
|
75
81
|
`toSession(ctx.auth).delegation?.scopes` (the `better-supabase-api` skill).
|
|
76
82
|
- RLS still decides the rows: `sub` is the user, so a client sees at most
|
|
@@ -106,6 +112,9 @@ membership lasts until the next refresh. When that window matters:
|
|
|
106
112
|
- Write policies that read the source of truth, for example
|
|
107
113
|
`organization_id in (select better_supabase.member_org_ids())`, instead of
|
|
108
114
|
the claim.
|
|
115
|
+
- For signed-out, banned or deleted users, add the `sessions` SQL module and a
|
|
116
|
+
restrictive policy with `using ((select better_supabase.session_active()))`
|
|
117
|
+
on the tables that guard sensitive data.
|
|
109
118
|
- After a change the user made themselves (joining an organization), call
|
|
110
119
|
`supabase.auth.refreshSession()` in the browser.
|
|
111
120
|
- In Next.js, drop the user's cached views with
|
|
@@ -136,6 +145,8 @@ claims, tenant or entitlements.
|
|
|
136
145
|
- Don't downgrade an `invalid` caller to `anon`.
|
|
137
146
|
- Don't build 401 or 403 JSON by hand; return the `DbError` or use `allow`
|
|
138
147
|
and `scopes`.
|
|
148
|
+
- Don't use `allow: ['anon']` for users from `signInAnonymously()`: `'anon'`
|
|
149
|
+
means no session, and only `'anonymous'` admits anonymous sign-ins.
|
|
139
150
|
|
|
140
151
|
## Docs
|
|
141
152
|
|
|
@@ -26,30 +26,97 @@ the other.
|
|
|
26
26
|
`hasEntitlement(session, ...)` works.
|
|
27
27
|
- Run `permdock supabase inspect --out` before `sql add entitlements`. With
|
|
28
28
|
`permdock.manifest.json` present, the module reads PermDock's
|
|
29
|
-
`member_<scope>_ids` helpers and doesn't add `tenant`.
|
|
30
|
-
|
|
31
|
-
scope
|
|
29
|
+
`member_<scope>_ids` helpers and doesn't add `tenant`. The scope is the
|
|
30
|
+
manifest's root scope (the `rls.scopes` entry without `within`); set
|
|
31
|
+
`entitlements.permdock: { scope }` in `better-supabase.config.ts` for
|
|
32
|
+
another one.
|
|
32
33
|
- Name the same tenant claim on both sides. A non-default `claims.tenant` in
|
|
33
34
|
`better-supabase.config.ts` must also be PermDock's `rls.tenantClaim` and
|
|
34
35
|
go to `subjectFromSupabase` or `subjectFromSupabaseSession` as `{ tenant }`.
|
|
35
36
|
- Validate claims with PermDock's schema: `betterSupabase.claims(supabaseClaims().extend(appClaims))`.
|
|
36
|
-
- The helpers live in PermDock's `rls.schema
|
|
37
|
-
|
|
37
|
+
- The helpers live in PermDock's `rls.schema` (`permdock` by default). Buckets
|
|
38
|
+
and topics in `permdock` mode call them in `permdock` unless their `schema`
|
|
39
|
+
option names another schema; it must equal `rls.schema`.
|
|
40
|
+
- Use the scope names the manifest declares (`organization`, not PermDock's
|
|
41
|
+
`tenant` alias): the helpers are named after them, and doctor reports a
|
|
42
|
+
key checked at another scope than its catalog entry.
|
|
43
|
+
- The entitlements module needs PermDock's hook to fill `claims.features`
|
|
44
|
+
from `better_supabase.feature_claims`
|
|
45
|
+
(`supabase.hook.claims: { features: 'better_supabase.feature_claims' }`).
|
|
38
46
|
- The `permdock` policy mode checks role and scope only. Use it only for
|
|
39
47
|
permissions whose `rowConditions` is `false` in `permissions.catalog.json`.
|
|
48
|
+
A missing flag or a key the catalog doesn't list is unknown and refused;
|
|
49
|
+
regenerate the catalog with a current `permdock catalog`.
|
|
40
50
|
Leave permissions with row conditions to the policies
|
|
41
51
|
`permdock rls generate` writes; doctor reports BS214 and `permdock doctor`
|
|
42
52
|
PD037 otherwise.
|
|
43
53
|
- `permdock` mode compares ids as text, so a path or topic segment must be
|
|
44
54
|
the id's lowercase form.
|
|
45
55
|
- In MCP servers, narrow `tool.meta` with PermDock's `isPermission`, answer
|
|
46
|
-
`visible` with `mayUse` and `authorize` with `can`.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
`visible` with `mayUse` and `authorize` with `can`. A tool without a
|
|
57
|
+
PermDock permission in `meta` is denied: `visible` returns `false` and
|
|
58
|
+
`authorize` returns `{ allowed: false }`. Use `decide` instead of `can` to
|
|
59
|
+
put the denial reason in the refusal. `can` decides without a row, so check
|
|
60
|
+
row-conditioned permissions inside `run` or rely on RLS.
|
|
61
|
+
- In oRPC and Hono, pass the adapter's auth state to PermDock:
|
|
62
|
+
`createPermDock(policy, { subject: ({ context }) => subjectFromSupabaseSession(context.auth) })`
|
|
63
|
+
from `permdock/orpc`, or `(c) => subjectFromSupabaseSession(c.get("auth"))`
|
|
64
|
+
from `permdock/hono`. Fail closed: export one builder that takes a
|
|
65
|
+
permission (`(permission) => authed.use(pd.protect(permission))`, or the
|
|
66
|
+
route helper `[bs.middleware(), pd.protect(permission)]` in Hono), and don't
|
|
67
|
+
install `bs.middleware()` app-wide, so a route without a permission has no
|
|
68
|
+
`db`.
|
|
69
|
+
- In Next.js, cache the permission snapshot in a `'use cache: private'`
|
|
70
|
+
loader. The user id and the snapshot only exist after `bs.cached()`
|
|
71
|
+
returns, so pass it the static tags only, then tag and time the entry from
|
|
72
|
+
inside the loader with Next.js's `cacheTag` and `cacheLife`:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { cacheLife, cacheTag } from "next/cache";
|
|
76
|
+
import { snapshotFor } from "permdock";
|
|
77
|
+
import { cacheLifeFor, snapshotTag } from "permdock/next";
|
|
78
|
+
import { subjectFromSupabaseSession } from "permdock/supabase";
|
|
79
|
+
|
|
80
|
+
export async function loadSnapshot(orgId: string) {
|
|
81
|
+
"use cache: private";
|
|
82
|
+
const { session } = await bs.cached({ tags: [`org:${orgId}`] });
|
|
83
|
+
const snapshot = snapshotFor(policy, subjectFromSupabaseSession(session), {
|
|
84
|
+
tenant: orgId,
|
|
85
|
+
});
|
|
86
|
+
if (session.kind === "user") cacheTag(snapshotTag(session.user.id));
|
|
87
|
+
cacheLife(cacheLifeFor(snapshot));
|
|
88
|
+
return snapshot;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`bs.cached()` already caps the stale time at the token's expiry, and Next.js
|
|
93
|
+
keeps the smaller `stale` of the two `cacheLife` calls. When a role or plan
|
|
94
|
+
changes, drop the user's entries and the snapshot with
|
|
95
|
+
`bs.invalidateSession(userId, { tags: [snapshotTag(userId)] })`.
|
|
96
|
+
|
|
52
97
|
- Doctor BS405 measures `memberships` plus `attrs` against PermDock's 1 KB
|
|
53
98
|
budget and the whole token against 2 KB; run `doctor --as <user id>`.
|
|
99
|
+
- For the SQL kit, set `kits.access.model: 'permdock'` and
|
|
100
|
+
`kits.access.functions.canAssign: 'permdock.permdock_can_assign({role}, {tenant}::text)'`
|
|
101
|
+
(use the manifest's `rls.schema`), then run `permdock supabase inspect --out`
|
|
102
|
+
before `sql add`. The kit reads the schema, the root scope and its id type
|
|
103
|
+
from `permdock.manifest.json`, and `sql add` stops instead of guessing when
|
|
104
|
+
the manifest can't give them. Every permission key the kit modules check
|
|
105
|
+
(`kitPermissionKeys` from `better-supabase/sql`) must be
|
|
106
|
+
`rowConditions: false` in `permissions.catalog.json`; map others with
|
|
107
|
+
`kits.<module>.permissions`. Doctor reports BS411.
|
|
108
|
+
- Under that model `can_user()` and `member_can()` answer for the caller only
|
|
109
|
+
and raise SQLSTATE `0A000` for another user. Invitations skip the inviter
|
|
110
|
+
re-check at accept time and notifications don't filter recipients by read
|
|
111
|
+
permission; check both in the app when they matter.
|
|
112
|
+
- The `act` claim says who acts: `kind: "support"` (support sessions,
|
|
113
|
+
`session.actor.kind === 'support'` with `sessionId` and `readOnly`),
|
|
114
|
+
`kind: "impersonation"` (`actingAs`), or no `kind` for an OAuth client or
|
|
115
|
+
agent chain (`oauth-client`, limited by `scopes`). Another `kind` makes the
|
|
116
|
+
session invalid. `session.impersonator` is set for the first two only.
|
|
117
|
+
`supabaseClaimFixtures` in `better-supabase/testing` has one token of each.
|
|
118
|
+
- Only `allow: ['anonymous']` admits `signInAnonymously()` users; `'anon'`
|
|
119
|
+
means no session. Pair the default `allow` with PermDock's
|
|
120
|
+
`rls: { anonymousSignIns: 'deny' }` in `permdock.config.ts`.
|
|
54
121
|
|
|
55
122
|
Docs: https://bettersupabase.com/docs/auth/permdock.md
|
package/dist/cli/gen-D5cLCULx.js
DELETED