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/README.md
CHANGED
|
@@ -1,12 +1,64 @@
|
|
|
1
1
|
# better-supabase
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Typed repositories, auth glue and a CLI for Supabase apps, APIs, MCP servers and jobs.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/better-supabase)
|
|
6
6
|
[](https://github.com/ScaleDockHQ/better-supabase/blob/main/LICENSE)
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
[Docs](https://bettersupabase.com/docs) · [Quickstart](https://bettersupabase.com/docs/getting-started) · [Examples](https://bettersupabase.com/docs/examples) · [Changelog](https://bettersupabase.com/changelog) · [GitHub](https://github.com/ScaleDockHQ/better-supabase)
|
|
10
|
+
|
|
11
|
+
better-supabase sits on top of the supabase-js client you already use. You keep your database, your RLS policies, Supabase Auth and Storage. The CLI reads your schema and generates a typed repository for every table, so a query is a typed object instead of a string, a typo in a column name is a compile error, and the row type follows what you selected.
|
|
12
|
+
|
|
13
|
+
It also writes the glue every Supabase app repeats: the session cookie and token refresh for Next.js, Hono, oRPC, Edge Functions and MCP servers, pagination, soft delete, timestamps, cache invalidation, background jobs and typed Storage and Realtime. It is built on [`@supabase/supabase-js`](https://github.com/supabase/supabase-js), [`@supabase/server`](https://github.com/supabase/server), [`@supabase/middleware`](https://github.com/supabase/middleware) and [`@supabase/ssr`](https://github.com/supabase/ssr). It is an independent open-source project, not made by Supabase.
|
|
14
|
+
|
|
15
|
+
## Before and after
|
|
16
|
+
|
|
17
|
+
With supabase-js, the query is a string and the error is a Postgres code:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const { data, error } = await supabase
|
|
21
|
+
.from("customers")
|
|
22
|
+
.select("id, name, organization(name), notes!inner(kind)")
|
|
23
|
+
.eq("status", "active")
|
|
24
|
+
.eq("notes.kind", "call")
|
|
25
|
+
.order("name")
|
|
26
|
+
.limit(20);
|
|
27
|
+
if (error) throw error;
|
|
28
|
+
|
|
29
|
+
const { error: insertError } = await supabase.from("tags").insert({ name });
|
|
30
|
+
if (insertError?.code === "23505") return { message: "Tag already exists" };
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
With better-supabase, every column, relation and operator is typed, and errors are values with a `kind`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const customers = await db.customers.findMany({
|
|
37
|
+
select: ["id", "name"],
|
|
38
|
+
where: { status: "active", notes: { some: { kind: "call" } } },
|
|
39
|
+
include: { organization: { select: ["name"] } },
|
|
40
|
+
orderBy: { name: "asc" },
|
|
41
|
+
limit: 20,
|
|
42
|
+
});
|
|
43
|
+
// customers.data: { id: string; name: string; organization: { name: string } }[]
|
|
44
|
+
|
|
45
|
+
const tag = await db.tags.create({ organizationId, name });
|
|
46
|
+
if (tag.error?.kind === "conflict") return { message: "Tag already exists" };
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Each call is still one PostgREST request, and it runs as the signed-in user, so your RLS policies decide what it can read and write. The [supabase-js migration guide](https://bettersupabase.com/docs/migration/supabase-js) maps every supabase-js call to its repository equivalent, and the two work side by side while you move over.
|
|
50
|
+
|
|
51
|
+
## What you get
|
|
52
|
+
|
|
53
|
+
- **Stronger generated types.** [`better-supabase gen`](https://bettersupabase.com/docs/cli/gen) writes the same `database.types.ts` as `supabase gen types` and adds relationship cardinality, unique keys, CHECK-constraint unions, typed jsonb and camelCase column maps.
|
|
54
|
+
- **A typed repository per table.** [`findMany`, `findUnique`, `create`, `upsert`, `paginate`, counts and aggregates](https://bettersupabase.com/docs/repository), with nested relation filters and includes, compiled to PostgREST or to SQL on a direct Postgres connection.
|
|
55
|
+
- **Results instead of exceptions.** Every call returns a [`Result` with a plain, serializable `DbError`](https://bettersupabase.com/docs/concepts/results) that you can return from server actions and RPC handlers. `.orThrow()` is opt-in, and adapters turn errors into RFC 9457 Problem Details with the right status.
|
|
56
|
+
- **Your casing.** Keep database names with `casing: 'snake'`, or get [camelCase rows](https://bettersupabase.com/docs/concepts/casing) renamed inside the PostgREST query.
|
|
57
|
+
- **Auth without extra round trips.** Valid access tokens are [verified locally](https://bettersupabase.com/docs/auth) against the JWKS and never reach the Auth server. Refresh happens once, in the proxy. The service role is an explicit `bs.admin()` call.
|
|
58
|
+
- **Cache tags that follow writes.** Mutations [invalidate the tables they change](https://bettersupabase.com/docs/concepts/caching), with read-your-writes in Next.js server actions and table-based invalidation in TanStack Query.
|
|
59
|
+
- **Plugins.** [Timestamps, soft delete, tenant scoping, actor columns, validation and rules](https://bettersupabase.com/docs/plugins), each versioned and opt-in.
|
|
60
|
+
- **Kits for the SQL every app repeats.** [Jobs on Supabase Queues](https://bettersupabase.com/docs/kits/jobs), webhook inboxes, idempotency keys, organizations and invitations, notifications, list pages, typed Storage paths and Realtime topics, vector search and Stripe entitlements. [`better-supabase sql add`](https://bettersupabase.com/docs/kits/sql) writes the tables, functions and policies into your declarative schema.
|
|
61
|
+
- **Tests and CI checks.** [`asUser`](https://bettersupabase.com/docs/testing) runs RLS tests as any user against the local stack, `gen --check` fails on schema drift, and [`doctor`](https://bettersupabase.com/docs/cli/doctor) reports security and performance findings, with SARIF output for code scanning.
|
|
10
62
|
|
|
11
63
|
## Install
|
|
12
64
|
|
|
@@ -15,18 +67,21 @@ pnpm add better-supabase @supabase/supabase-js
|
|
|
15
67
|
pnpm add -D pg
|
|
16
68
|
```
|
|
17
69
|
|
|
18
|
-
|
|
70
|
+
Before installing, `npx better-supabase init` detects your frameworks and prints the install command for your package manager. The package is ESM only. The CLI needs Node 24 or later, and the runtime entries run on every WinterTC runtime. On runtimes without a native `Temporal` (Node 24, Safari), load [`temporal-polyfill`](https://bettersupabase.com/docs/concepts/temporal) once at startup. TypeScript 6 and 7 are tested.
|
|
19
71
|
|
|
20
72
|
## Quick start
|
|
21
73
|
|
|
22
|
-
|
|
74
|
+
Generate the typed schema from your local stack:
|
|
23
75
|
|
|
24
76
|
```bash
|
|
25
77
|
supabase start
|
|
26
78
|
pnpm better-supabase init # config, src/lib/supabase/index.ts and framework glue
|
|
79
|
+
pnpm better-supabase env # URL and keys into .env.local
|
|
27
80
|
pnpm better-supabase gen # database.types.ts and generated.ts
|
|
28
81
|
```
|
|
29
82
|
|
|
83
|
+
Define the client once, then connect it per request with the current user's Supabase client:
|
|
84
|
+
|
|
30
85
|
```ts
|
|
31
86
|
import { createClient } from "@supabase/supabase-js";
|
|
32
87
|
import { defineSupabase } from "better-supabase";
|
|
@@ -37,16 +92,78 @@ export const betterSupabase = defineSupabase(schema);
|
|
|
37
92
|
|
|
38
93
|
const db = betterSupabase.connect(createClient(url, publishableKey));
|
|
39
94
|
|
|
40
|
-
const
|
|
41
|
-
select: ["id", "name"],
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
const customers = await db.customers
|
|
96
|
+
.findMany({ select: ["id", "name"], where: { status: "active" } })
|
|
97
|
+
.orThrow();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`defineSupabase` holds no secrets and no connection. Because you connect with the client for the current user, every query runs under their RLS policies.
|
|
101
|
+
|
|
102
|
+
## Use it where you run
|
|
103
|
+
|
|
104
|
+
An adapter verifies the session, refreshes it where it should and gives each handler a `db` for the caller. In Next.js:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
// src/lib/supabase/server.ts
|
|
108
|
+
export const bs = createNext(betterSupabase);
|
|
109
|
+
|
|
110
|
+
// src/proxy.ts
|
|
111
|
+
export const proxy = (request: NextRequest) => bs.proxy(request);
|
|
112
|
+
|
|
113
|
+
// app/customers/page.tsx
|
|
114
|
+
export default async function Customers() {
|
|
115
|
+
const { db } = await bs.context();
|
|
116
|
+
const customers = await db.customers
|
|
117
|
+
.findMany({ select: ["id", "name"] })
|
|
118
|
+
.orThrow();
|
|
119
|
+
return <CustomerList customers={customers} />;
|
|
120
|
+
}
|
|
48
121
|
```
|
|
49
122
|
|
|
123
|
+
| Where you run | What the adapter gives you | Docs |
|
|
124
|
+
| ---------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
125
|
+
| Next.js | Proxy, Server Components, route handlers, server actions and cache tags | [Next.js](https://bettersupabase.com/docs/frameworks/next) |
|
|
126
|
+
| Hono | Middleware, Result-aware handlers and REST resources that match OpenAPI | [Hono](https://bettersupabase.com/docs/frameworks/hono) |
|
|
127
|
+
| oRPC | A middleware that adds the caller's repositories to the oRPC context | [oRPC](https://bettersupabase.com/docs/frameworks/orpc) |
|
|
128
|
+
| Edge Functions, Deno, Bun, Workers | Fetch handlers with the caller's repositories | [Edge](https://bettersupabase.com/docs/frameworks/edge) |
|
|
129
|
+
| MCP servers | Tools from your tables and your own code, running as the signed-in user | [MCP](https://bettersupabase.com/docs/frameworks/mcp) |
|
|
130
|
+
| React and TanStack Query | A browser client, typed hooks, query and mutation options, live queries | [Frontend](https://bettersupabase.com/docs/frontend/query) |
|
|
131
|
+
|
|
132
|
+
## The CLI
|
|
133
|
+
|
|
134
|
+
The `better-supabase` command ships in this package:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pnpm better-supabase init # config, client and framework glue
|
|
138
|
+
pnpm better-supabase env # local URL and keys into .env.local
|
|
139
|
+
pnpm better-supabase gen # database.types.ts and generated.ts
|
|
140
|
+
pnpm better-supabase gen --check # exit 1 in CI when the schema drifted
|
|
141
|
+
pnpm better-supabase doctor # RLS, grants, indexes and Supabase advisors
|
|
142
|
+
pnpm better-supabase doctor --format sarif # the same findings for GitHub code scanning
|
|
143
|
+
pnpm better-supabase sql add jobs # a SQL kit module into supabase/schemas
|
|
144
|
+
pnpm better-supabase skills install # Agent Skills for your coding agent
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See the [CLI reference](https://bettersupabase.com/docs/cli) for every command and flag.
|
|
148
|
+
|
|
149
|
+
## Works with
|
|
150
|
+
|
|
151
|
+
| Area | Supported |
|
|
152
|
+
| ---------- | ------------------------------------------------------------------------- |
|
|
153
|
+
| Frameworks | Next.js 16, Hono, oRPC, Supabase Edge Functions, MCP servers |
|
|
154
|
+
| Runtimes | Node.js, Deno, Bun, Cloudflare Workers, Vercel Functions, Supabase Edge |
|
|
155
|
+
| Frontend | React 19, TanStack Query 5, live queries over Realtime |
|
|
156
|
+
| Validation | Zod, Valibot and any Standard Schema |
|
|
157
|
+
| Standards | OpenAPI 3.1, RFC 9457, OpenTelemetry, CloudEvents, Standard Webhooks, MCP |
|
|
158
|
+
| Testing | Vitest, pgTAP, the Supabase local stack |
|
|
159
|
+
|
|
160
|
+
## Good to know
|
|
161
|
+
|
|
162
|
+
- **It does not replace supabase-js.** Auth flows, Storage and Realtime stay on the supabase-js client, which is one property away as `db.$client`.
|
|
163
|
+
- **It does not replace RLS.** Queries run as the caller and your policies decide. Bypassing them takes an explicit `bs.admin()`.
|
|
164
|
+
- **A direct Postgres connection is optional.** Queries go through PostgREST by default. [`better-supabase/postgres`](https://bettersupabase.com/docs/auth/postgres) runs the same repository API over SQL for jobs, scripts and transactions.
|
|
165
|
+
- **PostgREST's limits still apply.** There are no transactions across requests, and a few filters only work on reads. The [limitations page](https://bettersupabase.com/docs/guides/limitations) lists each one and what to use instead.
|
|
166
|
+
|
|
50
167
|
## Subpaths
|
|
51
168
|
|
|
52
169
|
| Import | What it gives you |
|
|
@@ -64,6 +181,8 @@ if (!result.ok) return result; // DbError: kind, message, status, code
|
|
|
64
181
|
| `better-supabase/hono`, `/orpc`, `/edge` | Framework adapters |
|
|
65
182
|
| `better-supabase/mcp` | MCP servers whose tools run as the signed-in user |
|
|
66
183
|
| `better-supabase/jobs` | Supabase Queues jobs, idempotency keys and a webhook inbox |
|
|
184
|
+
| `better-supabase/orgs` | Organizations, members, invitations and switching from the SQL kit |
|
|
185
|
+
| `better-supabase/notifications` | Sending, listing and delivering notifications from the SQL kit |
|
|
67
186
|
| `better-supabase/list` | Search, facets, sorting and pagination from one definition |
|
|
68
187
|
| `better-supabase/storage`, `/realtime` | Typed bucket paths and broadcast topics |
|
|
69
188
|
| `better-supabase/env` | Validated Supabase settings |
|
|
@@ -75,7 +194,7 @@ if (!result.ok) return result; // DbError: kind, message, status, code
|
|
|
75
194
|
|
|
76
195
|
## For AI agents
|
|
77
196
|
|
|
78
|
-
The package ships [Agent Skills](https://agentskills.io)
|
|
197
|
+
The package ships [Agent Skills](https://agentskills.io) for queries, APIs, auth and tests:
|
|
79
198
|
|
|
80
199
|
```bash
|
|
81
200
|
npx skills add ScaleDockHQ/better-supabase
|
|
@@ -83,12 +202,12 @@ npx skills add ScaleDockHQ/better-supabase
|
|
|
83
202
|
pnpm better-supabase skills install
|
|
84
203
|
```
|
|
85
204
|
|
|
86
|
-
The docs are also available as [`/llms.txt`](https://bettersupabase.com/llms.txt), as Markdown at `/docs/<page>.md`, and through the docs MCP server at `https://bettersupabase.com/mcp`.
|
|
205
|
+
The docs are also available as [`/llms.txt`](https://bettersupabase.com/llms.txt), as Markdown at `/docs/<page>.md`, and through the read-only docs MCP server at `https://bettersupabase.com/mcp`.
|
|
87
206
|
|
|
88
207
|
## Documentation
|
|
89
208
|
|
|
90
|
-
[bettersupabase.com/docs](https://bettersupabase.com/docs)
|
|
209
|
+
The full documentation is at [bettersupabase.com/docs](https://bettersupabase.com/docs), and a runnable app for every adapter is in [`apps/examples`](https://github.com/ScaleDockHQ/better-supabase/tree/main/apps/examples).
|
|
91
210
|
|
|
92
211
|
## License
|
|
93
212
|
|
|
94
|
-
[MIT](https://github.com/ScaleDockHQ/better-supabase/blob/main/LICENSE)
|
|
213
|
+
[MIT](https://github.com/ScaleDockHQ/better-supabase/blob/main/LICENSE) © 2026 ScaleDock
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/auth/actor.d.ts
|
|
2
|
+
/** One level of an RFC 8693 `act` chain: who acts, and who acted before it. */
|
|
3
|
+
interface ActClaim {
|
|
4
|
+
readonly sub: string;
|
|
5
|
+
readonly act?: ActClaim;
|
|
6
|
+
readonly [claim: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Who acts for the user, from the outermost level of the RFC 8693 `act`
|
|
10
|
+
* claim, by its `kind`:
|
|
11
|
+
*
|
|
12
|
+
* - `oauth-client`: an `act` without `kind` (prior actors stay on `chain`),
|
|
13
|
+
* else the OAuth `client_id` of a Supabase OAuth server token. Only this
|
|
14
|
+
* kind is limited to the scopes the user delegated.
|
|
15
|
+
* - `support`: an admin in a support session (`act.kind: "support"`, from
|
|
16
|
+
* `supportClaims`). An `act` without `kind` but with `session_id`, as
|
|
17
|
+
* 0.5.0 minted it, counts as one until 0.6.
|
|
18
|
+
* - `impersonation`: an admin acting as the user (`act.kind:
|
|
19
|
+
* "impersonation"`, from `actClaim`).
|
|
20
|
+
*/
|
|
21
|
+
type SessionActor = {
|
|
22
|
+
readonly kind: "oauth-client";
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly chain?: ActClaim;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "support";
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly sessionId: string;
|
|
29
|
+
/** `act.read_only`; a support token without it is read-only. */
|
|
30
|
+
readonly readOnly: boolean;
|
|
31
|
+
readonly reason?: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "impersonation";
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly reason?: string;
|
|
36
|
+
};
|
|
37
|
+
/** What the user delegated to the actor: the token's OAuth `scope`. */
|
|
38
|
+
interface SessionDelegation {
|
|
39
|
+
readonly scopes: readonly string[];
|
|
40
|
+
readonly chain?: ActClaim;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { SessionActor as n, SessionDelegation as r, ActClaim as t };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
//#region src/notifications/types.d.ts
|
|
3
|
+
/** A subject a notification is about, e.g. `{ type: 'task', id: '42' }`. */
|
|
4
|
+
interface NotificationSubject {
|
|
5
|
+
readonly type: string;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
/** Shown in the list, e.g. the task title, kept as it was at send time. */
|
|
8
|
+
readonly label?: string;
|
|
9
|
+
}
|
|
10
|
+
/** One notification as its recipient sees it. */
|
|
11
|
+
interface NotificationItem<K extends string = string, D = unknown> {
|
|
12
|
+
/** The recipient row id: pass it to `markRead` and `dismiss`. */
|
|
13
|
+
readonly id: string;
|
|
14
|
+
/** The shared event id, the same for every recipient. */
|
|
15
|
+
readonly eventId: string;
|
|
16
|
+
readonly type: K;
|
|
17
|
+
readonly data: D;
|
|
18
|
+
readonly tenant: string | null;
|
|
19
|
+
readonly actorId: string | null;
|
|
20
|
+
readonly subject: NotificationSubject | null;
|
|
21
|
+
readonly summary: string | null;
|
|
22
|
+
readonly actionPath: string | null;
|
|
23
|
+
readonly priority: string | null;
|
|
24
|
+
readonly createdAt: Temporal.Instant;
|
|
25
|
+
readonly readAt: Temporal.Instant | null;
|
|
26
|
+
readonly resolvedAt: Temporal.Instant | null;
|
|
27
|
+
}
|
|
28
|
+
/** What `render` returns: the text for one notification, in one locale. */
|
|
29
|
+
interface RenderedText {
|
|
30
|
+
readonly title: string;
|
|
31
|
+
readonly body?: string;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/notifications/channel.d.ts
|
|
35
|
+
/** One pending delivery a channel sends: the notification and who gets it. */
|
|
36
|
+
interface NotificationMessage {
|
|
37
|
+
readonly deliveryId: string;
|
|
38
|
+
/** The channel name, e.g. `email`. */
|
|
39
|
+
readonly channel: string;
|
|
40
|
+
/** 1 on the first try. */
|
|
41
|
+
readonly attempts: number;
|
|
42
|
+
readonly userId: string;
|
|
43
|
+
/** The recipient's `auth.users` email, for email channels. */
|
|
44
|
+
readonly email: string | null;
|
|
45
|
+
readonly notification: NotificationItem;
|
|
46
|
+
/** What `render` returned for it, when `createNotifications` has one. */
|
|
47
|
+
readonly text?: RenderedText;
|
|
48
|
+
}
|
|
49
|
+
interface NotificationSendResult {
|
|
50
|
+
/** `skipped` when the channel decided not to send. Defaults to `sent`. */
|
|
51
|
+
readonly status?: "sent" | "skipped";
|
|
52
|
+
/** The provider's name, e.g. `resend` or `fcm`. */
|
|
53
|
+
readonly provider?: string;
|
|
54
|
+
/** The provider's id for the message, kept for support and webhooks. */
|
|
55
|
+
readonly providerMessageId?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sends notifications on one channel (email, push, Slack). The delivery
|
|
59
|
+
* worker (`notifications.deliver()`) claims pending deliveries for the
|
|
60
|
+
* channel's `name` and calls `send` for each. Throwing retries the delivery
|
|
61
|
+
* after its lease, up to `maxAttempts`, then marks it `failed`.
|
|
62
|
+
*/
|
|
63
|
+
interface NotificationChannel {
|
|
64
|
+
readonly apiVersion: 1;
|
|
65
|
+
/** Matches the `channel` the deliveries were created for. */
|
|
66
|
+
readonly name: string;
|
|
67
|
+
send(message: NotificationMessage): NotificationSendResult | void | Promise<NotificationSendResult | void>;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { NotificationSubject as a, NotificationItem as i, NotificationMessage as n, RenderedText as o, NotificationSendResult as r, NotificationChannel as t };
|
package/dist/cli/bin.js
CHANGED