better-supabase 0.3.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 (172) hide show
  1. package/README.md +7 -6
  2. package/bin/better-supabase.js +4 -0
  3. package/dist/{base64-CQeB1eSf.js → base64-D2gsioCY.js} +2 -2
  4. package/dist/claims-BAf6c7is.js +48 -0
  5. package/dist/cli/_format-Cax5EsM9.js +101 -0
  6. package/dist/cli/bin.d.ts +1 -0
  7. package/dist/cli/bin.js +48 -0
  8. package/dist/cli/command-CK14fP91.js +502 -0
  9. package/dist/cli/compare-BmIWVEui.js +9 -0
  10. package/dist/cli/config-D9R0o1LH.js +1895 -0
  11. package/dist/cli/config-SSKSTUl2.js +2 -0
  12. package/dist/cli/db-DOBPt660.js +52 -0
  13. package/dist/cli/dist-B1MvLv-M.js +129 -0
  14. package/dist/cli/dist-CFgGknmZ.js +1112 -0
  15. package/dist/cli/dist-Du7VnEUG.js +621 -0
  16. package/dist/cli/doctor-BOym3mbL.js +2382 -0
  17. package/dist/cli/env-BZ0uy8XK.js +108 -0
  18. package/dist/cli/env-jIFUADLY.js +511 -0
  19. package/dist/cli/errors-4v4DTtda.js +56 -0
  20. package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
  21. package/dist/cli/gen-Ccn_p90L.js +2008 -0
  22. package/dist/cli/gen-D5cLCULx.js +2 -0
  23. package/dist/cli/index.d.ts +493 -0
  24. package/dist/cli/index.js +184 -0
  25. package/dist/cli/init-ClnxRYBF.js +550 -0
  26. package/dist/cli/introspect-DxqfkVIv.js +81 -0
  27. package/dist/cli/io-k41_z-jT.js +24 -0
  28. package/dist/cli/json5-ChRycquj.js +787 -0
  29. package/dist/cli/jsonc-BUniUpOk.js +2 -0
  30. package/dist/cli/jsonc-D5Fu67jw.js +45 -0
  31. package/dist/cli/keys-ByKHnvP9.js +83 -0
  32. package/dist/cli/ohash-D7bG_EOb.js +277 -0
  33. package/dist/cli/openapi-8WHjxiTJ.js +66 -0
  34. package/dist/cli/project-B_GeXyHh.js +70 -0
  35. package/dist/cli/prompts-BNcNfAve.js +1142 -0
  36. package/dist/cli/prompts-BTYQGunc.js +2 -0
  37. package/dist/cli/read-sets-CMa29t9a.js +971 -0
  38. package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
  39. package/dist/cli/run-CP20LKk_.js +347 -0
  40. package/dist/cli/seed-5J6eu_Xg.js +98 -0
  41. package/dist/cli/skills-LJJIhLFR.js +137 -0
  42. package/dist/cli/sql-D4VbqmXi.js +199 -0
  43. package/dist/cli/style-C0NFdJRL.js +29 -0
  44. package/dist/cli/toml-DB-MNnFs.js +424 -0
  45. package/dist/cli/version-5mW_8sY1.js +4 -0
  46. package/dist/cli/yaml-CYCbJ6et.js +2252 -0
  47. package/dist/client/index.d.ts +2 -2
  48. package/dist/client/index.js +13 -13
  49. package/dist/{compiler-2zqGzxVZ.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
  50. package/dist/config/index.js +4 -4
  51. package/dist/{config-Be4GwsqI.js → config-BpxDfnbg.js} +1 -1
  52. package/dist/{config-DOsGYSiT.js → config-CbvFmGD8.js} +1 -1
  53. package/dist/{define-DF341GDj.js → define-Bh0AqTik.js} +160 -79
  54. package/dist/{define-DUbydmJa.d.ts → define-CRS2MRV3.d.ts} +32 -28
  55. package/dist/edge/index.d.ts +8 -12
  56. package/dist/edge/index.js +50 -24
  57. package/dist/{entitlements-DvJXoI02.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
  58. package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
  59. package/dist/events/index.d.ts +1 -1
  60. package/dist/events/index.js +4 -4
  61. package/dist/{events-Do8jXh4D.d.ts → events-B21UOhvP.d.ts} +4 -4
  62. package/dist/{executor-DynmIWhq.d.ts → executor-B5IoUxLE.d.ts} +1 -1
  63. package/dist/{executor-C57jx2pn.js → executor-CVt6dD0B.js} +3 -3
  64. package/dist/{executor-B26lgbHD.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
  65. package/dist/hono/index.d.ts +16 -23
  66. package/dist/hono/index.js +10 -10
  67. package/dist/{index-CTTMS6_1.d.ts → index-528Ugmyp.d.ts} +12 -3
  68. package/dist/{index-CADlNHK-.d.ts → index-BAy4tsAP.d.ts} +5 -5
  69. package/dist/{index-BNsABq7P.d.ts → index-BBdvo-5d.d.ts} +23 -25
  70. package/dist/{index-qriRM9cZ.d.ts → index-BhHEC04r.d.ts} +3 -3
  71. package/dist/{index-GV77NpRP.d.ts → index-D5IfUIDh.d.ts} +3 -3
  72. package/dist/{index-CriFFJ_p.d.ts → index-DFmOpBGC.d.ts} +11 -11
  73. package/dist/{index-UJ-LUxtI.d.ts → index-RlLXd8dL.d.ts} +12 -12
  74. package/dist/index.d.ts +15 -15
  75. package/dist/index.js +13 -13
  76. package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
  77. package/dist/jobs/index.d.ts +10 -4
  78. package/dist/jobs/index.js +32 -16
  79. package/dist/{json-schema--aOXAGfU.js → json-schema-DP6V7cbr.js} +1 -1
  80. package/dist/list/index.d.ts +1 -1
  81. package/dist/list/index.js +3 -3
  82. package/dist/{live-QE3UPbGm.d.ts → live-BHakSWWo.d.ts} +12 -12
  83. package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
  84. package/dist/lookup-BaP5bI27.js +18 -0
  85. package/dist/mcp/index.d.ts +18 -18
  86. package/dist/mcp/index.js +23 -18
  87. package/dist/{mfa-x3V3vGh-.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
  88. package/dist/{mfa-BxoR1RkN.js → mfa-byfjruR9.js} +118 -29
  89. package/dist/next/image/index.js +6 -3
  90. package/dist/next/index.d.ts +38 -35
  91. package/dist/next/index.js +41 -33
  92. package/dist/openapi/index.d.ts +1 -1
  93. package/dist/openapi/index.js +6 -6
  94. package/dist/orpc/index.d.ts +17 -20
  95. package/dist/orpc/index.js +11 -11
  96. package/dist/otel/index.d.ts +5 -5
  97. package/dist/otel/index.js +81 -62
  98. package/dist/{permdock-sql-Bta_G0v-.js → permdock-sql-C4KFzLc9.js} +1 -1
  99. package/dist/{plugin-DVRB8qNE.d.ts → plugin-bNhvJwRh.d.ts} +2 -2
  100. package/dist/plugins/actor/index.d.ts +1 -1
  101. package/dist/plugins/actor/index.js +1 -1
  102. package/dist/plugins/rules/index.d.ts +2 -2
  103. package/dist/plugins/rules/index.js +42 -12
  104. package/dist/plugins/soft-delete/index.d.ts +2 -2
  105. package/dist/plugins/soft-delete/index.js +2 -2
  106. package/dist/plugins/tenant/index.d.ts +2 -2
  107. package/dist/plugins/tenant/index.js +10 -4
  108. package/dist/plugins/timestamps/index.d.ts +1 -1
  109. package/dist/plugins/timestamps/index.js +1 -1
  110. package/dist/plugins/validation/index.d.ts +2 -2
  111. package/dist/plugins/validation/index.js +3 -3
  112. package/dist/{pool-B9qsLyFR.d.ts → pool-DFwEQwmN.d.ts} +11 -4
  113. package/dist/postgres/index.d.ts +5 -5
  114. package/dist/postgres/index.js +3 -2
  115. package/dist/{postgrest-DmBlevh-.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
  116. package/dist/{problem-CGt1ADix.js → problem-BihjPR0t.js} +1 -1
  117. package/dist/{problem-Z2RTQjZD.d.ts → problem-U8UEKccv.d.ts} +1 -1
  118. package/dist/query/index.d.ts +2 -2
  119. package/dist/query/index.js +2 -2
  120. package/dist/{query-BdKLTPlj.js → query-BMMSWbI7.js} +18 -18
  121. package/dist/react/index.d.ts +6 -6
  122. package/dist/react/index.js +1 -1
  123. package/dist/react/server.d.ts +5 -5
  124. package/dist/react/server.js +3 -3
  125. package/dist/react/session.d.ts +4 -4
  126. package/dist/react/session.js +2 -2
  127. package/dist/{react-DNBYNazu.js → react-CQHMaESR.js} +39 -39
  128. package/dist/{read-set-D0qBcboM.js → read-set-FWK8qQ58.js} +50 -15
  129. package/dist/realtime/index.d.ts +1 -1
  130. package/dist/realtime/index.js +13 -12
  131. package/dist/{index-kpgopLNv.d.ts → refresh-BwYkyNOQ.d.ts} +6 -3
  132. package/dist/{resolve-BRciX2jK.d.ts → resolve-BAqQcwMW.d.ts} +15 -10
  133. package/dist/{resource-CwVgiI5q.d.ts → resource-BJcaIfxi.d.ts} +5 -5
  134. package/dist/{resource-D30rp0GN.js → resource-My_y-Syh.js} +6 -6
  135. package/dist/{respond-DAkPIdTH.d.ts → respond-DgIbdrt8.d.ts} +31 -12
  136. package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
  137. package/dist/{result-c2OYgCV2.d.ts → result-CqEsbgYs.d.ts} +3 -3
  138. package/dist/server/index.d.ts +14 -14
  139. package/dist/server/index.js +12 -12
  140. package/dist/{respond-C44EZAJo.js → server-BA75B0mT.js} +139 -100
  141. package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
  142. package/dist/{simplify-LakqK1vH.js → simplify-Cqvfr9dj.js} +67 -24
  143. package/dist/sql/index.d.ts +1 -1
  144. package/dist/sql/index.js +7 -7
  145. package/dist/{sql-BIgk12ew.js → sql-B34HU2DI.js} +3 -2
  146. package/dist/ssr/index.d.ts +4 -4
  147. package/dist/ssr/index.js +2 -2
  148. package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
  149. package/dist/{standard-eudBiRk1.d.ts → standard-BsaCYQJc.d.ts} +1 -1
  150. package/dist/{stats-D78UQ8Yu.js → stats-C_YxA2r1.js} +33 -8
  151. package/dist/storage/index.d.ts +2 -2
  152. package/dist/storage/index.js +26 -8
  153. package/dist/{tables-Dhha3BXM.js → tables-Cb-3_VWc.js} +14 -0
  154. package/dist/{temporal-required-CMxeSVPo.js → temporal-required-CBNHT6ni.js} +1 -1
  155. package/dist/testing/index.d.ts +20 -21
  156. package/dist/testing/index.js +45 -45
  157. package/dist/{version-BfpG2gks.js → version-Bmf7_FzM.js} +1 -1
  158. package/dist/{view-3Po_PVlr.d.ts → view-B64o81wp.d.ts} +5 -5
  159. package/dist/{view-wZ-9qIUz.js → view-BrJI2LKp.js} +1 -1
  160. package/dist/webhooks/index.d.ts +3 -3
  161. package/dist/webhooks/index.js +1 -1
  162. package/dist/{webhooks-CAIFQSTM.js → webhooks-Bh_Jf_-l.js} +37 -18
  163. package/package.json +36 -4
  164. package/skills/better-supabase/SKILL.md +57 -10
  165. package/skills/better-supabase/references/plugins.md +7 -11
  166. package/skills/better-supabase/references/troubleshooting.md +24 -4
  167. package/skills/better-supabase-api/SKILL.md +66 -18
  168. package/skills/better-supabase-api/references/adapters.md +44 -26
  169. package/skills/better-supabase-auth/SKILL.md +146 -0
  170. package/skills/better-supabase-auth/references/permdock.md +55 -0
  171. package/skills/better-supabase-testing/SKILL.md +58 -4
  172. package/dist/claims-CoTobNC-.js +0 -25
@@ -1,12 +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-D78UQ8Yu.js";
4
- import { a as problemResponse } from "./problem-CGt1ADix.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
5
  import { loadEnv } from "./env/index.js";
6
- import { _ as actClaim, g as delegationOf, h as actorOf, i as authContext, o as resolveAuth, r as checkAal } from "./mfa-BxoR1RkN.js";
7
- import { n as postgresExecutor } from "./executor-C57jx2pn.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";
8
7
  import { parseCookieHeader, serializeCookieHeader } from "@supabase/ssr";
9
8
  import { createClient } from "@supabase/supabase-js";
9
+ import { PostgrestClient } from "@supabase/postgrest-js";
10
10
  //#region src/server/replicas.ts
11
11
  /** Cookie holding the epoch ms until which reads stay on the primary. */
12
12
  const PRIMARY_COOKIE = "bs-primary-until";
@@ -27,10 +27,10 @@ function replicaState(pinnedUntil = 0, now = Date.now()) {
27
27
  }
28
28
  };
29
29
  }
30
- /** `bs-primary-until` from a `cookie` header, or 0. */
31
- function pinnedUntil(cookieHeader) {
32
- if (!cookieHeader) return 0;
33
- const value = parseCookieHeader(cookieHeader).find((cookie) => cookie.name === PRIMARY_COOKIE)?.value;
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
34
  const until = Number(value);
35
35
  return value !== void 0 && Number.isFinite(until) ? until : 0;
36
36
  }
@@ -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() {
@@ -255,11 +360,12 @@ function createServer(sb, options = {}) {
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 } : {},
@@ -267,12 +373,12 @@ function createServer(sb, options = {}) {
267
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,72 +435,4 @@ function createServer(sb, options = {}) {
328
435
  };
329
436
  }
330
437
  //#endregion
331
- //#region src/server/respond.ts
332
- /** The scopes a delegated user token lacks; empty for the user's own session. */
333
- function missingScopes(auth, required) {
334
- if (required.length === 0) return [];
335
- const outcome = actorOf(auth.claims);
336
- if (!outcome.ok || !outcome.actor) return [];
337
- const granted = new Set(delegationOf(auth.claims)?.scopes);
338
- return required.filter((scope) => !granted.has(scope));
339
- }
340
- /** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
341
- function guard(auth, allow = ["user"], aal = "aal1", scopes = []) {
342
- if (auth.kind === "invalid") return auth.error;
343
- if (allow.includes(auth.kind)) {
344
- const denied = checkAal(auth, aal);
345
- if (denied || auth.kind !== "user") return denied;
346
- const missing = missingScopes(auth, scopes);
347
- return missing.length > 0 ? dbError("forbidden", `The token lacks the scope ${missing.join(" ")}`, {
348
- code: "INSUFFICIENT_SCOPE",
349
- scopes
350
- }) : void 0;
351
- }
352
- return auth.kind === "anon" ? dbError("unauthorized", "Sign in to continue", { code: "MISSING_CREDENTIALS" }) : dbError("forbidden", `A ${auth.kind} caller is not allowed here`);
353
- }
354
- function isResult(value) {
355
- return typeof value === "object" && value !== null && "ok" in value && "data" in value && "error" in value;
356
- }
357
- /**
358
- * Settles a handler's return value: `Result`s and `AsyncResult`s unwrap,
359
- * thrown `DbException`s (or errors whose `cause` is a `DbError`) become
360
- * errors, anything else is data. Other thrown
361
- * errors propagate.
362
- */
363
- async function settle(run) {
364
- try {
365
- const value = await run();
366
- if (!isResult(value)) return {
367
- ok: true,
368
- data: value
369
- };
370
- if (value.ok) return {
371
- ok: true,
372
- data: value.data
373
- };
374
- return {
375
- ok: false,
376
- error: isDbError(value.error) ? value.error : toDbError(value.error)
377
- };
378
- } catch (cause) {
379
- const thrown = dbErrorOf(cause);
380
- if (thrown) return {
381
- ok: false,
382
- error: thrown
383
- };
384
- throw cause;
385
- }
386
- }
387
- /** A handler's value as a response: `Response` as is, data as JSON, errors as Problem Details. */
388
- async function respond(run, options = {}) {
389
- const settled = await settle(run);
390
- if (!settled.ok) return problemResponse(settled.error, options);
391
- if (settled.data instanceof Response) return settled.data;
392
- if (settled.data === void 0) return new Response(null, { status: 204 });
393
- return Response.json(settled.data, { status: options.status ?? 200 });
394
- }
395
- function defaultExpose() {
396
- return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
397
- }
398
- //#endregion
399
- 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,6 +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";
2
3
  import { i as temporalText } from "./temporal-BwshQk0W.js";
3
- import { t as temporal } from "./temporal-required-CMxeSVPo.js";
4
+ import { t as temporal } from "./temporal-required-CBNHT6ni.js";
4
5
  import { i as or, r as not, t as and } from "./types-6cYK9pbJ.js";
5
6
  //#region src/schema/define.ts
6
7
  /**
@@ -31,7 +32,7 @@ function relationMeta(meta, table, relation) {
31
32
  //#region src/ir/codec.ts
32
33
  /** Wire form of an app value: Temporal values to ISO text, `bigint` to decimal text. */
33
34
  function encodeValue(value) {
34
- if (typeof value === "bigint") return value.toString();
35
+ if (typeof value !== "object" || value === null) return typeof value === "bigint" ? value.toString() : value;
35
36
  const text = temporalText(value);
36
37
  if (text !== void 0) return text;
37
38
  if (Array.isArray(value)) return value.map(encodeValue);
@@ -52,6 +53,15 @@ function decode(codec, value) {
52
53
  }
53
54
  /** Whether decoding `selection` changes anything, so plain reads skip the walk. */
54
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) {
55
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));
56
66
  }
57
67
  /** PostgREST returns `[{ count }]` for a `(count)` embed; SQL returns the number. */
@@ -67,38 +77,51 @@ function measureOf(measure, value) {
67
77
  return measure.codec ? decodeScalar(measure.codec, value) : value;
68
78
  }
69
79
  /** Adds `value` under `row[key][name]`, next to what is already there. */
70
- function fold(row, key, name, value) {
71
- row[key] = {
72
- ...row[key] ?? {},
73
- [name]: value
74
- };
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;
75
98
  }
76
99
  function decodeRow(selection, row) {
77
- 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();
78
104
  for (const column of selection.columns) if (column.codec && column.alias in out) out[column.alias] = decode(column.codec, out[column.alias]);
79
105
  if (selection.aggregate) {
80
106
  if (selection.aggregate.count) out["_count"] = countOf(out["_count"]);
81
107
  for (const measure of selection.aggregate.measures) {
82
- const value = out[measure.key];
83
- delete out[measure.key];
84
- 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));
85
110
  }
86
111
  }
87
112
  for (const include of selection.includes) {
88
- const value = out[include.alias];
113
+ const value = row[include.alias];
89
114
  if (include.count !== void 0) {
90
- if (!(include.alias in out)) continue;
91
- delete out[include.alias];
92
- fold(out, "_count", include.count, countOf(value));
115
+ if (!(include.alias in row)) continue;
116
+ fold(out, folded, "_count", include.count, countOf(value));
93
117
  continue;
94
118
  }
95
119
  if (include.aggregate !== void 0) {
96
- if (!(include.alias in out)) continue;
97
- delete out[include.alias];
120
+ if (!(include.alias in row)) continue;
98
121
  const inner = Array.isArray(value) ? value[0] : value;
99
122
  const measures = {};
100
123
  for (const measure of include.selection.aggregate?.measures ?? []) measures[measure.alias] = measureOf(measure, inner?.[measure.key]);
101
- fold(out, `_${include.aggregate.fn}`, include.aggregate.name, measures);
124
+ fold(out, folded, `_${include.aggregate.fn}`, include.aggregate.name, measures);
102
125
  continue;
103
126
  }
104
127
  if (Array.isArray(value)) out[include.alias] = value.map((item) => decodeRow(include.selection, item));
@@ -107,8 +130,8 @@ function decodeRow(selection, row) {
107
130
  return out;
108
131
  }
109
132
  /** Applies column codecs to returned rows, including embedded relations. */
110
- function decodeRows(selection, rows) {
111
- if (!selection || !needsDecoding(selection)) return rows;
133
+ function decodeRows(selection, rows, checked = false) {
134
+ if (!selection || !checked && !needsDecoding(selection)) return rows;
112
135
  return rows.map((row) => decodeRow(selection, row));
113
136
  }
114
137
  //#endregion
@@ -176,8 +199,20 @@ function writable(meta, mode) {
176
199
  function escapeLike(value) {
177
200
  return value.replaceAll(/[\\%_]/g, (char) => `\\${char}`);
178
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;
211
+ }
179
212
  var IrBuilder = class {
180
213
  meta;
214
+ /** Every column of a table, the selection of reads without `select`. */
215
+ #allColumns = /* @__PURE__ */ new WeakMap();
181
216
  constructor(meta) {
182
217
  this.meta = meta;
183
218
  }
@@ -323,7 +358,7 @@ var IrBuilder = class {
323
358
  }
324
359
  }
325
360
  selection(table, select, include) {
326
- 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);
327
362
  const includes = [];
328
363
  if (include !== void 0) {
329
364
  if (!isPlainObject(include)) invalidRequest(`"include" on "${table.key}" must be an object`, table.key);
@@ -424,6 +459,14 @@ var IrBuilder = class {
424
459
  codec
425
460
  };
426
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
+ }
427
470
  /** A selected column, with the cast and codec its metadata asks for. */
428
471
  selectColumn(table, alias) {
429
472
  const column = this.column(table, alias);
@@ -531,7 +574,7 @@ var IrBuilder = class {
531
574
  uniqueKey(table, input) {
532
575
  if (!isPlainObject(input)) invalidRequest(`"where" on "${table.key}" must be an object`, table.key);
533
576
  const given = Object.keys(input).filter((key) => input[key] !== void 0);
534
- const keys = [table.primaryKey, ...Object.values(table.uniqueKeys)];
577
+ const { keys } = lookupOf(table);
535
578
  const match = keys.find((key) => key.length > 0 && key.length === given.length && key.every((column) => given.includes(column)));
536
579
  if (!match) {
537
580
  const names = keys.filter((key) => key.length > 0).map((key) => `{ ${key.join(", ")} }`).join(", ");
@@ -656,4 +699,4 @@ function simplifyOrFalse(condition) {
656
699
  };
657
700
  }
658
701
  //#endregion
659
- 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 };
@@ -1,5 +1,5 @@
1
1
  import { s as ClaimsMeta } from "../types-DONv9SHN.js";
2
- import { B as ReadSet } from "../define-DUbydmJa.js";
2
+ import { B as ReadSet } from "../define-CRS2MRV3.js";
3
3
  import { b as ResolvedConfig } from "../config-C1CYcm6o.js";
4
4
  import { n as permdockKeys } from "../permdock-sql-DImJULY-.js";
5
5
  //#region src/sql/kit.d.ts
package/dist/sql/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { i as temporalText } from "../temporal-BwshQk0W.js";
2
- import { i as ok } from "../result-DWXatkd6.js";
3
- import { a as hasPlaceholder, l as splitPlaceholders, s as placeholderOf } from "../read-set-D0qBcboM.js";
4
- import { t as DEFAULT_CLAIMS } from "../claims-CoTobNC-.js";
5
- import { i as resolveJsonSchema } from "../config-Be4GwsqI.js";
6
- import { t as compileSql } from "../sql-BIgk12ew.js";
7
- import { a as sqlIdent, n as permdockKeys, o as sqlString } from "../permdock-sql-Bta_G0v-.js";
8
- import { t as VERSION } from "../version-BfpG2gks.js";
2
+ import { i as ok } from "../result-Bj6yt33D.js";
3
+ import { a as hasPlaceholder, l as splitPlaceholders, s as placeholderOf } from "../read-set-FWK8qQ58.js";
4
+ import { t as DEFAULT_CLAIMS } from "../claims-BAf6c7is.js";
5
+ import { i as resolveJsonSchema } from "../config-BpxDfnbg.js";
6
+ import { t as compileSql } from "../sql-B34HU2DI.js";
7
+ import { a as sqlIdent, n as permdockKeys, o as sqlString } from "../permdock-sql-C4KFzLc9.js";
8
+ import { t as VERSION } from "../version-Bmf7_FzM.js";
9
9
  //#region src/sql/kit.ts
10
10
  const requiresOf = (module, layout) => layout.permdock && module.permdockRequires ? module.permdockRequires : module.requires;
11
11
  function jwtClaim(name) {
@@ -1,4 +1,5 @@
1
- import { a as invalidRequest, n as simplifyOrFalse } from "./simplify-LakqK1vH.js";
1
+ import { a as invalidRequest, n as simplifyOrFalse } from "./simplify-Cqvfr9dj.js";
2
+ import { t as lookupOf } from "./lookup-BaP5bI27.js";
2
3
  //#region src/compile/sql.ts
3
4
  const MAX_PARAMS = 65535;
4
5
  const MAX_PAIRS = 50;
@@ -14,7 +15,7 @@ function dbColumn(table, app) {
14
15
  return meta.db;
15
16
  }
16
17
  function columnType(table, db) {
17
- return Object.values(table.columns).find((meta) => meta.db === db)?.type;
18
+ return lookupOf(table).byDb.get(db)?.[1].type;
18
19
  }
19
20
  /**
20
21
  * `a > x or (a = x and b > y) or ...` as `(a, b) > (x, y)`, which an index
@@ -1,5 +1,5 @@
1
- import { _ as readSession, a as ResolveAuthOptions, b as writeSession, d as CookieOptions, f as CookieRecord, g as parseCookies, h as applyCookieWrites, l as resolveAuth, m as StoredSession, n as AuthResolver, p as CookieWrite, r as AuthState, s as authContext, t as AuthResolution, u as AUTH_CACHE_HEADERS, v as serializeCookie, y as sessionCookieName } from "../resolve-BRciX2jK.js";
2
- import { n as toSession, t as AuthSession } from "../view-3Po_PVlr.js";
3
- import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-DvJXoI02.js";
4
- import { n as RefreshOutcome, r as refreshSession, t as RefreshOptions } from "../index-kpgopLNv.js";
1
+ import { _ as readSession, a as ResolveAuthOptions, b as writeSession, d as CookieOptions, f as CookieRecord, g as parseCookies, h as applyCookieWrites, l as resolveAuth, m as StoredSession, n as AuthResolver, p as CookieWrite, r as AuthState, s as authContext, t as AuthResolution, u as AUTH_CACHE_HEADERS, v as serializeCookie, y as sessionCookieName } from "../resolve-BAqQcwMW.js";
2
+ import { n as toSession, t as AuthSession } from "../view-B64o81wp.js";
3
+ import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-Dm-FHpNH.js";
4
+ import { n as RefreshOutcome, r as refreshSession, t as RefreshOptions } from "../refresh-BwYkyNOQ.js";
5
5
  export { AUTH_CACHE_HEADERS, type AuthResolution, type AuthResolver, type AuthSession, type AuthState, type CookieOptions, type CookieRecord, type CookieWrite, type EntitlementKey, type MembershipClaim, type RefreshOptions, type RefreshOutcome, type ResolveAuthOptions, type StoredSession, applyCookieWrites, authContext, hasEntitlement, parseCookies, readSession, refreshSession, resolveAuth, serializeCookie, sessionCookieName, toSession, writeSession };
package/dist/ssr/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { t as hasEntitlement } from "../entitlements-CTEBm7IB.js";
2
- import { c as applyCookieWrites, d as serializeCookie, f as sessionCookieName, i as authContext, l as parseCookies, m as refreshSession, o as resolveAuth, p as writeSession, s as AUTH_CACHE_HEADERS, u as readSession } from "../mfa-BxoR1RkN.js";
3
- import { t as toSession } from "../view-wZ-9qIUz.js";
2
+ import { c as AUTH_CACHE_HEADERS, d as readSession, f as serializeCookie, h as refreshSession, i as authContext, l as applyCookieWrites, m as writeSession, p as sessionCookieName, s as resolveAuth, u as parseCookies } from "../mfa-byfjruR9.js";
3
+ import { t as toSession } from "../view-BrJI2LKp.js";
4
4
  export { AUTH_CACHE_HEADERS, applyCookieWrites, authContext, hasEntitlement, parseCookies, readSession, refreshSession, resolveAuth, serializeCookie, sessionCookieName, toSession, writeSession };
@@ -1,5 +1,5 @@
1
- import { n as dbError } from "./errors-Bl3rkOzo.js";
2
- import { i as ok, n as err } from "./result-DWXatkd6.js";
1
+ import { n as dbError } from "./errors-L0xK41y_.js";
2
+ import { i as ok, n as err } from "./result-Bj6yt33D.js";
3
3
  //#region src/core/standard.ts
4
4
  /** Validates with any Standard Schema (zod, valibot, arktype, ...). */
5
5
  async function validate(schema, value, label = "value") {
@@ -1,4 +1,4 @@
1
- import { o as Result } from "./result-c2OYgCV2.js";
1
+ import { o as Result } from "./result-CqEsbgYs.js";
2
2
  import { StandardSchemaV1 as StandardSchemaV1$1 } from "@standard-schema/spec";
3
3
  //#region src/core/standard.d.ts
4
4
  /** Validates with any Standard Schema (zod, valibot, arktype, ...). */