better-supabase 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/dist/base64-D2gsioCY.js +42 -0
- package/dist/claims-BAf6c7is.js +48 -0
- package/dist/cli/_format-Cax5EsM9.js +101 -0
- package/dist/cli/bin.js +39 -6
- package/dist/cli/command-CK14fP91.js +502 -0
- package/dist/cli/compare-BmIWVEui.js +9 -0
- package/dist/cli/config-D9R0o1LH.js +1895 -0
- package/dist/cli/config-SSKSTUl2.js +2 -0
- package/dist/cli/db-DOBPt660.js +52 -0
- package/dist/cli/dist-B1MvLv-M.js +129 -0
- package/dist/cli/dist-CFgGknmZ.js +1112 -0
- package/dist/cli/dist-Du7VnEUG.js +621 -0
- package/dist/cli/doctor-BOym3mbL.js +2382 -0
- package/dist/cli/env-BZ0uy8XK.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-Ccn_p90L.js +2008 -0
- package/dist/cli/gen-D5cLCULx.js +2 -0
- package/dist/cli/index.d.ts +295 -28
- package/dist/cli/index.js +184 -3
- package/dist/cli/init-ClnxRYBF.js +550 -0
- package/dist/cli/introspect-DxqfkVIv.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-8WHjxiTJ.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-CMa29t9a.js +971 -0
- package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
- package/dist/cli/run-CP20LKk_.js +347 -0
- package/dist/cli/seed-5J6eu_Xg.js +98 -0
- package/dist/cli/skills-LJJIhLFR.js +137 -0
- package/dist/cli/sql-D4VbqmXi.js +199 -0
- package/dist/cli/style-C0NFdJRL.js +29 -0
- package/dist/cli/toml-DB-MNnFs.js +424 -0
- package/dist/{version-Cqq1m3UY.js → cli/version-5mW_8sY1.js} +1 -1
- package/dist/cli/yaml-CYCbJ6et.js +2252 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +15 -25
- package/dist/{compiler-CZ9nIuqy.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -3
- package/dist/config/index.js +5 -3
- package/dist/config-BpxDfnbg.js +129 -0
- package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
- package/dist/{config-ClDXmVtY.js → config-CbvFmGD8.js} +14 -137
- package/dist/{define-BXMYD9AY.js → define-Bh0AqTik.js} +221 -120
- package/dist/{define-e3zI4_4g.d.ts → define-CRS2MRV3.d.ts} +36 -31
- package/dist/edge/index.d.ts +9 -13
- package/dist/edge/index.js +51 -25
- package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
- package/dist/env/index.js +203 -1
- package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +139 -1
- package/dist/{events-DOt3nSO-.d.ts → events-B21UOhvP.d.ts} +4 -4
- package/dist/{executor-CGdNQ27g.d.ts → executor-B5IoUxLE.d.ts} +2 -2
- package/dist/{executor-DeppvirK.js → executor-CVt6dD0B.js} +9 -4
- package/dist/{executor-BYXQ9AGn.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
- package/dist/hono/index.d.ts +17 -24
- package/dist/hono/index.js +11 -11
- package/dist/{index-DuP14VHo.d.ts → index-528Ugmyp.d.ts} +30 -10
- package/dist/{index-Bw1bwtC0.d.ts → index-BAy4tsAP.d.ts} +28 -15
- package/dist/{index-CBDufH1M.d.ts → index-BBdvo-5d.d.ts} +24 -26
- package/dist/{index-DbxFr7XG.d.ts → index-BhHEC04r.d.ts} +9 -4
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-Ciu5Xlml.d.ts → index-D5IfUIDh.d.ts} +6 -5
- package/dist/{index-K-3WM2Hp.d.ts → index-DFmOpBGC.d.ts} +13 -13
- package/dist/{index-D4v7sncu.d.ts → index-RlLXd8dL.d.ts} +13 -13
- package/dist/index.d.ts +17 -17
- package/dist/index.js +23 -14
- package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
- package/dist/jobs/index.d.ts +33 -26
- package/dist/jobs/index.js +44 -22
- package/dist/{json-schema-BpYqkCKf.js → json-schema-DP6V7cbr.js} +8 -3
- package/dist/lint/index.js +1 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +131 -96
- package/dist/{live-DJyZMD6w.d.ts → live-BHakSWWo.d.ts} +26 -16
- package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
- package/dist/lookup-BaP5bI27.js +18 -0
- package/dist/mcp/index.d.ts +34 -27
- package/dist/mcp/index.js +59 -25
- package/dist/{mfa-TVia554c.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
- package/dist/{mfa-CpLfJSde.js → mfa-byfjruR9.js} +190 -46
- package/dist/next/image/index.js +6 -3
- package/dist/next/index.d.ts +64 -48
- package/dist/next/index.js +63 -50
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +61 -38
- package/dist/orpc/index.d.ts +18 -21
- package/dist/orpc/index.js +12 -12
- package/dist/otel/index.d.ts +5 -5
- package/dist/otel/index.js +84 -63
- package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-C4KFzLc9.js} +12 -7
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-C4TR9J0Z.d.ts → plugin-bNhvJwRh.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/actor/index.js +1 -1
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +46 -16
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +4 -4
- package/dist/plugins/tenant/index.d.ts +3 -3
- package/dist/plugins/tenant/index.js +10 -4
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +2 -2
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +3 -3
- package/dist/pool-DFwEQwmN.d.ts +78 -0
- package/dist/postgres/index.d.ts +10 -5
- package/dist/postgres/index.js +37 -16
- package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
- package/dist/{problem-BTkTtRV9.js → problem-BihjPR0t.js} +11 -5
- package/dist/{problem-DlgB0lYl.d.ts → problem-U8UEKccv.d.ts} +6 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +2 -2
- package/dist/{query-ChvDbsDE.js → query-BMMSWbI7.js} +19 -19
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +2 -2
- package/dist/react/server.d.ts +6 -6
- package/dist/react/server.js +3 -3
- package/dist/react/session.d.ts +21 -2
- package/dist/react/session.js +19 -1
- package/dist/{react-C83BdBGt.js → react-CQHMaESR.js} +59 -55
- package/dist/{read-set-4lI3W_gZ.js → read-set-FWK8qQ58.js} +51 -16
- package/dist/realtime/index.d.ts +4 -3
- package/dist/realtime/index.js +19 -17
- package/dist/{index-CG69s15v.d.ts → refresh-BwYkyNOQ.d.ts} +7 -3
- package/dist/{resolve-CjHILk2w.d.ts → resolve-BAqQcwMW.d.ts} +17 -11
- package/dist/{resource-CgZa3YG_.d.ts → resource-BJcaIfxi.d.ts} +17 -8
- package/dist/{resource-BHEWmQJK.js → resource-My_y-Syh.js} +43 -21
- package/dist/{respond-Dne1ARSa.d.ts → respond-DgIbdrt8.d.ts} +39 -14
- package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
- package/dist/{result-CTkCZ6JA.d.ts → result-CqEsbgYs.d.ts} +16 -11
- package/dist/server/index.d.ts +38 -13
- package/dist/server/index.js +43 -14
- package/dist/{respond-DMcbQwfN.js → server-BA75B0mT.js} +151 -96
- package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
- package/dist/{simplify-bwJpLnaS.js → simplify-Cqvfr9dj.js} +76 -39
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-DTjA1LPj.js → sql-B34HU2DI.js} +74 -17
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +2 -2
- package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
- package/dist/{standard-CyhRzctb.d.ts → standard-BsaCYQJc.d.ts} +1 -1
- package/dist/{stats-C5W-rBfR.js → stats-C_YxA2r1.js} +40 -14
- package/dist/storage/index.d.ts +4 -3
- package/dist/storage/index.js +548 -1
- package/dist/{tables-rZxRxNPW.js → tables-Cb-3_VWc.js} +19 -5
- package/dist/temporal-BwshQk0W.js +27 -0
- package/dist/temporal-required-CBNHT6ni.js +19 -0
- package/dist/testing/index.d.ts +25 -24
- package/dist/testing/index.js +165 -66
- package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
- package/dist/version-Bmf7_FzM.js +4 -0
- package/dist/{view-D1s0wa0T.d.ts → view-B64o81wp.d.ts} +36 -6
- package/dist/{view-aOYizTeE.js → view-BrJI2LKp.js} +11 -2
- package/dist/webhooks/index.d.ts +19 -17
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DMipjhSP.js → webhooks-Bh_Jf_-l.js} +45 -24
- package/package.json +50 -22
- package/schemas/config-v1.json +31 -1
- package/schemas/doctor-report-v1.json +17 -1
- package/schemas/snapshot-v2.json +35 -0
- package/skills/better-supabase/SKILL.md +77 -18
- package/skills/better-supabase/references/plugins.md +21 -22
- package/skills/better-supabase/references/troubleshooting.md +38 -18
- package/skills/better-supabase-api/SKILL.md +66 -18
- package/skills/better-supabase-api/references/adapters.md +64 -41
- package/skills/better-supabase-auth/SKILL.md +146 -0
- package/skills/better-supabase-auth/references/permdock.md +55 -0
- package/skills/better-supabase-testing/SKILL.md +68 -11
- package/dist/casing-da0uRTqt.js +0 -14
- package/dist/claims-CoTobNC-.js +0 -25
- package/dist/cli-DrSgDOQC.js +0 -6646
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-R2m_DkXi.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3LxnQ0e.d.ts +0 -22
- package/dist/session-CqEwzVup.js +0 -20
- package/dist/storage-DuGn38fr.js +0 -527
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { n as dbError } from "./errors-
|
|
1
|
+
import { n as dbError } from "./errors-L0xK41y_.js";
|
|
2
2
|
import { t as consoleLogger } from "./logger-glM28NVN.js";
|
|
3
|
+
import { n as decodeJwtPayload } from "./base64-D2gsioCY.js";
|
|
3
4
|
import { DEFAULT_COOKIE_OPTIONS, createChunks, isChunkLike, parseCookieHeader, serializeCookieHeader, stringFromBase64URL, stringToBase64URL } from "@supabase/ssr";
|
|
4
5
|
import { extractCredentials, verifyCredentials } from "@supabase/server/core";
|
|
5
6
|
//#region src/auth/impersonation.ts
|
|
@@ -22,6 +23,65 @@ function impersonatorOf(claims) {
|
|
|
22
23
|
} : { id: sub };
|
|
23
24
|
}
|
|
24
25
|
//#endregion
|
|
26
|
+
//#region src/auth/actor.ts
|
|
27
|
+
function isRecord(value) {
|
|
28
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
function copyAct(level) {
|
|
31
|
+
const entries = Object.entries(level).map(([key, value]) => [key, key === "act" && isRecord(value) ? copyAct(value) : value]);
|
|
32
|
+
return Object.fromEntries(entries);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads only `act` and `client_id`, the way PermDock's `actorOf` does. An
|
|
36
|
+
* `act` that is not a chain of objects each with a non-empty `sub` is
|
|
37
|
+
* `{ ok: false }`: the session must not pass as the user alone.
|
|
38
|
+
*/
|
|
39
|
+
function actorOf(claims) {
|
|
40
|
+
const outer = claims["act"];
|
|
41
|
+
if (outer !== void 0) {
|
|
42
|
+
let level = outer;
|
|
43
|
+
while (level !== void 0) {
|
|
44
|
+
if (!isRecord(level)) return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: "invalid-chain"
|
|
47
|
+
};
|
|
48
|
+
const sub = level["sub"];
|
|
49
|
+
if (typeof sub !== "string" || sub === "") return {
|
|
50
|
+
ok: false,
|
|
51
|
+
reason: "invalid-chain"
|
|
52
|
+
};
|
|
53
|
+
level = level["act"];
|
|
54
|
+
}
|
|
55
|
+
if (!isRecord(outer) || typeof outer["sub"] !== "string") return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "invalid-chain"
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
actor: {
|
|
62
|
+
id: outer["sub"],
|
|
63
|
+
kind: "oauth-client",
|
|
64
|
+
chain: copyAct(outer)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const client = claims["client_id"];
|
|
69
|
+
if (typeof client === "string" && client !== "") return {
|
|
70
|
+
ok: true,
|
|
71
|
+
actor: {
|
|
72
|
+
id: client,
|
|
73
|
+
kind: "oauth-client"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return { ok: true };
|
|
77
|
+
}
|
|
78
|
+
/** The OAuth `scope` claim (a space-separated string or a list); `undefined` when empty. */
|
|
79
|
+
function delegationOf(claims) {
|
|
80
|
+
const scope = claims["scope"];
|
|
81
|
+
const scopes = typeof scope === "string" ? scope.split(/\s+/u).filter(Boolean) : Array.isArray(scope) ? scope.filter((item) => typeof item === "string") : [];
|
|
82
|
+
return scopes.length > 0 ? { scopes } : void 0;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
25
85
|
//#region src/auth/refresh.ts
|
|
26
86
|
/**
|
|
27
87
|
* How long a finished refresh is reused for requests still carrying the old
|
|
@@ -29,6 +89,7 @@ function impersonatorOf(claims) {
|
|
|
29
89
|
* refresh-token reuse interval of Supabase Auth.
|
|
30
90
|
*/
|
|
31
91
|
const REUSE_MS = 1e4;
|
|
92
|
+
const DEFAULT_TIMEOUT_MS = 5e3;
|
|
32
93
|
const inflight = /* @__PURE__ */ new Map();
|
|
33
94
|
const recent = /* @__PURE__ */ new Map();
|
|
34
95
|
function shared(outcome) {
|
|
@@ -38,10 +99,14 @@ function shared(outcome) {
|
|
|
38
99
|
} : outcome;
|
|
39
100
|
}
|
|
40
101
|
function prune(now) {
|
|
41
|
-
for (const [token, entry] of recent)
|
|
102
|
+
for (const [token, entry] of recent) {
|
|
103
|
+
if (entry.until > now) return;
|
|
104
|
+
recent.delete(token);
|
|
105
|
+
}
|
|
42
106
|
}
|
|
43
107
|
async function request(refreshToken, options, now) {
|
|
44
108
|
const doFetch = options.fetch ?? fetch;
|
|
109
|
+
const signal = AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
45
110
|
let response;
|
|
46
111
|
try {
|
|
47
112
|
response = await doFetch(`${options.url}/auth/v1/token?grant_type=refresh_token`, {
|
|
@@ -51,7 +116,8 @@ async function request(refreshToken, options, now) {
|
|
|
51
116
|
"content-type": "application/json",
|
|
52
117
|
...options.forwardedFor ? { "sb-forwarded-for": options.forwardedFor.ip } : {}
|
|
53
118
|
},
|
|
54
|
-
body: JSON.stringify({ refresh_token: refreshToken })
|
|
119
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
120
|
+
signal
|
|
55
121
|
});
|
|
56
122
|
} catch (cause) {
|
|
57
123
|
return {
|
|
@@ -61,6 +127,11 @@ async function request(refreshToken, options, now) {
|
|
|
61
127
|
};
|
|
62
128
|
}
|
|
63
129
|
const body = await response.json().catch(() => null);
|
|
130
|
+
if (signal.aborted) return {
|
|
131
|
+
ok: false,
|
|
132
|
+
reason: "network",
|
|
133
|
+
message: "refresh timed out"
|
|
134
|
+
};
|
|
64
135
|
if (!response.ok || !body || typeof body["access_token"] !== "string" || typeof body["refresh_token"] !== "string") {
|
|
65
136
|
const message = typeof body?.["msg"] === "string" ? body["msg"] : typeof body?.["error_description"] === "string" ? body["error_description"] : `Refresh failed with ${String(response.status)}`;
|
|
66
137
|
return {
|
|
@@ -94,12 +165,16 @@ function refreshSession(refreshToken, options) {
|
|
|
94
165
|
const pending = inflight.get(refreshToken);
|
|
95
166
|
if (pending) return pending.then(shared);
|
|
96
167
|
const promise = request(refreshToken, options, now).then((outcome) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
168
|
+
if (outcome.ok || outcome.reason === "rejected") {
|
|
169
|
+
recent.delete(refreshToken);
|
|
170
|
+
recent.set(refreshToken, {
|
|
171
|
+
outcome,
|
|
172
|
+
until: now + REUSE_MS
|
|
173
|
+
});
|
|
174
|
+
}
|
|
102
175
|
return outcome;
|
|
176
|
+
}).finally(() => {
|
|
177
|
+
inflight.delete(refreshToken);
|
|
103
178
|
});
|
|
104
179
|
inflight.set(refreshToken, promise);
|
|
105
180
|
return promise;
|
|
@@ -115,8 +190,15 @@ const AUTH_CACHE_HEADERS = {
|
|
|
115
190
|
};
|
|
116
191
|
/** `sb-<first label of the hostname>-auth-token`, the supabase-js default. */
|
|
117
192
|
function sessionCookieName(url) {
|
|
118
|
-
|
|
193
|
+
let name = cookieNames.get(url);
|
|
194
|
+
if (name === void 0) {
|
|
195
|
+
name = `sb-${new URL(url).hostname.split(".")[0] ?? ""}-auth-token`;
|
|
196
|
+
if (cookieNames.size < 16) cookieNames.set(url, name);
|
|
197
|
+
}
|
|
198
|
+
return name;
|
|
119
199
|
}
|
|
200
|
+
/** Project URLs come from configuration, so a handful of entries suffice. */
|
|
201
|
+
const cookieNames = /* @__PURE__ */ new Map();
|
|
120
202
|
function parseCookies(header) {
|
|
121
203
|
return header ? parseCookieHeader(header) : [];
|
|
122
204
|
}
|
|
@@ -191,20 +273,9 @@ function applyCookieWrites(cookies, writes) {
|
|
|
191
273
|
}
|
|
192
274
|
//#endregion
|
|
193
275
|
//#region src/auth/resolve.ts
|
|
194
|
-
function decodePayload(token) {
|
|
195
|
-
const part = token.split(".")[1];
|
|
196
|
-
if (!part) return void 0;
|
|
197
|
-
try {
|
|
198
|
-
const json = atob(part.replace(/-/g, "+").replace(/_/g, "/"));
|
|
199
|
-
const parsed = JSON.parse(json);
|
|
200
|
-
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
201
|
-
} catch {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
276
|
function expiryOf(session) {
|
|
206
277
|
if (typeof session.expires_at === "number") return session.expires_at;
|
|
207
|
-
const exp =
|
|
278
|
+
const exp = decodeJwtPayload(session.access_token)?.["exp"];
|
|
208
279
|
return typeof exp === "number" ? exp : void 0;
|
|
209
280
|
}
|
|
210
281
|
function serverEnv(options) {
|
|
@@ -248,8 +319,31 @@ function remembered(memo, token, now) {
|
|
|
248
319
|
memo.set(token, hit);
|
|
249
320
|
return hit;
|
|
250
321
|
}
|
|
251
|
-
|
|
252
|
-
|
|
322
|
+
const NO_SCHEMA = {};
|
|
323
|
+
const checkedByMemo = /* @__PURE__ */ new WeakMap();
|
|
324
|
+
/**
|
|
325
|
+
* Tokens that already passed (or failed) the claims and `userMetadata`
|
|
326
|
+
* schemas, per verification memo and schema pair, so a memo hit skips
|
|
327
|
+
* validation too.
|
|
328
|
+
*/
|
|
329
|
+
function checkedFor(memo, options) {
|
|
330
|
+
const claims = options.claims ?? NO_SCHEMA;
|
|
331
|
+
const metadata = options.userMetadata ?? NO_SCHEMA;
|
|
332
|
+
let byClaims = checkedByMemo.get(memo);
|
|
333
|
+
if (!byClaims) checkedByMemo.set(memo, byClaims = /* @__PURE__ */ new WeakMap());
|
|
334
|
+
let byMetadata = byClaims.get(claims);
|
|
335
|
+
if (!byMetadata) byClaims.set(claims, byMetadata = /* @__PURE__ */ new WeakMap());
|
|
336
|
+
let checked = byMetadata.get(metadata);
|
|
337
|
+
if (!checked) byMetadata.set(metadata, checked = /* @__PURE__ */ new Map());
|
|
338
|
+
return checked;
|
|
339
|
+
}
|
|
340
|
+
function withoutSource(state) {
|
|
341
|
+
if (state.kind !== "user") return state;
|
|
342
|
+
const { source: _source, ...rest } = state;
|
|
343
|
+
return rest;
|
|
344
|
+
}
|
|
345
|
+
function remember(memo, token, user, expiresAt) {
|
|
346
|
+
if (expiresAt === null) return;
|
|
253
347
|
memo.set(token, user);
|
|
254
348
|
if (memo.size > MEMO_SIZE) memo.delete(memo.keys().next().value);
|
|
255
349
|
}
|
|
@@ -258,10 +352,20 @@ async function verify(credentials, modes, options, source) {
|
|
|
258
352
|
const memo = token ? memoFor(options) : void 0;
|
|
259
353
|
if (token && memo) {
|
|
260
354
|
const hit = remembered(memo, token, Math.floor((options.now ?? Date.now)() / 1e3));
|
|
261
|
-
if (hit)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
355
|
+
if (hit) {
|
|
356
|
+
const checked = checkedFor(memo, options);
|
|
357
|
+
const known = checked.get(token);
|
|
358
|
+
if (known) return known.kind === "user" ? {
|
|
359
|
+
...known,
|
|
360
|
+
source
|
|
361
|
+
} : known;
|
|
362
|
+
const state = await checkUser({
|
|
363
|
+
...hit,
|
|
364
|
+
source
|
|
365
|
+
}, options);
|
|
366
|
+
remember(checked, token, withoutSource(state), hit.expiresAt);
|
|
367
|
+
return state;
|
|
368
|
+
}
|
|
265
369
|
}
|
|
266
370
|
const state = await verifyOnce(credentials, modes, options, source);
|
|
267
371
|
if (token && memo && state.kind === "user") remember(memo, token, {
|
|
@@ -270,12 +374,20 @@ async function verify(credentials, modes, options, source) {
|
|
|
270
374
|
claims: state.claims,
|
|
271
375
|
user: state.user,
|
|
272
376
|
expiresAt: state.expiresAt
|
|
273
|
-
});
|
|
274
|
-
|
|
377
|
+
}, state.expiresAt);
|
|
378
|
+
const checked = await checkUser(state, options);
|
|
379
|
+
if (token && memo && state.kind === "user") remember(checkedFor(memo, options), token, withoutSource(checked), state.expiresAt);
|
|
380
|
+
return checked;
|
|
275
381
|
}
|
|
276
382
|
/** Validates a user's claims against `options.claims`, keeping every JWT claim. */
|
|
277
383
|
async function checkClaims(state, options) {
|
|
278
|
-
if (state.kind !== "user"
|
|
384
|
+
if (state.kind !== "user") return state;
|
|
385
|
+
if (!actorOf(state.claims).ok) return {
|
|
386
|
+
kind: "invalid",
|
|
387
|
+
reason: "actor",
|
|
388
|
+
error: dbError("unauthorized", "The token's act claim is not a chain of actors with a sub", { code: "ACTOR_INVALID" })
|
|
389
|
+
};
|
|
390
|
+
if (!options.claims) return state;
|
|
279
391
|
let outcome = options.claims["~standard"].validate(state.claims);
|
|
280
392
|
if (outcome instanceof Promise) outcome = await outcome;
|
|
281
393
|
if (outcome.issues) {
|
|
@@ -313,16 +425,40 @@ async function checkUser(state, options) {
|
|
|
313
425
|
};
|
|
314
426
|
if (!warnedMetadata.has(schema)) {
|
|
315
427
|
warnedMetadata.add(schema);
|
|
316
|
-
(options.logger ?? consoleLogger).warn("user_metadata does not match
|
|
428
|
+
(options.logger ?? consoleLogger).warn("user_metadata does not match betterSupabase.userMetadata(schema); session.profile is undefined", { paths: outcome.issues.map((issue) => issue.path?.map((segment) => typeof segment === "object" ? String(segment.key) : String(segment)).join(".") ?? "") });
|
|
317
429
|
}
|
|
318
430
|
return user;
|
|
319
431
|
}
|
|
432
|
+
/** A token whose `kid` matches no key: verifying it loads the JWKS and fails. */
|
|
433
|
+
const PREFETCH_TOKEN = `${base64url(JSON.stringify({
|
|
434
|
+
alg: "ES256",
|
|
435
|
+
kid: "better-supabase-prefetch",
|
|
436
|
+
typ: "JWT"
|
|
437
|
+
}))}.e30.AA`;
|
|
438
|
+
function base64url(text) {
|
|
439
|
+
return btoa(text).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Loads the remote JWKS into the verifier's cache so the first request
|
|
443
|
+
* doesn't wait for it. Never throws; a failure leaves the first request to
|
|
444
|
+
* fetch it as before.
|
|
445
|
+
*/
|
|
446
|
+
function prefetchJwks(options) {
|
|
447
|
+
if (options.jwks !== void 0) return Promise.resolve();
|
|
448
|
+
return verifyCredentials({
|
|
449
|
+
token: PREFETCH_TOKEN,
|
|
450
|
+
apikey: null
|
|
451
|
+
}, {
|
|
452
|
+
auth: ["user"],
|
|
453
|
+
env: serverEnv(options)
|
|
454
|
+
}).then(() => {}, () => {});
|
|
455
|
+
}
|
|
320
456
|
async function verifyOnce(credentials, modes, options, source) {
|
|
321
457
|
const { data, error } = await verifyCredentials(credentials, {
|
|
322
458
|
auth: modes,
|
|
323
459
|
env: serverEnv(options),
|
|
324
|
-
...options.audience
|
|
325
|
-
...options.issuer
|
|
460
|
+
...options.audience === void 0 ? {} : { audience: options.audience },
|
|
461
|
+
...options.issuer === void 0 ? {} : { issuer: options.issuer }
|
|
326
462
|
});
|
|
327
463
|
if (error) {
|
|
328
464
|
const kind = error.code === "JWKS_FETCH_FAILED" ? "network" : error.status >= 500 ? "unexpected" : "unauthorized";
|
|
@@ -351,11 +487,16 @@ async function verifyOnce(credentials, modes, options, source) {
|
|
|
351
487
|
}
|
|
352
488
|
function resolution(auth, cookies, writes = []) {
|
|
353
489
|
const headers = writes.length > 0 ? AUTH_CACHE_HEADERS : {};
|
|
490
|
+
let requestCookies;
|
|
354
491
|
return {
|
|
355
492
|
auth,
|
|
356
493
|
cookies: writes,
|
|
357
494
|
headers,
|
|
358
|
-
|
|
495
|
+
/** Parsed on first read: bearer requests rarely need their cookies. */
|
|
496
|
+
get requestCookies() {
|
|
497
|
+
requestCookies ??= writes.length > 0 ? applyCookieWrites(cookies(), writes) : cookies();
|
|
498
|
+
return requestCookies;
|
|
499
|
+
},
|
|
359
500
|
apply(response) {
|
|
360
501
|
if (writes.length === 0) return response;
|
|
361
502
|
let target = response;
|
|
@@ -385,7 +526,8 @@ function clientIp(request) {
|
|
|
385
526
|
* set, through a single-flight request, and only then are cookies written.
|
|
386
527
|
*/
|
|
387
528
|
async function resolveAuth(request, options) {
|
|
388
|
-
|
|
529
|
+
let parsed;
|
|
530
|
+
const cookies = () => parsed ??= parseCookies(request.headers.get("cookie"));
|
|
389
531
|
for (const resolver of options.resolvers ?? []) {
|
|
390
532
|
const state = await resolver.resolve(request);
|
|
391
533
|
if (!state) continue;
|
|
@@ -405,10 +547,10 @@ async function resolveAuth(request, options) {
|
|
|
405
547
|
apikey: credentials.apikey
|
|
406
548
|
}, options.secret === true ? ["secret"] : options.secret.map((name) => `secret:${name}`), options, "bearer"), cookies);
|
|
407
549
|
const name = options.cookie?.name ?? sessionCookieName(options.env.url);
|
|
408
|
-
const session = readSession(cookies, name);
|
|
550
|
+
const session = readSession(cookies(), name);
|
|
409
551
|
if (!session) {
|
|
410
|
-
const stale = cookies.some((cookie) => cookie.name === name || cookie.name.startsWith(`${name}.`));
|
|
411
|
-
const writes = stale && options.refresh ? writeSession(cookies, name, null, options.cookie?.options) : [];
|
|
552
|
+
const stale = cookies().some((cookie) => cookie.name === name || cookie.name.startsWith(`${name}.`));
|
|
553
|
+
const writes = stale && options.refresh ? writeSession(cookies(), name, null, options.cookie?.options) : [];
|
|
412
554
|
return resolution({
|
|
413
555
|
kind: "anon",
|
|
414
556
|
reason: stale ? "signed_out" : "none"
|
|
@@ -416,12 +558,13 @@ async function resolveAuth(request, options) {
|
|
|
416
558
|
}
|
|
417
559
|
const now = Math.floor((options.now ?? Date.now)() / 1e3);
|
|
418
560
|
const expiry = expiryOf(session);
|
|
419
|
-
|
|
561
|
+
const leeway = options.refresh ? options.leeway ?? 60 : 0;
|
|
562
|
+
if (expiry !== void 0 && expiry - leeway > now) {
|
|
420
563
|
const state = await verify({
|
|
421
564
|
token: session.access_token,
|
|
422
565
|
apikey: null
|
|
423
566
|
}, ["user"], options, "cookie");
|
|
424
|
-
if (state.kind !== "invalid" || state.reason
|
|
567
|
+
if (state.kind !== "invalid" || state.reason !== "token" || state.error.kind === "network") return resolution(state, cookies);
|
|
425
568
|
if (!options.refresh) return resolution({
|
|
426
569
|
kind: "anon",
|
|
427
570
|
reason: "signed_out"
|
|
@@ -441,7 +584,8 @@ async function resolveAuth(request, options) {
|
|
|
441
584
|
secretKey
|
|
442
585
|
} } : {},
|
|
443
586
|
...options.fetch ? { fetch: options.fetch } : {},
|
|
444
|
-
...options.now ? { now: options.now } : {}
|
|
587
|
+
...options.now ? { now: options.now } : {},
|
|
588
|
+
...options.refreshTimeoutMs === void 0 ? {} : { timeoutMs: options.refreshTimeoutMs }
|
|
445
589
|
});
|
|
446
590
|
options.onRefresh?.({
|
|
447
591
|
ok: outcome.ok,
|
|
@@ -456,9 +600,9 @@ async function resolveAuth(request, options) {
|
|
|
456
600
|
return resolution({
|
|
457
601
|
kind: "anon",
|
|
458
602
|
reason: "signed_out"
|
|
459
|
-
}, cookies, writeSession(cookies, name, null, options.cookie?.options));
|
|
603
|
+
}, cookies, writeSession(cookies(), name, null, options.cookie?.options));
|
|
460
604
|
}
|
|
461
|
-
const writes = writeSession(cookies, name, outcome.session, options.cookie?.options);
|
|
605
|
+
const writes = writeSession(cookies(), name, outcome.session, options.cookie?.options);
|
|
462
606
|
return resolution(await verify({
|
|
463
607
|
token: outcome.session.access_token,
|
|
464
608
|
apikey: null
|
|
@@ -473,8 +617,8 @@ function authContext(auth) {
|
|
|
473
617
|
actor: {
|
|
474
618
|
id: auth.user.id,
|
|
475
619
|
kind: "user",
|
|
476
|
-
...auth.user.role
|
|
477
|
-
...auth.user.email
|
|
620
|
+
...auth.user.role === void 0 ? {} : { role: auth.user.role },
|
|
621
|
+
...auth.user.email === void 0 ? {} : { email: auth.user.email },
|
|
478
622
|
...impersonator ? { impersonator: impersonator.id } : {}
|
|
479
623
|
},
|
|
480
624
|
claims: auth.claims
|
|
@@ -538,4 +682,4 @@ function checkAal(auth, required) {
|
|
|
538
682
|
});
|
|
539
683
|
}
|
|
540
684
|
//#endregion
|
|
541
|
-
export { clientIp as a,
|
|
685
|
+
export { delegationOf as _, clientIp as a, AUTH_CACHE_HEADERS as c, readSession as d, serializeCookie as f, actorOf as g, refreshSession as h, authContext as i, applyCookieWrites as l, writeSession as m, amrOf as n, prefetchJwks as o, sessionCookieName as p, checkAal as r, resolveAuth as s, aalOf as t, parseCookies as u, actClaim as v, impersonatorOf as y };
|
package/dist/next/image/index.js
CHANGED
|
@@ -21,13 +21,16 @@ const clamp = (value, min, max) => Math.min(max, Math.max(min, Math.round(value)
|
|
|
21
21
|
*/
|
|
22
22
|
function createImageLoader(options) {
|
|
23
23
|
const storage = `${options.url.replace(/\/+$/, "")}/storage/v1`;
|
|
24
|
-
const publicPrefixes = [`${storage}/object/public/`, `${storage}/render/image/public/`]
|
|
24
|
+
const publicPrefixes = [`${storage}/object/public/`, `${storage}/render/image/public/`].map((prefix) => ({
|
|
25
|
+
prefix,
|
|
26
|
+
length: new URL(prefix).pathname.length
|
|
27
|
+
}));
|
|
25
28
|
const fallback = options.fallback ?? (({ src }) => src);
|
|
26
29
|
return (props) => {
|
|
27
|
-
const prefix = publicPrefixes.find((candidate) => props.src.startsWith(candidate));
|
|
30
|
+
const prefix = publicPrefixes.find((candidate) => props.src.startsWith(candidate.prefix));
|
|
28
31
|
if (prefix === void 0) return fallback(props);
|
|
29
32
|
const source = new URL(props.src);
|
|
30
|
-
const object = source.pathname.slice(
|
|
33
|
+
const object = source.pathname.slice(prefix.length);
|
|
31
34
|
const target = new URL(`${storage}/render/image/public/${object}`);
|
|
32
35
|
for (const [key, value] of source.searchParams) target.searchParams.set(key, value);
|
|
33
36
|
target.searchParams.set("width", String(clamp(props.width, 1, MAX_WIDTH)));
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { n as AnyModels, t as AnyFunctions } from "../types-
|
|
2
|
-
import { p as DbError } from "../result-
|
|
3
|
-
import { B as ReadSet, P as DbStats, Z as QuerySpec, l as CacheAdapter, t as BetterSupabase } from "../define-
|
|
4
|
-
import { n as AmrEntry, s as Impersonator, t as Aal } from "../mfa-
|
|
5
|
-
import { r as AuthState } from "../resolve-
|
|
6
|
-
import { d as
|
|
7
|
-
import { n as toSession, t as AuthSession } from "../view-
|
|
8
|
-
import { r as LiveCountSeed, t as CountRunner } from "../live-
|
|
9
|
-
import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-
|
|
1
|
+
import { n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
|
|
2
|
+
import { p as DbError } from "../result-CqEsbgYs.js";
|
|
3
|
+
import { B as ReadSet, P as DbStats, Z as QuerySpec, l as CacheAdapter, t as BetterSupabase } from "../define-CRS2MRV3.js";
|
|
4
|
+
import { n as AmrEntry, s as Impersonator, t as Aal } from "../mfa-C7MNQ1Ob.js";
|
|
5
|
+
import { r as AuthState } from "../resolve-BAqQcwMW.js";
|
|
6
|
+
import { d as ContextOptions, f as ServerContext, n as GuardOptions, p as ServerOptions, t as AuthKind, u as BetterServer } from "../respond-DgIbdrt8.js";
|
|
7
|
+
import { n as toSession, t as AuthSession } from "../view-B64o81wp.js";
|
|
8
|
+
import { r as LiveCountSeed, t as CountRunner } from "../live-BHakSWWo.js";
|
|
9
|
+
import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-Dm-FHpNH.js";
|
|
10
10
|
import { NextRequest } from "next/server.js";
|
|
11
11
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
12
12
|
//#region src/next/collector.d.ts
|
|
@@ -17,8 +17,8 @@ interface DbBudget {
|
|
|
17
17
|
readonly waves?: number;
|
|
18
18
|
}
|
|
19
19
|
//#endregion
|
|
20
|
-
//#region src/next/
|
|
21
|
-
|
|
20
|
+
//#region src/next/create.d.ts
|
|
21
|
+
interface NextOptions extends ServerOptions {
|
|
22
22
|
/** Invalidate `tagFor(table)` cache tags after mutations. Defaults to true. */
|
|
23
23
|
readonly cacheTags?: boolean;
|
|
24
24
|
/** Include internal error messages in Problem Details. Defaults to development only. */
|
|
@@ -26,19 +26,19 @@ export interface NextOptions extends ServerOptions {
|
|
|
26
26
|
/** Per-request database call counts. See the Budget docs. */
|
|
27
27
|
readonly debug?: NextDebugOptions;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
interface NextDebugOptions {
|
|
30
30
|
/** Collect outside development too, e.g. for e2e runs against `next start`. */
|
|
31
31
|
readonly enabled?: boolean;
|
|
32
32
|
/** Header with `calls;waves;ms` on route handler responses. Defaults to `x-bs-db-calls`. */
|
|
33
33
|
readonly header?: string;
|
|
34
34
|
/** Warns in development when one render goes over it. */
|
|
35
35
|
readonly budget?: DbBudget;
|
|
36
|
-
/** Where `
|
|
36
|
+
/** Where `bs.debugRoute()` is mounted. Defaults to `/api/bs-stats`. */
|
|
37
37
|
readonly route?: string;
|
|
38
38
|
}
|
|
39
|
-
/** Response header with the URL of the request's totals on `
|
|
39
|
+
/** Response header with the URL of the request's totals on `bs.debugRoute()`. */
|
|
40
40
|
export declare const STATS_URL_HEADER = "x-bs-stats";
|
|
41
|
-
|
|
41
|
+
interface ProxyOptions<C = unknown, P = unknown> {
|
|
42
42
|
/**
|
|
43
43
|
* Another middleware to compose with, such as next-intl's. It runs on the
|
|
44
44
|
* original request, alongside auth; its rewrite, redirect or request
|
|
@@ -46,16 +46,16 @@ export interface ProxyOptions<C = unknown, U = unknown> {
|
|
|
46
46
|
*/
|
|
47
47
|
readonly before?: (request: NextRequest) => Response | undefined | Promise<Response | undefined>;
|
|
48
48
|
/** Answer before rendering, e.g. redirect signed-out users. Refreshed cookies are kept. */
|
|
49
|
-
readonly protect?: (auth: AuthState<C,
|
|
49
|
+
readonly protect?: (auth: AuthState<C, P>, request: NextRequest) => Response | undefined | Promise<Response | undefined>;
|
|
50
50
|
/** Post-processes the final response; return a new one to replace it. */
|
|
51
|
-
readonly after?: (response: Response, auth: AuthState<C,
|
|
51
|
+
readonly after?: (response: Response, auth: AuthState<C, P>) => Response | undefined | void | Promise<Response | undefined | void>;
|
|
52
52
|
/**
|
|
53
53
|
* Adds `Server-Timing: bs-proxy;dur=..., bs-verify;dur=...` (ms): the whole
|
|
54
54
|
* proxy, and resolving the session (a local verify, or a refresh).
|
|
55
55
|
*/
|
|
56
56
|
readonly serverTiming?: boolean;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
type ActionResult<T> = {
|
|
59
59
|
readonly ok: true;
|
|
60
60
|
readonly data: T;
|
|
61
61
|
readonly error: null;
|
|
@@ -64,7 +64,7 @@ export type ActionResult<T> = {
|
|
|
64
64
|
readonly data: null;
|
|
65
65
|
readonly error: DbError;
|
|
66
66
|
};
|
|
67
|
-
|
|
67
|
+
interface ActionOptions<S extends StandardSchemaV1 | undefined> extends GuardOptions {
|
|
68
68
|
/** Validates the input (plain object or `FormData`) with any Standard Schema. */
|
|
69
69
|
readonly input?: S;
|
|
70
70
|
}
|
|
@@ -76,57 +76,66 @@ type Unwrapped<T> = T extends {
|
|
|
76
76
|
} | {
|
|
77
77
|
readonly ok: false;
|
|
78
78
|
} ? D : T;
|
|
79
|
-
|
|
79
|
+
interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends BetterServer<M, F, E, C, P> {
|
|
80
80
|
/** `proxy.ts`: refreshes sessions for page loads and server actions. */
|
|
81
|
-
proxy(request: NextRequest, options?: ProxyOptions<C,
|
|
82
|
-
/**
|
|
83
|
-
|
|
81
|
+
proxy(request: NextRequest, options?: ProxyOptions<C, P>): Promise<Response>;
|
|
82
|
+
/**
|
|
83
|
+
* The caller's context. Without a request (Server Components, actions) it
|
|
84
|
+
* reads the incoming headers and is memoized per request.
|
|
85
|
+
*/
|
|
86
|
+
context(request?: Request, options?: ContextOptions): Promise<ServerContext<M, F, E, C, P>>;
|
|
84
87
|
/**
|
|
85
88
|
* The verified caller as serializable data (no token, no clients), memoized
|
|
86
89
|
* per request. Wrap it in a `'use cache: private'` function with Cache
|
|
87
90
|
* Components and pass the promise to `<SessionProvider>`.
|
|
88
91
|
*/
|
|
89
|
-
session(): Promise<AuthSession<C,
|
|
92
|
+
session(): Promise<AuthSession<C, P>>;
|
|
90
93
|
/**
|
|
91
94
|
* The context for a session and its token, without reading the request.
|
|
92
95
|
* The token is verified again (memoized, no network call); a token for
|
|
93
96
|
* another user than `session` gives an `invalid` context.
|
|
94
97
|
*/
|
|
95
|
-
|
|
98
|
+
contextForSession(session: AuthSession<C, P>, options: {
|
|
96
99
|
readonly token: string | null;
|
|
97
|
-
}): Promise<ServerContext<M, F, E, C,
|
|
100
|
+
}): Promise<ServerContext<M, F, E, C, P>>;
|
|
98
101
|
/**
|
|
99
102
|
* First statement of an app-authored `'use cache: private'` function: sets
|
|
100
|
-
* `cacheLife` from the session's expiry (`sessionStale
|
|
101
|
-
* `bs:session:<user id>`
|
|
103
|
+
* `cacheLife` from the session's expiry (`sessionStale`, capped by
|
|
104
|
+
* `life.stale`), tags the entry `bs:session:<user id>` plus `tags`, and
|
|
105
|
+
* returns the caller's context plus `session`.
|
|
102
106
|
*
|
|
103
107
|
* ```ts
|
|
104
108
|
* async function getCustomers() {
|
|
105
109
|
* 'use cache: private';
|
|
106
|
-
* const { db } = await
|
|
110
|
+
* const { db } = await bs.cached();
|
|
107
111
|
* return db.customers.findMany().orThrow();
|
|
108
112
|
* }
|
|
109
113
|
* ```
|
|
110
114
|
*/
|
|
111
|
-
cached(options?: CachedOptions): Promise<CachedContext<M, F, E, C,
|
|
112
|
-
/**
|
|
113
|
-
|
|
115
|
+
cached(options?: CachedOptions): Promise<CachedContext<M, F, E, C, P>>;
|
|
116
|
+
/**
|
|
117
|
+
* Drops every `bs.cached()` entry of a user, e.g. after a role change,
|
|
118
|
+
* and the entries tagged with `tags` (such as PermDock's `snapshotTag(userId)`).
|
|
119
|
+
*/
|
|
120
|
+
invalidateSession(userId: string, options?: {
|
|
121
|
+
readonly tags?: readonly string[];
|
|
122
|
+
}): void;
|
|
114
123
|
/**
|
|
115
124
|
* Counts on the server and returns a serializable seed for
|
|
116
125
|
* `useLiveCount(seed)`, so the badge renders with a number and the client
|
|
117
126
|
* only refetches on changes. Pass `db` inside `'use cache: private'`
|
|
118
|
-
* (from `
|
|
127
|
+
* (from `bs.cached()`); otherwise it uses `bs.context()`. A failed
|
|
119
128
|
* count gives `count: null` instead of throwing.
|
|
120
129
|
*/
|
|
121
130
|
liveCount<T extends Extract<keyof M, string>>(spec: QuerySpec<T, "count", number>, db?: CountRunner): Promise<LiveCountSeed<T>>;
|
|
122
131
|
/** Route handler with auth, Result unwrapping and Problem Details errors. */
|
|
123
|
-
route<
|
|
124
|
-
readonly params:
|
|
132
|
+
route<Params = Record<string, string | string[]>>(handler: (request: NextRequest, ctx: ServerContext<M, F, E, C, P> & {
|
|
133
|
+
readonly params: Params;
|
|
125
134
|
}) => unknown, options?: GuardOptions): (request: NextRequest, segment: {
|
|
126
|
-
readonly params: Promise<
|
|
135
|
+
readonly params: Promise<Params>;
|
|
127
136
|
}) => Promise<Response>;
|
|
128
137
|
/** Server action returning a serializable `ActionResult`. */
|
|
129
|
-
action<S extends StandardSchemaV1 | undefined, T>(options: ActionOptions<S>, fn: (input: ActionParsed<S>, ctx: ServerContext<M, F, E, C,
|
|
138
|
+
action<S extends StandardSchemaV1 | undefined, T>(options: ActionOptions<S>, fn: (input: ActionParsed<S>, ctx: ServerContext<M, F, E, C, P>) => Promise<T> | T): (input: ActionInput<S>) => Promise<ActionResult<Unwrapped<Awaited<T>>>>;
|
|
130
139
|
/** Tags the current `"use cache"` scope with a table (and row) tag. */
|
|
131
140
|
cacheTag(table: Extract<keyof M, string>, id?: string | number): void;
|
|
132
141
|
/**
|
|
@@ -141,7 +150,7 @@ export interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C =
|
|
|
141
150
|
*/
|
|
142
151
|
debugRoute(): (request: Request) => Promise<Response>;
|
|
143
152
|
}
|
|
144
|
-
|
|
153
|
+
interface SessionStaleOptions {
|
|
145
154
|
/** Below this, the result would not be prefetched anyway. Defaults to 30. */
|
|
146
155
|
readonly min?: number;
|
|
147
156
|
/** 300 (5 minutes) joins the route's App Shell. Defaults to 300. */
|
|
@@ -152,17 +161,24 @@ export interface SessionStaleOptions {
|
|
|
152
161
|
* the token's expiry, and at least `min`.
|
|
153
162
|
*/
|
|
154
163
|
export declare function sessionStale(session: AuthSession, options?: SessionStaleOptions, now?: number): number;
|
|
155
|
-
|
|
156
|
-
/**
|
|
164
|
+
interface CachedOptions {
|
|
165
|
+
/**
|
|
166
|
+
* `cacheLife` for the entry. `stale` is `sessionStale`, and never more than
|
|
167
|
+
* `stale` when it is set: pass PermDock's `cacheLifeFor(snapshot)` so the
|
|
168
|
+
* entry goes stale with the snapshot too.
|
|
169
|
+
*/
|
|
157
170
|
readonly life?: SessionStaleOptions & {
|
|
171
|
+
readonly stale?: number;
|
|
158
172
|
readonly revalidate?: number;
|
|
159
173
|
readonly expire?: number;
|
|
160
174
|
};
|
|
175
|
+
/** More `cacheTag`s for the entry, e.g. `snapshotTag(sub)` or `org:<id>`. */
|
|
176
|
+
readonly tags?: readonly string[];
|
|
161
177
|
}
|
|
162
|
-
|
|
163
|
-
readonly session: AuthSession<C,
|
|
178
|
+
type CachedContext<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> = ServerContext<M, F, E, C, P> & {
|
|
179
|
+
readonly session: AuthSession<C, P>;
|
|
164
180
|
};
|
|
165
|
-
/** The tag `
|
|
181
|
+
/** The tag `bs.cached()` puts on a user's entries. */
|
|
166
182
|
export declare function sessionTag(userId: string): string;
|
|
167
183
|
/** `bs:<table>` or `bs:<table>:<id>`. Mutations invalidate both. */
|
|
168
184
|
export declare function tagFor(table: string, id?: string | number): string;
|
|
@@ -172,7 +188,7 @@ export declare function tagFor(table: string, id?: string | number): string;
|
|
|
172
188
|
* `cacheTags: false`.
|
|
173
189
|
*/
|
|
174
190
|
export declare function nextCache(): CacheAdapter;
|
|
175
|
-
|
|
191
|
+
interface RequireAalOptions {
|
|
176
192
|
/**
|
|
177
193
|
* Page that enrolls or challenges a second factor. Signed-in users below
|
|
178
194
|
* the level are redirected there with `?next=<path>`; without it they get
|
|
@@ -189,7 +205,7 @@ export interface RequireAalOptions {
|
|
|
189
205
|
* combine it with your sign-in redirect.
|
|
190
206
|
*
|
|
191
207
|
* ```ts
|
|
192
|
-
*
|
|
208
|
+
* bs.proxy(request, {
|
|
193
209
|
* protect: requireAal('aal2', { redirect: '/mfa', match: (path) => path.startsWith('/settings') }),
|
|
194
210
|
* });
|
|
195
211
|
* ```
|
|
@@ -202,9 +218,9 @@ export declare function shouldRefresh(request: Request): boolean;
|
|
|
202
218
|
* actions and cache tags, on top of `createServer`.
|
|
203
219
|
*
|
|
204
220
|
* ```ts
|
|
205
|
-
* export const
|
|
221
|
+
* export const bs = createNext(betterSupabase);
|
|
206
222
|
* ```
|
|
207
223
|
*/
|
|
208
|
-
export declare function createNext<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown,
|
|
224
|
+
export declare function createNext<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown, P = unknown>(betterSupabase: BetterSupabase<M, D, F, E, C, P>, options?: NextOptions): BetterNext<M, F, E, C, P>;
|
|
209
225
|
//#endregion
|
|
210
|
-
export { type Aal, type AmrEntry, type AuthKind, type AuthSession, type DbBudget, type DbStats, type EntitlementKey, type GuardOptions, type Impersonator, type MembershipClaim, hasEntitlement, toSession };
|
|
226
|
+
export { type Aal, type ActionOptions, type ActionResult, type AmrEntry, type AuthKind, type AuthSession, type BetterNext, type CachedContext, type CachedOptions, type DbBudget, type DbStats, type EntitlementKey, type GuardOptions, type Impersonator, type LiveCountSeed, type MembershipClaim, type NextDebugOptions, type NextOptions, type ProxyOptions, type RequireAalOptions, type SessionStaleOptions, hasEntitlement, toSession };
|