@run402/functions 2.4.0 → 2.5.1

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 CHANGED
@@ -98,6 +98,113 @@ if (!user) return new Response("unauthorized", { status: 401 });
98
98
 
99
99
  The function's own `RUN402_PROJECT_ID` is used to scope the verification.
100
100
 
101
+ **Note on `user.role`:** this is the JWT system role (`anon`, `authenticated`, `project_admin`, …) — the same value PostgREST uses to evaluate RLS. It is **not** your application role from a declarative `requireRole` gate (admin/moderator/etc.). For the gate-resolved application role, use `getRole(req)` (see "Function-level auth gates" below). Don't write `getUser(req).role === "admin"` thinking you're checking the gate role — `"admin"` is not a JWT role.
102
+
103
+ ## Function-level auth gates
104
+
105
+ A function can declare auth requirements directly on its `FunctionSpec`. When you set `requireAuth: true` or `requireRole: { ... }`, the gateway enforces them **before** invoking your function — unauthorized callers get `401` / `403` without your code running, and the gateway injects the resolved identity into request headers your function can trust.
106
+
107
+ This lets you delete the hand-rolled "fetch JWT, query members table, check role, return 403" boilerplate from every privileged function. Declare the gate in your `FunctionSpec`; read the resolved identity with `getUserId(req)` and `getRole(req)`.
108
+
109
+ ### Declaring a gate (deploy spec)
110
+
111
+ ```ts
112
+ import { run402 } from "@run402/sdk/node";
113
+
114
+ const r = run402();
115
+ await r.project(projectId).apply({
116
+ functions: {
117
+ patch: {
118
+ set: {
119
+ // 1. Authentication only — any valid JWT for this project passes.
120
+ "list-my-items": {
121
+ source: { sha256, size },
122
+ requireAuth: true,
123
+ },
124
+
125
+ // 2. Authentication + role check against your members table.
126
+ "delete-content": {
127
+ source: { sha256, size },
128
+ requireRole: {
129
+ table: "members", // project-schema table
130
+ idColumn: "user_id", // FK to the user.id from the JWT
131
+ roleColumn: "role", // column holding the role string
132
+ allowed: ["admin"], // case-sensitive byte-equality allowlist
133
+ cacheTtl: 60, // optional, seconds, default 60, max 600, 0 disables
134
+ },
135
+ },
136
+
137
+ // 3. Multi-role — any role in `allowed` passes the gate.
138
+ "moderate-content": {
139
+ source: { sha256, size },
140
+ requireRole: {
141
+ table: "members",
142
+ idColumn: "user_id",
143
+ roleColumn: "role",
144
+ allowed: ["admin", "moderator"],
145
+ },
146
+ },
147
+ },
148
+ },
149
+ },
150
+ });
151
+ ```
152
+
153
+ `requireAuth` and `requireRole` are independent. `requireRole` on its own implies authentication (no valid JWT → 401), then runs the role lookup. `requireAuth: true` alone does a session check with no DB lookup. Set neither to opt out of platform auth (your function owns the check, as today).
154
+
155
+ **Single role-table per release:** all `requireRole` blocks in a single release must share the same `(table, idColumn, roleColumn)` triple. Different `allowed` sets are fine; different tables are not. The gateway rejects conflicting triples at plan time with `INVALID_SPEC`.
156
+
157
+ ### Reading the gate result inside your function
158
+
159
+ ```ts
160
+ import { getUserId, getRole } from "@run402/functions";
161
+
162
+ export default async (req: Request): Promise<Response> => {
163
+ const userId = getUserId(req); // string | null
164
+ const role = getRole(req); // string | null
165
+
166
+ // For a gated function reached through the gateway, both are guaranteed:
167
+ // - getUserId(req) is non-null when requireAuth OR requireRole is on.
168
+ // - getRole(req) is non-null when requireRole is on (and is one of `allowed`).
169
+ // The null case covers local invokes / direct Lambda tests / ungated functions.
170
+
171
+ if (role === "admin") {
172
+ // Privileged path — the gate already verified.
173
+ } else {
174
+ // role === "moderator" (the only other value `allowed` permits).
175
+ }
176
+
177
+ return Response.json({ ok: true, actor: userId, role });
178
+ };
179
+ ```
180
+
181
+ The two headers (`x-run402-user-id`, `x-run402-user-role`) are injected by the gateway after the gate passes, and inbound `x-run402-*` headers from the browser are stripped before injection — so the values are trustworthy without any further verification.
182
+
183
+ ### Direct vs routed invocation
184
+
185
+ The gate applies to **both** routed (browser via `/your/route`) and direct (`POST /functions/v1/:name` with an API key plus a user JWT) invocation. Direct invocation still requires the project API key at the edge; the gate runs after API-key authentication, against the user JWT.
186
+
187
+ ### Deploy-time validation
188
+
189
+ If a `requireRole` block references a table or column that doesn't exist in the project schema at activation time, the deploy fails with `DEPLOY_INVALID_ROLE_GATE` (HTTP 422) **before** flipping the live release. Schema-qualified identifiers (`public.members`), empty `allowed`, and out-of-range `cacheTtl` are rejected earlier at plan time with `INVALID_SPEC` (HTTP 400).
190
+
191
+ ### Caching and staleness
192
+
193
+ Role lookups are cached per `(projectId, userId)` for `cacheTtl` seconds (default 60, max 600). **A demoted user keeps the cached role until the TTL expires** — for high-stakes operations where instant revocation matters, set `cacheTtl: 0` to issue a fresh lookup on every request. The cache is bypassed when no `requireRole` gate runs.
194
+
195
+ ### Relationship to `getUser`
196
+
197
+ `getUser(req)` decodes the JWT and gives you `{ id, role, email }` where `role` is the JWT system role. The gate-injected headers give you the gate-resolved identity:
198
+
199
+ | Helper | Source | Role meaning |
200
+ |---|---|---|
201
+ | `getUser(req).id` | JWT `sub` (decoded in-function) | — |
202
+ | `getUser(req).role` | JWT `role` claim | System role (`anon`, `authenticated`, `project_admin`) |
203
+ | `getUserId(req)` | `x-run402-user-id` header (injected by gateway) | — |
204
+ | `getRole(req)` | `x-run402-user-role` header (injected by gateway) | Application role from your `members` table |
205
+
206
+ For a gated function reached through the gateway, `getUserId(req)` and `getUser(req).id` will agree. The gate-side helpers skip the JWT decode (the gateway already did it), so they're slightly cheaper and stringly-typed against the trusted headers; use them when the gate guarantees the identity.
207
+
101
208
  ## `email.send(...)` — send mail from the project's mailbox
102
209
 
103
210
  Auto-discovers the project's mailbox on first call (the project must already have one — create it once with `run402 email create <slug>` or the `create_mailbox` MCP tool). After that the mailbox id is cached for the function's lifetime.
package/dist/auth.d.ts CHANGED
@@ -6,6 +6,52 @@ export interface User {
6
6
  /**
7
7
  * Verify the caller's JWT and return user identity.
8
8
  * Returns { id, role, email } or null if unauthenticated/invalid.
9
+ *
10
+ * NOTE: `role` here is the JWT claim (`anon`, `authenticated`,
11
+ * `project_admin`, …) — the PostgREST/RLS system role, NOT the
12
+ * application role from a declarative `requireRole` gate. For the
13
+ * gate-resolved application role, use {@link getRole}.
9
14
  */
10
15
  export declare function getUser(req: Request): User | null;
16
+ /**
17
+ * Read the gate-resolved user id from the request.
18
+ *
19
+ * Returns the value of the `x-run402-user-id` request header, which the
20
+ * Run402 gateway injects when a function-level `requireAuth` or
21
+ * `requireRole` gate evaluates successfully on this dispatch. Inbound
22
+ * `x-run402-*` headers from the browser are stripped by the gateway
23
+ * before injection, so the value is trustworthy.
24
+ *
25
+ * Returns `null` when the function has no gate declared, or when the
26
+ * function is invoked outside the gateway (local test harness, direct
27
+ * Lambda invoke). For gated functions reached through the gateway, the
28
+ * value is non-null by construction.
29
+ *
30
+ * This is the gate-side companion to {@link getUser}, which decodes
31
+ * the JWT directly. The two layers are independent: a function with
32
+ * only `requireRole` runs the role lookup against the project's
33
+ * `members` table via gateway-side RLS-bypass; user code does not
34
+ * need to re-decode the JWT.
35
+ */
36
+ export declare function getUserId(req: Request): string | null;
37
+ /**
38
+ * Read the gate-resolved application role from the request.
39
+ *
40
+ * Returns the value of the `x-run402-user-role` request header, which
41
+ * the Run402 gateway injects when a function-level `requireRole` gate
42
+ * evaluates successfully on this dispatch. The value is the role string
43
+ * from the project-schema `members.role` (or whatever
44
+ * `(table, idColumn, roleColumn)` triple the gate declared), already
45
+ * confirmed to be in `requireRole.allowed`. Inbound `x-run402-*`
46
+ * headers are stripped, so the value is trustworthy.
47
+ *
48
+ * Returns `null` when no `requireRole` gate ran on this dispatch
49
+ * (function has only `requireAuth`, no gate at all, or is invoked
50
+ * outside the gateway).
51
+ *
52
+ * This is the application role, NOT the JWT role from
53
+ * {@link getUser}. The two are independent — see the JSDoc on
54
+ * `getUser` for the distinction.
55
+ */
56
+ export declare function getRole(req: Request): string | null;
11
57
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAmBjD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAmBjD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAErD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEnD"}
package/dist/auth.js CHANGED
@@ -3,6 +3,11 @@ import { config } from "./config.js";
3
3
  /**
4
4
  * Verify the caller's JWT and return user identity.
5
5
  * Returns { id, role, email } or null if unauthenticated/invalid.
6
+ *
7
+ * NOTE: `role` here is the JWT claim (`anon`, `authenticated`,
8
+ * `project_admin`, …) — the PostgREST/RLS system role, NOT the
9
+ * application role from a declarative `requireRole` gate. For the
10
+ * gate-resolved application role, use {@link getRole}.
6
11
  */
7
12
  export function getUser(req) {
8
13
  const authHeader = typeof req.headers.get === "function"
@@ -21,4 +26,49 @@ export function getUser(req) {
21
26
  return null;
22
27
  }
23
28
  }
29
+ /**
30
+ * Read the gate-resolved user id from the request.
31
+ *
32
+ * Returns the value of the `x-run402-user-id` request header, which the
33
+ * Run402 gateway injects when a function-level `requireAuth` or
34
+ * `requireRole` gate evaluates successfully on this dispatch. Inbound
35
+ * `x-run402-*` headers from the browser are stripped by the gateway
36
+ * before injection, so the value is trustworthy.
37
+ *
38
+ * Returns `null` when the function has no gate declared, or when the
39
+ * function is invoked outside the gateway (local test harness, direct
40
+ * Lambda invoke). For gated functions reached through the gateway, the
41
+ * value is non-null by construction.
42
+ *
43
+ * This is the gate-side companion to {@link getUser}, which decodes
44
+ * the JWT directly. The two layers are independent: a function with
45
+ * only `requireRole` runs the role lookup against the project's
46
+ * `members` table via gateway-side RLS-bypass; user code does not
47
+ * need to re-decode the JWT.
48
+ */
49
+ export function getUserId(req) {
50
+ return req.headers.get("x-run402-user-id");
51
+ }
52
+ /**
53
+ * Read the gate-resolved application role from the request.
54
+ *
55
+ * Returns the value of the `x-run402-user-role` request header, which
56
+ * the Run402 gateway injects when a function-level `requireRole` gate
57
+ * evaluates successfully on this dispatch. The value is the role string
58
+ * from the project-schema `members.role` (or whatever
59
+ * `(table, idColumn, roleColumn)` triple the gate declared), already
60
+ * confirmed to be in `requireRole.allowed`. Inbound `x-run402-*`
61
+ * headers are stripped, so the value is trustworthy.
62
+ *
63
+ * Returns `null` when no `requireRole` gate ran on this dispatch
64
+ * (function has only `requireAuth`, no gate at all, or is invoked
65
+ * outside the gateway).
66
+ *
67
+ * This is the application role, NOT the JWT role from
68
+ * {@link getUser}. The two are independent — see the JSDoc on
69
+ * `getUser` for the distinction.
70
+ */
71
+ export function getRole(req) {
72
+ return req.headers.get("x-run402-user-role");
73
+ }
24
74
  //# sourceMappingURL=auth.js.map
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,MAAM,UAAU,GACd,OAAQ,GAAG,CAAC,OAA6C,CAAC,GAAG,KAAK,UAAU;QAC1E,CAAC,CAAE,GAAG,CAAC,OAAmB,CAAC,GAAG,CAAC,eAAe,CAAC;QAC/C,CAAC,CAAE,GAAG,CAAC,OAAyD,EAAE,aAAa,CAAC;IACpF,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAKlD,CAAC;QACF,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAC1D,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,MAAM,UAAU,GACd,OAAQ,GAAG,CAAC,OAA6C,CAAC,GAAG,KAAK,UAAU;QAC1E,CAAC,CAAE,GAAG,CAAC,OAAmB,CAAC,GAAG,CAAC,eAAe,CAAC;QAC/C,CAAC,CAAE,GAAG,CAAC,OAAyD,EAAE,aAAa,CAAC;IACpF,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAKlD,CAAC;QACF,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAC1D,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAC/C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { db, adminDb, QueryBuilder } from "./db.js";
2
- export { getUser } from "./auth.js";
2
+ export { getUser, getUserId, getRole } from "./auth.js";
3
3
  export type { User } from "./auth.js";
4
4
  export { email } from "./email.js";
5
5
  export type { EmailSendOptions, EmailRawOptions, EmailTemplateOptions, EmailSendResult } from "./email.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,QAAQ,EACR,eAAe,EACf,YAAY,EAEZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,SAAS,GACV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5E,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,QAAQ,EACR,eAAe,EACf,YAAY,EAEZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,SAAS,GACV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5E,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { db, adminDb, QueryBuilder } from "./db.js";
2
- export { getUser } from "./auth.js";
2
+ export { getUser, getUserId, getRole } from "./auth.js";
3
3
  export { email } from "./email.js";
4
4
  export { ai } from "./ai.js";
5
5
  export { assets } from "./assets.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAS7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAgBrC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAS7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAgBrC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC"}
@@ -34,14 +34,23 @@ export interface RoutedHttpRequestV1 {
34
34
  /** Capability `routed-locale-context`. Canonical locale tag (byte-
35
35
  * identical to an entry in the active release's `i18n.locales[]`)
36
36
  * when the release declares an i18n slice, `null` otherwise.
37
- * OPTIONAL on the type: gateways predating `@run402/functions`
38
- * 2.3.0 omit this field entirely, and the additive-compat contract
39
- * requires the runtime to treat absence as `null`. Read as
40
- * `event.context.locale ?? null`. */
37
+ *
38
+ * **User functions read this via REQUEST HEADERS, not via `event.context`.**
39
+ * The bundled `@run402/functions` runtime translates the routed envelope
40
+ * into a Web-standard `Request` before calling user code, dropping
41
+ * `event.context`. The gateway also adds `x-run402-locale` to the
42
+ * forwarded request headers, alongside `x-run402-project-id` etc.:
43
+ * `const locale = req.headers.get('x-run402-locale');`
44
+ *
45
+ * This `context.locale` field is preserved for raw-envelope consumers
46
+ * (custom Lambda runtimes that bypass `buildEntryWrapper`). OPTIONAL on
47
+ * the type: gateways predating 2.3.0 / releases without `spec.i18n`
48
+ * omit this field entirely. Read as `event.context.locale ?? null`. */
41
49
  locale?: string | null;
42
50
  /** Echoed verbatim from the active release's `i18n.defaultLocale`,
43
- * `null` when the release has no i18n slice. Same additive-compat
44
- * rules as `locale`. Read as `event.context.defaultLocale ?? null`. */
51
+ * `null` when the release has no i18n slice. Same access rules as
52
+ * `locale`: user functions read `req.headers.get('x-run402-default-locale')`;
53
+ * raw-envelope consumers read `event.context.defaultLocale ?? null`. */
45
54
  defaultLocale?: string | null;
46
55
  };
47
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"routed-http.d.ts","sourceRoot":"","sources":["../src/routed-http.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAChC,IAAI,EAAE,IAAI,GAAG;QACX,QAAQ,EAAE,QAAQ,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,OAAO,EAAE;QACP,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;QAC9B,WAAW,EAAE;YAAE,IAAI,EAAE,UAAU,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAChD,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB;;;;;;8CAMsC;QACtC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB;;gFAEwE;QACxE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG;QACZ,QAAQ,EAAE,QAAQ,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,sBAA2B,GAChC,oBAAoB,CAMtB;AAED,wBAAgB,IAAI,CAClB,KAAK,EAAE,OAAO,EACd,IAAI,GAAE,sBAA2B,GAChC,oBAAoB,CAMtB;AAED,wBAAgB,KAAK,CACnB,KAAK,EAAE,UAAU,EACjB,IAAI,GAAE,sBAA2B,GAChC,oBAAoB,CAEtB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAEtE;AAED,eAAO,MAAM,UAAU;;;;;CAKtB,CAAC"}
1
+ {"version":3,"file":"routed-http.d.ts","sourceRoot":"","sources":["../src/routed-http.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAChC,IAAI,EAAE,IAAI,GAAG;QACX,QAAQ,EAAE,QAAQ,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,OAAO,EAAE;QACP,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;QAC9B,WAAW,EAAE;YAAE,IAAI,EAAE,UAAU,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAChD,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB;;;;;;;;;;;;;;gFAcwE;QACxE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB;;;iFAGyE;QACzE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG;QACZ,QAAQ,EAAE,QAAQ,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,sBAA2B,GAChC,oBAAoB,CAMtB;AAED,wBAAgB,IAAI,CAClB,KAAK,EAAE,OAAO,EACd,IAAI,GAAE,sBAA2B,GAChC,oBAAoB,CAMtB;AAED,wBAAgB,KAAK,CACnB,KAAK,EAAE,UAAU,EACjB,IAAI,GAAE,sBAA2B,GAChC,oBAAoB,CAEtB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAEtE;AAED,eAAO,MAAM,UAAU;;;;;CAKtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"routed-http.js","sourceRoot":"","sources":["../src/routed-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA+DrC,MAAM,UAAU,IAAI,CAClB,IAAY,EACZ,OAA+B,EAAE;IAEjC,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;QACzC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,sBAAsB,CAAC,IAAI,CAAC,OAAO,EAAE,2BAA2B,CAAC;QAC1E,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,IAAI,CAClB,KAAc,EACd,OAA+B,EAAE;IAEjC,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,EAAE;QAC1D,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,sBAAsB,CAAC,IAAI,CAAC,OAAO,EAAE,iCAAiC,CAAC;QAChF,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,KAAK,CACnB,KAAiB,EACjB,OAA+B,EAAE;IAEjC,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,uBAAuB,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,SAAS;CACV,CAAC;AAEF,SAAS,QAAQ,CACf,KAAiB,EACjB,IAA4B;IAE5B,MAAM,SAAS,GAAG,KAAK,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAyB;QACrC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG;QAC1B,IAAI,EAAE;YACJ,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAClC,IAAI,EAAE,SAAS,CAAC,UAAU;SAC3B;KACF,CAAC;IACF,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAChE,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAChE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,sBAAsB,CAC7B,OAAyC,EACzC,WAAmB;IAEnB,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,MAAM,cAAc,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,CAAC;IACnF,IAAI,CAAC,cAAc;QAAE,GAAG,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"routed-http.js","sourceRoot":"","sources":["../src/routed-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAwErC,MAAM,UAAU,IAAI,CAClB,IAAY,EACZ,OAA+B,EAAE;IAEjC,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;QACzC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,sBAAsB,CAAC,IAAI,CAAC,OAAO,EAAE,2BAA2B,CAAC;QAC1E,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,IAAI,CAClB,KAAc,EACd,OAA+B,EAAE;IAEjC,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,EAAE;QAC1D,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,sBAAsB,CAAC,IAAI,CAAC,OAAO,EAAE,iCAAiC,CAAC;QAChF,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,KAAK,CACnB,KAAiB,EACjB,OAA+B,EAAE;IAEjC,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,uBAAuB,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,SAAS;CACV,CAAC;AAEF,SAAS,QAAQ,CACf,KAAiB,EACjB,IAA4B;IAE5B,MAAM,SAAS,GAAG,KAAK,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAyB;QACrC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG;QAC1B,IAAI,EAAE;YACJ,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAClC,IAAI,EAAE,SAAS,CAAC,UAAU;SAC3B;KACF,CAAC;IACF,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAChE,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAChE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,sBAAsB,CAC7B,OAAyC,EACzC,WAAmB;IAEnB,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,MAAM,cAAc,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,CAAC;IACnF,IAAI,CAAC,cAAc;QAAE,GAAG,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC;AACrD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@run402/functions",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "In-function helper library for Run402 serverless functions — db, adminDb, getUser, email, ai, assets. Auto-bundled into deployed functions; also installable for local TypeScript autocomplete.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",