better-supabase 0.2.0 → 0.3.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.
- package/README.md +34 -31
- package/dist/base64-CQeB1eSf.js +42 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +4 -14
- package/dist/{compiler-CZ9nIuqy.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -3
- package/dist/config/index.js +5 -3
- package/dist/config-Be4GwsqI.js +129 -0
- package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
- package/dist/{config-ClDXmVtY.js → config-DOsGYSiT.js} +14 -137
- package/dist/{define-BXMYD9AY.js → define-DF341GDj.js} +71 -51
- package/dist/{define-e3zI4_4g.d.ts → define-DUbydmJa.d.ts} +10 -9
- package/dist/edge/index.d.ts +4 -4
- package/dist/edge/index.js +4 -4
- package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-DvJXoI02.d.ts} +1 -1
- package/dist/env/index.js +203 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +139 -1
- package/dist/{events-DOt3nSO-.d.ts → events-Do8jXh4D.d.ts} +3 -3
- package/dist/{executor-BYXQ9AGn.d.ts → executor-B26lgbHD.d.ts} +2 -2
- package/dist/{executor-DeppvirK.js → executor-C57jx2pn.js} +7 -2
- package/dist/{executor-CGdNQ27g.d.ts → executor-DynmIWhq.d.ts} +2 -2
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +4 -4
- package/dist/{index-CBDufH1M.d.ts → index-BNsABq7P.d.ts} +8 -8
- package/dist/{index-Bw1bwtC0.d.ts → index-CADlNHK-.d.ts} +27 -14
- package/dist/{index-DuP14VHo.d.ts → index-CTTMS6_1.d.ts} +19 -8
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-K-3WM2Hp.d.ts → index-CriFFJ_p.d.ts} +4 -4
- package/dist/{index-Ciu5Xlml.d.ts → index-GV77NpRP.d.ts} +5 -4
- package/dist/{index-D4v7sncu.d.ts → index-UJ-LUxtI.d.ts} +3 -3
- package/dist/{index-CG69s15v.d.ts → index-kpgopLNv.d.ts} +4 -3
- package/dist/{index-DbxFr7XG.d.ts → index-qriRM9cZ.d.ts} +7 -2
- package/dist/index.d.ts +12 -12
- package/dist/index.js +16 -7
- package/dist/jobs/index.d.ts +25 -24
- package/dist/jobs/index.js +16 -10
- package/dist/{json-schema-BpYqkCKf.js → json-schema--aOXAGfU.js} +7 -2
- package/dist/lint/index.js +1 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +128 -93
- package/dist/{live-DJyZMD6w.d.ts → live-QE3UPbGm.d.ts} +16 -6
- package/dist/mcp/index.d.ts +24 -17
- package/dist/mcp/index.js +41 -12
- package/dist/{mfa-CpLfJSde.js → mfa-BxoR1RkN.js} +74 -19
- package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
- package/dist/next/index.d.ts +39 -26
- package/dist/next/index.js +28 -23
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +57 -34
- package/dist/orpc/index.d.ts +6 -6
- package/dist/orpc/index.js +3 -3
- package/dist/otel/index.d.ts +2 -2
- package/dist/otel/index.js +4 -2
- package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-Bta_G0v-.js} +11 -6
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-C4TR9J0Z.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +4 -4
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +2 -2
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +1 -1
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/pool-B9qsLyFR.d.ts +71 -0
- package/dist/postgres/index.d.ts +10 -5
- package/dist/postgres/index.js +36 -16
- package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
- package/dist/{problem-BTkTtRV9.js → problem-CGt1ADix.js} +10 -4
- package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +1 -1
- package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +2 -2
- package/dist/react/server.d.ts +3 -3
- package/dist/react/server.js +1 -1
- package/dist/react/session.d.ts +21 -2
- package/dist/react/session.js +19 -1
- package/dist/{react-C83BdBGt.js → react-DNBYNazu.js} +20 -16
- package/dist/{read-set-4lI3W_gZ.js → read-set-D0qBcboM.js} +3 -3
- package/dist/realtime/index.d.ts +4 -3
- package/dist/realtime/index.js +7 -6
- package/dist/{resolve-CjHILk2w.d.ts → resolve-BRciX2jK.d.ts} +6 -5
- package/dist/{resource-CgZa3YG_.d.ts → resource-CwVgiI5q.d.ts} +16 -7
- package/dist/{resource-BHEWmQJK.js → resource-D30rp0GN.js} +38 -16
- package/dist/{respond-DMcbQwfN.js → respond-C44EZAJo.js} +35 -19
- package/dist/{respond-Dne1ARSa.d.ts → respond-DAkPIdTH.d.ts} +15 -9
- package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
- package/dist/server/index.d.ts +35 -10
- package/dist/server/index.js +38 -9
- package/dist/{simplify-bwJpLnaS.js → simplify-LakqK1vH.js} +10 -16
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-DTjA1LPj.js → sql-BIgk12ew.js} +72 -16
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +2 -2
- package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
- package/dist/{stats-C5W-rBfR.js → stats-D78UQ8Yu.js} +8 -7
- package/dist/storage/index.d.ts +4 -3
- package/dist/storage/index.js +530 -1
- package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
- package/dist/temporal-BwshQk0W.js +27 -0
- package/dist/temporal-required-CMxeSVPo.js +19 -0
- package/dist/testing/index.d.ts +13 -11
- package/dist/testing/index.js +128 -29
- package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
- package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
- package/dist/{view-D1s0wa0T.d.ts → view-3Po_PVlr.d.ts} +34 -4
- package/dist/{view-aOYizTeE.js → view-wZ-9qIUz.js} +11 -2
- package/dist/webhooks/index.d.ts +18 -16
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DMipjhSP.js → webhooks-CAIFQSTM.js} +11 -9
- package/package.json +24 -28
- package/schemas/config-v1.json +31 -1
- package/schemas/doctor-report-v1.json +17 -1
- package/schemas/snapshot-v2.json +35 -0
- package/skills/better-supabase/SKILL.md +23 -11
- package/skills/better-supabase/references/plugins.md +21 -18
- package/skills/better-supabase/references/troubleshooting.md +15 -15
- package/skills/better-supabase-api/SKILL.md +7 -7
- package/skills/better-supabase-api/references/adapters.md +30 -25
- package/skills/better-supabase-testing/SKILL.md +11 -8
- package/bin/better-supabase.js +0 -4
- package/dist/casing-da0uRTqt.js +0 -14
- package/dist/cli/bin.d.ts +0 -1
- package/dist/cli/bin.js +0 -15
- package/dist/cli/index.d.ts +0 -226
- package/dist/cli/index.js +0 -3
- package/dist/cli-DrSgDOQC.js +0 -6646
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-R2m_DkXi.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3LxnQ0e.d.ts +0 -22
- package/dist/session-CqEwzVup.js +0 -20
- package/dist/storage-DuGn38fr.js +0 -527
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-supabase",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Strongly typed Supabase: generated schema, typed repositories, plugins, auth glue and framework integrations
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Strongly typed Supabase: generated schema, typed repositories, plugins, auth glue and framework integrations. The CLI ships as @better-supabase/cli.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hono",
|
|
7
7
|
"mcp",
|
|
@@ -25,11 +25,7 @@
|
|
|
25
25
|
"url": "git+https://github.com/ScaleDockHQ/better-supabase.git",
|
|
26
26
|
"directory": "packages/better-supabase"
|
|
27
27
|
},
|
|
28
|
-
"bin": {
|
|
29
|
-
"better-supabase": "./bin/better-supabase.js"
|
|
30
|
-
},
|
|
31
28
|
"files": [
|
|
32
|
-
"bin",
|
|
33
29
|
"dist",
|
|
34
30
|
"schemas",
|
|
35
31
|
"skills",
|
|
@@ -160,14 +156,14 @@
|
|
|
160
156
|
"types": "./dist/plugins/validation/index.d.ts",
|
|
161
157
|
"default": "./dist/plugins/validation/index.js"
|
|
162
158
|
},
|
|
159
|
+
"./sql": {
|
|
160
|
+
"types": "./dist/sql/index.d.ts",
|
|
161
|
+
"default": "./dist/sql/index.js"
|
|
162
|
+
},
|
|
163
163
|
"./testing": {
|
|
164
164
|
"types": "./dist/testing/index.d.ts",
|
|
165
165
|
"default": "./dist/testing/index.js"
|
|
166
166
|
},
|
|
167
|
-
"./cli": {
|
|
168
|
-
"types": "./dist/cli/index.d.ts",
|
|
169
|
-
"default": "./dist/cli/index.js"
|
|
170
|
-
},
|
|
171
167
|
"./schemas/*.json": "./schemas/*.json",
|
|
172
168
|
"./package.json": "./package.json"
|
|
173
169
|
},
|
|
@@ -177,19 +173,17 @@
|
|
|
177
173
|
},
|
|
178
174
|
"dependencies": {
|
|
179
175
|
"@standard-schema/spec": "1.1.0",
|
|
180
|
-
"@supabase/middleware": "0.
|
|
181
|
-
"@supabase/postgrest-typegen": "0.
|
|
182
|
-
"@supabase/server": "1.
|
|
176
|
+
"@supabase/middleware": "1.0.0",
|
|
177
|
+
"@supabase/postgrest-typegen": "0.3.1",
|
|
178
|
+
"@supabase/server": "1.9.0",
|
|
183
179
|
"@supabase/ssr": "0.12.7"
|
|
184
180
|
},
|
|
185
181
|
"devDependencies": {
|
|
186
182
|
"@arethetypeswrong/cli": "0.18.5",
|
|
187
183
|
"@better-supabase/ox-config": "0.0.0",
|
|
188
184
|
"@better-supabase/typescript-config": "0.0.0",
|
|
189
|
-
"@effect/platform-node": "4.0.0-rc.117",
|
|
190
185
|
"@opentelemetry/api": "1.9.1",
|
|
191
186
|
"@orpc/server": "2.0.0-beta.40",
|
|
192
|
-
"@supabase/config": "0.10.0",
|
|
193
187
|
"@supabase/supabase-js": "2.117.2",
|
|
194
188
|
"@tanstack/query-core": "5.104.0",
|
|
195
189
|
"@tanstack/react-query": "5.104.0",
|
|
@@ -198,32 +192,33 @@
|
|
|
198
192
|
"@types/react": "19.3.0",
|
|
199
193
|
"@types/react-dom": "19.3.0",
|
|
200
194
|
"@valibot/to-json-schema": "1.8.0",
|
|
201
|
-
"@vitest/coverage-v8": "5.0.
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"
|
|
195
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
196
|
+
"ajv": "8.20.0",
|
|
197
|
+
"ajv-formats": "3.0.1",
|
|
198
|
+
"hono": "4.13.12",
|
|
199
|
+
"next": "16.3.8",
|
|
206
200
|
"oxlint": "1.86.0",
|
|
207
|
-
"pg": "8.23.
|
|
201
|
+
"pg": "8.23.1",
|
|
208
202
|
"publint": "0.3.24",
|
|
209
203
|
"react": "19.3.0",
|
|
210
204
|
"react-dom": "19.3.0",
|
|
205
|
+
"temporal-polyfill": "1.0.5",
|
|
211
206
|
"tsdown": "0.23.0",
|
|
212
207
|
"typescript": "7.0.2",
|
|
213
208
|
"valibot": "1.5.0",
|
|
214
|
-
"vitest": "5.0.
|
|
209
|
+
"vitest": "5.0.3",
|
|
215
210
|
"zod": "4.6.5"
|
|
216
211
|
},
|
|
217
212
|
"peerDependencies": {
|
|
218
213
|
"@opentelemetry/api": ">=1.9",
|
|
219
214
|
"@orpc/server": ">=2.0.0-0",
|
|
220
|
-
"@supabase/config": ">=0.9.0 <0.11",
|
|
221
215
|
"@supabase/supabase-js": "^2.116.0",
|
|
222
216
|
"@tanstack/query-core": "^5",
|
|
223
217
|
"hono": ">=4",
|
|
224
218
|
"next": ">=16",
|
|
225
219
|
"pg": ">=8",
|
|
226
|
-
"react": "^19"
|
|
220
|
+
"react": "^19",
|
|
221
|
+
"temporal-polyfill": ">=1"
|
|
227
222
|
},
|
|
228
223
|
"peerDependenciesMeta": {
|
|
229
224
|
"@opentelemetry/api": {
|
|
@@ -232,9 +227,6 @@
|
|
|
232
227
|
"@orpc/server": {
|
|
233
228
|
"optional": true
|
|
234
229
|
},
|
|
235
|
-
"@supabase/config": {
|
|
236
|
-
"optional": true
|
|
237
|
-
},
|
|
238
230
|
"@tanstack/query-core": {
|
|
239
231
|
"optional": true
|
|
240
232
|
},
|
|
@@ -249,6 +241,9 @@
|
|
|
249
241
|
},
|
|
250
242
|
"react": {
|
|
251
243
|
"optional": true
|
|
244
|
+
},
|
|
245
|
+
"temporal-polyfill": {
|
|
246
|
+
"optional": true
|
|
252
247
|
}
|
|
253
248
|
},
|
|
254
249
|
"engines": {
|
|
@@ -260,6 +255,7 @@
|
|
|
260
255
|
"check:publish": "publint && pnpm pack --out pack.tgz && attw --profile esm-only pack.tgz && rm -f pack.tgz",
|
|
261
256
|
"lint": "oxlint",
|
|
262
257
|
"test": "vitest run --coverage",
|
|
263
|
-
"
|
|
258
|
+
"transit": "true",
|
|
259
|
+
"typecheck": "tsc --noEmit"
|
|
264
260
|
}
|
|
265
261
|
}
|
package/schemas/config-v1.json
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"type": "object",
|
|
68
68
|
"additionalProperties": false,
|
|
69
69
|
"properties": {
|
|
70
|
-
"timestamptz": { "enum": ["string", "
|
|
70
|
+
"timestamptz": { "enum": ["string", "instant"], "default": "string" },
|
|
71
71
|
"int8": { "enum": ["number", "string", "bigint"], "default": "number" },
|
|
72
72
|
"numeric": { "enum": ["number", "string"], "default": "number" }
|
|
73
73
|
}
|
|
@@ -283,6 +283,36 @@
|
|
|
283
283
|
"description": "The tenant id column of that table.",
|
|
284
284
|
"type": "string",
|
|
285
285
|
"default": "id"
|
|
286
|
+
},
|
|
287
|
+
"permdock": {
|
|
288
|
+
"description": "With a PermDock manifest, read PermDock's memberships for this scope instead of better_supabase.memberships; false keeps the kit's table.",
|
|
289
|
+
"oneOf": [
|
|
290
|
+
{ "const": false },
|
|
291
|
+
{
|
|
292
|
+
"type": "object",
|
|
293
|
+
"additionalProperties": false,
|
|
294
|
+
"properties": {
|
|
295
|
+
"scope": { "type": "string", "default": "organization" }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
"permdock": {
|
|
303
|
+
"description": "Where PermDock's JSON outputs are.",
|
|
304
|
+
"type": "object",
|
|
305
|
+
"additionalProperties": false,
|
|
306
|
+
"properties": {
|
|
307
|
+
"manifest": {
|
|
308
|
+
"description": "What `permdock supabase inspect --out` writes.",
|
|
309
|
+
"type": "string",
|
|
310
|
+
"default": "permdock.manifest.json"
|
|
311
|
+
},
|
|
312
|
+
"catalog": {
|
|
313
|
+
"description": "What `permdock catalog` writes.",
|
|
314
|
+
"type": "string",
|
|
315
|
+
"default": "permissions.catalog.json"
|
|
286
316
|
}
|
|
287
317
|
}
|
|
288
318
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://unpkg.com/better-supabase/schemas/doctor-report-v1.json",
|
|
4
4
|
"title": "better-supabase doctor report",
|
|
5
|
-
"description": "Output of `better-supabase doctor --
|
|
5
|
+
"description": "Output of `better-supabase doctor --json`.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": ["$schema", "version", "tool", "summary", "findings"],
|
|
@@ -42,6 +42,12 @@
|
|
|
42
42
|
"BS106",
|
|
43
43
|
"BS107",
|
|
44
44
|
"BS108",
|
|
45
|
+
"BS109",
|
|
46
|
+
"BS110",
|
|
47
|
+
"BS111",
|
|
48
|
+
"BS112",
|
|
49
|
+
"BS113",
|
|
50
|
+
"BS114",
|
|
45
51
|
"BS200",
|
|
46
52
|
"BS204",
|
|
47
53
|
"BS205",
|
|
@@ -52,6 +58,15 @@
|
|
|
52
58
|
"BS210",
|
|
53
59
|
"BS211",
|
|
54
60
|
"BS212",
|
|
61
|
+
"BS213",
|
|
62
|
+
"BS214",
|
|
63
|
+
"BS215",
|
|
64
|
+
"BS216",
|
|
65
|
+
"BS217",
|
|
66
|
+
"BS218",
|
|
67
|
+
"BS219",
|
|
68
|
+
"BS220",
|
|
69
|
+
"BS221",
|
|
55
70
|
"BS301",
|
|
56
71
|
"BS302",
|
|
57
72
|
"BS303",
|
|
@@ -65,6 +80,7 @@
|
|
|
65
80
|
"BS405",
|
|
66
81
|
"BS406",
|
|
67
82
|
"BS407",
|
|
83
|
+
"BS408",
|
|
68
84
|
"BS501",
|
|
69
85
|
"BS502"
|
|
70
86
|
]
|
package/schemas/snapshot-v2.json
CHANGED
|
@@ -690,6 +690,12 @@
|
|
|
690
690
|
"additionalProperties": {
|
|
691
691
|
"type": "string"
|
|
692
692
|
}
|
|
693
|
+
},
|
|
694
|
+
"execute": {
|
|
695
|
+
"type": "array",
|
|
696
|
+
"items": {
|
|
697
|
+
"type": "string"
|
|
698
|
+
}
|
|
693
699
|
}
|
|
694
700
|
}
|
|
695
701
|
}
|
|
@@ -901,6 +907,12 @@
|
|
|
901
907
|
},
|
|
902
908
|
"partial": {
|
|
903
909
|
"type": "boolean"
|
|
910
|
+
},
|
|
911
|
+
"method": {
|
|
912
|
+
"type": "string"
|
|
913
|
+
},
|
|
914
|
+
"predicate": {
|
|
915
|
+
"type": ["string", "null"]
|
|
904
916
|
}
|
|
905
917
|
}
|
|
906
918
|
}
|
|
@@ -996,6 +1008,29 @@
|
|
|
996
1008
|
}
|
|
997
1009
|
}
|
|
998
1010
|
},
|
|
1011
|
+
"columnGrants": {
|
|
1012
|
+
"description": "Column-level insert and update grants to anon and authenticated. Absent in snapshots taken before they were introspected.",
|
|
1013
|
+
"type": "array",
|
|
1014
|
+
"items": {
|
|
1015
|
+
"type": "object",
|
|
1016
|
+
"additionalProperties": false,
|
|
1017
|
+
"required": ["column", "role", "privileges"],
|
|
1018
|
+
"properties": {
|
|
1019
|
+
"column": {
|
|
1020
|
+
"type": "string"
|
|
1021
|
+
},
|
|
1022
|
+
"role": {
|
|
1023
|
+
"type": "string"
|
|
1024
|
+
},
|
|
1025
|
+
"privileges": {
|
|
1026
|
+
"type": "array",
|
|
1027
|
+
"items": {
|
|
1028
|
+
"type": "string"
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
999
1034
|
"large": {
|
|
1000
1035
|
"description": "Postgres estimates at least 10,000 rows; the unbounded-read lint rule reads it.",
|
|
1001
1036
|
"const": true
|
|
@@ -11,13 +11,22 @@ The data layer is `sb = defineSupabase(schema)` in `src/lib/supabase.ts`.
|
|
|
11
11
|
|
|
12
12
|
## Workflow: change the schema
|
|
13
13
|
|
|
14
|
-
1. Write the migration
|
|
15
|
-
`supabase
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
1. Write the migration: edit `supabase/schemas/*.sql`, then run
|
|
15
|
+
`supabase db schema declarative sync -f <name>` (pg-delta;
|
|
16
|
+
`supabase db diff -f <name>` when `config.toml` has no
|
|
17
|
+
`[experimental.pgdelta] enabled = true`), or `supabase migration new` for
|
|
18
|
+
data and role settings.
|
|
19
|
+
2. For a new table in an exposed schema, in the same file: `enable row level
|
|
20
|
+
security`, a policy per role and command the app uses, and an index on
|
|
21
|
+
every column a policy filters by. List the table in `expose` in
|
|
22
|
+
`better-supabase.config.ts` and run `pnpm better-supabase sql add grants`:
|
|
23
|
+
new tables get no Data API grants on their own.
|
|
24
|
+
3. Apply it with `supabase db reset` (or `supabase migration up`).
|
|
25
|
+
4. Run `pnpm better-supabase gen`, then fix the type errors it surfaces.
|
|
26
|
+
The command comes from the `@better-supabase/cli` dev dependency.
|
|
27
|
+
5. Run `pnpm better-supabase doctor` and fix every error it reports (RLS off,
|
|
28
|
+
missing policies, missing grants, unindexed foreign keys, drift).
|
|
29
|
+
6. Commit the generated files. CI runs `better-supabase gen --check`.
|
|
21
30
|
|
|
22
31
|
Done when `gen --check` and `doctor` exit 0 and the project typechecks.
|
|
23
32
|
|
|
@@ -48,10 +57,10 @@ stay hidden.
|
|
|
48
57
|
|
|
49
58
|
```ts
|
|
50
59
|
const result = await db.customers.findMany({
|
|
51
|
-
select: [
|
|
52
|
-
where: { status:
|
|
53
|
-
include: { organization: { select: [
|
|
54
|
-
orderBy: { name:
|
|
60
|
+
select: ["id", "name"],
|
|
61
|
+
where: { status: "active", notes: { some: { kind: "call" } } },
|
|
62
|
+
include: { organization: { select: ["name"] } },
|
|
63
|
+
orderBy: { name: "asc" },
|
|
55
64
|
limit: 20,
|
|
56
65
|
});
|
|
57
66
|
if (!result.ok) return result; // DbError: kind, message, status, code
|
|
@@ -82,3 +91,6 @@ See [references/troubleshooting.md](references/troubleshooting.md) for each
|
|
|
82
91
|
https://bettersupabase.com/docs. Every page is also served as Markdown at
|
|
83
92
|
`https://bettersupabase.com/docs/<path>.md`, and
|
|
84
93
|
https://bettersupabase.com/llms.txt lists them all.
|
|
94
|
+
|
|
95
|
+
For Supabase itself (Auth, Storage, RLS, Postgres performance), install
|
|
96
|
+
Supabase's skills next to these: `npx skills add supabase/agent-skills`.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Plugins
|
|
2
2
|
|
|
3
3
|
```ts title="src/lib/supabase.ts"
|
|
4
|
-
import { defineSupabase } from
|
|
5
|
-
import { actor } from
|
|
6
|
-
import { softDelete } from
|
|
7
|
-
import { tenant } from
|
|
8
|
-
import { timestamps } from
|
|
9
|
-
import { validation } from
|
|
4
|
+
import { defineSupabase } from "better-supabase";
|
|
5
|
+
import { actor } from "better-supabase/plugins/actor";
|
|
6
|
+
import { softDelete } from "better-supabase/plugins/soft-delete";
|
|
7
|
+
import { tenant } from "better-supabase/plugins/tenant";
|
|
8
|
+
import { timestamps } from "better-supabase/plugins/timestamps";
|
|
9
|
+
import { validation } from "better-supabase/plugins/validation";
|
|
10
10
|
|
|
11
|
-
import { schema } from
|
|
12
|
-
import { validators } from
|
|
11
|
+
import { schema } from "./supabase/generated.ts";
|
|
12
|
+
import { validators } from "./supabase/generated.zod.ts";
|
|
13
13
|
|
|
14
14
|
export const sb = defineSupabase(schema)
|
|
15
15
|
.use(timestamps())
|
|
@@ -34,14 +34,14 @@ plugins: {
|
|
|
34
34
|
A table without the columns is left alone, and the types follow:
|
|
35
35
|
`restore()` and `withDeleted` only exist on soft-delete tables.
|
|
36
36
|
|
|
37
|
-
| Plugin
|
|
38
|
-
|
|
|
39
|
-
| `timestamps()` | Sets `createdAt` and `updatedAt`
|
|
40
|
-
| `softDelete()` | Hides deleted rows, turns `delete` into an update, adds `restore`
|
|
41
|
-
| `tenant()`
|
|
42
|
-
| `actor()`
|
|
43
|
-
| `validation()` | Validates writes with any Standard Schema
|
|
44
|
-
| `rules()`
|
|
37
|
+
| Plugin | What it does | Don't |
|
|
38
|
+
| -------------- | --------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
39
|
+
| `timestamps()` | Sets `createdAt` and `updatedAt` | set them in `create` or `update` |
|
|
40
|
+
| `softDelete()` | Hides deleted rows, turns `delete` into an update, adds `restore` | filter `deletedAt: null` by hand |
|
|
41
|
+
| `tenant()` | Scopes queries to the request's tenant and fills it on insert | pass `organizationId` from the client |
|
|
42
|
+
| `actor()` | Sets `createdBy` and `updatedBy` | pass the user id yourself |
|
|
43
|
+
| `validation()` | Validates writes with any Standard Schema | validate the same input twice |
|
|
44
|
+
| `rules()` | Flags unbounded reads, missing tenants, sensitive columns and admin keys in the browser | disable a rule without a reason |
|
|
45
45
|
|
|
46
46
|
## Order
|
|
47
47
|
|
|
@@ -59,7 +59,10 @@ Never read it from `user_metadata`, a URL or a request body. When the project
|
|
|
59
59
|
has a `permdock.config.ts`, PermDock's hook writes that claim and the
|
|
60
60
|
memberships; don't run `sql add tenant` there or call `membership_claims()`
|
|
61
61
|
from a hook. `sql add entitlements` is fine: `features` is not a PermDock
|
|
62
|
-
claim
|
|
63
|
-
|
|
62
|
+
claim. Add `claims: { features: 'better_supabase.feature_claims' }` to
|
|
63
|
+
`supabase.hook` in `permdock.config.ts`, run `permdock supabase hook generate`,
|
|
64
|
+
and `hasEntitlement(session, ...)` works. Run `permdock supabase inspect --out`
|
|
65
|
+
first: with `permdock.manifest.json` present, the entitlements module reads
|
|
66
|
+
PermDock's `member_<scope>_ids` helpers and doesn't add `tenant`.
|
|
64
67
|
|
|
65
68
|
Docs: https://bettersupabase.com/docs/plugins.md
|
|
@@ -7,21 +7,21 @@ Start with `pnpm better-supabase doctor`. It checks the database, RLS,
|
|
|
7
7
|
|
|
8
8
|
Branch on `result.error.kind`, not on the message.
|
|
9
9
|
|
|
10
|
-
| Kind
|
|
11
|
-
|
|
|
12
|
-
| `not_found`
|
|
13
|
-
| `unauthorized`
|
|
14
|
-
| `forbidden`
|
|
15
|
-
| `conflict`
|
|
16
|
-
| `foreign_key`
|
|
17
|
-
| `not_null`, `check`, `exclusion`
|
|
18
|
-
| `invalid_input`
|
|
19
|
-
| `validation`
|
|
20
|
-
| `invalid_request`
|
|
21
|
-
| `multiple_rows`
|
|
22
|
-
| `stale`
|
|
23
|
-
| `serialization`, `timeout`, `network`, `rate_limited` | Transient
|
|
24
|
-
| `raised`
|
|
10
|
+
| Kind | Usual cause | Fix |
|
|
11
|
+
| ----------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
|
|
12
|
+
| `not_found` | The row doesn't exist, or RLS hides it from this user | Check the policy with an `asUser` test before assuming the row is gone |
|
|
13
|
+
| `unauthorized` | No valid session or token | Get `db` from the adapter for this request |
|
|
14
|
+
| `forbidden` | RLS or a grant rejected the write | Fix the policy or the grant; don't switch to `server.admin()` |
|
|
15
|
+
| `conflict` | Unique violation | Use `upsert`, or return the error to the caller |
|
|
16
|
+
| `foreign_key` | Referenced row missing, or still referenced on delete | Create the parent first, or delete children |
|
|
17
|
+
| `not_null`, `check`, `exclusion` | A constraint rejected the row | Validate input earlier with the `validation()` plugin |
|
|
18
|
+
| `invalid_input` | Postgres couldn't parse a value (bad uuid, enum) | Validate before the call |
|
|
19
|
+
| `validation` | A Standard Schema rejected the input; see `issues` | Show the issues to the user |
|
|
20
|
+
| `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
|
+
| `multiple_rows` | `findUnique` or a single-row write matched more than one row | Filter by a unique key |
|
|
22
|
+
| `stale` | `update(..., { expect })` found a newer row | Reload the row and retry |
|
|
23
|
+
| `serialization`, `timeout`, `network`, `rate_limited` | Transient | Retry with backoff, or surface the error |
|
|
24
|
+
| `raised` | A database function raised an exception | Read `message` and `code` from the function |
|
|
25
25
|
|
|
26
26
|
## Other symptoms
|
|
27
27
|
|
|
@@ -32,13 +32,13 @@ and `['service']` for machine callers. Rejected callers get a 401 with a
|
|
|
32
32
|
|
|
33
33
|
## Adapters
|
|
34
34
|
|
|
35
|
-
| Where
|
|
36
|
-
|
|
|
37
|
-
| Next.js
|
|
38
|
-
| Hono
|
|
39
|
-
| oRPC
|
|
40
|
-
| Edge Functions | `createEdge(sb, { cors: true })`
|
|
41
|
-
| MCP
|
|
35
|
+
| Where | Setup | Handler |
|
|
36
|
+
| -------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
37
|
+
| Next.js | `createNext(sb)` in `lib/supabase.server.ts` | `next.route((req, { db }) => ...)`, `next.action({ input: schema }, (input, { db }) => ...)` |
|
|
38
|
+
| Hono | `createHono(sb)`, `.use('/api/*', bs.middleware())` | `c.var.db`; `bs.resource('customers', {...})` for REST |
|
|
39
|
+
| oRPC | `createOrpc(sb)`, `base.use(bs.middleware())` | `bs.unwrap(context.db.customers.findMany(...))` |
|
|
40
|
+
| Edge Functions | `createEdge(sb, { cors: true })` | `Deno.serve(bs.handler((req, { db }) => ...))` |
|
|
41
|
+
| MCP | `createMcp(sb, { name, version, resources })` | `.tool({ name, input, run: (args, { db }) => ... })` |
|
|
42
42
|
|
|
43
43
|
Don't build error JSON by hand; errors become Problem Details.
|
|
44
44
|
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Each adapter wraps the same `sb` from `src/lib/supabase.ts`:
|
|
4
4
|
|
|
5
5
|
```ts title="src/lib/supabase.ts"
|
|
6
|
-
import { defineSupabase } from
|
|
6
|
+
import { defineSupabase } from "better-supabase";
|
|
7
7
|
|
|
8
|
-
import { schema } from
|
|
8
|
+
import { schema } from "./supabase/generated";
|
|
9
9
|
|
|
10
|
-
export type { Functions, Models } from
|
|
10
|
+
export type { Functions, Models } from "./supabase/generated";
|
|
11
11
|
|
|
12
12
|
export const sb = defineSupabase(schema);
|
|
13
13
|
```
|
|
@@ -15,17 +15,17 @@ export const sb = defineSupabase(schema);
|
|
|
15
15
|
## Next.js
|
|
16
16
|
|
|
17
17
|
```ts title="src/lib/supabase.server.ts"
|
|
18
|
-
import { createNext } from
|
|
18
|
+
import { createNext } from "better-supabase/next";
|
|
19
19
|
|
|
20
|
-
import { sb } from
|
|
20
|
+
import { sb } from "./supabase";
|
|
21
21
|
|
|
22
22
|
export const next = createNext(sb);
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
```ts title="src/proxy.ts"
|
|
26
|
-
import type { NextRequest } from
|
|
26
|
+
import type { NextRequest } from "next/server";
|
|
27
27
|
|
|
28
|
-
import { next } from
|
|
28
|
+
import { next } from "./lib/supabase.server";
|
|
29
29
|
|
|
30
30
|
export const proxy = (request: NextRequest) => next.proxy(request);
|
|
31
31
|
```
|
|
@@ -35,24 +35,29 @@ route handlers and actions read the verified token:
|
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
const { db } = await next.server();
|
|
38
|
-
export const GET = next.route((request, { db }) =>
|
|
38
|
+
export const GET = next.route((request, { db }) =>
|
|
39
|
+
db.customers.findMany({ limit: 20 }),
|
|
40
|
+
);
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
## Hono
|
|
42
44
|
|
|
43
45
|
```ts title="src/server.ts"
|
|
44
|
-
import { type BetterEnv, createHono } from
|
|
45
|
-
import { Hono } from
|
|
46
|
+
import { type BetterEnv, createHono } from "better-supabase/hono";
|
|
47
|
+
import { Hono } from "hono";
|
|
46
48
|
|
|
47
|
-
import { type Functions, type Models, sb } from
|
|
49
|
+
import { type Functions, type Models, sb } from "./lib/supabase";
|
|
48
50
|
|
|
49
51
|
const bs = createHono(sb);
|
|
50
52
|
|
|
51
53
|
const app = new Hono<BetterEnv<Models, Functions, unknown>>()
|
|
52
54
|
.onError(bs.onError)
|
|
53
|
-
.use(
|
|
54
|
-
.get(
|
|
55
|
-
.route(
|
|
55
|
+
.use("/api/*", bs.middleware())
|
|
56
|
+
.get("/api/me", (c) => c.json({ kind: c.var.auth.kind }))
|
|
57
|
+
.route(
|
|
58
|
+
"/api/customers",
|
|
59
|
+
bs.resource("customers", { select: ["id", "name"] }),
|
|
60
|
+
);
|
|
56
61
|
|
|
57
62
|
export default app;
|
|
58
63
|
```
|
|
@@ -60,10 +65,10 @@ export default app;
|
|
|
60
65
|
## oRPC
|
|
61
66
|
|
|
62
67
|
```ts title="src/router.ts"
|
|
63
|
-
import { os } from
|
|
64
|
-
import { createOrpc, type OrpcRequestContext } from
|
|
68
|
+
import { os } from "@orpc/server";
|
|
69
|
+
import { createOrpc, type OrpcRequestContext } from "better-supabase/orpc";
|
|
65
70
|
|
|
66
|
-
import { sb } from
|
|
71
|
+
import { sb } from "./lib/supabase";
|
|
67
72
|
|
|
68
73
|
export const bs = createOrpc(sb);
|
|
69
74
|
const authed = os.$context<OrpcRequestContext>().use(bs.middleware());
|
|
@@ -78,28 +83,28 @@ export const router = {
|
|
|
78
83
|
## Edge Functions
|
|
79
84
|
|
|
80
85
|
```ts title="supabase/functions/api/index.ts"
|
|
81
|
-
import { createEdge } from
|
|
86
|
+
import { createEdge } from "better-supabase/edge";
|
|
82
87
|
|
|
83
|
-
import { sb } from
|
|
88
|
+
import { sb } from "../_shared/supabase.ts";
|
|
84
89
|
|
|
85
90
|
const bs = createEdge(sb, { cors: true });
|
|
86
91
|
|
|
87
92
|
Deno.serve(
|
|
88
|
-
bs.resources({ customers: { select: [
|
|
93
|
+
bs.resources({ customers: { select: ["id", "name"] } }, { basePath: "/api" }),
|
|
89
94
|
);
|
|
90
95
|
```
|
|
91
96
|
|
|
92
97
|
## MCP
|
|
93
98
|
|
|
94
99
|
```ts title="supabase/functions/mcp/index.ts"
|
|
95
|
-
import { createMcp } from
|
|
100
|
+
import { createMcp } from "better-supabase/mcp";
|
|
96
101
|
|
|
97
|
-
import { sb } from
|
|
102
|
+
import { sb } from "../_shared/supabase.ts";
|
|
98
103
|
|
|
99
104
|
const mcp = createMcp(sb, {
|
|
100
|
-
name:
|
|
101
|
-
version:
|
|
102
|
-
resources: { customers: { select: [
|
|
105
|
+
name: "crm",
|
|
106
|
+
version: "0.1.0",
|
|
107
|
+
resources: { customers: { select: ["id", "name"] } },
|
|
103
108
|
});
|
|
104
109
|
|
|
105
110
|
Deno.serve(mcp.fetch);
|
|
@@ -11,7 +11,8 @@ supabase-js, and don't use the service role for anything a user does.
|
|
|
11
11
|
## Workflow: test a table or policy
|
|
12
12
|
|
|
13
13
|
1. Start the stack (`supabase start`) and write `.env.local` with
|
|
14
|
-
`better-supabase env`.
|
|
14
|
+
`better-supabase env`. In a sandbox without Docker, start it with
|
|
15
|
+
`SUPABASE_EXPERIMENTAL_STACK=1` (the Supabase CLI's native stack).
|
|
15
16
|
2. Add the rows the test needs to `supabase/seed.ts`, including one row
|
|
16
17
|
owned by another tenant, then `supabase db reset`.
|
|
17
18
|
3. Write the RLS test with `asUser` for each role that matters.
|
|
@@ -29,24 +30,26 @@ again once you restore it.
|
|
|
29
30
|
- Typed fixtures live in `supabase/seed.ts`:
|
|
30
31
|
|
|
31
32
|
```ts
|
|
32
|
-
import { defineSeed } from
|
|
33
|
-
import { sb } from
|
|
33
|
+
import { defineSeed } from "better-supabase/testing";
|
|
34
|
+
import { sb } from "../src/lib/supabase.ts";
|
|
34
35
|
|
|
35
36
|
export const seed = defineSeed(sb, {
|
|
36
|
-
organizations: { acme: { id: ACME, name:
|
|
37
|
-
customers: { first: { id: FIRST, organizationId: ACME, name:
|
|
37
|
+
organizations: { acme: { id: ACME, name: "Acme" } },
|
|
38
|
+
customers: { first: { id: FIRST, organizationId: ACME, name: "First" } },
|
|
38
39
|
});
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
`better-supabase seed` renders them to SQL for `supabase db reset`, and
|
|
43
|
+
tests import the same rows (`seed.rows.customers.first.id`).
|
|
43
44
|
|
|
44
45
|
## RLS tests
|
|
45
46
|
|
|
46
47
|
```ts
|
|
47
48
|
const alice = await asUser(sb, { sub: aliceId, tenant_id: ACME }, { postgres });
|
|
48
49
|
expect(await alice.db.customers.count().orThrow()).toBe(1);
|
|
49
|
-
expect(await alice.db.customers.findById(OTHER_ORG_CUSTOMER)).toMatchObject({
|
|
50
|
+
expect(await alice.db.customers.findById(OTHER_ORG_CUSTOMER)).toMatchObject({
|
|
51
|
+
ok: false,
|
|
52
|
+
});
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
- Check both directions: the user sees their own rows and never sees another tenant's.
|
package/bin/better-supabase.js
DELETED