better-supabase 0.3.0 → 0.5.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.
- package/README.md +9 -6
- package/bin/better-supabase.js +4 -0
- package/dist/{base64-CQeB1eSf.js → base64-D2gsioCY.js} +2 -2
- package/dist/channel-FQW-CnGR.d.ts +70 -0
- package/dist/claims-BAf6c7is.js +48 -0
- package/dist/cli/_format-Cax5EsM9.js +101 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +48 -0
- package/dist/cli/codemod-DqV7612m.js +388 -0
- package/dist/cli/command-CK14fP91.js +502 -0
- package/dist/cli/compare-BmIWVEui.js +9 -0
- package/dist/cli/config-CPVpXP5I.js +1965 -0
- package/dist/cli/config-dtoekM0v.js +2 -0
- package/dist/cli/db-DOBPt660.js +52 -0
- package/dist/cli/diff-SjkO5rNr.js +752 -0
- package/dist/cli/dist-DBPdlPYK.js +1112 -0
- package/dist/cli/dist-DreZ24Sz.js +129 -0
- package/dist/cli/dist-xn4wlboc.js +732 -0
- package/dist/cli/doctor-L7Z8mYTx.js +2863 -0
- package/dist/cli/env-DmKv_G4A.js +108 -0
- package/dist/cli/env-jIFUADLY.js +511 -0
- package/dist/cli/errors-4v4DTtda.js +56 -0
- package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
- package/dist/cli/gen-BJc9vdyu.js +2 -0
- package/dist/cli/gen-C2osRcr5.js +2204 -0
- package/dist/cli/index.d.ts +493 -0
- package/dist/cli/index.js +184 -0
- package/dist/cli/init-Saf5-R9T.js +550 -0
- package/dist/cli/introspect--H7oGmcd.js +81 -0
- package/dist/cli/io-k41_z-jT.js +24 -0
- package/dist/cli/json5-ChRycquj.js +787 -0
- package/dist/cli/jsonc-BUniUpOk.js +2 -0
- package/dist/cli/jsonc-D5Fu67jw.js +45 -0
- package/dist/cli/keys-ByKHnvP9.js +83 -0
- package/dist/cli/ohash-D7bG_EOb.js +277 -0
- package/dist/cli/openapi-Cp5Fyta-.js +66 -0
- package/dist/cli/project-B_GeXyHh.js +70 -0
- package/dist/cli/prompts-BNcNfAve.js +1142 -0
- package/dist/cli/prompts-BTYQGunc.js +2 -0
- package/dist/cli/read-sets-BfMQcKy8.js +289 -0
- package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
- package/dist/cli/run-BKR2W_79.js +351 -0
- package/dist/cli/seed-CUlqWh-8.js +98 -0
- package/dist/cli/skills-LJJIhLFR.js +137 -0
- package/dist/cli/sql-DzPbfngx.js +348 -0
- package/dist/cli/style-C0NFdJRL.js +29 -0
- package/dist/cli/toml-DB-MNnFs.js +424 -0
- package/dist/cli/version-BDJuO0UR.js +4 -0
- package/dist/cli/yaml-CYCbJ6et.js +2252 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +15 -22
- package/dist/clone-BfgmLU90.js +21 -0
- package/dist/{compiler-2zqGzxVZ.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -4
- package/dist/config/index.js +5 -5
- package/dist/{config-DOsGYSiT.js → config-DreERlKo.js} +1 -1
- package/dist/{config-C1CYcm6o.d.ts → config-T_ejwyd3.d.ts} +176 -11
- package/dist/{define-DUbydmJa.d.ts → define-Bp_BZa5h.d.ts} +37 -29
- package/dist/{define-DF341GDj.js → define-cNKzqwL_.js} +356 -89
- package/dist/edge/index.d.ts +20 -13
- package/dist/edge/index.js +55 -26
- package/dist/{entitlements-DvJXoI02.d.ts → entitlements-DUxU4oHe.d.ts} +2 -2
- package/dist/env/index.d.ts +2 -2
- package/dist/env/index.js +34 -1
- package/dist/{errors-Bl3rkOzo.js → errors-DtwXa6b8.js} +2 -1
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.js +69 -16
- package/dist/events-aIO7vsIk.d.ts +230 -0
- package/dist/executor-BunPOO-k.d.ts +402 -0
- package/dist/{executor-B26lgbHD.d.ts → executor-Cghdstwu.d.ts} +1 -2
- package/dist/{executor-C57jx2pn.js → executor-XycESQPH.js} +3 -3
- package/dist/hono/index.d.ts +27 -23
- package/dist/hono/index.js +46 -11
- package/dist/impersonation-VfaGc4Q_.js +41 -0
- package/dist/{index-BNsABq7P.d.ts → index-BGdgBPdf.d.ts} +66 -25
- package/dist/{index-qriRM9cZ.d.ts → index-C7f8cOBM.d.ts} +4 -4
- package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
- package/dist/{index-GV77NpRP.d.ts → index-DOESKG27.d.ts} +34 -9
- package/dist/{index-CriFFJ_p.d.ts → index-DgILJRXj.d.ts} +43 -17
- package/dist/{index-UJ-LUxtI.d.ts → index-DmXSxbLi.d.ts} +14 -14
- package/dist/{index-Cf-D2jk1.d.ts → index-Dxn2a5cw.d.ts} +2 -2
- package/dist/{index-CTTMS6_1.d.ts → index-SaTsEfGP.d.ts} +86 -9
- package/dist/{index-CADlNHK-.d.ts → index-fag8VJZ2.d.ts} +5 -6
- package/dist/index.d.ts +34 -17
- package/dist/index.js +15 -14
- package/dist/jobs/index.d.ts +147 -95
- package/dist/jobs/index.js +773 -250
- package/dist/{json-schema--aOXAGfU.js → json-schema-m1fiwKgN.js} +23 -2
- package/dist/kit-events-CTFDfSwb.js +60 -0
- package/dist/kit-transport-B82k1djG.d.ts +33 -0
- package/dist/kit-transport-DJlHQUBU.js +55 -0
- package/dist/{config-Be4GwsqI.js → kits-CGLLrNPd.js} +38 -10
- package/dist/list/index.d.ts +1 -1
- package/dist/list/index.js +3 -3
- package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
- package/dist/{live-QE3UPbGm.d.ts → live-D9W2M5aO.d.ts} +16 -15
- package/dist/lookup-BaP5bI27.js +18 -0
- package/dist/mcp/index.d.ts +39 -22
- package/dist/mcp/index.js +39 -25
- package/dist/{mfa-x3V3vGh-.d.ts → mfa-CusDgPaD.d.ts} +5 -1
- package/dist/next/image/index.js +6 -3
- package/dist/next/index.d.ts +60 -36
- package/dist/next/index.js +167 -50
- package/dist/notifications/index.d.ts +162 -0
- package/dist/notifications/index.js +264 -0
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +6 -6
- package/dist/orgs/index.d.ts +132 -0
- package/dist/orgs/index.js +245 -0
- package/dist/orpc/index.d.ts +37 -20
- package/dist/orpc/index.js +24 -11
- package/dist/otel/index.d.ts +24 -5
- package/dist/otel/index.js +124 -64
- package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
- package/dist/permdock-sql-DgOfeixi.js +51 -0
- package/dist/plugin-DwARMuQp.js +59 -0
- package/dist/{plugin-DVRB8qNE.d.ts → plugin-o144f322.d.ts} +17 -5
- package/dist/plugins/actor/index.d.ts +5 -2
- package/dist/plugins/actor/index.js +22 -8
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +52 -17
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +14 -5
- package/dist/plugins/tenant/index.d.ts +3 -3
- package/dist/plugins/tenant/index.js +45 -21
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +4 -3
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +26 -6
- package/dist/{pool-B9qsLyFR.d.ts → pool-C9voM4Kw.d.ts} +24 -6
- package/dist/postgres/index.d.ts +5 -5
- package/dist/postgres/index.js +20 -10
- package/dist/{postgrest-DmBlevh-.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
- package/dist/{problem-CGt1ADix.js → problem-BW1KhzSG.js} +2 -1
- package/dist/{problem-Z2RTQjZD.d.ts → problem-Blxe8S31.d.ts} +1 -1
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -3
- package/dist/{query-BdKLTPlj.js → query-CCbo44lG.js} +18 -18
- package/dist/queue-BYUCjr59.d.ts +252 -0
- package/dist/react/index.d.ts +8 -7
- package/dist/react/index.js +4 -3
- package/dist/react/server.d.ts +16 -5
- package/dist/react/server.js +12 -4
- package/dist/react/session.d.ts +10 -4
- package/dist/react/session.js +11 -3
- package/dist/{react-DNBYNazu.js → react-C_s7kiNz.js} +138 -46
- package/dist/{read-set-D0qBcboM.js → read-set-DQFt1N_Y.js} +71 -15
- package/dist/realtime/index.d.ts +3 -3
- package/dist/realtime/index.js +14 -12
- package/dist/{index-kpgopLNv.d.ts → refresh-BTrQ_ZTp.d.ts} +6 -3
- package/dist/{resolve-BRciX2jK.d.ts → resolve-C15TJq77.d.ts} +22 -11
- package/dist/{resource-D30rp0GN.js → resource-CRxxwkQq.js} +24 -9
- package/dist/{resource-CwVgiI5q.d.ts → resource-D8iqWMr-.d.ts} +6 -6
- package/dist/respond-D2OQl62-.d.ts +371 -0
- package/dist/{result-DWXatkd6.js → result-BKiliPY7.js} +2 -2
- package/dist/server/index.d.ts +17 -15
- package/dist/server/index.js +16 -22
- package/dist/server-Csx2sRjg.js +655 -0
- package/dist/{shared-_6-uPL7E.js → shared-D1dRUrAg.js} +19 -4
- package/dist/signers-Cc6r98SW.d.ts +162 -0
- package/dist/{simplify-LakqK1vH.js → simplify-_0oUKqCd.js} +27 -95
- package/dist/sql/index.d.ts +247 -10
- package/dist/sql/index.js +8591 -780
- package/dist/{sql-BIgk12ew.js → sql-Coh6n4cC.js} +11 -3
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +1 -2
- package/dist/{standard-FUi3Z3-7.js → standard-D7r6dMUu.js} +6 -3
- package/dist/{standard-eudBiRk1.d.ts → standard-J4YgMOLm.d.ts} +1 -1
- package/dist/{stats-D78UQ8Yu.js → stats-Bqob3WqT.js} +34 -9
- package/dist/storage/index.d.ts +4 -4
- package/dist/storage/index.js +174 -27
- package/dist/support-DT-OhSxD.d.ts +88 -0
- package/dist/support-Dk6vdmEw.js +291 -0
- package/dist/support-view-Da5peKFg.d.ts +17 -0
- package/dist/support-view-g4YLBeqX.js +18 -0
- package/dist/{tables-Dhha3BXM.js → tables-Cb-3_VWc.js} +14 -0
- package/dist/{permdock-sql-Bta_G0v-.js → template-TxrGI5jR.js} +2 -31
- package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
- package/dist/{temporal-required-CMxeSVPo.js → temporal-required-CAt4Dc9Z.js} +2 -2
- package/dist/testing/index.d.ts +113 -24
- package/dist/testing/index.js +375 -47
- package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
- package/dist/user-change-CHFEUro1.js +49 -0
- package/dist/{webhooks-CAIFQSTM.js → verify-Ce49ETPz.js} +38 -19
- package/dist/{version-BfpG2gks.js → version-DM_Roffb.js} +1 -1
- package/dist/{view-3Po_PVlr.d.ts → view-BEf0diC5.d.ts} +10 -5
- package/dist/{mfa-BxoR1RkN.js → view-Bq64ki8-.js} +210 -64
- package/dist/webhooks/index.d.ts +136 -5
- package/dist/webhooks/index.js +588 -2
- package/dist/wire-D2dT86vi.js +35 -0
- package/package.json +50 -10
- package/schemas/config-v1.json +131 -1
- package/schemas/doctor-report-v1.json +10 -0
- package/skills/better-supabase/SKILL.md +60 -10
- package/skills/better-supabase/references/plugins.md +13 -14
- package/skills/better-supabase/references/troubleshooting.md +25 -4
- package/skills/better-supabase-api/SKILL.md +74 -19
- package/skills/better-supabase-api/references/adapters.md +48 -27
- package/skills/better-supabase-auth/SKILL.md +149 -0
- package/skills/better-supabase-auth/references/permdock.md +100 -0
- package/skills/better-supabase-testing/SKILL.md +58 -4
- package/dist/claims-CoTobNC-.js +0 -25
- package/dist/events-Do8jXh4D.d.ts +0 -84
- package/dist/executor-DynmIWhq.d.ts +0 -195
- package/dist/invalidate-DbxuL2jL.js +0 -18
- package/dist/permdock-sql-DImJULY-.d.ts +0 -17
- package/dist/plugin-BqR6wKMB.js +0 -23
- package/dist/respond-C44EZAJo.js +0 -399
- package/dist/respond-DAkPIdTH.d.ts +0 -175
- package/dist/result-c2OYgCV2.d.ts +0 -203
- package/dist/view-wZ-9qIUz.js +0 -45
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import { n as dbError, o as isDbError, r as dbErrorOf } from "./errors-DtwXa6b8.js";
|
|
2
|
+
import { i as ok, n as err, o as toDbError, t as AsyncResult } from "./result-BKiliPY7.js";
|
|
3
|
+
import { a as postgrestExecutor, n as StatsRecorder } from "./stats-Bqob3WqT.js";
|
|
4
|
+
import { a as problemResponse } from "./problem-BW1KhzSG.js";
|
|
5
|
+
import { r as userContext, t as actClaim } from "./impersonation-VfaGc4Q_.js";
|
|
6
|
+
import { loadEnv } from "./env/index.js";
|
|
7
|
+
import { a as checkAal, b as delegationOf, c as prefetchJwks, l as rememberVerified, o as authContext, t as isAnonymousUser, u as resolveAuth, y as actorOf } from "./view-Bq64ki8-.js";
|
|
8
|
+
import { parseCookieHeader, serializeCookieHeader } from "@supabase/ssr";
|
|
9
|
+
import { createClient } from "@supabase/supabase-js";
|
|
10
|
+
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
11
|
+
//#region src/server/replicas.ts
|
|
12
|
+
/** Cookie holding the epoch ms until which reads stay on the primary. */
|
|
13
|
+
const PRIMARY_COOKIE = "bs-primary-until";
|
|
14
|
+
/** Default time reads stay on the primary after a write. */
|
|
15
|
+
const DEFAULT_PIN_MS = 5e3;
|
|
16
|
+
function replicaState(pinnedUntil = 0, now = Date.now()) {
|
|
17
|
+
let wrote = false;
|
|
18
|
+
const inherited = pinnedUntil > now;
|
|
19
|
+
return {
|
|
20
|
+
get pinned() {
|
|
21
|
+
return wrote || inherited;
|
|
22
|
+
},
|
|
23
|
+
get wrote() {
|
|
24
|
+
return wrote;
|
|
25
|
+
},
|
|
26
|
+
pin() {
|
|
27
|
+
wrote = true;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** `bs-primary-until` from a `cookie` header or parsed cookies, or 0. */
|
|
32
|
+
function pinnedUntil(cookies) {
|
|
33
|
+
if (!cookies) return 0;
|
|
34
|
+
const value = (typeof cookies === "string" ? parseCookieHeader(cookies) : cookies).find((cookie) => cookie.name === PRIMARY_COOKIE)?.value;
|
|
35
|
+
const until = Number(value);
|
|
36
|
+
return value !== void 0 && Number.isFinite(until) ? until : 0;
|
|
37
|
+
}
|
|
38
|
+
/** Options of the `bs-primary-until` cookie, for frameworks that set cookies themselves. */
|
|
39
|
+
function primaryCookieOptions(pinMs) {
|
|
40
|
+
return {
|
|
41
|
+
path: "/",
|
|
42
|
+
maxAge: Math.max(1, Math.ceil(pinMs / 1e3)),
|
|
43
|
+
httpOnly: true,
|
|
44
|
+
sameSite: "lax"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** A `Set-Cookie` value keeping the next requests on the primary for `pinMs`. */
|
|
48
|
+
function primaryCookie(pinMs, now = Date.now()) {
|
|
49
|
+
return serializeCookieHeader(PRIMARY_COOKIE, String(now + pinMs), primaryCookieOptions(pinMs));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `response` with the `bs-primary-until` cookie when the request wrote, so the
|
|
53
|
+
* caller's next requests read their own writes. A response with immutable
|
|
54
|
+
* headers is copied.
|
|
55
|
+
*/
|
|
56
|
+
function withPrimaryPin(response, replica, pinMs) {
|
|
57
|
+
if (!replica?.wrote) return response;
|
|
58
|
+
const cookie = primaryCookie(pinMs);
|
|
59
|
+
try {
|
|
60
|
+
response.headers.append("set-cookie", cookie);
|
|
61
|
+
return response;
|
|
62
|
+
} catch {
|
|
63
|
+
const copy = new Response(response.body, response);
|
|
64
|
+
copy.headers.append("set-cookie", cookie);
|
|
65
|
+
return copy;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Reads go to `replica` until the context is pinned; writes, and every read
|
|
70
|
+
* after a successful write, go to `primary`.
|
|
71
|
+
*/
|
|
72
|
+
function routedExecutor(primary, replica, state) {
|
|
73
|
+
const batch = primary.batch?.bind(primary);
|
|
74
|
+
const replicaBatch = replica.batch?.bind(replica);
|
|
75
|
+
return {
|
|
76
|
+
name: primary.name,
|
|
77
|
+
...primary.functionSources && replica.functionSources ? { functionSources: true } : {},
|
|
78
|
+
async execute(op, context) {
|
|
79
|
+
if (op.kind === "select" && !state.pinned) return replica.execute(op, context);
|
|
80
|
+
const result = await primary.execute(op, context);
|
|
81
|
+
if (op.kind !== "select" && result.ok) state.pin();
|
|
82
|
+
return result;
|
|
83
|
+
},
|
|
84
|
+
...primary.rpc ? { async rpc(name, args, context) {
|
|
85
|
+
if (context.get && !state.pinned && replica.rpc) return replica.rpc(name, args, context);
|
|
86
|
+
const result = await primary.rpc(name, args, context);
|
|
87
|
+
if (!context.get && result.ok) state.pin();
|
|
88
|
+
return result;
|
|
89
|
+
} } : {},
|
|
90
|
+
...batch ? { batch: (ops, context) => !state.pinned && replicaBatch ? replicaBatch(ops, context) : batch(ops, context) } : {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/server/delete-account.ts
|
|
95
|
+
const REMOVE_BATCH = 1e3;
|
|
96
|
+
/** Maps an `AuthApiError` from `auth.admin.deleteUser` to a `DbError`. */
|
|
97
|
+
function fromAuthError(raw) {
|
|
98
|
+
if (isDbError(raw)) return raw;
|
|
99
|
+
const failure = typeof raw === "object" && raw !== null ? raw : {};
|
|
100
|
+
const message = failure.message ?? "Auth request failed";
|
|
101
|
+
const base = {
|
|
102
|
+
table: "auth.users",
|
|
103
|
+
...failure.code ? { code: failure.code } : {}
|
|
104
|
+
};
|
|
105
|
+
if (failure.code === "user_not_found" || failure.status === 404) return dbError("not_found", message, base);
|
|
106
|
+
if (/database error deleting user/i.test(message)) return dbError("conflict", message, {
|
|
107
|
+
...base,
|
|
108
|
+
hint: "A foreign key to auth.users without on delete cascade or set null blocks the delete (doctor BS406)"
|
|
109
|
+
});
|
|
110
|
+
if (failure.status === 401) return dbError("unauthorized", message, base);
|
|
111
|
+
if (failure.status === 403) return dbError("forbidden", message, base);
|
|
112
|
+
return failure.name === "AuthRetryableFetchError" || (failure.status ?? 0) >= 500 ? dbError("network", message, base) : dbError("unexpected", message, base);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Removes a user's Storage objects, then the Auth user, then announces the
|
|
116
|
+
* delete. Rows referencing `auth.users` follow their foreign keys.
|
|
117
|
+
*/
|
|
118
|
+
function deleteAccount(betterSupabase, service, userId, options = {}) {
|
|
119
|
+
return AsyncResult.from(async () => {
|
|
120
|
+
const client = service();
|
|
121
|
+
const removed = {};
|
|
122
|
+
for (const bucket of options.buckets ?? []) {
|
|
123
|
+
const param = bucket.owner;
|
|
124
|
+
if (!param) continue;
|
|
125
|
+
options.signal?.throwIfAborted();
|
|
126
|
+
const storage = bucket.connect(client, { allTenants: true });
|
|
127
|
+
const listed = await storage.list({ [param]: userId }, options.signal ? { signal: options.signal } : {});
|
|
128
|
+
if (!listed.ok) return listed;
|
|
129
|
+
const paths = listed.data.map((object) => object.path).filter((path) => {
|
|
130
|
+
return bucket.match(path)?.[param] === userId;
|
|
131
|
+
});
|
|
132
|
+
for (let start = 0; start < paths.length; start += REMOVE_BATCH) {
|
|
133
|
+
const batch = await storage.remove(paths.slice(start, start + REMOVE_BATCH));
|
|
134
|
+
if (!batch.ok) return batch;
|
|
135
|
+
}
|
|
136
|
+
removed[bucket.id] = paths.length;
|
|
137
|
+
}
|
|
138
|
+
options.signal?.throwIfAborted();
|
|
139
|
+
try {
|
|
140
|
+
const { error } = await client.auth.admin.deleteUser(userId);
|
|
141
|
+
if (error) return err(fromAuthError(error));
|
|
142
|
+
} catch (cause) {
|
|
143
|
+
return err(fromAuthError(cause));
|
|
144
|
+
}
|
|
145
|
+
const context = options.context ?? {};
|
|
146
|
+
betterSupabase.events.emit("mutation", {
|
|
147
|
+
table: "auth.users",
|
|
148
|
+
kind: "delete",
|
|
149
|
+
rows: [{ id: userId }],
|
|
150
|
+
context
|
|
151
|
+
});
|
|
152
|
+
for (const table of options.cascades ?? []) betterSupabase.events.emit("mutation", {
|
|
153
|
+
table,
|
|
154
|
+
kind: "delete",
|
|
155
|
+
rows: [],
|
|
156
|
+
context
|
|
157
|
+
});
|
|
158
|
+
return ok({
|
|
159
|
+
userId,
|
|
160
|
+
removed
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/core/json.ts
|
|
166
|
+
/** A `JSON.stringify` replacer: `bigint` values become decimal strings, as the wire codec writes them. */
|
|
167
|
+
function jsonReplacer(_key, value) {
|
|
168
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
169
|
+
}
|
|
170
|
+
/** `Response.json` that writes `bigint` values as decimal strings instead of throwing. */
|
|
171
|
+
function jsonResponse(data, init = {}) {
|
|
172
|
+
const headers = new Headers(init.headers);
|
|
173
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
174
|
+
return new Response(JSON.stringify(data, jsonReplacer), {
|
|
175
|
+
...init,
|
|
176
|
+
headers
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/server/respond.ts
|
|
181
|
+
/** The scopes a delegated user token lacks; empty for the user's own session. */
|
|
182
|
+
function missingScopes(auth, required) {
|
|
183
|
+
if (required.length === 0) return [];
|
|
184
|
+
const outcome = actorOf(auth.claims);
|
|
185
|
+
if (!outcome.ok || !outcome.actor) return [];
|
|
186
|
+
const granted = new Set(delegationOf(auth.claims)?.scopes);
|
|
187
|
+
return required.filter((scope) => !granted.has(scope));
|
|
188
|
+
}
|
|
189
|
+
/** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
|
|
190
|
+
function guard(auth, allow = ["user"], aal = "aal1", scopes = []) {
|
|
191
|
+
if (auth.kind === "invalid") return auth.error;
|
|
192
|
+
if (allow.includes(auth.kind)) {
|
|
193
|
+
const denied = checkAal(auth, aal);
|
|
194
|
+
if (denied || auth.kind !== "user") return denied;
|
|
195
|
+
if (isAnonymousUser(auth.claims) && !allow.includes("anonymous") && !allow.includes("anon")) return dbError("forbidden", "Sign up to continue", { code: "ANONYMOUS_USER" });
|
|
196
|
+
const missing = missingScopes(auth, scopes);
|
|
197
|
+
return missing.length > 0 ? dbError("forbidden", `The token lacks the scope ${missing.join(" ")}`, {
|
|
198
|
+
code: "INSUFFICIENT_SCOPE",
|
|
199
|
+
scopes
|
|
200
|
+
}) : void 0;
|
|
201
|
+
}
|
|
202
|
+
return auth.kind === "anon" ? dbError("unauthorized", "Sign in to continue", { code: "MISSING_CREDENTIALS" }) : dbError("forbidden", `A ${auth.kind} caller is not allowed here`);
|
|
203
|
+
}
|
|
204
|
+
function isResult(value) {
|
|
205
|
+
return typeof value === "object" && value !== null && "ok" in value && "data" in value && "error" in value;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Settles a handler's return value: `Result`s and `AsyncResult`s unwrap,
|
|
209
|
+
* thrown `DbException`s (or errors whose `cause` is a `DbError`) become
|
|
210
|
+
* errors, anything else is data. Other thrown
|
|
211
|
+
* errors propagate.
|
|
212
|
+
*/
|
|
213
|
+
async function settle(run) {
|
|
214
|
+
try {
|
|
215
|
+
const value = await run();
|
|
216
|
+
if (!isResult(value)) return {
|
|
217
|
+
ok: true,
|
|
218
|
+
data: value
|
|
219
|
+
};
|
|
220
|
+
if (value.ok) return {
|
|
221
|
+
ok: true,
|
|
222
|
+
data: value.data
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
ok: false,
|
|
226
|
+
error: isDbError(value.error) ? value.error : toDbError(value.error)
|
|
227
|
+
};
|
|
228
|
+
} catch (cause) {
|
|
229
|
+
const thrown = dbErrorOf(cause);
|
|
230
|
+
if (thrown) return {
|
|
231
|
+
ok: false,
|
|
232
|
+
error: thrown
|
|
233
|
+
};
|
|
234
|
+
throw cause;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** A handler's value as a response: `Response` as is, data as JSON, errors as Problem Details. */
|
|
238
|
+
async function respond(run, options = {}) {
|
|
239
|
+
const settled = await settle(run);
|
|
240
|
+
if (!settled.ok) return problemResponse(settled.error, options);
|
|
241
|
+
if (settled.data instanceof Response) return settled.data;
|
|
242
|
+
if (settled.data === void 0) return new Response(null, { status: 204 });
|
|
243
|
+
return jsonResponse(settled.data, { status: options.status ?? 200 });
|
|
244
|
+
}
|
|
245
|
+
/** Prefetching the JWKS would add a network call to every test that builds a server. */
|
|
246
|
+
function defaultPrefetchJwks() {
|
|
247
|
+
return typeof process === "undefined" || process.env["NODE_ENV"] !== "test";
|
|
248
|
+
}
|
|
249
|
+
function defaultExpose() {
|
|
250
|
+
return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/auth/support-cookie.ts
|
|
254
|
+
/** The cookie that carries the support session id. */
|
|
255
|
+
const SUPPORT_COOKIE = "bs-support";
|
|
256
|
+
/** `Set-Cookie` for a session: HttpOnly, SameSite=Lax, gone when the session expires. */
|
|
257
|
+
function supportCookie(session, options = {}, now = Date.now()) {
|
|
258
|
+
const maxAge = Math.max(0, Math.ceil((session.expiresAt.epochMilliseconds - now) / 1e3));
|
|
259
|
+
return cookieLine(options, encodeURIComponent(session.id), maxAge);
|
|
260
|
+
}
|
|
261
|
+
/** `Set-Cookie` that removes the support cookie. */
|
|
262
|
+
function clearSupportCookie(options = {}) {
|
|
263
|
+
return cookieLine(options, "", 0);
|
|
264
|
+
}
|
|
265
|
+
function cookieLine(options, value, maxAge) {
|
|
266
|
+
return [
|
|
267
|
+
`${options.name ?? "bs-support"}=${value}`,
|
|
268
|
+
`Path=${options.path ?? "/"}`,
|
|
269
|
+
`Max-Age=${String(maxAge)}`,
|
|
270
|
+
"HttpOnly",
|
|
271
|
+
"SameSite=Lax",
|
|
272
|
+
...options.secure === false ? [] : ["Secure"]
|
|
273
|
+
].join("; ");
|
|
274
|
+
}
|
|
275
|
+
/** The support session id in a `Cookie` header, or `undefined`. */
|
|
276
|
+
function supportCookieValue(cookieHeader, name = SUPPORT_COOKIE) {
|
|
277
|
+
if (!cookieHeader) return void 0;
|
|
278
|
+
for (const part of cookieHeader.split(";")) {
|
|
279
|
+
const index = part.indexOf("=");
|
|
280
|
+
if (index === -1 || part.slice(0, index).trim() !== name) continue;
|
|
281
|
+
const value = decodeURIComponent(part.slice(index + 1).trim());
|
|
282
|
+
return value === "" ? void 0 : value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/server/support-off.ts
|
|
287
|
+
const NOT_CONFIGURED = "Support sessions need createServer(betterSupabase, { support: supportSessions({ store }) })";
|
|
288
|
+
const notConfigured = () => AsyncResult.err(dbError("unexpected", NOT_CONFIGURED));
|
|
289
|
+
/** `bs.support` without `ServerOptions.support`: every call fails. */
|
|
290
|
+
const supportOff = {
|
|
291
|
+
start: notConfigured,
|
|
292
|
+
stop: notConfigured,
|
|
293
|
+
revoke: notConfigured,
|
|
294
|
+
current: () => Promise.resolve(void 0),
|
|
295
|
+
list: notConfigured,
|
|
296
|
+
sessionIdOf: () => void 0,
|
|
297
|
+
clearCookie: () => clearSupportCookie()
|
|
298
|
+
};
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/server/server.ts
|
|
301
|
+
/** The header `current_tenant_id()` reads over the Data API. */
|
|
302
|
+
const TENANT_HEADER = "x-bs-tenant";
|
|
303
|
+
const STATELESS = {
|
|
304
|
+
persistSession: false,
|
|
305
|
+
autoRefreshToken: false,
|
|
306
|
+
detectSessionInUrl: false
|
|
307
|
+
};
|
|
308
|
+
/** `{ ...server, ...extra }` without reading the lazy `env` getter. */
|
|
309
|
+
function extendServer(server, extra) {
|
|
310
|
+
return Object.defineProperties({}, {
|
|
311
|
+
...Object.getOwnPropertyDescriptors(server),
|
|
312
|
+
...Object.getOwnPropertyDescriptors(extra)
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** `{ ...base, ...extra }` that keeps lazy getters lazy. */
|
|
316
|
+
function withExtra(base, extra) {
|
|
317
|
+
return Object.defineProperties({}, {
|
|
318
|
+
...Object.getOwnPropertyDescriptors(base),
|
|
319
|
+
...Object.getOwnPropertyDescriptors(extra)
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Server-side entry point for APIs, jobs, MCP servers and framework
|
|
324
|
+
* adapters. Holds the secrets; `betterSupabase` stays isomorphic.
|
|
325
|
+
*/
|
|
326
|
+
function createServer(betterSupabase, options = {}) {
|
|
327
|
+
let loaded;
|
|
328
|
+
const env = () => loaded ??= options.env ?? loadEnv();
|
|
329
|
+
let adminClient;
|
|
330
|
+
let anonClient;
|
|
331
|
+
if ((options.prefetchJwks ?? defaultPrefetchJwks()) && options.auth?.jwks === void 0) try {
|
|
332
|
+
prefetchJwks({
|
|
333
|
+
...options.auth,
|
|
334
|
+
env: env()
|
|
335
|
+
});
|
|
336
|
+
} catch {}
|
|
337
|
+
const secretKey = () => {
|
|
338
|
+
const key = env().secretKey;
|
|
339
|
+
if (!key) throw new TypeError("admin() needs SUPABASE_SECRET_KEY; it is not set");
|
|
340
|
+
return key;
|
|
341
|
+
};
|
|
342
|
+
const customFetch = options.fetch ? { fetch: options.fetch } : void 0;
|
|
343
|
+
const sharedGlobal = customFetch ? { global: customFetch } : {};
|
|
344
|
+
const serviceClient = () => {
|
|
345
|
+
adminClient ??= createClient(env().url, secretKey(), {
|
|
346
|
+
auth: STATELESS,
|
|
347
|
+
...sharedGlobal
|
|
348
|
+
});
|
|
349
|
+
return adminClient;
|
|
350
|
+
};
|
|
351
|
+
const readUrl = () => options.readUrl === false ? void 0 : options.readUrl?.replace(/\/+$/, "") ?? env().readUrl;
|
|
352
|
+
/** Clients for the primary share the service and anon clients; others don't. */
|
|
353
|
+
const supabaseAt = (url, auth, headers = {}) => {
|
|
354
|
+
const custom = Object.keys(headers).length > 0;
|
|
355
|
+
const global = custom ? { global: {
|
|
356
|
+
headers,
|
|
357
|
+
...customFetch
|
|
358
|
+
} } : sharedGlobal;
|
|
359
|
+
const shared = !custom && url === env().url;
|
|
360
|
+
switch (auth.kind) {
|
|
361
|
+
case "user": return createClient(url, env().publishableKey, {
|
|
362
|
+
accessToken: () => Promise.resolve(auth.token),
|
|
363
|
+
...global
|
|
364
|
+
});
|
|
365
|
+
case "service": return shared ? serviceClient() : createClient(url, secretKey(), {
|
|
366
|
+
auth: STATELESS,
|
|
367
|
+
...global
|
|
368
|
+
});
|
|
369
|
+
case "anon":
|
|
370
|
+
case "invalid":
|
|
371
|
+
if (!shared) return createClient(url, env().publishableKey, {
|
|
372
|
+
auth: STATELESS,
|
|
373
|
+
...global
|
|
374
|
+
});
|
|
375
|
+
anonClient ??= createClient(url, env().publishableKey, {
|
|
376
|
+
auth: STATELESS,
|
|
377
|
+
...sharedGlobal
|
|
378
|
+
});
|
|
379
|
+
return anonClient;
|
|
380
|
+
default: return auth;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const supabaseFor = (auth, headers = {}) => supabaseAt(env().url, auth, headers);
|
|
384
|
+
/**
|
|
385
|
+
* The client queries run on. A user's requests need only PostgREST, so they
|
|
386
|
+
* skip building Realtime, Storage and Auth clients per request; the shared
|
|
387
|
+
* service and anon clients are built once anyway.
|
|
388
|
+
*/
|
|
389
|
+
const restAt = (url, auth, headers = {}) => {
|
|
390
|
+
if (auth.kind !== "user") return supabaseAt(url, auth, headers);
|
|
391
|
+
const key = env().publishableKey;
|
|
392
|
+
return new PostgrestClient(new URL("rest/v1", url.endsWith("/") ? url : `${url}/`).href, {
|
|
393
|
+
headers: {
|
|
394
|
+
...headers,
|
|
395
|
+
apikey: key,
|
|
396
|
+
Authorization: `Bearer ${auth.token}`
|
|
397
|
+
},
|
|
398
|
+
...customFetch
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
/** `$client` stays the full Supabase client, built when first read. */
|
|
402
|
+
const withLazyClient = (db, client) => Object.defineProperty(db, "$client", {
|
|
403
|
+
get: client,
|
|
404
|
+
enumerable: true,
|
|
405
|
+
configurable: true
|
|
406
|
+
});
|
|
407
|
+
const dbFor = (auth, context = {}) => {
|
|
408
|
+
let supabase;
|
|
409
|
+
const rest = restAt(env().url, auth);
|
|
410
|
+
const db = betterSupabase.connect(rest, {
|
|
411
|
+
...authContext(auth),
|
|
412
|
+
...context
|
|
413
|
+
});
|
|
414
|
+
return withLazyClient(db, () => supabase ??= supabaseFor(auth));
|
|
415
|
+
};
|
|
416
|
+
const sqlFor = (claims, context, stats, session) => {
|
|
417
|
+
if (!options.postgres) throw new TypeError("Direct Postgres access needs createServer(betterSupabase, { postgres: createPostgres() })");
|
|
418
|
+
return betterSupabase.connect(session ? options.postgres.executorFor(claims, session) : options.postgres.executorFor(claims), context, stats ? { stats } : {});
|
|
419
|
+
};
|
|
420
|
+
/** `ServerOptions.tenant` outside `context()`, where it can't be awaited. */
|
|
421
|
+
const syncTenant = (request, auth) => {
|
|
422
|
+
const tenant = options.tenant?.(request, auth);
|
|
423
|
+
if (tenant !== void 0 && typeof tenant !== "string") throw new TypeError("ServerOptions.tenant returned a promise: use context(request), or pass { tenant } to contextFromResolution");
|
|
424
|
+
return tenant;
|
|
425
|
+
};
|
|
426
|
+
const pinMs = options.replicas?.pinMs ?? 5e3;
|
|
427
|
+
if (options.support && !options.postgres) throw new TypeError("Support sessions need createServer(betterSupabase, { postgres: createPostgres(), support })");
|
|
428
|
+
const support = options.support ? options.support.create(betterSupabase.events, options.auth?.claims ?? betterSupabase.claimsSchema) : supportOff;
|
|
429
|
+
/**
|
|
430
|
+
* The target's context in a support session. Queries run over direct
|
|
431
|
+
* Postgres with the target's claims plus `act`, read-only unless the
|
|
432
|
+
* session allows writes; nothing reaches Supabase with the admin's token.
|
|
433
|
+
*/
|
|
434
|
+
const supportContextFor = (resolution, active, parent, tenant) => {
|
|
435
|
+
const { session, claims } = active;
|
|
436
|
+
const record = (key) => {
|
|
437
|
+
const value = claims[key];
|
|
438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : void 0;
|
|
439
|
+
};
|
|
440
|
+
const appMetadata = record("app_metadata");
|
|
441
|
+
const userMetadata = record("user_metadata");
|
|
442
|
+
const user = {
|
|
443
|
+
id: session.targetUserId,
|
|
444
|
+
role: typeof claims["role"] === "string" ? claims["role"] : "authenticated",
|
|
445
|
+
...typeof claims["email"] === "string" ? { email: claims["email"] } : {},
|
|
446
|
+
...appMetadata ? { appMetadata } : {},
|
|
447
|
+
...userMetadata ? { userMetadata } : {}
|
|
448
|
+
};
|
|
449
|
+
const auth = {
|
|
450
|
+
kind: "user",
|
|
451
|
+
token: "",
|
|
452
|
+
claims,
|
|
453
|
+
user,
|
|
454
|
+
source: "support",
|
|
455
|
+
expiresAt: Math.floor(session.expiresAt.epochMilliseconds / 1e3)
|
|
456
|
+
};
|
|
457
|
+
const scope = tenant ?? session.tenant;
|
|
458
|
+
const base = userContext(user, claims);
|
|
459
|
+
const context = scope === void 0 ? base : {
|
|
460
|
+
...base,
|
|
461
|
+
tenant: scope
|
|
462
|
+
};
|
|
463
|
+
const sessionOptions = {
|
|
464
|
+
...session.readOnly ? { readOnly: true } : {},
|
|
465
|
+
...scope === void 0 ? {} : { settings: { "better_supabase.tenant": scope } }
|
|
466
|
+
};
|
|
467
|
+
const recorder = new StatsRecorder(parent);
|
|
468
|
+
const sqlClaims = claims;
|
|
469
|
+
const unavailable = () => {
|
|
470
|
+
throw new TypeError("ctx.supabase and ctx.db.$client are not available in a support session; ctx.db and ctx.sql run as the target");
|
|
471
|
+
};
|
|
472
|
+
let db;
|
|
473
|
+
let sql;
|
|
474
|
+
return {
|
|
475
|
+
auth,
|
|
476
|
+
resolution,
|
|
477
|
+
support: active,
|
|
478
|
+
get supabase() {
|
|
479
|
+
return unavailable();
|
|
480
|
+
},
|
|
481
|
+
get db() {
|
|
482
|
+
db ??= Object.defineProperty(sqlFor(sqlClaims, context, recorder, sessionOptions), "$client", {
|
|
483
|
+
get: unavailable,
|
|
484
|
+
enumerable: true,
|
|
485
|
+
configurable: true
|
|
486
|
+
});
|
|
487
|
+
return db;
|
|
488
|
+
},
|
|
489
|
+
get sql() {
|
|
490
|
+
sql ??= sqlFor(sqlClaims, context, recorder, sessionOptions);
|
|
491
|
+
return sql;
|
|
492
|
+
},
|
|
493
|
+
replica: void 0,
|
|
494
|
+
stats: () => recorder.snapshot(),
|
|
495
|
+
apply: (response) => resolution.apply(response)
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
/** Clients and repositories are built on first access: most scopes use one of them. */
|
|
499
|
+
const contextFor = (resolution, requestHeaders, parent, until = 0, tenant, active) => {
|
|
500
|
+
if (active) return supportContextFor(resolution, active, parent, tenant);
|
|
501
|
+
const { auth } = resolution;
|
|
502
|
+
const context = tenant === void 0 ? authContext(auth) : {
|
|
503
|
+
...authContext(auth),
|
|
504
|
+
tenant
|
|
505
|
+
};
|
|
506
|
+
const headers = tenant === void 0 ? requestHeaders : {
|
|
507
|
+
...requestHeaders,
|
|
508
|
+
[TENANT_HEADER]: tenant
|
|
509
|
+
};
|
|
510
|
+
const session = tenant === void 0 ? void 0 : { settings: { "better_supabase.tenant": tenant } };
|
|
511
|
+
const recorder = new StatsRecorder(parent);
|
|
512
|
+
let supabase;
|
|
513
|
+
let db;
|
|
514
|
+
let sql;
|
|
515
|
+
const sqlClaims = auth.kind === "user" ? auth.claims : auth.kind === "anon" ? { role: "anon" } : void 0;
|
|
516
|
+
const client = () => supabase ??= supabaseFor(auth, headers);
|
|
517
|
+
let replica;
|
|
518
|
+
const replicaUrl = () => {
|
|
519
|
+
const url = readUrl();
|
|
520
|
+
if (url) replica ??= replicaState(until);
|
|
521
|
+
return url;
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
auth,
|
|
525
|
+
resolution,
|
|
526
|
+
get supabase() {
|
|
527
|
+
return client();
|
|
528
|
+
},
|
|
529
|
+
get db() {
|
|
530
|
+
if (db) return db;
|
|
531
|
+
const url = replicaUrl();
|
|
532
|
+
const rest = supabase ?? restAt(env().url, auth, headers);
|
|
533
|
+
const connected = betterSupabase.connect(rest, context, {
|
|
534
|
+
stats: recorder,
|
|
535
|
+
...url && replica ? { executor: routedExecutor(postgrestExecutor(rest), postgrestExecutor(restAt(url, auth, headers)), replica) } : {}
|
|
536
|
+
});
|
|
537
|
+
db = withLazyClient(connected, client);
|
|
538
|
+
return db;
|
|
539
|
+
},
|
|
540
|
+
get replica() {
|
|
541
|
+
replicaUrl();
|
|
542
|
+
return replica;
|
|
543
|
+
},
|
|
544
|
+
get sql() {
|
|
545
|
+
if (!options.postgres || !sqlClaims) return;
|
|
546
|
+
sql ??= sqlFor(sqlClaims, context, recorder, session);
|
|
547
|
+
return sql;
|
|
548
|
+
},
|
|
549
|
+
stats: () => recorder.snapshot(),
|
|
550
|
+
apply: (response) => withPrimaryPin(resolution.apply(response), replica, pinMs)
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
const fromResolution = (resolution, request, contextOptions, tenant) => contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? (readUrl() ? pinnedUntil(resolution.requestCookies) : 0), tenant, contextOptions.support);
|
|
554
|
+
const resolve = async (request, resolveOptions = {}) => {
|
|
555
|
+
const claims = options.auth?.claims ?? betterSupabase.claimsSchema;
|
|
556
|
+
const userMetadata = betterSupabase.userMetadataSchema;
|
|
557
|
+
const resolution = await resolveAuth(request, {
|
|
558
|
+
logger: betterSupabase.events.logger,
|
|
559
|
+
...options.auth,
|
|
560
|
+
...claims ? { claims } : {},
|
|
561
|
+
...userMetadata ? { userMetadata } : {},
|
|
562
|
+
env: env(),
|
|
563
|
+
...resolveOptions.refresh === void 0 ? {} : { refresh: resolveOptions.refresh },
|
|
564
|
+
...resolveOptions.cookies === void 0 ? {} : { cookies: resolveOptions.cookies },
|
|
565
|
+
onRefresh: (event) => {
|
|
566
|
+
options.auth?.onRefresh?.(event);
|
|
567
|
+
betterSupabase.events.emit("refresh", event);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
if (betterSupabase.events.has("auth")) {
|
|
571
|
+
const { auth } = resolution;
|
|
572
|
+
betterSupabase.events.emit("auth", {
|
|
573
|
+
source: auth.kind === "user" ? auth.source === "cookie" ? "cookie" : "bearer" : auth.kind === "service" ? "bearer" : "none",
|
|
574
|
+
ok: auth.kind !== "invalid",
|
|
575
|
+
...auth.kind === "user" ? {
|
|
576
|
+
userId: auth.user.id,
|
|
577
|
+
rawSource: auth.source
|
|
578
|
+
} : {},
|
|
579
|
+
...auth.kind === "anon" || auth.kind === "invalid" ? { reason: auth.reason } : {}
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return resolution;
|
|
583
|
+
};
|
|
584
|
+
const server = {
|
|
585
|
+
get env() {
|
|
586
|
+
return env();
|
|
587
|
+
},
|
|
588
|
+
resolve,
|
|
589
|
+
supabaseFor,
|
|
590
|
+
dbFor,
|
|
591
|
+
contextFor: (auth, contextOptions = {}) => contextFor({
|
|
592
|
+
auth,
|
|
593
|
+
cookies: [],
|
|
594
|
+
headers: {},
|
|
595
|
+
requestCookies: [],
|
|
596
|
+
apply: (response) => response
|
|
597
|
+
}, {}, contextOptions.stats, contextOptions.pinnedUntil, contextOptions.tenant, contextOptions.support),
|
|
598
|
+
async context(request, contextOptions = {}) {
|
|
599
|
+
const resolution = await resolve(request, {
|
|
600
|
+
refresh: contextOptions.refresh ?? false,
|
|
601
|
+
...contextOptions.cookies === void 0 ? {} : { cookies: contextOptions.cookies }
|
|
602
|
+
});
|
|
603
|
+
const [tenant, active] = await Promise.all([contextOptions.tenant ?? options.tenant?.(request, resolution.auth), contextOptions.support ?? support.current(request, resolution.auth)]);
|
|
604
|
+
return fromResolution(resolution, request, active ? {
|
|
605
|
+
...contextOptions,
|
|
606
|
+
support: active
|
|
607
|
+
} : contextOptions, tenant);
|
|
608
|
+
},
|
|
609
|
+
contextFromResolution: (resolution, request, contextOptions = {}) => fromResolution(resolution, request, contextOptions, contextOptions.tenant ?? syncTenant(request, resolution.auth)),
|
|
610
|
+
admin: (context = {}) => betterSupabase.connect(serviceClient(), {
|
|
611
|
+
actor: {
|
|
612
|
+
id: "service",
|
|
613
|
+
kind: "service",
|
|
614
|
+
role: "service_role"
|
|
615
|
+
},
|
|
616
|
+
claims: { role: "service_role" },
|
|
617
|
+
...context
|
|
618
|
+
}),
|
|
619
|
+
support,
|
|
620
|
+
deleteAccount: (userId, deleteOptions) => deleteAccount(betterSupabase, serviceClient, userId, deleteOptions),
|
|
621
|
+
actingAs: (userId, claims = {}, impersonation) => {
|
|
622
|
+
const full = {
|
|
623
|
+
role: "authenticated",
|
|
624
|
+
...claims,
|
|
625
|
+
sub: userId,
|
|
626
|
+
...impersonation ? { act: actClaim(impersonation) } : {}
|
|
627
|
+
};
|
|
628
|
+
return sqlFor(full, {
|
|
629
|
+
actor: {
|
|
630
|
+
id: userId,
|
|
631
|
+
kind: "user",
|
|
632
|
+
role: full.role ?? "authenticated",
|
|
633
|
+
...impersonation ? { impersonator: impersonation.actor } : {}
|
|
634
|
+
},
|
|
635
|
+
claims: full
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
verifiedSeeders.set(server, (verified) => rememberVerified({
|
|
640
|
+
...options.auth,
|
|
641
|
+
env: env()
|
|
642
|
+
}, verified));
|
|
643
|
+
return server;
|
|
644
|
+
}
|
|
645
|
+
const verifiedSeeders = /* @__PURE__ */ new WeakMap();
|
|
646
|
+
/**
|
|
647
|
+
* Hands `server` a bearer token another layer verified for this request
|
|
648
|
+
* (`@supabase/server`'s `withSupabase`), so `context()` doesn't verify it
|
|
649
|
+
* again. See `rememberVerified` for when it is accepted.
|
|
650
|
+
*/
|
|
651
|
+
function rememberVerifiedFor(server, verified) {
|
|
652
|
+
return verifiedSeeders.get(server)?.(verified) ?? false;
|
|
653
|
+
}
|
|
654
|
+
//#endregion
|
|
655
|
+
export { DEFAULT_PIN_MS as _, withExtra as a, withPrimaryPin as b, supportCookie as c, guard as d, isResult as f, jsonResponse as g, jsonReplacer as h, rememberVerifiedFor as i, supportCookieValue as l, settle as m, createServer as n, SUPPORT_COOKIE as o, respond as p, extendServer as r, clearSupportCookie as s, TENANT_HEADER as t, defaultExpose as u, PRIMARY_COOKIE as v, primaryCookieOptions as y };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { n as dbError, t as DbException } from "./errors-
|
|
1
|
+
import { n as dbError, t as DbException } from "./errors-DtwXa6b8.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) {
|
|
@@ -34,6 +35,20 @@ function withDefault(row, column, value) {
|
|
|
34
35
|
[column]: value
|
|
35
36
|
};
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Refuses caller-supplied values for columns a plugin fills (timestamps,
|
|
40
|
+
* actor, soft delete) unless the call passes `{ override: true }`. Returns
|
|
41
|
+
* whether the caller may keep its own values.
|
|
42
|
+
*/
|
|
43
|
+
function guardManaged(op, columns, plugin, options) {
|
|
44
|
+
if (options["override"] === true) return true;
|
|
45
|
+
const rows = op.kind === "insert" ? op.rows : op.kind === "update" ? [op.set] : [];
|
|
46
|
+
for (const column of columns) {
|
|
47
|
+
if (column === void 0) continue;
|
|
48
|
+
if (rows.some((row) => column in row)) throw new DbException(dbError("invalid_request", `"${column}" on ${op.table.key} is filled by ${plugin}(); pass { override: true } to set it yourself`, { table: op.table.key }));
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
37
52
|
/** Upserts that may update existing rows must not overwrite "created" columns. */
|
|
38
53
|
function insertsOnly(op) {
|
|
39
54
|
return !op.onConflict || op.onConflict.action === "ignore";
|
|
@@ -44,14 +59,14 @@ function forbidden(message) {
|
|
|
44
59
|
/** Database-keyed row to app-keyed row. */
|
|
45
60
|
function toApp(table, row) {
|
|
46
61
|
const out = {};
|
|
47
|
-
for (const [app, meta] of
|
|
62
|
+
for (const [app, meta] of lookupOf(table).entries) if (meta.db in row) out[app] = row[meta.db];
|
|
48
63
|
return out;
|
|
49
64
|
}
|
|
50
65
|
/** App-keyed row to database-keyed row. Unknown keys are dropped. */
|
|
51
66
|
function toDb(table, row) {
|
|
52
67
|
const out = {};
|
|
53
|
-
for (const [app, meta] of
|
|
68
|
+
for (const [app, meta] of lookupOf(table).entries) if (app in row) out[meta.db] = row[app];
|
|
54
69
|
return out;
|
|
55
70
|
}
|
|
56
71
|
//#endregion
|
|
57
|
-
export {
|
|
72
|
+
export { insertsOnly as a, toApp as c, guardManaged as i, toDb as l, equals as n, isNotNull as o, forbidden as r, isNull as s, dbName as t, withDefault as u };
|