better-supabase 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +37 -33
  2. package/dist/base64-D2gsioCY.js +42 -0
  3. package/dist/claims-BAf6c7is.js +48 -0
  4. package/dist/cli/_format-Cax5EsM9.js +101 -0
  5. package/dist/cli/bin.js +39 -6
  6. package/dist/cli/command-CK14fP91.js +502 -0
  7. package/dist/cli/compare-BmIWVEui.js +9 -0
  8. package/dist/cli/config-D9R0o1LH.js +1895 -0
  9. package/dist/cli/config-SSKSTUl2.js +2 -0
  10. package/dist/cli/db-DOBPt660.js +52 -0
  11. package/dist/cli/dist-B1MvLv-M.js +129 -0
  12. package/dist/cli/dist-CFgGknmZ.js +1112 -0
  13. package/dist/cli/dist-Du7VnEUG.js +621 -0
  14. package/dist/cli/doctor-BOym3mbL.js +2382 -0
  15. package/dist/cli/env-BZ0uy8XK.js +108 -0
  16. package/dist/cli/env-jIFUADLY.js +511 -0
  17. package/dist/cli/errors-4v4DTtda.js +56 -0
  18. package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
  19. package/dist/cli/gen-Ccn_p90L.js +2008 -0
  20. package/dist/cli/gen-D5cLCULx.js +2 -0
  21. package/dist/cli/index.d.ts +295 -28
  22. package/dist/cli/index.js +184 -3
  23. package/dist/cli/init-ClnxRYBF.js +550 -0
  24. package/dist/cli/introspect-DxqfkVIv.js +81 -0
  25. package/dist/cli/io-k41_z-jT.js +24 -0
  26. package/dist/cli/json5-ChRycquj.js +787 -0
  27. package/dist/cli/jsonc-BUniUpOk.js +2 -0
  28. package/dist/cli/jsonc-D5Fu67jw.js +45 -0
  29. package/dist/cli/keys-ByKHnvP9.js +83 -0
  30. package/dist/cli/ohash-D7bG_EOb.js +277 -0
  31. package/dist/cli/openapi-8WHjxiTJ.js +66 -0
  32. package/dist/cli/project-B_GeXyHh.js +70 -0
  33. package/dist/cli/prompts-BNcNfAve.js +1142 -0
  34. package/dist/cli/prompts-BTYQGunc.js +2 -0
  35. package/dist/cli/read-sets-CMa29t9a.js +971 -0
  36. package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
  37. package/dist/cli/run-CP20LKk_.js +347 -0
  38. package/dist/cli/seed-5J6eu_Xg.js +98 -0
  39. package/dist/cli/skills-LJJIhLFR.js +137 -0
  40. package/dist/cli/sql-D4VbqmXi.js +199 -0
  41. package/dist/cli/style-C0NFdJRL.js +29 -0
  42. package/dist/cli/toml-DB-MNnFs.js +424 -0
  43. package/dist/{version-Cqq1m3UY.js → cli/version-5mW_8sY1.js} +1 -1
  44. package/dist/cli/yaml-CYCbJ6et.js +2252 -0
  45. package/dist/client/index.d.ts +2 -2
  46. package/dist/client/index.js +15 -25
  47. package/dist/{compiler-CZ9nIuqy.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
  48. package/dist/config/index.d.ts +4 -3
  49. package/dist/config/index.js +5 -3
  50. package/dist/config-BpxDfnbg.js +129 -0
  51. package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
  52. package/dist/{config-ClDXmVtY.js → config-CbvFmGD8.js} +14 -137
  53. package/dist/{define-BXMYD9AY.js → define-Bh0AqTik.js} +221 -120
  54. package/dist/{define-e3zI4_4g.d.ts → define-CRS2MRV3.d.ts} +36 -31
  55. package/dist/edge/index.d.ts +9 -13
  56. package/dist/edge/index.js +51 -25
  57. package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
  58. package/dist/env/index.js +203 -1
  59. package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
  60. package/dist/events/index.d.ts +1 -1
  61. package/dist/events/index.js +139 -1
  62. package/dist/{events-DOt3nSO-.d.ts → events-B21UOhvP.d.ts} +4 -4
  63. package/dist/{executor-CGdNQ27g.d.ts → executor-B5IoUxLE.d.ts} +2 -2
  64. package/dist/{executor-DeppvirK.js → executor-CVt6dD0B.js} +9 -4
  65. package/dist/{executor-BYXQ9AGn.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
  66. package/dist/hono/index.d.ts +17 -24
  67. package/dist/hono/index.js +11 -11
  68. package/dist/{index-DuP14VHo.d.ts → index-528Ugmyp.d.ts} +30 -10
  69. package/dist/{index-Bw1bwtC0.d.ts → index-BAy4tsAP.d.ts} +28 -15
  70. package/dist/{index-CBDufH1M.d.ts → index-BBdvo-5d.d.ts} +24 -26
  71. package/dist/{index-DbxFr7XG.d.ts → index-BhHEC04r.d.ts} +9 -4
  72. package/dist/index-Cf-D2jk1.d.ts +50 -0
  73. package/dist/{index-Ciu5Xlml.d.ts → index-D5IfUIDh.d.ts} +6 -5
  74. package/dist/{index-K-3WM2Hp.d.ts → index-DFmOpBGC.d.ts} +13 -13
  75. package/dist/{index-D4v7sncu.d.ts → index-RlLXd8dL.d.ts} +13 -13
  76. package/dist/index.d.ts +17 -17
  77. package/dist/index.js +23 -14
  78. package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
  79. package/dist/jobs/index.d.ts +33 -26
  80. package/dist/jobs/index.js +44 -22
  81. package/dist/{json-schema-BpYqkCKf.js → json-schema-DP6V7cbr.js} +8 -3
  82. package/dist/lint/index.js +1 -1
  83. package/dist/list/index.d.ts +2 -2
  84. package/dist/list/index.js +131 -96
  85. package/dist/{live-DJyZMD6w.d.ts → live-BHakSWWo.d.ts} +26 -16
  86. package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
  87. package/dist/lookup-BaP5bI27.js +18 -0
  88. package/dist/mcp/index.d.ts +34 -27
  89. package/dist/mcp/index.js +59 -25
  90. package/dist/{mfa-TVia554c.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
  91. package/dist/{mfa-CpLfJSde.js → mfa-byfjruR9.js} +190 -46
  92. package/dist/next/image/index.js +6 -3
  93. package/dist/next/index.d.ts +64 -48
  94. package/dist/next/index.js +63 -50
  95. package/dist/openapi/index.d.ts +1 -1
  96. package/dist/openapi/index.js +61 -38
  97. package/dist/orpc/index.d.ts +18 -21
  98. package/dist/orpc/index.js +12 -12
  99. package/dist/otel/index.d.ts +5 -5
  100. package/dist/otel/index.js +84 -63
  101. package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-C4KFzLc9.js} +12 -7
  102. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  103. package/dist/{plugin-C4TR9J0Z.d.ts → plugin-bNhvJwRh.d.ts} +5 -4
  104. package/dist/plugins/actor/index.d.ts +1 -1
  105. package/dist/plugins/actor/index.js +1 -1
  106. package/dist/plugins/rules/index.d.ts +2 -2
  107. package/dist/plugins/rules/index.js +46 -16
  108. package/dist/plugins/soft-delete/index.d.ts +3 -3
  109. package/dist/plugins/soft-delete/index.js +4 -4
  110. package/dist/plugins/tenant/index.d.ts +3 -3
  111. package/dist/plugins/tenant/index.js +10 -4
  112. package/dist/plugins/timestamps/index.d.ts +1 -1
  113. package/dist/plugins/timestamps/index.js +2 -2
  114. package/dist/plugins/validation/index.d.ts +2 -2
  115. package/dist/plugins/validation/index.js +3 -3
  116. package/dist/pool-DFwEQwmN.d.ts +78 -0
  117. package/dist/postgres/index.d.ts +10 -5
  118. package/dist/postgres/index.js +37 -16
  119. package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
  120. package/dist/{problem-BTkTtRV9.js → problem-BihjPR0t.js} +11 -5
  121. package/dist/{problem-DlgB0lYl.d.ts → problem-U8UEKccv.d.ts} +6 -4
  122. package/dist/query/index.d.ts +2 -2
  123. package/dist/query/index.js +2 -2
  124. package/dist/{query-ChvDbsDE.js → query-BMMSWbI7.js} +19 -19
  125. package/dist/react/index.d.ts +7 -7
  126. package/dist/react/index.js +2 -2
  127. package/dist/react/server.d.ts +6 -6
  128. package/dist/react/server.js +3 -3
  129. package/dist/react/session.d.ts +21 -2
  130. package/dist/react/session.js +19 -1
  131. package/dist/{react-C83BdBGt.js → react-CQHMaESR.js} +59 -55
  132. package/dist/{read-set-4lI3W_gZ.js → read-set-FWK8qQ58.js} +51 -16
  133. package/dist/realtime/index.d.ts +4 -3
  134. package/dist/realtime/index.js +19 -17
  135. package/dist/{index-CG69s15v.d.ts → refresh-BwYkyNOQ.d.ts} +7 -3
  136. package/dist/{resolve-CjHILk2w.d.ts → resolve-BAqQcwMW.d.ts} +17 -11
  137. package/dist/{resource-CgZa3YG_.d.ts → resource-BJcaIfxi.d.ts} +17 -8
  138. package/dist/{resource-BHEWmQJK.js → resource-My_y-Syh.js} +43 -21
  139. package/dist/{respond-Dne1ARSa.d.ts → respond-DgIbdrt8.d.ts} +39 -14
  140. package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
  141. package/dist/{result-CTkCZ6JA.d.ts → result-CqEsbgYs.d.ts} +16 -11
  142. package/dist/server/index.d.ts +38 -13
  143. package/dist/server/index.js +43 -14
  144. package/dist/{respond-DMcbQwfN.js → server-BA75B0mT.js} +151 -96
  145. package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
  146. package/dist/{simplify-bwJpLnaS.js → simplify-Cqvfr9dj.js} +76 -39
  147. package/dist/sql/index.d.ts +139 -0
  148. package/dist/sql/index.js +1983 -0
  149. package/dist/{sql-DTjA1LPj.js → sql-B34HU2DI.js} +74 -17
  150. package/dist/ssr/index.d.ts +4 -4
  151. package/dist/ssr/index.js +2 -2
  152. package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
  153. package/dist/{standard-CyhRzctb.d.ts → standard-BsaCYQJc.d.ts} +1 -1
  154. package/dist/{stats-C5W-rBfR.js → stats-C_YxA2r1.js} +40 -14
  155. package/dist/storage/index.d.ts +4 -3
  156. package/dist/storage/index.js +548 -1
  157. package/dist/{tables-rZxRxNPW.js → tables-Cb-3_VWc.js} +19 -5
  158. package/dist/temporal-BwshQk0W.js +27 -0
  159. package/dist/temporal-required-CBNHT6ni.js +19 -0
  160. package/dist/testing/index.d.ts +25 -24
  161. package/dist/testing/index.js +165 -66
  162. package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
  163. package/dist/version-Bmf7_FzM.js +4 -0
  164. package/dist/{view-D1s0wa0T.d.ts → view-B64o81wp.d.ts} +36 -6
  165. package/dist/{view-aOYizTeE.js → view-BrJI2LKp.js} +11 -2
  166. package/dist/webhooks/index.d.ts +19 -17
  167. package/dist/webhooks/index.js +1 -1
  168. package/dist/{webhooks-DMipjhSP.js → webhooks-Bh_Jf_-l.js} +45 -24
  169. package/package.json +50 -22
  170. package/schemas/config-v1.json +31 -1
  171. package/schemas/doctor-report-v1.json +17 -1
  172. package/schemas/snapshot-v2.json +35 -0
  173. package/skills/better-supabase/SKILL.md +77 -18
  174. package/skills/better-supabase/references/plugins.md +21 -22
  175. package/skills/better-supabase/references/troubleshooting.md +38 -18
  176. package/skills/better-supabase-api/SKILL.md +66 -18
  177. package/skills/better-supabase-api/references/adapters.md +64 -41
  178. package/skills/better-supabase-auth/SKILL.md +146 -0
  179. package/skills/better-supabase-auth/references/permdock.md +55 -0
  180. package/skills/better-supabase-testing/SKILL.md +68 -11
  181. package/dist/casing-da0uRTqt.js +0 -14
  182. package/dist/claims-CoTobNC-.js +0 -25
  183. package/dist/cli-DrSgDOQC.js +0 -6646
  184. package/dist/env-BEToab0I.js +0 -204
  185. package/dist/events-BuzTgn2D.js +0 -135
  186. package/dist/pool-R2m_DkXi.d.ts +0 -37
  187. package/dist/seed-CzecODcJ.js +0 -101
  188. package/dist/session-C3LxnQ0e.d.ts +0 -22
  189. package/dist/session-CqEwzVup.js +0 -20
  190. package/dist/storage-DuGn38fr.js +0 -527
@@ -1,11 +1,12 @@
1
- import { n as dbError, o as isDbError, r as dbErrorOf } from "./errors-Bl3rkOzo.js";
2
- import { i as ok, n as err, o as toDbError, t as AsyncResult } from "./result-DWXatkd6.js";
3
- import { a as postgrestExecutor, n as StatsRecorder } from "./stats-C5W-rBfR.js";
4
- import { a as problemResponse } from "./problem-BTkTtRV9.js";
5
- import { i as loadEnv } from "./env-BEToab0I.js";
6
- import { h as actClaim, i as authContext, o as resolveAuth, r as checkAal } from "./mfa-CpLfJSde.js";
7
- import { n as postgresExecutor } from "./executor-DeppvirK.js";
1
+ import { n as dbError, o as isDbError, r as dbErrorOf } from "./errors-L0xK41y_.js";
2
+ import { i as ok, n as err, o as toDbError, t as AsyncResult } from "./result-Bj6yt33D.js";
3
+ import { a as postgrestExecutor, n as StatsRecorder } from "./stats-C_YxA2r1.js";
4
+ import { a as problemResponse } from "./problem-BihjPR0t.js";
5
+ import { loadEnv } from "./env/index.js";
6
+ import { _ as delegationOf, g as actorOf, i as authContext, o as prefetchJwks, r as checkAal, s as resolveAuth, v as actClaim } from "./mfa-byfjruR9.js";
7
+ import { parseCookieHeader, serializeCookieHeader } from "@supabase/ssr";
8
8
  import { createClient } from "@supabase/supabase-js";
9
+ import { PostgrestClient } from "@supabase/postgrest-js";
9
10
  //#region src/server/replicas.ts
10
11
  /** Cookie holding the epoch ms until which reads stay on the primary. */
11
12
  const PRIMARY_COOKIE = "bs-primary-until";
@@ -26,22 +27,21 @@ function replicaState(pinnedUntil = 0, now = Date.now()) {
26
27
  }
27
28
  };
28
29
  }
29
- /** `bs-primary-until` from a `cookie` header, or 0. */
30
- function pinnedUntil(cookieHeader) {
31
- if (!cookieHeader) return 0;
32
- for (const part of cookieHeader.split(";")) {
33
- const [name, value] = part.trim().split("=", 2);
34
- if (name === "bs-primary-until") {
35
- const until = Number(value);
36
- return Number.isFinite(until) ? until : 0;
37
- }
38
- }
39
- return 0;
30
+ /** `bs-primary-until` from a `cookie` header or parsed cookies, or 0. */
31
+ function pinnedUntil(cookies) {
32
+ if (!cookies) return 0;
33
+ const value = (typeof cookies === "string" ? parseCookieHeader(cookies) : cookies).find((cookie) => cookie.name === PRIMARY_COOKIE)?.value;
34
+ const until = Number(value);
35
+ return value !== void 0 && Number.isFinite(until) ? until : 0;
40
36
  }
41
37
  /** A `Set-Cookie` value keeping the next requests on the primary for `pinMs`. */
42
38
  function primaryCookie(pinMs, now = Date.now()) {
43
- const seconds = Math.max(1, Math.ceil(pinMs / 1e3));
44
- return `${PRIMARY_COOKIE}=${String(now + pinMs)}; Path=/; Max-Age=${String(seconds)}; HttpOnly; SameSite=Lax`;
39
+ return serializeCookieHeader(PRIMARY_COOKIE, String(now + pinMs), {
40
+ path: "/",
41
+ maxAge: Math.max(1, Math.ceil(pinMs / 1e3)),
42
+ httpOnly: true,
43
+ sameSite: "lax"
44
+ });
45
45
  }
46
46
  /**
47
47
  * Reads go to `replica` until the context is pinned; writes, and every read
@@ -93,7 +93,7 @@ function fromAuthError(raw) {
93
93
  * Removes a user's Storage objects, then the Auth user, then announces the
94
94
  * delete. Rows referencing `auth.users` follow their foreign keys.
95
95
  */
96
- function deleteAccount(sb, service, userId, options = {}) {
96
+ function deleteAccount(betterSupabase, service, userId, options = {}) {
97
97
  return AsyncResult.from(async () => {
98
98
  const client = service();
99
99
  const removed = {};
@@ -121,13 +121,13 @@ function deleteAccount(sb, service, userId, options = {}) {
121
121
  return err(fromAuthError(cause));
122
122
  }
123
123
  const context = options.context ?? {};
124
- sb.events.emit("mutation", {
124
+ betterSupabase.events.emit("mutation", {
125
125
  table: "auth.users",
126
126
  kind: "delete",
127
127
  rows: [{ id: userId }],
128
128
  context
129
129
  });
130
- for (const table of options.cascades ?? []) sb.events.emit("mutation", {
130
+ for (const table of options.cascades ?? []) betterSupabase.events.emit("mutation", {
131
131
  table,
132
132
  kind: "delete",
133
133
  rows: [],
@@ -140,6 +140,78 @@ function deleteAccount(sb, service, userId, options = {}) {
140
140
  });
141
141
  }
142
142
  //#endregion
143
+ //#region src/server/respond.ts
144
+ /** The scopes a delegated user token lacks; empty for the user's own session. */
145
+ function missingScopes(auth, required) {
146
+ if (required.length === 0) return [];
147
+ const outcome = actorOf(auth.claims);
148
+ if (!outcome.ok || !outcome.actor) return [];
149
+ const granted = new Set(delegationOf(auth.claims)?.scopes);
150
+ return required.filter((scope) => !granted.has(scope));
151
+ }
152
+ /** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
153
+ function guard(auth, allow = ["user"], aal = "aal1", scopes = []) {
154
+ if (auth.kind === "invalid") return auth.error;
155
+ if (allow.includes(auth.kind)) {
156
+ const denied = checkAal(auth, aal);
157
+ if (denied || auth.kind !== "user") return denied;
158
+ const missing = missingScopes(auth, scopes);
159
+ return missing.length > 0 ? dbError("forbidden", `The token lacks the scope ${missing.join(" ")}`, {
160
+ code: "INSUFFICIENT_SCOPE",
161
+ scopes
162
+ }) : void 0;
163
+ }
164
+ return auth.kind === "anon" ? dbError("unauthorized", "Sign in to continue", { code: "MISSING_CREDENTIALS" }) : dbError("forbidden", `A ${auth.kind} caller is not allowed here`);
165
+ }
166
+ function isResult(value) {
167
+ return typeof value === "object" && value !== null && "ok" in value && "data" in value && "error" in value;
168
+ }
169
+ /**
170
+ * Settles a handler's return value: `Result`s and `AsyncResult`s unwrap,
171
+ * thrown `DbException`s (or errors whose `cause` is a `DbError`) become
172
+ * errors, anything else is data. Other thrown
173
+ * errors propagate.
174
+ */
175
+ async function settle(run) {
176
+ try {
177
+ const value = await run();
178
+ if (!isResult(value)) return {
179
+ ok: true,
180
+ data: value
181
+ };
182
+ if (value.ok) return {
183
+ ok: true,
184
+ data: value.data
185
+ };
186
+ return {
187
+ ok: false,
188
+ error: isDbError(value.error) ? value.error : toDbError(value.error)
189
+ };
190
+ } catch (cause) {
191
+ const thrown = dbErrorOf(cause);
192
+ if (thrown) return {
193
+ ok: false,
194
+ error: thrown
195
+ };
196
+ throw cause;
197
+ }
198
+ }
199
+ /** A handler's value as a response: `Response` as is, data as JSON, errors as Problem Details. */
200
+ async function respond(run, options = {}) {
201
+ const settled = await settle(run);
202
+ if (!settled.ok) return problemResponse(settled.error, options);
203
+ if (settled.data instanceof Response) return settled.data;
204
+ if (settled.data === void 0) return new Response(null, { status: 204 });
205
+ return Response.json(settled.data, { status: options.status ?? 200 });
206
+ }
207
+ /** Prefetching the JWKS would add a network call to every test that builds a server. */
208
+ function defaultPrefetchJwks() {
209
+ return typeof process === "undefined" || process.env["NODE_ENV"] !== "test";
210
+ }
211
+ function defaultExpose() {
212
+ return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
213
+ }
214
+ //#endregion
143
215
  //#region src/server/server.ts
144
216
  const STATELESS = {
145
217
  persistSession: false,
@@ -162,13 +234,19 @@ function withExtra(base, extra) {
162
234
  }
163
235
  /**
164
236
  * Server-side entry point for APIs, jobs, MCP servers and framework
165
- * adapters. Holds the secrets; `sb` stays isomorphic.
237
+ * adapters. Holds the secrets; `betterSupabase` stays isomorphic.
166
238
  */
167
- function createServer(sb, options = {}) {
239
+ function createServer(betterSupabase, options = {}) {
168
240
  let loaded;
169
241
  const env = () => loaded ??= options.env ?? loadEnv();
170
242
  let adminClient;
171
243
  let anonClient;
244
+ if ((options.prefetchJwks ?? defaultPrefetchJwks()) && options.auth?.jwks === void 0) try {
245
+ prefetchJwks({
246
+ ...options.auth,
247
+ env: env()
248
+ });
249
+ } catch {}
172
250
  const secretKey = () => {
173
251
  const key = env().secretKey;
174
252
  if (!key) throw new TypeError("admin() needs SUPABASE_SECRET_KEY; it is not set");
@@ -204,13 +282,38 @@ function createServer(sb, options = {}) {
204
282
  }
205
283
  };
206
284
  const supabaseFor = (auth, headers = {}) => supabaseAt(env().url, auth, headers);
207
- const dbFor = (auth, context = {}) => sb.connect(supabaseFor(auth), {
208
- ...authContext(auth),
209
- ...context
285
+ /**
286
+ * The client queries run on. A user's requests need only PostgREST, so they
287
+ * skip building Realtime, Storage and Auth clients per request; the shared
288
+ * service and anon clients are built once anyway.
289
+ */
290
+ const restAt = (url, auth, headers = {}) => {
291
+ if (auth.kind !== "user") return supabaseAt(url, auth, headers);
292
+ const key = env().publishableKey;
293
+ return new PostgrestClient(new URL("rest/v1", url.endsWith("/") ? url : `${url}/`).href, { headers: {
294
+ ...headers,
295
+ apikey: key,
296
+ Authorization: `Bearer ${auth.token}`
297
+ } });
298
+ };
299
+ /** `$client` stays the full Supabase client, built when first read. */
300
+ const withLazyClient = (db, client) => Object.defineProperty(db, "$client", {
301
+ get: client,
302
+ enumerable: true,
303
+ configurable: true
210
304
  });
305
+ const dbFor = (auth, context = {}) => {
306
+ let supabase;
307
+ const rest = restAt(env().url, auth);
308
+ const db = betterSupabase.connect(rest, {
309
+ ...authContext(auth),
310
+ ...context
311
+ });
312
+ return withLazyClient(db, () => supabase ??= supabaseFor(auth));
313
+ };
211
314
  const sqlFor = (claims, context, stats) => {
212
- if (!options.postgres) throw new TypeError("Direct Postgres access needs createServer(sb, { postgres: createPostgres() })");
213
- return sb.connect(postgresExecutor(options.postgres.asUser(claims)), context, stats ? { stats } : {});
315
+ if (!options.postgres) throw new TypeError("Direct Postgres access needs createServer(betterSupabase, { postgres: createPostgres() })");
316
+ return betterSupabase.connect(options.postgres.executorFor(claims), context, stats ? { stats } : {});
214
317
  };
215
318
  /** Clients and repositories are built on first access: most scopes use one of them. */
216
319
  const contextFor = (resolution, headers, parent, until = 0) => {
@@ -237,10 +340,12 @@ function createServer(sb, options = {}) {
237
340
  get db() {
238
341
  if (db) return db;
239
342
  const url = replicaUrl();
240
- db = sb.connect(client(), context, {
343
+ const rest = supabase ?? restAt(env().url, auth, headers);
344
+ const connected = betterSupabase.connect(rest, context, {
241
345
  stats: recorder,
242
- ...url && replica ? { executor: routedExecutor(postgrestExecutor(client()), postgrestExecutor(supabaseAt(url, auth, headers)), replica) } : {}
346
+ ...url && replica ? { executor: routedExecutor(postgrestExecutor(rest), postgrestExecutor(restAt(url, auth, headers)), replica) } : {}
243
347
  });
348
+ db = withLazyClient(connected, client);
244
349
  return db;
245
350
  },
246
351
  get replica() {
@@ -248,31 +353,32 @@ function createServer(sb, options = {}) {
248
353
  return replica;
249
354
  },
250
355
  get sql() {
251
- if (!options.postgres || !sqlClaims) return void 0;
356
+ if (!options.postgres || !sqlClaims) return;
252
357
  sql ??= sqlFor(sqlClaims, context, recorder);
253
358
  return sql;
254
359
  },
255
360
  stats: () => recorder.snapshot()
256
361
  };
257
362
  };
363
+ const fromResolution = (resolution, request, contextOptions) => contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? (readUrl() ? pinnedUntil(resolution.requestCookies) : 0));
258
364
  const resolve = async (request, resolveOptions = {}) => {
259
- const claims = options.auth?.claims ?? sb.claimsSchema;
260
- const userMetadata = sb.userMetadataSchema;
365
+ const claims = options.auth?.claims ?? betterSupabase.claimsSchema;
366
+ const userMetadata = betterSupabase.userMetadataSchema;
261
367
  const resolution = await resolveAuth(request, {
262
- logger: sb.events.logger,
368
+ logger: betterSupabase.events.logger,
263
369
  ...options.auth,
264
370
  ...claims ? { claims } : {},
265
371
  ...userMetadata ? { userMetadata } : {},
266
372
  env: env(),
267
- ...resolveOptions.refresh !== void 0 ? { refresh: resolveOptions.refresh } : {},
373
+ ...resolveOptions.refresh === void 0 ? {} : { refresh: resolveOptions.refresh },
268
374
  onRefresh: (event) => {
269
375
  options.auth?.onRefresh?.(event);
270
- sb.events.emit("refresh", event);
376
+ betterSupabase.events.emit("refresh", event);
271
377
  }
272
378
  });
273
- if (sb.events.has("auth")) {
379
+ if (betterSupabase.events.has("auth")) {
274
380
  const { auth } = resolution;
275
- sb.events.emit("auth", {
381
+ betterSupabase.events.emit("auth", {
276
382
  source: auth.kind === "user" ? auth.source === "cookie" ? "cookie" : "bearer" : auth.kind === "service" ? "bearer" : "none",
277
383
  ok: auth.kind !== "invalid",
278
384
  ...auth.kind === "user" ? { userId: auth.user.id } : {}
@@ -296,9 +402,10 @@ function createServer(sb, options = {}) {
296
402
  }, {}, contextOptions.stats, contextOptions.pinnedUntil),
297
403
  async context(request, contextOptions = {}) {
298
404
  const resolution = await resolve(request, { refresh: contextOptions.refresh ?? false });
299
- return contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? pinnedUntil(request.headers.get("cookie")));
405
+ return fromResolution(resolution, request, contextOptions);
300
406
  },
301
- admin: (context = {}) => sb.connect(serviceClient(), {
407
+ contextFromResolution: (resolution, request, contextOptions = {}) => fromResolution(resolution, request, contextOptions),
408
+ admin: (context = {}) => betterSupabase.connect(serviceClient(), {
302
409
  actor: {
303
410
  id: "service",
304
411
  kind: "service",
@@ -307,7 +414,7 @@ function createServer(sb, options = {}) {
307
414
  claims: { role: "service_role" },
308
415
  ...context
309
416
  }),
310
- deleteAccount: (userId, deleteOptions) => deleteAccount(sb, serviceClient, userId, deleteOptions),
417
+ deleteAccount: (userId, deleteOptions) => deleteAccount(betterSupabase, serviceClient, userId, deleteOptions),
311
418
  actingAs: (userId, claims = {}, impersonation) => {
312
419
  const full = {
313
420
  role: "authenticated",
@@ -328,56 +435,4 @@ function createServer(sb, options = {}) {
328
435
  };
329
436
  }
330
437
  //#endregion
331
- //#region src/server/respond.ts
332
- /** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
333
- function guard(auth, allow = ["user"], aal = "aal1") {
334
- if (auth.kind === "invalid") return auth.error;
335
- if (allow.includes(auth.kind)) return checkAal(auth, aal);
336
- return auth.kind === "anon" ? dbError("unauthorized", "Sign in to continue", { code: "MISSING_CREDENTIALS" }) : dbError("forbidden", `A ${auth.kind} caller is not allowed here`);
337
- }
338
- function isResult(value) {
339
- return typeof value === "object" && value !== null && "ok" in value && "data" in value && "error" in value;
340
- }
341
- /**
342
- * Settles a handler's return value: `Result`s and `AsyncResult`s unwrap,
343
- * thrown `DbException`s (or errors whose `cause` is a `DbError`) become
344
- * errors, anything else is data. Other thrown
345
- * errors propagate.
346
- */
347
- async function settle(run) {
348
- try {
349
- const value = await run();
350
- if (!isResult(value)) return {
351
- ok: true,
352
- data: value
353
- };
354
- if (value.ok) return {
355
- ok: true,
356
- data: value.data
357
- };
358
- return {
359
- ok: false,
360
- error: isDbError(value.error) ? value.error : toDbError(value.error)
361
- };
362
- } catch (cause) {
363
- const thrown = dbErrorOf(cause);
364
- if (thrown) return {
365
- ok: false,
366
- error: thrown
367
- };
368
- throw cause;
369
- }
370
- }
371
- /** A handler's value as a response: `Response` as is, data as JSON, errors as Problem Details. */
372
- async function respond(run, options = {}) {
373
- const settled = await settle(run);
374
- if (!settled.ok) return problemResponse(settled.error, options);
375
- if (settled.data instanceof Response) return settled.data;
376
- if (settled.data === void 0) return new Response(null, { status: 204 });
377
- return Response.json(settled.data, { status: options.status ?? 200 });
378
- }
379
- function defaultExpose() {
380
- return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
381
- }
382
- //#endregion
383
- export { settle as a, withExtra as c, primaryCookie as d, respond as i, DEFAULT_PIN_MS as l, guard as n, createServer as o, isResult as r, extendServer as s, defaultExpose as t, PRIMARY_COOKIE as u };
438
+ export { guard as a, settle as c, primaryCookie as d, defaultExpose as i, DEFAULT_PIN_MS as l, extendServer as n, isResult as o, withExtra as r, respond as s, createServer as t, PRIMARY_COOKIE as u };
@@ -1,4 +1,5 @@
1
- import { n as dbError, t as DbException } from "./errors-Bl3rkOzo.js";
1
+ import { n as dbError, t as DbException } from "./errors-L0xK41y_.js";
2
+ import { t as lookupOf } from "./lookup-BaP5bI27.js";
2
3
  //#region src/plugins/shared.ts
3
4
  /** Database column name for an app column, or `undefined` if it is unknown. */
4
5
  function dbName(table, app) {
@@ -44,13 +45,13 @@ function forbidden(message) {
44
45
  /** Database-keyed row to app-keyed row. */
45
46
  function toApp(table, row) {
46
47
  const out = {};
47
- for (const [app, meta] of Object.entries(table.columns)) if (meta.db in row) out[app] = row[meta.db];
48
+ for (const [app, meta] of lookupOf(table).entries) if (meta.db in row) out[app] = row[meta.db];
48
49
  return out;
49
50
  }
50
51
  /** App-keyed row to database-keyed row. Unknown keys are dropped. */
51
52
  function toDb(table, row) {
52
53
  const out = {};
53
- for (const [app, meta] of Object.entries(table.columns)) if (app in row) out[meta.db] = row[app];
54
+ for (const [app, meta] of lookupOf(table).entries) if (app in row) out[meta.db] = row[app];
54
55
  return out;
55
56
  }
56
57
  //#endregion
@@ -1,4 +1,7 @@
1
- import { n as dbError, t as DbException } from "./errors-Bl3rkOzo.js";
1
+ import { n as dbError, t as DbException } from "./errors-L0xK41y_.js";
2
+ import { t as lookupOf } from "./lookup-BaP5bI27.js";
3
+ import { i as temporalText } from "./temporal-BwshQk0W.js";
4
+ import { t as temporal } from "./temporal-required-CBNHT6ni.js";
2
5
  import { i as or, r as not, t as and } from "./types-6cYK9pbJ.js";
3
6
  //#region src/schema/define.ts
4
7
  /**
@@ -27,17 +30,19 @@ function relationMeta(meta, table, relation) {
27
30
  }
28
31
  //#endregion
29
32
  //#region src/ir/codec.ts
30
- /** Wire form of an app value: `Date` to ISO text, `bigint` to decimal text. */
33
+ /** Wire form of an app value: Temporal values to ISO text, `bigint` to decimal text. */
31
34
  function encodeValue(value) {
32
- if (value instanceof Date) return value.toISOString();
33
- if (typeof value === "bigint") return value.toString();
35
+ if (typeof value !== "object" || value === null) return typeof value === "bigint" ? value.toString() : value;
36
+ const text = temporalText(value);
37
+ if (text !== void 0) return text;
34
38
  if (Array.isArray(value)) return value.map(encodeValue);
35
39
  return value;
36
40
  }
37
41
  function decodeScalar(codec, value) {
38
42
  if (value === null || value === void 0) return value;
39
43
  switch (codec) {
40
- case "date": return new Date(value);
44
+ case "instant": return temporal().Instant.from(String(value));
45
+ case "plainDateTime": return temporal().PlainDateTime.from(String(value));
41
46
  case "bigint": return BigInt(value);
42
47
  case "string": return String(value);
43
48
  default: return codec;
@@ -48,6 +53,15 @@ function decode(codec, value) {
48
53
  }
49
54
  /** Whether decoding `selection` changes anything, so plain reads skip the walk. */
50
55
  function needsDecoding(selection) {
56
+ let needs = decodes.get(selection);
57
+ if (needs === void 0) {
58
+ needs = computeNeedsDecoding(selection);
59
+ decodes.set(selection, needs);
60
+ }
61
+ return needs;
62
+ }
63
+ const decodes = /* @__PURE__ */ new WeakMap();
64
+ function computeNeedsDecoding(selection) {
51
65
  return selection.aggregate !== void 0 || selection.columns.some((column) => column.codec !== void 0) || selection.includes.some((include) => include.count !== void 0 || include.aggregate !== void 0 || needsDecoding(include.selection));
52
66
  }
53
67
  /** PostgREST returns `[{ count }]` for a `(count)` embed; SQL returns the number. */
@@ -63,38 +77,51 @@ function measureOf(measure, value) {
63
77
  return measure.codec ? decodeScalar(measure.codec, value) : value;
64
78
  }
65
79
  /** Adds `value` under `row[key][name]`, next to what is already there. */
66
- function fold(row, key, name, value) {
67
- row[key] = {
68
- ...row[key] ?? {},
69
- [name]: value
70
- };
80
+ function fold(row, folded, key, name, value) {
81
+ let target = folded.get(key);
82
+ if (!target) {
83
+ target = { ...row[key] };
84
+ folded.set(key, target);
85
+ row[key] = target;
86
+ }
87
+ target[name] = value;
88
+ }
89
+ const droppedKeys = /* @__PURE__ */ new WeakMap();
90
+ /** Wire keys that decoding folds into `_count`, `_sum` and the rest. */
91
+ function droppedOf(selection) {
92
+ let dropped = droppedKeys.get(selection);
93
+ if (!dropped) {
94
+ dropped = /* @__PURE__ */ new Set([...selection.aggregate?.measures.map((measure) => measure.key) ?? [], ...selection.includes.filter((include) => include.count !== void 0 || include.aggregate !== void 0).map((include) => include.alias)]);
95
+ droppedKeys.set(selection, dropped);
96
+ }
97
+ return dropped;
71
98
  }
72
99
  function decodeRow(selection, row) {
73
- const out = { ...row };
100
+ const dropped = droppedOf(selection);
101
+ const out = {};
102
+ for (const key in row) if (!dropped.has(key) && Object.hasOwn(row, key)) out[key] = row[key];
103
+ const folded = /* @__PURE__ */ new Map();
74
104
  for (const column of selection.columns) if (column.codec && column.alias in out) out[column.alias] = decode(column.codec, out[column.alias]);
75
105
  if (selection.aggregate) {
76
106
  if (selection.aggregate.count) out["_count"] = countOf(out["_count"]);
77
107
  for (const measure of selection.aggregate.measures) {
78
- const value = out[measure.key];
79
- delete out[measure.key];
80
- fold(out, `_${measure.fn}`, measure.alias, measureOf(measure, value));
108
+ const value = row[measure.key];
109
+ fold(out, folded, `_${measure.fn}`, measure.alias, measureOf(measure, value));
81
110
  }
82
111
  }
83
112
  for (const include of selection.includes) {
84
- const value = out[include.alias];
113
+ const value = row[include.alias];
85
114
  if (include.count !== void 0) {
86
- if (!(include.alias in out)) continue;
87
- delete out[include.alias];
88
- fold(out, "_count", include.count, countOf(value));
115
+ if (!(include.alias in row)) continue;
116
+ fold(out, folded, "_count", include.count, countOf(value));
89
117
  continue;
90
118
  }
91
119
  if (include.aggregate !== void 0) {
92
- if (!(include.alias in out)) continue;
93
- delete out[include.alias];
120
+ if (!(include.alias in row)) continue;
94
121
  const inner = Array.isArray(value) ? value[0] : value;
95
122
  const measures = {};
96
123
  for (const measure of include.selection.aggregate?.measures ?? []) measures[measure.alias] = measureOf(measure, inner?.[measure.key]);
97
- fold(out, `_${include.aggregate.fn}`, include.aggregate.name, measures);
124
+ fold(out, folded, `_${include.aggregate.fn}`, include.aggregate.name, measures);
98
125
  continue;
99
126
  }
100
127
  if (Array.isArray(value)) out[include.alias] = value.map((item) => decodeRow(include.selection, item));
@@ -103,8 +130,8 @@ function decodeRow(selection, row) {
103
130
  return out;
104
131
  }
105
132
  /** Applies column codecs to returned rows, including embedded relations. */
106
- function decodeRows(selection, rows) {
107
- if (!selection || !needsDecoding(selection)) return rows;
133
+ function decodeRows(selection, rows, checked = false) {
134
+ if (!selection || !checked && !needsDecoding(selection)) return rows;
108
135
  return rows.map((row) => decodeRow(selection, row));
109
136
  }
110
137
  //#endregion
@@ -170,10 +197,22 @@ function writable(meta, mode) {
170
197
  }
171
198
  /** Escapes LIKE wildcards so user input matches literally. */
172
199
  function escapeLike(value) {
173
- return value.replace(/[\\%_]/g, (char) => `\\${char}`);
200
+ return value.replaceAll(/[\\%_]/g, (char) => `\\${char}`);
201
+ }
202
+ const builders = /* @__PURE__ */ new WeakMap();
203
+ /** The shared builder for a schema; a builder holds no per-call state. */
204
+ function builderFor(meta) {
205
+ let builder = builders.get(meta);
206
+ if (!builder) {
207
+ builder = new IrBuilder(meta);
208
+ builders.set(meta, builder);
209
+ }
210
+ return builder;
174
211
  }
175
212
  var IrBuilder = class {
176
213
  meta;
214
+ /** Every column of a table, the selection of reads without `select`. */
215
+ #allColumns = /* @__PURE__ */ new WeakMap();
177
216
  constructor(meta) {
178
217
  this.meta = meta;
179
218
  }
@@ -319,7 +358,7 @@ var IrBuilder = class {
319
358
  }
320
359
  }
321
360
  selection(table, select, include) {
322
- const columns = (select ?? Object.keys(table.columns)).map((alias) => this.selectColumn(table, alias));
361
+ const columns = select?.map((alias) => this.selectColumn(table, alias)) ?? this.allColumns(table);
323
362
  const includes = [];
324
363
  if (include !== void 0) {
325
364
  if (!isPlainObject(include)) invalidRequest(`"include" on "${table.key}" must be an object`, table.key);
@@ -411,13 +450,7 @@ var IrBuilder = class {
411
450
  alias,
412
451
  column
413
452
  };
414
- return codec === "date" ? {
415
- fn,
416
- key,
417
- alias,
418
- column,
419
- codec
420
- } : {
453
+ return {
421
454
  fn,
422
455
  key,
423
456
  alias,
@@ -426,6 +459,14 @@ var IrBuilder = class {
426
459
  codec
427
460
  };
428
461
  }
462
+ allColumns(table) {
463
+ let columns = this.#allColumns.get(table);
464
+ if (!columns) {
465
+ columns = Object.keys(table.columns).map((alias) => this.selectColumn(table, alias));
466
+ this.#allColumns.set(table, columns);
467
+ }
468
+ return columns;
469
+ }
429
470
  /** A selected column, with the cast and codec its metadata asks for. */
430
471
  selectColumn(table, alias) {
431
472
  const column = this.column(table, alias);
@@ -434,11 +475,7 @@ var IrBuilder = class {
434
475
  alias,
435
476
  column
436
477
  };
437
- return codec === "date" ? {
438
- alias,
439
- column,
440
- codec
441
- } : {
478
+ return {
442
479
  alias,
443
480
  column,
444
481
  cast: "text",
@@ -537,7 +574,7 @@ var IrBuilder = class {
537
574
  uniqueKey(table, input) {
538
575
  if (!isPlainObject(input)) invalidRequest(`"where" on "${table.key}" must be an object`, table.key);
539
576
  const given = Object.keys(input).filter((key) => input[key] !== void 0);
540
- const keys = [table.primaryKey, ...Object.values(table.uniqueKeys)];
577
+ const { keys } = lookupOf(table);
541
578
  const match = keys.find((key) => key.length > 0 && key.length === given.length && key.every((column) => given.includes(column)));
542
579
  if (!match) {
543
580
  const names = keys.filter((key) => key.length > 0).map((key) => `{ ${key.join(", ")} }`).join(", ");
@@ -662,4 +699,4 @@ function simplifyOrFalse(condition) {
662
699
  };
663
700
  }
664
701
  //#endregion
665
- export { invalidRequest as a, needsDecoding as c, escapeLike as i, defineSchema as l, simplifyOrFalse as n, decodeRows as o, IrBuilder as r, encodeValue as s, simplify as t, tableMeta as u };
702
+ export { invalidRequest as a, needsDecoding as c, escapeLike as i, defineSchema as l, simplifyOrFalse as n, decodeRows as o, builderFor as r, encodeValue as s, simplify as t, tableMeta as u };