@run402/functions 2.5.1 → 2.7.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.
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Minimal HS256/HS512 JWT sign + verify built directly on `node:crypto`.
3
+ *
4
+ * Used internally by `@run402/functions` to verify project JWTs in
5
+ * `getUser`. Replaces a runtime dependency on the `jsonwebtoken` package
6
+ * (and its transitive tree `jws` / `jwa` / `ecdsa-sig-formatter` /
7
+ * `lodash.*` / `ms` / `semver` / `safe-buffer`), so this published SDK
8
+ * has zero crypto deps beyond `node:crypto`. The companion file in
9
+ * the private gateway package (`packages/gateway/src/lib/jwt.ts`) is
10
+ * intentionally a verbatim duplicate — the published SDK cannot pull
11
+ * from the workspace-private `@run402/shared`.
12
+ */
13
+ import { createHmac, timingSafeEqual } from "node:crypto";
14
+ export class JsonWebTokenError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "JsonWebTokenError";
18
+ }
19
+ }
20
+ export class TokenExpiredError extends JsonWebTokenError {
21
+ expiredAt;
22
+ constructor(message, expiredAt) {
23
+ super(message);
24
+ this.name = "TokenExpiredError";
25
+ this.expiredAt = expiredAt;
26
+ }
27
+ }
28
+ export class NotBeforeError extends JsonWebTokenError {
29
+ date;
30
+ constructor(message, date) {
31
+ super(message);
32
+ this.name = "NotBeforeError";
33
+ this.date = date;
34
+ }
35
+ }
36
+ function b64urlEncode(input) {
37
+ const buf = typeof input === "string" ? Buffer.from(input, "utf8") : input;
38
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
39
+ }
40
+ function b64urlDecodeToBuffer(input) {
41
+ if (!/^[A-Za-z0-9_-]*$/.test(input))
42
+ throw new JsonWebTokenError("invalid token");
43
+ const norm = input.replace(/-/g, "+").replace(/_/g, "/");
44
+ const pad = norm.length % 4 === 0 ? "" : "=".repeat(4 - (norm.length % 4));
45
+ return Buffer.from(norm + pad, "base64");
46
+ }
47
+ const DURATION_RE = /^(-?\d+(?:\.\d+)?)\s*(s|sec|secs|seconds?|m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|weeks?|y|years?)?$/i;
48
+ function parseDurationSeconds(input) {
49
+ if (typeof input === "number") {
50
+ if (!Number.isFinite(input))
51
+ throw new TypeError(`Invalid expiresIn: ${input}`);
52
+ return Math.floor(input);
53
+ }
54
+ const m = DURATION_RE.exec(input.trim());
55
+ if (!m)
56
+ throw new TypeError(`Invalid expiresIn duration: ${input}`);
57
+ const n = parseFloat(m[1]);
58
+ const unit = (m[2] ?? "s").toLowerCase();
59
+ if (unit.startsWith("y"))
60
+ return Math.floor(n * 31_557_600);
61
+ if (unit.startsWith("w"))
62
+ return Math.floor(n * 604_800);
63
+ if (unit.startsWith("d"))
64
+ return Math.floor(n * 86_400);
65
+ if (unit.startsWith("h"))
66
+ return Math.floor(n * 3_600);
67
+ if (unit === "m" || unit.startsWith("min"))
68
+ return Math.floor(n * 60);
69
+ return Math.floor(n);
70
+ }
71
+ function hmacForAlg(alg) {
72
+ return alg === "HS256" ? "sha256" : "sha512";
73
+ }
74
+ export function sign(payload, secret, options) {
75
+ const alg = options?.algorithm ?? "HS256";
76
+ if (alg !== "HS256" && alg !== "HS512") {
77
+ throw new JsonWebTokenError(`Unsupported algorithm: ${alg}`);
78
+ }
79
+ if (typeof secret !== "string" || secret.length === 0) {
80
+ throw new JsonWebTokenError("secretOrPrivateKey must be provided");
81
+ }
82
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
83
+ throw new JsonWebTokenError("payload must be a plain object");
84
+ }
85
+ const claims = { ...payload };
86
+ const noTimestamp = options?.noTimestamp ?? false;
87
+ if (!noTimestamp && claims.iat === undefined) {
88
+ claims.iat = Math.floor(Date.now() / 1000);
89
+ }
90
+ if (options?.expiresIn !== undefined) {
91
+ if (claims.exp !== undefined) {
92
+ throw new JsonWebTokenError("Bad options.expiresIn option the payload already has an exp property");
93
+ }
94
+ const offset = parseDurationSeconds(options.expiresIn);
95
+ const iatRef = typeof claims.iat === "number" ? claims.iat : Math.floor(Date.now() / 1000);
96
+ claims.exp = iatRef + offset;
97
+ }
98
+ const header = { alg, typ: "JWT" };
99
+ const headerB64 = b64urlEncode(JSON.stringify(header));
100
+ const payloadB64 = b64urlEncode(JSON.stringify(claims));
101
+ const sig = createHmac(hmacForAlg(alg), secret)
102
+ .update(`${headerB64}.${payloadB64}`)
103
+ .digest();
104
+ return `${headerB64}.${payloadB64}.${b64urlEncode(sig)}`;
105
+ }
106
+ export function verify(token, secret, options) {
107
+ if (typeof token !== "string" || token.length === 0) {
108
+ throw new JsonWebTokenError("jwt must be provided");
109
+ }
110
+ if (typeof secret !== "string" || secret.length === 0) {
111
+ throw new JsonWebTokenError("secret must be provided");
112
+ }
113
+ const parts = token.split(".");
114
+ if (parts.length !== 3)
115
+ throw new JsonWebTokenError("jwt malformed");
116
+ const [headerB64, payloadB64, sigB64] = parts;
117
+ let header;
118
+ try {
119
+ header = JSON.parse(b64urlDecodeToBuffer(headerB64).toString("utf8"));
120
+ }
121
+ catch {
122
+ throw new JsonWebTokenError("invalid token");
123
+ }
124
+ const allowed = (options?.algorithms ?? ["HS256"]);
125
+ if (typeof header.alg !== "string" || !allowed.includes(header.alg)) {
126
+ throw new JsonWebTokenError("invalid algorithm");
127
+ }
128
+ if (header.alg !== "HS256" && header.alg !== "HS512") {
129
+ throw new JsonWebTokenError("invalid algorithm");
130
+ }
131
+ const expected = createHmac(hmacForAlg(header.alg), secret)
132
+ .update(`${headerB64}.${payloadB64}`)
133
+ .digest();
134
+ let got;
135
+ try {
136
+ got = b64urlDecodeToBuffer(sigB64);
137
+ }
138
+ catch {
139
+ throw new JsonWebTokenError("invalid signature");
140
+ }
141
+ if (got.length !== expected.length || !timingSafeEqual(got, expected)) {
142
+ throw new JsonWebTokenError("invalid signature");
143
+ }
144
+ let payload;
145
+ try {
146
+ payload = JSON.parse(b64urlDecodeToBuffer(payloadB64).toString("utf8"));
147
+ }
148
+ catch {
149
+ throw new JsonWebTokenError("invalid token");
150
+ }
151
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
152
+ throw new JsonWebTokenError("invalid token");
153
+ }
154
+ const clockTolerance = options?.clockTolerance ?? 0;
155
+ const now = Math.floor(Date.now() / 1000);
156
+ if (typeof payload.exp === "number" && payload.exp + clockTolerance < now) {
157
+ throw new TokenExpiredError("jwt expired", new Date(payload.exp * 1000));
158
+ }
159
+ if (typeof payload.nbf === "number" && payload.nbf - clockTolerance > now) {
160
+ throw new NotBeforeError("jwt not active", new Date(payload.nbf * 1000));
161
+ }
162
+ if (options?.issuer !== undefined && payload.iss !== options.issuer) {
163
+ throw new JsonWebTokenError(`jwt issuer invalid. expected: ${options.issuer}`);
164
+ }
165
+ if (options?.audience !== undefined) {
166
+ const wantArr = Array.isArray(options.audience) ? options.audience : [options.audience];
167
+ const haveArr = Array.isArray(payload.aud)
168
+ ? payload.aud
169
+ : payload.aud !== undefined
170
+ ? [payload.aud]
171
+ : [];
172
+ if (!wantArr.some((w) => haveArr.includes(w))) {
173
+ throw new JsonWebTokenError("jwt audience invalid");
174
+ }
175
+ }
176
+ return payload;
177
+ }
178
+ export function decode(token) {
179
+ if (typeof token !== "string")
180
+ return null;
181
+ const parts = token.split(".");
182
+ if (parts.length !== 3)
183
+ return null;
184
+ try {
185
+ const decoded = JSON.parse(b64urlDecodeToBuffer(parts[1]).toString("utf8"));
186
+ if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded))
187
+ return null;
188
+ return decoded;
189
+ }
190
+ catch {
191
+ return null;
192
+ }
193
+ }
194
+ const jwt = { sign, verify, decode };
195
+ export default jwt;
196
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/lib/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA4B1D,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;IACtD,SAAS,CAAO;IAChB,YAAY,OAAe,EAAE,SAAe;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,iBAAiB;IACnD,IAAI,CAAO;IACX,YAAY,OAAe,EAAE,IAAU;QACrC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,SAAS,YAAY,CAAC,KAAsB;IAC1C,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3E,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa;IACzC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAClF,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,WAAW,GAAG,6GAA6G,CAAC;AAElI,SAAS,oBAAoB,CAAC,KAAsB;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,sBAAsB,KAAK,EAAE,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;IACzD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IACvD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,UAAU,CAAC,GAAuB;IACzC,OAAO,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,IAAI,CAClB,OAAe,EACf,MAAc,EACd,OAAqB;IAErB,MAAM,GAAG,GAAuB,OAAO,EAAE,SAAS,IAAI,OAAO,CAAC;IAC9D,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;QACvC,MAAM,IAAI,iBAAiB,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,iBAAiB,CAAC,qCAAqC,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,iBAAiB,CAAC,gCAAgC,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,GAAI,OAAmC,EAAE,CAAC;IACpF,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,KAAK,CAAC;IAClD,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7C,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS,EAAE,CAAC;QACrC,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,iBAAiB,CACzB,sEAAsE,CACvE,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC3F,MAAM,CAAC,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;SAC5C,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC;SACpC,MAAM,EAAE,CAAC;IACZ,OAAO,GAAG,SAAS,IAAI,UAAU,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,KAAa,EACb,MAAc,EACd,OAAuB;IAEvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,iBAAiB,CAAC,sBAAsB,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,iBAAiB,CAAC,yBAAyB,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAAC;IACrE,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAE9C,IAAI,MAAwC,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAGnE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,CAA0B,CAAC;IAC5E,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;QACrD,MAAM,IAAI,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;SACxD,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC;SACpC,MAAM,EAAE,CAAC;IACZ,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAe,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,GAAG,cAAc,GAAG,GAAG,EAAE,CAAC;QAC1E,MAAM,IAAI,iBAAiB,CAAC,aAAa,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,GAAG,cAAc,GAAG,GAAG,EAAE,CAAC;QAC1E,MAAM,IAAI,cAAc,CAAC,gBAAgB,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,OAAO,EAAE,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACpE,MAAM,IAAI,iBAAiB,CAAC,iCAAiC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,OAAO,EAAE,QAAQ,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;YACxC,CAAC,CAAC,OAAO,CAAC,GAAG;YACb,CAAC,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS;gBACzB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;gBACf,CAAC,CAAC,EAAE,CAAC;QACT,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,iBAAiB,CAAC,sBAAsB,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,OAAO,OAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,MAAM,CAAiB,KAAa;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAY,CAAC;QACvF,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3F,OAAO,OAAY,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACrC,eAAe,GAAG,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `getRun402Context(request)` — zero-dependency helper for **non-Astro**
3
+ * Node22 functions (plain webhook handlers, auth endpoints, admin tools)
4
+ * to read the per-request context the gateway already populates as
5
+ * `x-run402-*` request headers. Returns the same shape `Astro.locals.run402`
6
+ * exposes in `@run402/astro`, so Astro and plain-function code share one
7
+ * mental model.
8
+ *
9
+ * Why this exists: every non-Astro function in production today hand-rolls
10
+ * header parsing (`request.headers.get('x-run402-request-id')` ...). That's
11
+ * error-prone, untyped, and inconsistent — different functions read
12
+ * different headers, miss optional fields, mis-spell names. This helper
13
+ * centralizes the shape so:
14
+ *
15
+ * - Adding a new request-context field is one place to update (here
16
+ * and in `@run402/astro`'s `Run402Locals`).
17
+ * - Header names stay private to the helper — if the gateway renames
18
+ * `x-run402-*` to something else in v2, the helper signature is
19
+ * unchanged.
20
+ * - Plain functions get the same per-field typing Astro pages get.
21
+ *
22
+ * Header sources (all set by the gateway on the forwarded `Request`):
23
+ *
24
+ * - `x-run402-request-id` → `requestId`
25
+ * - `x-run402-project-id` → `projectId`
26
+ * - `x-run402-release-id` → `releaseId` (null if no active release)
27
+ * - `x-run402-host` → `host`
28
+ * - `x-run402-locale` → `locale` (null when no spec.i18n)
29
+ * - `x-run402-default-locale` → `defaultLocale` (null when no spec.i18n)
30
+ *
31
+ * If the gateway didn't set a given header (rare — would mean the request
32
+ * bypassed the routed-http pipeline), the field is `null`. The helper
33
+ * never throws.
34
+ */
35
+ /**
36
+ * Per-request context the gateway populates on routed-http invocations.
37
+ * Identical shape to `@run402/astro`'s `Run402Locals` so Astro and plain-
38
+ * function code can pass the value around interchangeably.
39
+ */
40
+ export interface Run402RequestContext {
41
+ /** Gateway-assigned request id; flows to logs + error envelopes. */
42
+ requestId: string | null;
43
+ /** Project id this function is deployed under. */
44
+ projectId: string | null;
45
+ /** Active release id at request time; `null` until the first activation. */
46
+ releaseId: string | null;
47
+ /** Host portion of the request URL (e.g., `eagles.kychon.com`). */
48
+ host: string | null;
49
+ /** Negotiated locale per the release's `spec.i18n.detect` chain.
50
+ * `null` when the release has no i18n slice. Byte-identical to a
51
+ * `spec.i18n.locales[]` entry when non-null. */
52
+ locale: string | null;
53
+ /** Release's default locale (`spec.i18n.defaultLocale`).
54
+ * `null` when the release has no i18n slice. */
55
+ defaultLocale: string | null;
56
+ }
57
+ /**
58
+ * Web-standard headers object — `Request.headers`, `Headers`, or a
59
+ * `Record<string, string>`. We accept the loose shape so callers don't
60
+ * have to construct a `Headers` instance just to read a few fields.
61
+ */
62
+ type HeaderSource = {
63
+ get(name: string): string | null;
64
+ } | {
65
+ headers: {
66
+ get(name: string): string | null;
67
+ };
68
+ } | Record<string, string | string[] | undefined>;
69
+ /**
70
+ * Read the Run402 per-request context off the inbound `Request`.
71
+ *
72
+ * Accepts either a `Request` directly, or any object with a `.headers`
73
+ * property exposing `.get(name)` (which covers AWS Lambda v2 event
74
+ * objects, Node `http.IncomingMessage` wrappers, etc.), or a plain
75
+ * `Record<string, string | string[]>` (which covers `event.headers`
76
+ * shapes from various function platforms).
77
+ *
78
+ * @example
79
+ * // In a routed-http function (`@run402/functions`'s `routedHttp` helper):
80
+ * export default routedHttp(async (req) => {
81
+ * const { requestId, locale, host } = getRun402Context(req);
82
+ * console.log(`[${requestId}] serving ${host} in ${locale ?? 'default'}`);
83
+ * return text(`Hello, ${host}`);
84
+ * });
85
+ *
86
+ * // In a raw envelope handler (no routedHttp wrapper):
87
+ * export const handler = async (event) => {
88
+ * const ctx = getRun402Context(event);
89
+ * ...
90
+ * };
91
+ */
92
+ export declare function getRun402Context(source: HeaderSource): Run402RequestContext;
93
+ export {};
94
+ //# sourceMappingURL=request-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;qDAEiD;IACjD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;qDACiD;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,KAAK,YAAY,GACb;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GACpC;IAAE,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,GACjD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,oBAAoB,CAU3E"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `getRun402Context(request)` — zero-dependency helper for **non-Astro**
3
+ * Node22 functions (plain webhook handlers, auth endpoints, admin tools)
4
+ * to read the per-request context the gateway already populates as
5
+ * `x-run402-*` request headers. Returns the same shape `Astro.locals.run402`
6
+ * exposes in `@run402/astro`, so Astro and plain-function code share one
7
+ * mental model.
8
+ *
9
+ * Why this exists: every non-Astro function in production today hand-rolls
10
+ * header parsing (`request.headers.get('x-run402-request-id')` ...). That's
11
+ * error-prone, untyped, and inconsistent — different functions read
12
+ * different headers, miss optional fields, mis-spell names. This helper
13
+ * centralizes the shape so:
14
+ *
15
+ * - Adding a new request-context field is one place to update (here
16
+ * and in `@run402/astro`'s `Run402Locals`).
17
+ * - Header names stay private to the helper — if the gateway renames
18
+ * `x-run402-*` to something else in v2, the helper signature is
19
+ * unchanged.
20
+ * - Plain functions get the same per-field typing Astro pages get.
21
+ *
22
+ * Header sources (all set by the gateway on the forwarded `Request`):
23
+ *
24
+ * - `x-run402-request-id` → `requestId`
25
+ * - `x-run402-project-id` → `projectId`
26
+ * - `x-run402-release-id` → `releaseId` (null if no active release)
27
+ * - `x-run402-host` → `host`
28
+ * - `x-run402-locale` → `locale` (null when no spec.i18n)
29
+ * - `x-run402-default-locale` → `defaultLocale` (null when no spec.i18n)
30
+ *
31
+ * If the gateway didn't set a given header (rare — would mean the request
32
+ * bypassed the routed-http pipeline), the field is `null`. The helper
33
+ * never throws.
34
+ */
35
+ /**
36
+ * Read the Run402 per-request context off the inbound `Request`.
37
+ *
38
+ * Accepts either a `Request` directly, or any object with a `.headers`
39
+ * property exposing `.get(name)` (which covers AWS Lambda v2 event
40
+ * objects, Node `http.IncomingMessage` wrappers, etc.), or a plain
41
+ * `Record<string, string | string[]>` (which covers `event.headers`
42
+ * shapes from various function platforms).
43
+ *
44
+ * @example
45
+ * // In a routed-http function (`@run402/functions`'s `routedHttp` helper):
46
+ * export default routedHttp(async (req) => {
47
+ * const { requestId, locale, host } = getRun402Context(req);
48
+ * console.log(`[${requestId}] serving ${host} in ${locale ?? 'default'}`);
49
+ * return text(`Hello, ${host}`);
50
+ * });
51
+ *
52
+ * // In a raw envelope handler (no routedHttp wrapper):
53
+ * export const handler = async (event) => {
54
+ * const ctx = getRun402Context(event);
55
+ * ...
56
+ * };
57
+ */
58
+ export function getRun402Context(source) {
59
+ const get = pickHeaderGetter(source);
60
+ return {
61
+ requestId: nonEmpty(get("x-run402-request-id")),
62
+ projectId: nonEmpty(get("x-run402-project-id")),
63
+ releaseId: nonEmpty(get("x-run402-release-id")),
64
+ host: nonEmpty(get("x-run402-host")),
65
+ locale: nonEmpty(get("x-run402-locale")),
66
+ defaultLocale: nonEmpty(get("x-run402-default-locale")),
67
+ };
68
+ }
69
+ /**
70
+ * Resolve a `(name) => string | null` accessor regardless of which of
71
+ * the four supported input shapes the caller passed. Case-insensitive
72
+ * matching, because Node-style header maps preserve original casing
73
+ * while Web `Headers` lowercase on insert — the helper papers over the
74
+ * difference.
75
+ */
76
+ function pickHeaderGetter(source) {
77
+ // Shape 1: object with `.get()` (e.g., `Headers` instance).
78
+ if (typeof source.get === "function") {
79
+ const g = source.get.bind(source);
80
+ return (name) => g(name) ?? g(name.toLowerCase()) ?? null;
81
+ }
82
+ // Shape 2: object with `.headers.get()` (e.g., `Request`).
83
+ const nested = source.headers;
84
+ if (nested && typeof nested.get === "function") {
85
+ const g = nested.get.bind(nested);
86
+ return (name) => g(name) ?? g(name.toLowerCase()) ?? null;
87
+ }
88
+ // Shape 3: plain object map. Build a lowercased lookup once so any
89
+ // casing in the input keys (`X-Run402-Request-Id`, `x-run402-request-id`,
90
+ // `X-RUN402-REQUEST-ID` ...) resolves identically.
91
+ const map = source;
92
+ const lowered = {};
93
+ for (const k of Object.keys(map)) {
94
+ lowered[k.toLowerCase()] = map[k];
95
+ }
96
+ return (name) => {
97
+ const v = lowered[name.toLowerCase()];
98
+ if (typeof v === "string")
99
+ return v;
100
+ if (Array.isArray(v) && v.length > 0) {
101
+ const first = v[0];
102
+ if (typeof first === "string")
103
+ return first;
104
+ }
105
+ return null;
106
+ };
107
+ }
108
+ function nonEmpty(v) {
109
+ if (v === null)
110
+ return null;
111
+ const trimmed = v.trim();
112
+ return trimmed.length === 0 ? null : trimmed;
113
+ }
114
+ //# sourceMappingURL=request-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-context.js","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAmCH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO;QACL,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QAC/C,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QAC/C,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QAC/C,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACpC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACxC,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;KACxD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CACvB,MAAoB;IAEpB,4DAA4D;IAC5D,IAAI,OAAQ,MAA4B,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAC5D,MAAM,CAAC,GAAI,MAA+C,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAC;IAC5D,CAAC;IACD,2DAA2D;IAC3D,MAAM,MAAM,GAAI,MAA0C,CAAC,OAAO,CAAC;IACnE,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAC;IAC5D,CAAC;IACD,mEAAmE;IACnE,0EAA0E;IAC1E,mDAAmD;IACnD,MAAM,GAAG,GAAG,MAAuD,CAAC;IACpE,MAAM,OAAO,GAAkD,EAAE,CAAC;IAClE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC;QAC9C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,CAAgB;IAChC,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACzB,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * SSR request context primitive — capability `functions-sdk-auth-model`.
3
+ *
4
+ * The SSR Lambda runtime establishes this context via `als.run(...)`
5
+ * BEFORE importing or executing the Astro server bundle (or any other
6
+ * user-supplied code). SDK functions that need request-scoped state
7
+ * (`db()`, `getUser()`, `cache.*`, `assets.*`, `email.*`, `ai.*`) read
8
+ * from `getCurrentContext()` rather than requiring explicit context
9
+ * parameters at the call site.
10
+ *
11
+ * The `active` flag exists because Node's AsyncLocalStorage propagates
12
+ * into timers, microtasks, and unawaited promises created inside
13
+ * `als.run()`. Without an explicit flag, a `setTimeout(() => db()..., 60_000)`
14
+ * scheduled inside a handler would, when the timer fires later, still
15
+ * observe an `als.getStore()` pointing at the (now-completed) request —
16
+ * leading to stale or incorrect SDK behavior. The SSR runtime sets
17
+ * `active.value = false` IMMEDIATELY after the response body is fully
18
+ * materialized; SDK functions check the flag and throw
19
+ * `R402_SDK_OUTSIDE_REQUEST_CONTEXT` when it's false.
20
+ *
21
+ * Likewise `cacheBypassTainted.value` is set to `true` by `getUser()`
22
+ * and payment-primitive SDK calls during render; the SSR runtime returns
23
+ * its final value to the gateway in the Lambda response metadata envelope
24
+ * (NOT via in-process ALS — the gateway runs in a different process).
25
+ *
26
+ * @see openspec/changes/astro-ssr-runtime/specs/functions-sdk-auth-model/spec.md
27
+ * @see openspec/changes/astro-ssr-runtime/specs/routed-http-functions/spec.md
28
+ */
29
+ import { AsyncLocalStorage } from "node:async_hooks";
30
+ /** The shape stored in AsyncLocalStorage. See the spec referenced above
31
+ * for the canonical definition. */
32
+ export interface RunRequestContext {
33
+ /** Unique per-request id; matches `x-run402-request-id`. */
34
+ requestId: string;
35
+ projectId: string;
36
+ releaseId: string;
37
+ /** v1.49-negotiated locale string. `null` when the active release has
38
+ * no i18n slice. */
39
+ locale: string | null;
40
+ /** Echoed verbatim from the active release's `i18n.defaultLocale`,
41
+ * `null` when the release has no i18n slice. */
42
+ defaultLocale: string | null;
43
+ /** Validated host from the routed-function envelope — NOT the raw
44
+ * `Host` header. Cache-key host comes from here. */
45
+ host: string;
46
+ /** Request information SDK functions need (cookies for `getUser()`,
47
+ * url for invalidate path-form, etc.). Body is intentionally absent
48
+ * — user code reads body via the standard Web Request API passed to
49
+ * the handler. */
50
+ request: {
51
+ method: string;
52
+ url: string;
53
+ headers: Record<string, string | string[] | undefined>;
54
+ };
55
+ /** Mutable ref: SDK functions that read request-scoped auth or invoke
56
+ * payment primitives set `value = true`. The SSR Lambda runtime
57
+ * returns the final value to the gateway in the response metadata
58
+ * envelope. */
59
+ cacheBypassTainted: {
60
+ value: boolean;
61
+ };
62
+ /** Mutable ref: SSR runtime sets `value = false` after the response
63
+ * body has been fully materialized. SDK functions check this AND
64
+ * the store presence; either being false produces
65
+ * `R402_SDK_OUTSIDE_REQUEST_CONTEXT`. */
66
+ active: {
67
+ value: boolean;
68
+ };
69
+ }
70
+ /** The shared ALS instance. Modules in @run402/functions read from
71
+ * this; the SSR Lambda runtime (in @run402/astro) writes to it. */
72
+ export declare const als: AsyncLocalStorage<RunRequestContext>;
73
+ /**
74
+ * Read the current request context, or `undefined` if no SSR request
75
+ * is in flight. SDK functions check both this AND `active.value` to
76
+ * decide whether to honor the call.
77
+ */
78
+ export declare function getCurrentContext(): RunRequestContext | undefined;
79
+ /**
80
+ * Establish a request context and run a callback inside it. The SSR
81
+ * Lambda runtime calls this exactly once per invocation, wrapping the
82
+ * full `App.render(request)` + body-materialization sequence.
83
+ *
84
+ * The `active` flag is set to `true` initially; the caller (the SSR
85
+ * runtime) flips it to `false` after the response body is materialized.
86
+ * Don't call this from user code — it's the runtime's primitive.
87
+ */
88
+ export declare function runWithContext<T>(context: Omit<RunRequestContext, "cacheBypassTainted" | "active"> & Partial<Pick<RunRequestContext, "cacheBypassTainted" | "active">>, callback: () => Promise<T> | T): Promise<T> | T;
89
+ /**
90
+ * Throw a structured `R402_SDK_OUTSIDE_REQUEST_CONTEXT` error. Used by
91
+ * SDK functions when they're invoked with no ALS store OR while the
92
+ * context has been marked inactive.
93
+ *
94
+ * Per the api-error-envelope spec, the thrown error carries:
95
+ * - `code: 'R402_SDK_OUTSIDE_REQUEST_CONTEXT'`
96
+ * - `message`: names the SDK function and the cause
97
+ * - `suggestedFix`: recommends moving the call inside a handler OR
98
+ * not scheduling background work that outlives the response
99
+ * - `docs`: `https://run402.com/errors/#R402_SDK_OUTSIDE_REQUEST_CONTEXT`
100
+ */
101
+ export declare class Run402OutsideRequestContextError extends Error {
102
+ readonly code = "R402_SDK_OUTSIDE_REQUEST_CONTEXT";
103
+ readonly docs = "https://run402.com/errors/#R402_SDK_OUTSIDE_REQUEST_CONTEXT";
104
+ readonly suggestedFix: string;
105
+ readonly sdkFunction: string;
106
+ readonly cause: "no_context" | "context_inactive";
107
+ constructor(sdkFunction: string, cause: "no_context" | "context_inactive");
108
+ }
109
+ /**
110
+ * Assert that a request context is active. Returns the context on
111
+ * success; throws `Run402OutsideRequestContextError` on failure.
112
+ *
113
+ * SDK functions call this as the first line, e.g.:
114
+ *
115
+ * const ctx = requireActiveContext("db");
116
+ * // use ctx.projectId, ctx.request.headers, etc.
117
+ */
118
+ export declare function requireActiveContext(sdkFunction: string): RunRequestContext;
119
+ /**
120
+ * Flip the cache-bypass taint flag on the current context. Called by
121
+ * `getUser()` and payment-primitive SDK calls to signal that the
122
+ * rendered response depends on request-scoped auth state and therefore
123
+ * MUST NOT be cached publicly.
124
+ *
125
+ * No-op if there is no active context (rather than throwing — the taint
126
+ * is moot when there's no cache layer to inform).
127
+ */
128
+ export declare function taintCacheBypass(): void;
129
+ /**
130
+ * Canonical registry of SDK functions that are "payment primitives" for
131
+ * cache-taint purposes. Calling any of these inside an active request
132
+ * context MUST flip `cacheBypassTainted.value = true`. The set is
133
+ * defined here so the spec, runtime, and docs share one source of truth.
134
+ *
135
+ * To add a new payment primitive: append to this set AND ensure the
136
+ * function's implementation calls `taintCacheBypass()` on every entry.
137
+ *
138
+ * @see openspec/changes/astro-ssr-runtime/specs/functions-sdk-auth-model/spec.md
139
+ */
140
+ export declare const PAYMENT_PRIMITIVES: ReadonlySet<string>;
141
+ /**
142
+ * Helper for payment-primitive implementations. Wraps the body in a
143
+ * taint-on-entry call so the registry contract is enforced at the
144
+ * SDK layer rather than relying on each implementation to remember.
145
+ *
146
+ * Example:
147
+ * export const requirePayment = withPaymentTaint("payments.require", async (opts) => {
148
+ * // ... actual implementation
149
+ * });
150
+ */
151
+ export declare function withPaymentTaint<TArgs extends unknown[], TReturn>(name: string, impl: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
152
+ //# sourceMappingURL=runtime-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-context.d.ts","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;oCACoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;yBACqB;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;qDACiD;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;yDACqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb;;;uBAGmB;IACnB,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;KACxD,CAAC;IACF;;;oBAGgB;IAChB,kBAAkB,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IACvC;;;8CAG0C;IAC1C,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CAC5B;AAED;oEACoE;AACpE,eAAO,MAAM,GAAG,sCAA6C,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,GAAG,SAAS,CAEjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,GAAG,QAAQ,CAAC,CAAC,EACrI,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAOhB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,sCAAsC;IACnD,QAAQ,CAAC,IAAI,iEAAiE;IAC9E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,kBAAkB,CAAC;gBAEtC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,kBAAkB;CAc1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,CAS3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAUjD,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EAC/D,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,GAChC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAe7B"}