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.
Files changed (180) hide show
  1. package/README.md +135 -16
  2. package/dist/actor-DaJjqD5R.d.ts +43 -0
  3. package/dist/channel-FQW-CnGR.d.ts +70 -0
  4. package/dist/cli/bin.js +1 -1
  5. package/dist/cli/codemod-DqV7612m.js +388 -0
  6. package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
  7. package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
  8. package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
  9. package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
  10. package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
  11. package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
  12. package/dist/cli/{doctor-BOym3mbL.js → doctor-C_A-KEAz.js} +533 -50
  13. package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
  14. package/dist/cli/gen-CIdSQZad.js +2 -0
  15. package/dist/cli/{gen-Ccn_p90L.js → gen-CjElD1Vw.js} +321 -88
  16. package/dist/cli/index.d.ts +1 -1
  17. package/dist/cli/index.js +4 -4
  18. package/dist/cli/{init-ClnxRYBF.js → init-DMa6M7hV.js} +2 -2
  19. package/dist/cli/{introspect-DxqfkVIv.js → introspect-BKlaIu_M.js} +2 -2
  20. package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
  21. package/dist/cli/read-sets-VQcPgxU9.js +436 -0
  22. package/dist/cli/{run-CP20LKk_.js → run-Bo_NLT5n.js} +16 -12
  23. package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
  24. package/dist/cli/sql-CVx-1O8G.js +373 -0
  25. package/dist/cli/{version-5mW_8sY1.js → version-BCtlgDQx.js} +1 -1
  26. package/dist/client/index.d.ts +1 -1
  27. package/dist/client/index.js +3 -10
  28. package/dist/clone-BfgmLU90.js +21 -0
  29. package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
  30. package/dist/config/index.d.ts +4 -4
  31. package/dist/config/index.js +4 -4
  32. package/dist/{config-C1CYcm6o.d.ts → config-Bv7gvfzC.d.ts} +185 -11
  33. package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
  34. package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
  35. package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
  36. package/dist/edge/index.d.ts +16 -5
  37. package/dist/edge/index.js +10 -7
  38. package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-BiJZ4t49.d.ts} +1 -1
  39. package/dist/env/index.d.ts +2 -2
  40. package/dist/env/index.js +34 -1
  41. package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
  42. package/dist/events/index.d.ts +3 -2
  43. package/dist/events/index.js +65 -12
  44. package/dist/events-aIO7vsIk.d.ts +230 -0
  45. package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
  46. package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
  47. package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
  48. package/dist/hono/index.d.ts +17 -6
  49. package/dist/hono/index.js +40 -5
  50. package/dist/impersonation-BgSHAYLf.js +133 -0
  51. package/dist/{resolve-BAqQcwMW.d.ts → impersonation-BmePsuxz.d.ts} +36 -6
  52. package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
  53. package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
  54. package/dist/{index-Cf-D2jk1.d.ts → index-CWMDdEr4.d.ts} +2 -2
  55. package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
  56. package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
  57. package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
  58. package/dist/{index-BBdvo-5d.d.ts → index-Dp2VDwog.d.ts} +49 -6
  59. package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
  60. package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
  61. package/dist/index.d.ts +30 -13
  62. package/dist/index.js +10 -9
  63. package/dist/jobs/index.d.ts +147 -101
  64. package/dist/jobs/index.js +744 -237
  65. package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
  66. package/dist/kit-events-CTFDfSwb.js +60 -0
  67. package/dist/kit-transport-B82k1djG.d.ts +33 -0
  68. package/dist/kit-transport-DJlHQUBU.js +55 -0
  69. package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
  70. package/dist/list/index.d.ts +1 -1
  71. package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
  72. package/dist/mcp/index.d.ts +25 -8
  73. package/dist/mcp/index.js +23 -14
  74. package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-Cu96oJdw.d.ts} +2 -16
  75. package/dist/next/index.d.ts +31 -10
  76. package/dist/next/index.js +136 -27
  77. package/dist/notifications/index.d.ts +162 -0
  78. package/dist/notifications/index.js +264 -0
  79. package/dist/openapi/index.d.ts +1 -1
  80. package/dist/openapi/index.js +2 -2
  81. package/dist/orgs/index.d.ts +132 -0
  82. package/dist/orgs/index.js +245 -0
  83. package/dist/orpc/index.d.ts +28 -8
  84. package/dist/orpc/index.js +16 -3
  85. package/dist/otel/index.d.ts +21 -2
  86. package/dist/otel/index.js +50 -9
  87. package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
  88. package/dist/permdock-sql-DgOfeixi.js +51 -0
  89. package/dist/plugin-DwARMuQp.js +59 -0
  90. package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
  91. package/dist/plugins/actor/index.d.ts +5 -2
  92. package/dist/plugins/actor/index.js +22 -8
  93. package/dist/plugins/rules/index.d.ts +2 -2
  94. package/dist/plugins/rules/index.js +15 -10
  95. package/dist/plugins/soft-delete/index.d.ts +3 -3
  96. package/dist/plugins/soft-delete/index.js +14 -5
  97. package/dist/plugins/tenant/index.d.ts +2 -2
  98. package/dist/plugins/tenant/index.js +38 -20
  99. package/dist/plugins/timestamps/index.d.ts +1 -1
  100. package/dist/plugins/timestamps/index.js +4 -3
  101. package/dist/plugins/validation/index.d.ts +2 -2
  102. package/dist/plugins/validation/index.js +26 -6
  103. package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
  104. package/dist/postgres/index.d.ts +5 -5
  105. package/dist/postgres/index.js +20 -11
  106. package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
  107. package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
  108. package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
  109. package/dist/query/index.d.ts +2 -2
  110. package/dist/query/index.js +3 -3
  111. package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
  112. package/dist/queue-BYUCjr59.d.ts +252 -0
  113. package/dist/react/index.d.ts +8 -7
  114. package/dist/react/index.js +4 -3
  115. package/dist/react/server.d.ts +14 -3
  116. package/dist/react/server.js +10 -2
  117. package/dist/react/session.d.ts +7 -1
  118. package/dist/react/session.js +9 -1
  119. package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
  120. package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
  121. package/dist/realtime/index.d.ts +3 -3
  122. package/dist/realtime/index.js +5 -4
  123. package/dist/{refresh-BwYkyNOQ.d.ts → refresh-CFPX0Qft.d.ts} +1 -1
  124. package/dist/{resource-BJcaIfxi.d.ts → resource-DNRFP7KR.d.ts} +5 -5
  125. package/dist/{resource-My_y-Syh.js → resource-mqfJW4YF.js} +22 -7
  126. package/dist/respond-BR1fJWOB.d.ts +377 -0
  127. package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
  128. package/dist/server/index.d.ts +15 -12
  129. package/dist/server/index.js +11 -17
  130. package/dist/{server-BA75B0mT.js → server-B1mZ7CgQ.js} +259 -37
  131. package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
  132. package/dist/signers-Cc6r98SW.d.ts +162 -0
  133. package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
  134. package/dist/sql/index.d.ts +283 -11
  135. package/dist/sql/index.js +8705 -775
  136. package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
  137. package/dist/ssr/index.d.ts +4 -4
  138. package/dist/ssr/index.js +1 -2
  139. package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
  140. package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
  141. package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
  142. package/dist/storage/index.d.ts +4 -4
  143. package/dist/storage/index.js +155 -26
  144. package/dist/support-CEPwlw2G.d.ts +89 -0
  145. package/dist/support-view-B8SlToQX.d.ts +17 -0
  146. package/dist/support-view-g4YLBeqX.js +18 -0
  147. package/dist/support-wHnK6zp-.js +293 -0
  148. package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
  149. package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
  150. package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
  151. package/dist/testing/index.d.ts +132 -10
  152. package/dist/testing/index.js +476 -8
  153. package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
  154. package/dist/user-change-CHFEUro1.js +49 -0
  155. package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
  156. package/dist/{version-Bmf7_FzM.js → version-DOYTYwHP.js} +1 -1
  157. package/dist/{mfa-byfjruR9.js → view-Bn6HNKUe.js} +97 -98
  158. package/dist/{view-B64o81wp.d.ts → view-CbgObAeb.d.ts} +22 -31
  159. package/dist/webhooks/index.d.ts +135 -4
  160. package/dist/webhooks/index.js +588 -2
  161. package/dist/wire-D2dT86vi.js +35 -0
  162. package/package.json +27 -19
  163. package/schemas/config-v1.json +131 -1
  164. package/schemas/doctor-report-v1.json +11 -0
  165. package/skills/better-supabase/SKILL.md +9 -6
  166. package/skills/better-supabase/references/plugins.md +6 -3
  167. package/skills/better-supabase/references/troubleshooting.md +1 -0
  168. package/skills/better-supabase-api/SKILL.md +9 -2
  169. package/skills/better-supabase-api/references/adapters.md +11 -8
  170. package/skills/better-supabase-auth/SKILL.md +18 -7
  171. package/skills/better-supabase-auth/references/permdock.md +78 -11
  172. package/dist/cli/gen-D5cLCULx.js +0 -2
  173. package/dist/cli/sql-D4VbqmXi.js +0 -199
  174. package/dist/events-B21UOhvP.d.ts +0 -84
  175. package/dist/executor-B5IoUxLE.d.ts +0 -195
  176. package/dist/invalidate-CgxmRamI.js +0 -24
  177. package/dist/permdock-sql-DImJULY-.d.ts +0 -17
  178. package/dist/plugin-BqR6wKMB.js +0 -23
  179. package/dist/respond-DgIbdrt8.d.ts +0 -194
  180. package/dist/view-BrJI2LKp.js +0 -45
package/README.md CHANGED
@@ -1,12 +1,64 @@
1
1
  # better-supabase
2
2
 
3
- **A typed data layer, auth glue and CLI for Supabase apps, APIs, MCP servers and jobs.**
3
+ **Typed repositories, auth glue and a CLI for Supabase apps, APIs, MCP servers and jobs.**
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/better-supabase)](https://www.npmjs.com/package/better-supabase)
6
6
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ScaleDockHQ/better-supabase/blob/main/LICENSE)
7
7
  ![TypeScript](https://img.shields.io/badge/TypeScript-6%20%7C%207-3178c6.svg)
8
8
 
9
- `better-supabase gen` extends `supabase gen types` with relation cardinality, unique keys, CHECK unions and typed jsonb. The generated `schema` gives every table a typed repository that compiles to one PostgREST request, returns a `Result` instead of throwing, and runs as the caller so RLS always applies.
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
- ESM only. The package includes the `better-supabase` CLI, which needs Node 24 or later; the runtime entries run on every WinterTC runtime (Node, Deno, Bun, Workers, Supabase Edge Functions). TypeScript 6 and 7 are tested.
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
- Or start with `npx better-supabase init`, which detects your frameworks and prints the install command.
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 result = await db.customers.findMany({
41
- select: ["id", "name"],
42
- where: { status: "active", notes: { some: { kind: "call" } } },
43
- include: { organization: { select: ["name"] } },
44
- orderBy: { name: "asc" },
45
- limit: 20,
46
- });
47
- if (!result.ok) return result; // DbError: kind, message, status, code
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) in `skills/`:
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as run } from "./run-CP20LKk_.js";
2
+ import { i as run } from "./run-Bo_NLT5n.js";
3
3
  import { t as colorEnabled } from "./style-C0NFdJRL.js";
4
4
  import { text } from "node:stream/consumers";
5
5
  //#region src/cli/bin.ts