better-supabase 0.0.0 → 0.1.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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +3 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +15 -0
  7. package/dist/cli/index.d.ts +226 -0
  8. package/dist/cli/index.js +3 -0
  9. package/dist/cli-CoByWYja.js +6472 -0
  10. package/dist/client/index.d.ts +2 -0
  11. package/dist/client/index.js +112 -0
  12. package/dist/compiler-DL18ukPe.d.ts +14 -0
  13. package/dist/config/index.d.ts +3 -0
  14. package/dist/config/index.js +3 -0
  15. package/dist/config-D-aR2ZoN.js +276 -0
  16. package/dist/define-CTfWw-SS.d.ts +662 -0
  17. package/dist/define-QGkq04oe.js +1068 -0
  18. package/dist/edge/index.d.ts +43 -0
  19. package/dist/edge/index.js +103 -0
  20. package/dist/entitlements-BBg61DQZ.d.ts +20 -0
  21. package/dist/entitlements-BC_khwRy.js +17 -0
  22. package/dist/env/index.d.ts +2 -0
  23. package/dist/env/index.js +2 -0
  24. package/dist/env-BEToab0I.js +204 -0
  25. package/dist/errors-Bl3rkOzo.js +196 -0
  26. package/dist/events/index.d.ts +2 -0
  27. package/dist/events/index.js +2 -0
  28. package/dist/events-2OJsVG_d.d.ts +84 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/executor-Bazhgsgq.d.ts +195 -0
  31. package/dist/executor-Bje4suQF.d.ts +25 -0
  32. package/dist/executor-Ceq60hPM.js +89 -0
  33. package/dist/hono/index.d.ts +45 -0
  34. package/dist/hono/index.js +70 -0
  35. package/dist/index-BD6wKthp.d.ts +597 -0
  36. package/dist/index-DQVvHzjW.d.ts +74 -0
  37. package/dist/index-DRid0qIv.d.ts +76 -0
  38. package/dist/index-Dup-nSe9.d.ts +117 -0
  39. package/dist/index-Dy6kuF0P.d.ts +71 -0
  40. package/dist/index-Hf8PP9sP.d.ts +116 -0
  41. package/dist/index-JXHBuS53.d.ts +144 -0
  42. package/dist/index-Nxo3TJcm.d.ts +208 -0
  43. package/dist/index-Sw43J8HG.d.ts +62 -0
  44. package/dist/index-cEeweTeM.d.ts +38 -0
  45. package/dist/index.d.ts +110 -0
  46. package/dist/index.js +42 -0
  47. package/dist/invalidate-DbxuL2jL.js +18 -0
  48. package/dist/jobs/index.d.ts +173 -0
  49. package/dist/jobs/index.js +477 -0
  50. package/dist/json-schema-BbwO2mZg.js +181 -0
  51. package/dist/lint/index.d.ts +75 -0
  52. package/dist/lint/index.js +167 -0
  53. package/dist/list/index.d.ts +2 -0
  54. package/dist/list/index.js +410 -0
  55. package/dist/live-6b9i72Ux.d.ts +180 -0
  56. package/dist/live-D8rV1Ldw.js +168 -0
  57. package/dist/mcp/index.d.ts +105 -0
  58. package/dist/mcp/index.js +330 -0
  59. package/dist/mfa-CpogP66z.js +521 -0
  60. package/dist/mfa-TVia554c.d.ts +44 -0
  61. package/dist/next/image/index.d.ts +34 -0
  62. package/dist/next/image/index.js +40 -0
  63. package/dist/next/index.d.ts +210 -0
  64. package/dist/next/index.js +426 -0
  65. package/dist/openapi/index.d.ts +2 -0
  66. package/dist/openapi/index.js +332 -0
  67. package/dist/orpc/index.d.ts +46 -0
  68. package/dist/orpc/index.js +50 -0
  69. package/dist/otel/index.d.ts +40 -0
  70. package/dist/otel/index.js +168 -0
  71. package/dist/path-Cv__-OTj.d.ts +17 -0
  72. package/dist/plugin-BqR6wKMB.js +23 -0
  73. package/dist/plugin-HvGqCurB.d.ts +118 -0
  74. package/dist/plugins/actor/index.d.ts +13 -0
  75. package/dist/plugins/actor/index.js +43 -0
  76. package/dist/plugins/rules/index.d.ts +88 -0
  77. package/dist/plugins/rules/index.js +174 -0
  78. package/dist/plugins/soft-delete/index.d.ts +38 -0
  79. package/dist/plugins/soft-delete/index.js +69 -0
  80. package/dist/plugins/tenant/index.d.ts +43 -0
  81. package/dist/plugins/tenant/index.js +73 -0
  82. package/dist/plugins/timestamps/index.d.ts +9 -0
  83. package/dist/plugins/timestamps/index.js +37 -0
  84. package/dist/plugins/validation/index.d.ts +24 -0
  85. package/dist/plugins/validation/index.js +59 -0
  86. package/dist/pool-BFeqzwQS.d.ts +37 -0
  87. package/dist/postgres/index.d.ts +28 -0
  88. package/dist/postgres/index.js +79 -0
  89. package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
  90. package/dist/problem-DPu6Fh-t.js +97 -0
  91. package/dist/problem-DlgB0lYl.d.ts +47 -0
  92. package/dist/query/index.d.ts +2 -0
  93. package/dist/query/index.js +3 -0
  94. package/dist/query-ChvDbsDE.js +186 -0
  95. package/dist/react/index.d.ts +7 -0
  96. package/dist/react/index.js +5 -0
  97. package/dist/react/server.d.ts +13 -0
  98. package/dist/react/server.js +25 -0
  99. package/dist/react/session.d.ts +2 -0
  100. package/dist/react/session.js +3 -0
  101. package/dist/react-CrOvrnIP.js +241 -0
  102. package/dist/read-set-DhX2d04c.js +216 -0
  103. package/dist/realtime/index.d.ts +3 -0
  104. package/dist/realtime/index.js +253 -0
  105. package/dist/resolve-DW8h9NPm.d.ts +155 -0
  106. package/dist/resource-CYiu6Ygi.js +175 -0
  107. package/dist/resource-SkPTRJDD.d.ts +65 -0
  108. package/dist/respond-4-dhoV7x.js +380 -0
  109. package/dist/respond-PoTpy5DT.d.ts +169 -0
  110. package/dist/result-CTkCZ6JA.d.ts +198 -0
  111. package/dist/result-DWXatkd6.js +103 -0
  112. package/dist/scope-CDHcS7dE.js +90 -0
  113. package/dist/seed-CzecODcJ.js +101 -0
  114. package/dist/server/index.d.ts +42 -0
  115. package/dist/server/index.js +68 -0
  116. package/dist/session-C3osL21k.d.ts +21 -0
  117. package/dist/session-CD-3orO_.js +19 -0
  118. package/dist/shared-_6-uPL7E.js +57 -0
  119. package/dist/simplify-oHiDKRVQ.js +665 -0
  120. package/dist/spec-pins-CJPVghHy.d.ts +20 -0
  121. package/dist/spec-pins-t6sPaHvl.js +20 -0
  122. package/dist/sql-M8l3fnS0.js +260 -0
  123. package/dist/ssr/index.d.ts +5 -0
  124. package/dist/ssr/index.js +4 -0
  125. package/dist/standard-CyhRzctb.d.ts +7 -0
  126. package/dist/standard-FUi3Z3-7.js +21 -0
  127. package/dist/stats-3iy8u3_P.js +566 -0
  128. package/dist/storage/index.d.ts +4 -0
  129. package/dist/storage/index.js +2 -0
  130. package/dist/storage-BWXVUXGk.js +461 -0
  131. package/dist/tables-rZxRxNPW.js +59 -0
  132. package/dist/template-BeOGnynM.js +93 -0
  133. package/dist/template-mbvlysOo.d.ts +6 -0
  134. package/dist/testing/index.d.ts +294 -0
  135. package/dist/testing/index.js +751 -0
  136. package/dist/types-6cYK9pbJ.js +29 -0
  137. package/dist/types-C2fslP1z.d.ts +195 -0
  138. package/dist/version-Cqq1m3UY.js +4 -0
  139. package/dist/view-B0ltUmTi.d.ts +43 -0
  140. package/dist/view-B6F9QbG-.js +35 -0
  141. package/dist/webhooks/index.d.ts +152 -0
  142. package/dist/webhooks/index.js +2 -0
  143. package/dist/webhooks-DbMTJJX2.js +139 -0
  144. package/package.json +246 -17
  145. package/schemas/config-v1.json +328 -0
  146. package/schemas/doctor-report-v1.json +105 -0
  147. package/schemas/snapshot-v2.json +1002 -0
  148. package/skills/better-supabase/SKILL.md +84 -0
  149. package/skills/better-supabase/references/plugins.md +56 -0
  150. package/skills/better-supabase/references/troubleshooting.md +39 -0
  151. package/skills/better-supabase-api/SKILL.md +67 -0
  152. package/skills/better-supabase-api/references/adapters.md +108 -0
  153. package/skills/better-supabase-testing/SKILL.md +65 -0
  154. package/index.js +0 -1
@@ -0,0 +1,380 @@
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-3iy8u3_P.js";
4
+ import { a as problemResponse } from "./problem-DPu6Fh-t.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-CpogP66z.js";
7
+ import { n as postgresExecutor } from "./executor-Ceq60hPM.js";
8
+ import { createClient } from "@supabase/supabase-js";
9
+ //#region src/server/replicas.ts
10
+ /** Cookie holding the epoch ms until which reads stay on the primary. */
11
+ const PRIMARY_COOKIE = "bs-primary-until";
12
+ /** Default time reads stay on the primary after a write. */
13
+ const DEFAULT_PIN_MS = 5e3;
14
+ function replicaState(pinnedUntil = 0, now = Date.now()) {
15
+ let wrote = false;
16
+ const inherited = pinnedUntil > now;
17
+ return {
18
+ get pinned() {
19
+ return wrote || inherited;
20
+ },
21
+ get wrote() {
22
+ return wrote;
23
+ },
24
+ pin() {
25
+ wrote = true;
26
+ }
27
+ };
28
+ }
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;
40
+ }
41
+ /** A `Set-Cookie` value keeping the next requests on the primary for `pinMs`. */
42
+ 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`;
45
+ }
46
+ /**
47
+ * Reads go to `replica` until the context is pinned; writes, and every read
48
+ * after a successful write, go to `primary`.
49
+ */
50
+ function routedExecutor(primary, replica, state) {
51
+ const batch = primary.batch?.bind(primary);
52
+ const replicaBatch = replica.batch?.bind(replica);
53
+ return {
54
+ name: primary.name,
55
+ ...primary.functionSources && replica.functionSources ? { functionSources: true } : {},
56
+ async execute(op, context) {
57
+ if (op.kind === "select" && !state.pinned) return replica.execute(op, context);
58
+ const result = await primary.execute(op, context);
59
+ if (op.kind !== "select" && result.ok) state.pin();
60
+ return result;
61
+ },
62
+ ...primary.rpc ? { async rpc(name, args, context) {
63
+ if (context.get && !state.pinned && replica.rpc) return replica.rpc(name, args, context);
64
+ const result = await primary.rpc(name, args, context);
65
+ if (!context.get && result.ok) state.pin();
66
+ return result;
67
+ } } : {},
68
+ ...batch ? { batch: (ops, context) => !state.pinned && replicaBatch ? replicaBatch(ops, context) : batch(ops, context) } : {}
69
+ };
70
+ }
71
+ //#endregion
72
+ //#region src/server/delete-account.ts
73
+ const REMOVE_BATCH = 1e3;
74
+ /** Maps an `AuthApiError` from `auth.admin.deleteUser` to a `DbError`. */
75
+ function fromAuthError(raw) {
76
+ if (isDbError(raw)) return raw;
77
+ const failure = typeof raw === "object" && raw !== null ? raw : {};
78
+ const message = failure.message ?? "Auth request failed";
79
+ const base = {
80
+ table: "auth.users",
81
+ ...failure.code ? { code: failure.code } : {}
82
+ };
83
+ if (failure.code === "user_not_found" || failure.status === 404) return dbError("not_found", message, base);
84
+ if (/database error deleting user/i.test(message)) return dbError("conflict", message, {
85
+ ...base,
86
+ hint: "A foreign key to auth.users without on delete cascade or set null blocks the delete (doctor BS406)"
87
+ });
88
+ if (failure.status === 401) return dbError("unauthorized", message, base);
89
+ if (failure.status === 403) return dbError("forbidden", message, base);
90
+ return failure.name === "AuthRetryableFetchError" || (failure.status ?? 0) >= 500 ? dbError("network", message, base) : dbError("unexpected", message, base);
91
+ }
92
+ /**
93
+ * Removes a user's Storage objects, then the Auth user, then announces the
94
+ * delete. Rows referencing `auth.users` follow their foreign keys.
95
+ */
96
+ function deleteAccount(sb, service, userId, options = {}) {
97
+ return AsyncResult.from(async () => {
98
+ const client = service();
99
+ const removed = {};
100
+ for (const bucket of options.buckets ?? []) {
101
+ const param = bucket.owner;
102
+ if (!param) continue;
103
+ options.signal?.throwIfAborted();
104
+ const storage = bucket.connect(client);
105
+ const listed = await storage.list({ [param]: userId }, options.signal ? { signal: options.signal } : {});
106
+ if (!listed.ok) return listed;
107
+ const paths = listed.data.map((object) => object.path).filter((path) => {
108
+ return bucket.match(path)?.[param] === userId;
109
+ });
110
+ for (let start = 0; start < paths.length; start += REMOVE_BATCH) {
111
+ const batch = await storage.remove(paths.slice(start, start + REMOVE_BATCH));
112
+ if (!batch.ok) return batch;
113
+ }
114
+ removed[bucket.id] = paths.length;
115
+ }
116
+ options.signal?.throwIfAborted();
117
+ try {
118
+ const { error } = await client.auth.admin.deleteUser(userId);
119
+ if (error) return err(fromAuthError(error));
120
+ } catch (cause) {
121
+ return err(fromAuthError(cause));
122
+ }
123
+ const context = options.context ?? {};
124
+ sb.events.emit("mutation", {
125
+ table: "auth.users",
126
+ kind: "delete",
127
+ rows: [{ id: userId }],
128
+ context
129
+ });
130
+ for (const table of options.cascades ?? []) sb.events.emit("mutation", {
131
+ table,
132
+ kind: "delete",
133
+ rows: [],
134
+ context
135
+ });
136
+ return ok({
137
+ userId,
138
+ removed
139
+ });
140
+ });
141
+ }
142
+ //#endregion
143
+ //#region src/server/server.ts
144
+ const STATELESS = {
145
+ persistSession: false,
146
+ autoRefreshToken: false,
147
+ detectSessionInUrl: false
148
+ };
149
+ /** `{ ...server, ...extra }` without reading the lazy `env` getter. */
150
+ function extendServer(server, extra) {
151
+ return Object.defineProperties({}, {
152
+ ...Object.getOwnPropertyDescriptors(server),
153
+ ...Object.getOwnPropertyDescriptors(extra)
154
+ });
155
+ }
156
+ /** `{ ...base, ...extra }` that keeps lazy getters lazy. */
157
+ function withExtra(base, extra) {
158
+ return Object.defineProperties({}, {
159
+ ...Object.getOwnPropertyDescriptors(base),
160
+ ...Object.getOwnPropertyDescriptors(extra)
161
+ });
162
+ }
163
+ /**
164
+ * Server-side entry point for APIs, jobs, MCP servers and framework
165
+ * adapters. Holds the secrets; `sb` stays isomorphic.
166
+ */
167
+ function createServer(sb, options = {}) {
168
+ let loaded;
169
+ const env = () => loaded ??= options.env ?? loadEnv();
170
+ let adminClient;
171
+ let anonClient;
172
+ const secretKey = () => {
173
+ const key = env().secretKey;
174
+ if (!key) throw new TypeError("admin() needs SUPABASE_SECRET_KEY; it is not set");
175
+ return key;
176
+ };
177
+ const serviceClient = () => {
178
+ adminClient ??= createClient(env().url, secretKey(), { auth: STATELESS });
179
+ return adminClient;
180
+ };
181
+ const readUrl = () => options.readUrl === false ? void 0 : options.readUrl?.replace(/\/+$/, "") ?? env().readUrl;
182
+ /** Clients for the primary share the service and anon clients; others don't. */
183
+ const supabaseAt = (url, auth, headers = {}) => {
184
+ const global = Object.keys(headers).length > 0 ? { global: { headers } } : void 0;
185
+ const shared = !global && url === env().url;
186
+ switch (auth.kind) {
187
+ case "user": return createClient(url, env().publishableKey, {
188
+ accessToken: () => Promise.resolve(auth.token),
189
+ ...global
190
+ });
191
+ case "service": return shared ? serviceClient() : createClient(url, secretKey(), {
192
+ auth: STATELESS,
193
+ ...global
194
+ });
195
+ case "anon":
196
+ case "invalid":
197
+ if (!shared) return createClient(url, env().publishableKey, {
198
+ auth: STATELESS,
199
+ ...global
200
+ });
201
+ anonClient ??= createClient(url, env().publishableKey, { auth: STATELESS });
202
+ return anonClient;
203
+ default: return auth;
204
+ }
205
+ };
206
+ const supabaseFor = (auth, headers = {}) => supabaseAt(env().url, auth, headers);
207
+ const dbFor = (auth, context = {}) => sb.connect(supabaseFor(auth), {
208
+ ...authContext(auth),
209
+ ...context
210
+ });
211
+ 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 } : {});
214
+ };
215
+ /** Clients and repositories are built on first access: most scopes use one of them. */
216
+ const contextFor = (resolution, headers, parent, until = 0) => {
217
+ const { auth } = resolution;
218
+ const context = authContext(auth);
219
+ const recorder = new StatsRecorder(parent);
220
+ let supabase;
221
+ let db;
222
+ let sql;
223
+ const sqlClaims = auth.kind === "user" ? auth.claims : auth.kind === "anon" ? { role: "anon" } : void 0;
224
+ const client = () => supabase ??= supabaseFor(auth, headers);
225
+ let replica;
226
+ const replicaUrl = () => {
227
+ const url = readUrl();
228
+ if (url) replica ??= replicaState(until);
229
+ return url;
230
+ };
231
+ return {
232
+ auth,
233
+ resolution,
234
+ get supabase() {
235
+ return client();
236
+ },
237
+ get db() {
238
+ if (db) return db;
239
+ const url = replicaUrl();
240
+ db = sb.connect(client(), context, {
241
+ stats: recorder,
242
+ ...url && replica ? { executor: routedExecutor(postgrestExecutor(client()), postgrestExecutor(supabaseAt(url, auth, headers)), replica) } : {}
243
+ });
244
+ return db;
245
+ },
246
+ get replica() {
247
+ replicaUrl();
248
+ return replica;
249
+ },
250
+ get sql() {
251
+ if (!options.postgres || !sqlClaims) return void 0;
252
+ sql ??= sqlFor(sqlClaims, context, recorder);
253
+ return sql;
254
+ },
255
+ stats: () => recorder.snapshot()
256
+ };
257
+ };
258
+ const resolve = async (request, resolveOptions = {}) => {
259
+ const claims = options.auth?.claims ?? sb.claimsSchema;
260
+ const resolution = await resolveAuth(request, {
261
+ ...options.auth,
262
+ ...claims ? { claims } : {},
263
+ env: env(),
264
+ ...resolveOptions.refresh !== void 0 ? { refresh: resolveOptions.refresh } : {},
265
+ onRefresh: (event) => {
266
+ options.auth?.onRefresh?.(event);
267
+ sb.events.emit("refresh", event);
268
+ }
269
+ });
270
+ if (sb.events.has("auth")) {
271
+ const { auth } = resolution;
272
+ sb.events.emit("auth", {
273
+ source: auth.kind === "user" ? auth.source === "cookie" ? "cookie" : "bearer" : auth.kind === "service" ? "bearer" : "none",
274
+ ok: auth.kind !== "invalid",
275
+ ...auth.kind === "user" ? { userId: auth.user.id } : {}
276
+ });
277
+ }
278
+ return resolution;
279
+ };
280
+ return {
281
+ get env() {
282
+ return env();
283
+ },
284
+ resolve,
285
+ supabaseFor,
286
+ dbFor,
287
+ contextFor: (auth, contextOptions = {}) => contextFor({
288
+ auth,
289
+ cookies: [],
290
+ headers: {},
291
+ requestCookies: [],
292
+ apply: (response) => response
293
+ }, {}, contextOptions.stats, contextOptions.pinnedUntil),
294
+ async context(request, contextOptions = {}) {
295
+ const resolution = await resolve(request, { refresh: contextOptions.refresh ?? false });
296
+ return contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? pinnedUntil(request.headers.get("cookie")));
297
+ },
298
+ admin: (context = {}) => sb.connect(serviceClient(), {
299
+ actor: {
300
+ id: "service",
301
+ kind: "service",
302
+ role: "service_role"
303
+ },
304
+ claims: { role: "service_role" },
305
+ ...context
306
+ }),
307
+ deleteAccount: (userId, deleteOptions) => deleteAccount(sb, serviceClient, userId, deleteOptions),
308
+ actingAs: (userId, claims = {}, impersonation) => {
309
+ const full = {
310
+ role: "authenticated",
311
+ ...claims,
312
+ sub: userId,
313
+ ...impersonation ? { act: actClaim(impersonation) } : {}
314
+ };
315
+ return sqlFor(full, {
316
+ actor: {
317
+ id: userId,
318
+ kind: "user",
319
+ role: full.role ?? "authenticated",
320
+ ...impersonation ? { impersonator: impersonation.actor } : {}
321
+ },
322
+ claims: full
323
+ });
324
+ }
325
+ };
326
+ }
327
+ //#endregion
328
+ //#region src/server/respond.ts
329
+ /** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
330
+ function guard(auth, allow = ["user"], aal = "aal1") {
331
+ if (auth.kind === "invalid") return auth.error;
332
+ if (allow.includes(auth.kind)) return checkAal(auth, aal);
333
+ return auth.kind === "anon" ? dbError("unauthorized", "Sign in to continue", { code: "MISSING_CREDENTIALS" }) : dbError("forbidden", `A ${auth.kind} caller is not allowed here`);
334
+ }
335
+ function isResult(value) {
336
+ return typeof value === "object" && value !== null && "ok" in value && "data" in value && "error" in value;
337
+ }
338
+ /**
339
+ * Settles a handler's return value: `Result`s and `AsyncResult`s unwrap,
340
+ * thrown `DbException`s (or errors whose `cause` is a `DbError`) become
341
+ * errors, anything else is data. Other thrown
342
+ * errors propagate.
343
+ */
344
+ async function settle(run) {
345
+ try {
346
+ const value = await run();
347
+ if (!isResult(value)) return {
348
+ ok: true,
349
+ data: value
350
+ };
351
+ if (value.ok) return {
352
+ ok: true,
353
+ data: value.data
354
+ };
355
+ return {
356
+ ok: false,
357
+ error: isDbError(value.error) ? value.error : toDbError(value.error)
358
+ };
359
+ } catch (cause) {
360
+ const thrown = dbErrorOf(cause);
361
+ if (thrown) return {
362
+ ok: false,
363
+ error: thrown
364
+ };
365
+ throw cause;
366
+ }
367
+ }
368
+ /** A handler's value as a response: `Response` as is, data as JSON, errors as Problem Details. */
369
+ async function respond(run, options = {}) {
370
+ const settled = await settle(run);
371
+ if (!settled.ok) return problemResponse(settled.error, options);
372
+ if (settled.data instanceof Response) return settled.data;
373
+ if (settled.data === void 0) return new Response(null, { status: 204 });
374
+ return Response.json(settled.data, { status: options.status ?? 200 });
375
+ }
376
+ function defaultExpose() {
377
+ return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
378
+ }
379
+ //#endregion
380
+ 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 };
@@ -0,0 +1,169 @@
1
+ import { n as AnyModels, t as AnyFunctions } from "./types-C2fslP1z.js";
2
+ import { p as DbError, t as AsyncResult } from "./result-CTkCZ6JA.js";
3
+ import { I as StatsRecorder, P as DbStats, t as BetterSupabase, y as Db } from "./define-CTfWw-SS.js";
4
+ import { h as RequestContext } from "./plugin-HvGqCurB.js";
5
+ import { t as BetterSupabaseEnv } from "./index-Dy6kuF0P.js";
6
+ import { r as SqlClaims, t as Postgres } from "./pool-BFeqzwQS.js";
7
+ import { o as ImpersonationOptions, t as Aal } from "./mfa-TVia554c.js";
8
+ import { a as ResolveAuthOptions, r as AuthState, t as AuthResolution } from "./resolve-DW8h9NPm.js";
9
+ import { n as Bucket } from "./index-Nxo3TJcm.js";
10
+ import { SupabaseClient } from "@supabase/supabase-js";
11
+ //#region src/server/delete-account.d.ts
12
+ interface DeleteAccountOptions {
13
+ /**
14
+ * Buckets to clear first. Objects whose `owner` placeholder (`{userId}`,
15
+ * or `owner.param` for `owner` buckets) is the user are removed; buckets
16
+ * without one are skipped.
17
+ */
18
+ readonly buckets?: readonly Bucket<string>[];
19
+ /**
20
+ * App keys of tables whose rows the database deletes or updates with the
21
+ * user (`on delete cascade` or `set null` to `auth.users`). They get a
22
+ * table-wide `mutation` notice, so caches drop their reads.
23
+ */
24
+ readonly cascades?: readonly string[];
25
+ readonly context?: RequestContext;
26
+ readonly signal?: AbortSignal;
27
+ }
28
+ interface DeleteAccountResult {
29
+ readonly userId: string;
30
+ /** Removed objects per bucket id. */
31
+ readonly removed: Readonly<Record<string, number>>;
32
+ }
33
+ //#endregion
34
+ //#region src/server/replicas.d.ts
35
+ /** Cookie holding the epoch ms until which reads stay on the primary. */
36
+ declare const PRIMARY_COOKIE = "bs-primary-until";
37
+ /** Where a request context sends its reads. */
38
+ interface ReplicaState {
39
+ /** Reads go to the primary: this context wrote, or a recent request did. */
40
+ readonly pinned: boolean;
41
+ /** This context wrote through its repositories (or called `pin()`). */
42
+ readonly wrote: boolean;
43
+ /** Sends later reads to the primary, e.g. after a write through `$client` or `$sql`. */
44
+ pin(): void;
45
+ }
46
+ //#endregion
47
+ //#region src/server/server.d.ts
48
+ interface ServerOptions {
49
+ /** Defaults to `loadEnv()`. */
50
+ readonly env?: BetterSupabaseEnv;
51
+ /** Enables `ctx.sql` and `actingAs()`. */
52
+ readonly postgres?: Postgres;
53
+ readonly auth?: Omit<ResolveAuthOptions, "env">;
54
+ /**
55
+ * Extra headers on the caller's PostgREST requests, readable in Postgres as
56
+ * `current_setting('request.headers')` (channel, request id, client IP for audit).
57
+ */
58
+ readonly headers?: (request: Request) => Readonly<Record<string, string>>;
59
+ /**
60
+ * PostgREST URL for the caller's reads: a read replica or the `<ref>-all`
61
+ * load balancer. Defaults to `SUPABASE_READ_URL`; `false` reads from the
62
+ * primary.
63
+ */
64
+ readonly readUrl?: string | false;
65
+ readonly replicas?: {
66
+ /** How long the next requests read from the primary after a write. Defaults to 5 s. */
67
+ readonly pinMs?: number;
68
+ };
69
+ }
70
+ interface ServerContext<M extends AnyModels, F extends AnyFunctions, E, C = unknown> {
71
+ readonly auth: AuthState<C>;
72
+ readonly resolution: AuthResolution<C>;
73
+ /** supabase-js client scoped to the caller (RLS applies). */
74
+ readonly supabase: SupabaseClient;
75
+ /** Repositories over PostgREST as the caller. */
76
+ readonly db: Db<M, F, E, SupabaseClient>;
77
+ /** Repositories over direct Postgres as the caller, when `postgres` is configured. */
78
+ readonly sql: Db<M, F, E, undefined> | undefined;
79
+ /** Calls, waves, tables and time for `db` and `sql` together. */
80
+ stats(): DbStats;
81
+ /** Where `db` reads go, when a read URL is configured. */
82
+ readonly replica: ReplicaState | undefined;
83
+ }
84
+ interface ContextOptions {
85
+ readonly refresh?: boolean;
86
+ /** Epoch ms until which `db` reads from the primary (the `bs-primary-until` cookie). */
87
+ readonly pinnedUntil?: number;
88
+ /** Also records this context's calls into a request-wide recorder. */
89
+ readonly stats?: StatsRecorder;
90
+ }
91
+ interface BetterServer<M extends AnyModels, F extends AnyFunctions, E, C = unknown> {
92
+ readonly env: BetterSupabaseEnv;
93
+ resolve(request: Request, options?: {
94
+ readonly refresh?: boolean;
95
+ }): Promise<AuthResolution<C>>;
96
+ /**
97
+ * Resolves auth and binds repositories to the caller. Refreshes an expired
98
+ * cookie session only with `refresh: true`; send `ctx.resolution.apply(response)`.
99
+ */
100
+ context(request: Request, options?: ContextOptions): Promise<ServerContext<M, F, E, C>>;
101
+ /** The context for an auth state resolved elsewhere (the proxy, a queue message). */
102
+ contextFor(auth: AuthState<C>, options?: ContextOptions): ServerContext<M, F, E, C>;
103
+ /** A stateless supabase-js client for an auth state, optionally with extra request headers. */
104
+ supabaseFor(auth: AuthState, headers?: Readonly<Record<string, string>>): SupabaseClient;
105
+ /** Repositories for an auth state (e.g. one resolved by the proxy). */
106
+ dbFor(auth: AuthState, context?: RequestContext): Db<M, F, E, SupabaseClient>;
107
+ /** Service-role repositories. Bypasses RLS; the actor is `service`. */
108
+ admin(context?: RequestContext): Db<M, F, E, SupabaseClient>;
109
+ /**
110
+ * Deletes a user: their objects in `buckets`, then the Auth user, then a
111
+ * `mutation` notice for `auth.users`. Needs the secret key. Access tokens
112
+ * already issued stay valid until they expire.
113
+ */
114
+ deleteAccount(userId: string, options?: DeleteAccountOptions): AsyncResult<DeleteAccountResult>;
115
+ /**
116
+ * Repositories running as a user, with RLS, over direct Postgres. With
117
+ * `impersonation`, the claims carry `act: { sub: actor, reason }`, which
118
+ * the `audit` and `actor` SQL kit modules record.
119
+ */
120
+ actingAs(userId: string, claims?: Omit<SqlClaims, "sub" | "act">, impersonation?: ImpersonationOptions): Db<M, F, E, undefined>;
121
+ }
122
+ /**
123
+ * Server-side entry point for APIs, jobs, MCP servers and framework
124
+ * adapters. Holds the secrets; `sb` stays isomorphic.
125
+ */
126
+ declare function createServer<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown>(sb: BetterSupabase<M, D, F, E, C>, options?: ServerOptions): BetterServer<M, F, E, C>;
127
+ //#endregion
128
+ //#region src/server/respond.d.ts
129
+ type AuthKind = AuthState["kind"];
130
+ interface GuardOptions {
131
+ /** Auth kinds allowed through. Defaults to `['user']`. */
132
+ readonly allow?: readonly Exclude<AuthKind, "invalid">[];
133
+ /**
134
+ * Assurance level user sessions need. `aal2` answers 403 with
135
+ * `required: 'aal2'` until the user verifies a second factor.
136
+ */
137
+ readonly aal?: Aal;
138
+ }
139
+ /** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
140
+ declare function guard(auth: AuthState, allow?: GuardOptions["allow"], aal?: Aal): DbError | undefined;
141
+ declare function isResult(value: unknown): value is {
142
+ ok: boolean;
143
+ data: unknown;
144
+ error: unknown;
145
+ };
146
+ type Settled = {
147
+ readonly ok: true;
148
+ readonly data: unknown;
149
+ } | {
150
+ readonly ok: false;
151
+ readonly error: DbError;
152
+ };
153
+ /**
154
+ * Settles a handler's return value: `Result`s and `AsyncResult`s unwrap,
155
+ * thrown `DbException`s (or errors whose `cause` is a `DbError`) become
156
+ * errors, anything else is data. Other thrown
157
+ * errors propagate.
158
+ */
159
+ declare function settle(run: () => unknown): Promise<Settled>;
160
+ interface RespondOptions {
161
+ readonly instance?: string;
162
+ readonly expose?: boolean;
163
+ /** Status for data responses. Defaults to 200 (204 without data). */
164
+ readonly status?: number;
165
+ }
166
+ /** A handler's value as a response: `Response` as is, data as JSON, errors as Problem Details. */
167
+ declare function respond(run: () => unknown, options?: RespondOptions): Promise<Response>;
168
+ //#endregion
169
+ export { DeleteAccountResult as _, guard as a, settle as c, ServerContext as d, ServerOptions as f, DeleteAccountOptions as g, ReplicaState as h, Settled as i, BetterServer as l, PRIMARY_COOKIE as m, GuardOptions as n, isResult as o, createServer as p, RespondOptions as r, respond as s, AuthKind as t, ContextOptions as u };