@timber-js/app 0.2.0-alpha.204 → 0.2.0-alpha.205

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/_chunks/{actions-HUdJADAD.js → actions-jNrXdzgs.js} +4 -3
  2. package/dist/_chunks/{actions-HUdJADAD.js.map → actions-jNrXdzgs.js.map} +1 -1
  3. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
  4. package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
  5. package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
  6. package/dist/_chunks/{cache-api-CAPbZTga.js → cache-api-CUD9Ezaq.js} +3 -2
  7. package/dist/_chunks/{cache-api-CAPbZTga.js.map → cache-api-CUD9Ezaq.js.map} +1 -1
  8. package/dist/_chunks/{chains-C9Y7Oyno.js → chains-BjGKq9Je.js} +2 -2
  9. package/dist/_chunks/{chains-C9Y7Oyno.js.map → chains-BjGKq9Je.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-BzMGuIH6.js → cli-check-3WE_GyoL.js} +3 -3
  11. package/dist/_chunks/{cli-check-BzMGuIH6.js.map → cli-check-3WE_GyoL.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-DnXqcIIj.js → cli-schema-sync-D3iIZjku.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-DnXqcIIj.js.map → cli-schema-sync-D3iIZjku.js.map} +1 -1
  14. package/dist/_chunks/{cloudflare-DxX1SU0g.js → cloudflare-BFb__LYG.js} +3 -3
  15. package/dist/_chunks/{cloudflare-DxX1SU0g.js.map → cloudflare-BFb__LYG.js.map} +1 -1
  16. package/dist/_chunks/{convention-lint-DHOFvX5s.js → convention-lint-BnO5TyHD.js} +2 -2
  17. package/dist/_chunks/{convention-lint-DHOFvX5s.js.map → convention-lint-BnO5TyHD.js.map} +1 -1
  18. package/dist/_chunks/{dev-server-BV01iyUs.js → dev-server-Dbdgx2dD.js} +8 -195
  19. package/dist/_chunks/dev-server-Dbdgx2dD.js.map +1 -0
  20. package/dist/_chunks/{error-boundary-BQKxl6EX.js → error-boundary-BndF-3Td.js} +2 -2
  21. package/dist/_chunks/{error-boundary-BQKxl6EX.js.map → error-boundary-BndF-3Td.js.map} +1 -1
  22. package/dist/_chunks/{graph-cache-Cv3njEH8.js → graph-cache-CP4GEmf9.js} +2 -2
  23. package/dist/_chunks/{graph-cache-Cv3njEH8.js.map → graph-cache-CP4GEmf9.js.map} +1 -1
  24. package/dist/_chunks/{live-graph-DCLQzV6m.js → live-graph-BM2UbbNx.js} +3 -3
  25. package/dist/_chunks/{live-graph-DCLQzV6m.js.map → live-graph-BM2UbbNx.js.map} +1 -1
  26. package/dist/_chunks/logger-k2DQ4EUf.js +506 -0
  27. package/dist/_chunks/logger-k2DQ4EUf.js.map +1 -0
  28. package/dist/_chunks/{poison-scan-CX_NaNDi.js → poison-scan-g83aAXhs.js} +2 -2
  29. package/dist/_chunks/{poison-scan-CX_NaNDi.js.map → poison-scan-g83aAXhs.js.map} +1 -1
  30. package/dist/_chunks/{logger-DqJ2VoAY.js → primitives-DLAnvsrk.js} +17 -518
  31. package/dist/_chunks/primitives-DLAnvsrk.js.map +1 -0
  32. package/dist/_chunks/{scanner-B_tnqFcF.js → scanner-BhNruzv0.js} +3 -3
  33. package/dist/_chunks/{scanner-B_tnqFcF.js.map → scanner-BhNruzv0.js.map} +1 -1
  34. package/dist/_chunks/{walkers-Cm3PC5JT.js → walkers-BNsswm-h.js} +2 -2
  35. package/dist/_chunks/{walkers-Cm3PC5JT.js.map → walkers-BNsswm-h.js.map} +1 -1
  36. package/dist/adapters/cloudflare-dev.js +1 -1
  37. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  38. package/dist/adapters/cloudflare.js +1 -1
  39. package/dist/analyze/crawl-entry.js +2 -2
  40. package/dist/analyze/graph-command.js +3 -3
  41. package/dist/cache/index.js +1 -1
  42. package/dist/cli.js +3 -3
  43. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
  44. package/dist/client/error-boundary.js +1 -1
  45. package/dist/client/internal.js +1 -1
  46. package/dist/dev-tools/index.d.ts +1 -1
  47. package/dist/dev-tools/index.d.ts.map +1 -1
  48. package/dist/dev-tools/overlay.d.ts +1 -21
  49. package/dist/dev-tools/overlay.d.ts.map +1 -1
  50. package/dist/index.js +6 -7
  51. package/dist/index.js.map +1 -1
  52. package/dist/plugins/dev-server.d.ts.map +1 -1
  53. package/dist/routing/index.js +2 -2
  54. package/dist/server/als-registry.d.ts +0 -7
  55. package/dist/server/als-registry.d.ts.map +1 -1
  56. package/dist/server/deny-renderer.d.ts +2 -6
  57. package/dist/server/deny-renderer.d.ts.map +1 -1
  58. package/dist/server/index.js +3 -2
  59. package/dist/server/index.js.map +1 -1
  60. package/dist/server/internal.js +180 -7
  61. package/dist/server/internal.js.map +1 -1
  62. package/dist/server/pipeline.d.ts +1 -13
  63. package/dist/server/pipeline.d.ts.map +1 -1
  64. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
  65. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  66. package/dist/server/rsc-entry/helpers.d.ts +0 -38
  67. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  68. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  69. package/dist/server/rsc-entry/render-route.d.ts +0 -1
  70. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  71. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  72. package/dist/server/rsc-entry/rsc-stream.d.ts +1 -7
  73. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  74. package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -1
  75. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  76. package/dist/server/slot-resolver.d.ts.map +1 -1
  77. package/dist/server/slot-subtree-contain.d.ts +1 -1
  78. package/dist/server/slot-subtree-contain.d.ts.map +1 -1
  79. package/package.json +1 -1
  80. package/src/client/browser-entry/rsc-stream.ts +1 -82
  81. package/src/dev-tools/index.ts +0 -2
  82. package/src/dev-tools/overlay.ts +1 -65
  83. package/src/plugins/dev-server.ts +4 -45
  84. package/src/server/als-registry.ts +0 -8
  85. package/src/server/deny-renderer.ts +0 -7
  86. package/src/server/pipeline.ts +1 -14
  87. package/src/server/rsc-entry/deny-fallback.ts +1 -10
  88. package/src/server/rsc-entry/error-renderer.ts +1 -11
  89. package/src/server/rsc-entry/helpers.ts +0 -92
  90. package/src/server/rsc-entry/index.ts +1 -14
  91. package/src/server/rsc-entry/render-route.ts +2 -23
  92. package/src/server/rsc-entry/rsc-payload.ts +1 -2
  93. package/src/server/rsc-entry/rsc-stream.ts +3 -29
  94. package/src/server/rsc-entry/ssr-renderer.ts +2 -15
  95. package/src/server/slot-resolver.ts +7 -29
  96. package/src/server/slot-subtree-contain.ts +22 -14
  97. package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
  98. package/dist/_chunks/csp-nonce-hOGniaG4.js +0 -227
  99. package/dist/_chunks/csp-nonce-hOGniaG4.js.map +0 -1
  100. package/dist/_chunks/dev-server-BV01iyUs.js.map +0 -1
  101. package/dist/_chunks/logger-DqJ2VoAY.js.map +0 -1
  102. package/dist/dev-tools/debug-channel.d.ts +0 -55
  103. package/dist/dev-tools/debug-channel.d.ts.map +0 -1
  104. package/src/dev-tools/debug-channel.ts +0 -151
@@ -1,227 +0,0 @@
1
- import { r as requestContextAls } from "./als-registry-C6kcfprT.js";
2
- import { R as getSetCookieHeaders, m as setLogger, q as getTraceId } from "./logger-DqJ2VoAY.js";
3
- import { r as isRscRequest } from "./rsc-media-type-DDc7duTD.js";
4
- //#region src/server/instrumentation.ts
5
- /**
6
- * Instrumentation — loads and runs the user's instrumentation.ts file.
7
- *
8
- * instrumentation.ts is a file convention at the project root that exports:
9
- * - register() — called once at server startup, before the first request
10
- * - onRequestError() — called for every unhandled server error
11
- * - logger — any object with info/warn/error/debug methods
12
- *
13
- * See design/17-logging.md §"instrumentation.ts — The Entry Point"
14
- */
15
- var _initialized = false;
16
- var _onRequestError = null;
17
- /**
18
- * Load and initialize the user's instrumentation.ts module.
19
- *
20
- * - Awaits register() before returning (server blocks on this).
21
- * - Picks up the logger export and wires it into the framework logger.
22
- * - Stores onRequestError for later invocation.
23
- *
24
- * @param loader - Function that dynamically imports the user's instrumentation module.
25
- * Returns null if no instrumentation.ts exists.
26
- */
27
- async function loadInstrumentation(loader) {
28
- if (_initialized) return;
29
- _initialized = true;
30
- let mod;
31
- try {
32
- mod = await loader();
33
- } catch (error) {
34
- console.error("[timber] Failed to load instrumentation.ts:", error);
35
- return;
36
- }
37
- if (!mod) return;
38
- if (mod.logger && typeof mod.logger.info === "function") setLogger(mod.logger);
39
- if (typeof mod.onRequestError === "function") _onRequestError = mod.onRequestError;
40
- if (typeof mod.register === "function") try {
41
- await mod.register();
42
- } catch (error) {
43
- console.error("[timber] instrumentation.ts register() threw:", error);
44
- throw error;
45
- }
46
- }
47
- /**
48
- * Call the user's onRequestError hook. Catches and logs any errors thrown
49
- * by the hook itself — it must not affect the response.
50
- */
51
- async function callOnRequestError(error, request, context) {
52
- if (!_onRequestError) return;
53
- try {
54
- await _onRequestError(error, request, context);
55
- } catch (hookError) {
56
- console.error("[timber] onRequestError hook threw:", hookError);
57
- }
58
- }
59
- /**
60
- * Check if onRequestError is registered.
61
- */
62
- function hasOnRequestError() {
63
- return _onRequestError !== null;
64
- }
65
- //#endregion
66
- //#region src/server/pipeline-helpers.ts
67
- /**
68
- * Build a proxy resolver closure from the declared source. Called exactly
69
- * once at `createPipeline` setup time, so the hot path sees only the branch
70
- * that corresponds to this pipeline's configured variant.
71
- *
72
- * Returns `null` when the app has no proxy.ts — the hot path short-circuits
73
- * around `runProxyPhase` entirely in that case.
74
- *
75
- * Accepts the sugar form (a bare `ProxyExport` — function or function array)
76
- * and normalises it to the static variant. Functions and arrays are
77
- * structurally distinct from the tagged `{ kind: 'lazy', loader }` object,
78
- * so discrimination is unambiguous.
79
- */
80
- function makeProxyResolver(proxy) {
81
- if (proxy === void 0) return null;
82
- if (typeof proxy === "function" || Array.isArray(proxy)) {
83
- const exp = proxy;
84
- return () => exp;
85
- }
86
- if (proxy.kind === "static") {
87
- const exp = proxy.export;
88
- return () => exp;
89
- }
90
- const loader = proxy.loader;
91
- return async () => (await loader()).default;
92
- }
93
- /**
94
- * Apply all Set-Cookie headers from the cookie jar to a Headers object.
95
- * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.
96
- */
97
- function applyCookieJar(headers) {
98
- for (const value of getSetCookieHeaders()) headers.append("Set-Cookie", value);
99
- }
100
- /**
101
- * Merge framework-managed response headers onto a terminal response without
102
- * overwriting headers the terminal response already set itself.
103
- */
104
- function mergeMissingHeaders(target, source) {
105
- const existingKeys = new Set([...target.keys()].map((key) => key.toLowerCase()));
106
- for (const [key, value] of source.entries()) if (!existingKeys.has(key.toLowerCase())) target.append(key, value);
107
- }
108
- /**
109
- * Clone a Response into a fresh one whose header bag is guaranteed mutable.
110
- *
111
- * `Response.redirect()` and some platform-level passthrough responses (notably
112
- * on Cloudflare Workers) return objects with frozen header bags. Calling
113
- * `.set()` or `.append()` on them throws `TypeError: immutable`, which the
114
- * pipeline can hit when it appends Set-Cookie or Server-Timing entries.
115
- *
116
- * The pipeline calls this at the producer sites where user-controlled
117
- * responses enter the framework — `outcomeToResponse` for all phase outcomes,
118
- * and `handleRequest` for metadata-route and auto-sitemap user handlers — so
119
- * downstream code can write headers without runtime feature-detection.
120
- *
121
- * The clone is unconditional. This is a deliberate trade: we avoid a
122
- * try/catch + thrown `TypeError` on every request (the previous probe-based
123
- * approach paid that cost on the hot path) and accept one cheap Response
124
- * rewrap at the framework boundary instead.
125
- */
126
- function cloneWithMutableHeaders(response) {
127
- return new Response(response.body, {
128
- status: response.status,
129
- statusText: response.statusText,
130
- headers: new Headers(response.headers)
131
- });
132
- }
133
- /**
134
- * Build a redirect Response from a RedirectSignal.
135
- *
136
- * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect
137
- * so the client router can perform a soft SPA redirect. A raw 302 would be
138
- * turned into an opaque redirect by fetch({redirect:'manual'}), crashing
139
- * createFromFetch. See design/19-client-navigation.md.
140
- */
141
- function buildRedirectResponse(signal, req, headers) {
142
- if (isRscRequest(req)) {
143
- headers.set("X-Timber-Redirect", signal.location);
144
- return new Response(null, {
145
- status: 204,
146
- headers
147
- });
148
- }
149
- headers.set("Location", signal.location);
150
- return new Response(null, {
151
- status: signal.status,
152
- headers
153
- });
154
- }
155
- /**
156
- * Fire the user's onRequestError hook with request context.
157
- * Extracts request info from the Request object and calls the instrumentation hook.
158
- */
159
- async function fireOnRequestError(error, req, phase) {
160
- const url = new URL(req.url);
161
- const headersObj = {};
162
- req.headers.forEach((v, k) => {
163
- headersObj[k] = v;
164
- });
165
- await callOnRequestError(error, {
166
- method: req.method,
167
- path: url.pathname,
168
- headers: headersObj
169
- }, {
170
- phase,
171
- routePath: url.pathname,
172
- routeType: "page",
173
- traceId: getTraceId()
174
- });
175
- }
176
- //#endregion
177
- //#region src/server/csp-nonce.ts
178
- /**
179
- * CSP nonce — per-request, generated on demand.
180
- *
181
- * timber emits several inline `<script>` tags per HTML response — React's
182
- * bootstrap script and Fizz runtime, the Flight init/chunk scripts, the
183
- * segment-tree script, and the post-flush noindex script. Under a strict
184
- * `Content-Security-Policy` (`script-src 'nonce-…' 'strict-dynamic'`) every
185
- * one of them needs the request's nonce or the browser refuses to run it.
186
- *
187
- * The app opts in by reading `ctx.nonce` in `middleware.ts` and putting it
188
- * in the CSP header it sets through `ctx.headers`. The first read generates
189
- * the value and stores it on the request context; the render pipeline reads
190
- * it back with `getCspNonce()` and stamps it on every inline script. A
191
- * request whose middleware never reads `ctx.nonce` has no nonce, and its
192
- * markup is byte-identical to a render without this feature. timber does
193
- * not parse the CSP header — the app owns the policy, timber owns the
194
- * markup, and `ctx.nonce` is the one value both are built from.
195
- *
196
- * `proxy.ts` cannot participate: it only sees the finished response from
197
- * `next()`, after the markup exists.
198
- *
199
- * See design/13-security.md §"Content Security Policy" (TIM-1414).
200
- */
201
- /**
202
- * Generate a CSP nonce: 128 random bits, base64. Matches CSP's
203
- * `base64-value` grammar, so it is valid in the header verbatim and safe
204
- * inside a double-quoted HTML attribute without escaping.
205
- */
206
- function generateNonce() {
207
- const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
208
- let binary = "";
209
- for (const b of bytes) binary += String.fromCharCode(b);
210
- return btoa(binary);
211
- }
212
- /**
213
- * The request's CSP nonce, creating it on first call. Backs the
214
- * `ctx.nonce` getter on `MiddlewareContext`. Outside a request context
215
- * (tests, build-time rendering) a fresh value is returned but not
216
- * retained, since there is no request for it to belong to.
217
- */
218
- function requestCspNonce() {
219
- const store = requestContextAls.getStore();
220
- if (!store) return generateNonce();
221
- store.cspNonce ??= generateNonce();
222
- return store.cspNonce;
223
- }
224
- //#endregion
225
- export { fireOnRequestError as a, callOnRequestError as c, cloneWithMutableHeaders as i, hasOnRequestError as l, applyCookieJar as n, makeProxyResolver as o, buildRedirectResponse as r, mergeMissingHeaders as s, requestCspNonce as t, loadInstrumentation as u };
226
-
227
- //# sourceMappingURL=csp-nonce-hOGniaG4.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"csp-nonce-hOGniaG4.js","names":[],"sources":["../../src/server/instrumentation.ts","../../src/server/pipeline-helpers.ts","../../src/server/csp-nonce.ts"],"sourcesContent":["/**\n * Instrumentation — loads and runs the user's instrumentation.ts file.\n *\n * instrumentation.ts is a file convention at the project root that exports:\n * - register() — called once at server startup, before the first request\n * - onRequestError() — called for every unhandled server error\n * - logger — any object with info/warn/error/debug methods\n *\n * See design/17-logging.md §\"instrumentation.ts — The Entry Point\"\n */\n\nimport { setLogger, type TimberLogger } from './logger.ts';\n\n// ─── Instrumentation Types ────────────────────────────────────────────────\n\nexport type InstrumentationOnRequestError = (\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n) => void | Promise<void>;\n\nexport interface InstrumentationRequestInfo {\n /** HTTP method: 'GET', 'POST', etc. */\n method: string;\n /** Request path: '/dashboard/projects/123' */\n path: string;\n /** Request headers as a plain object. */\n headers: Record<string, string>;\n}\n\nexport interface InstrumentationErrorContext {\n /** Which pipeline phase the error occurred in. */\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route';\n /** The route pattern: '/dashboard/projects/[id]' */\n routePath: string;\n /** Type of route that was matched. */\n routeType: 'page' | 'route' | 'action';\n /** Always set — OTEL trace ID or UUID fallback. */\n traceId: string;\n}\n\n// ─── Instrumentation Module Shape ─────────────────────────────────────────\n\ninterface InstrumentationModule {\n register?: () => void | Promise<void>;\n onRequestError?: InstrumentationOnRequestError;\n logger?: TimberLogger;\n}\n\n// ─── State ────────────────────────────────────────────────────────────────\n//\n// Intentional per-app singletons (not per-request). Instrumentation loads\n// once at server startup and persists for the lifetime of the process/isolate.\n// These must NOT be migrated to ALS — they are correctly scoped to the app.\n\nlet _initialized = false;\nlet _onRequestError: InstrumentationOnRequestError | null = null;\n\n/**\n * Load and initialize the user's instrumentation.ts module.\n *\n * - Awaits register() before returning (server blocks on this).\n * - Picks up the logger export and wires it into the framework logger.\n * - Stores onRequestError for later invocation.\n *\n * @param loader - Function that dynamically imports the user's instrumentation module.\n * Returns null if no instrumentation.ts exists.\n */\nexport async function loadInstrumentation(\n loader: () => Promise<InstrumentationModule | null>\n): Promise<void> {\n if (_initialized) return;\n _initialized = true;\n\n let mod: InstrumentationModule | null;\n try {\n mod = await loader();\n } catch (error) {\n console.error('[timber] Failed to load instrumentation.ts:', error);\n return;\n }\n\n if (!mod) return;\n\n // Wire up the logger export\n if (mod.logger && typeof mod.logger.info === 'function') {\n setLogger(mod.logger);\n }\n\n // Store onRequestError for later\n if (typeof mod.onRequestError === 'function') {\n _onRequestError = mod.onRequestError;\n }\n\n // Await register() — server does not accept requests until this resolves\n if (typeof mod.register === 'function') {\n try {\n await mod.register();\n } catch (error) {\n console.error('[timber] instrumentation.ts register() threw:', error);\n throw error;\n }\n }\n}\n\n/**\n * Call the user's onRequestError hook. Catches and logs any errors thrown\n * by the hook itself — it must not affect the response.\n */\nexport async function callOnRequestError(\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n): Promise<void> {\n if (!_onRequestError) return;\n try {\n await _onRequestError(error, request, context);\n } catch (hookError) {\n console.error('[timber] onRequestError hook threw:', hookError);\n }\n}\n\n/**\n * Check if onRequestError is registered.\n */\nexport function hasOnRequestError(): boolean {\n return _onRequestError !== null;\n}\n\n/**\n * Reset instrumentation state. Test-only.\n */\nexport function resetInstrumentation(): void {\n _initialized = false;\n _onRequestError = null;\n}\n","/**\n * Pipeline helpers — small utility functions used by `pipeline.ts` and\n * `pipeline-phases.ts`. Lifted out of `pipeline.ts` to keep that file\n * focused on the request handler entry point.\n *\n * Each helper is intentionally a free function with no closure capture, so\n * it can be unit-tested in isolation.\n *\n * See design/07-routing.md §\"Request Lifecycle\".\n */\n\nimport type { ProxyExport } from './proxy.ts';\nimport { getSetCookieHeaders } from './cookie-context.ts';\nimport { callOnRequestError } from './instrumentation.ts';\nimport { getTraceId } from './tracing.ts';\nimport { RedirectSignal } from './primitives.ts';\nimport type { ProxyConfig } from './pipeline.ts';\nimport { isRscRequest } from '../shared/rsc-media-type.ts';\n\n// ─── Prototype-Pollution-Safe Sanitizer ────────────────────────────────────\n\n// ─── Proxy Resolver ────────────────────────────────────────────────────────\n\n/**\n * Resolver closure produced once at pipeline construction. The lazy variant\n * still calls `loader()` per-request (HMR relies on re-importing), but the\n * choice of which branch to take is made once, not on every request.\n */\nexport type ProxyResolver = () => ProxyExport | Promise<ProxyExport>;\n\n/**\n * Build a proxy resolver closure from the declared source. Called exactly\n * once at `createPipeline` setup time, so the hot path sees only the branch\n * that corresponds to this pipeline's configured variant.\n *\n * Returns `null` when the app has no proxy.ts — the hot path short-circuits\n * around `runProxyPhase` entirely in that case.\n *\n * Accepts the sugar form (a bare `ProxyExport` — function or function array)\n * and normalises it to the static variant. Functions and arrays are\n * structurally distinct from the tagged `{ kind: 'lazy', loader }` object,\n * so discrimination is unambiguous.\n */\nexport function makeProxyResolver(\n proxy: ProxyConfig | ProxyExport | undefined\n): ProxyResolver | null {\n if (proxy === undefined) return null;\n // Sugar: a bare ProxyExport (function or function array) — treat as static.\n if (typeof proxy === 'function' || Array.isArray(proxy)) {\n const exp = proxy;\n return () => exp;\n }\n if (proxy.kind === 'static') {\n const exp = proxy.export;\n return () => exp;\n }\n const loader = proxy.loader;\n return async () => (await loader()).default;\n}\n\n// ─── Cookie / Header Helpers ───────────────────────────────────────────────\n\n/**\n * Apply all Set-Cookie headers from the cookie jar to a Headers object.\n * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.\n */\nexport function applyCookieJar(headers: Headers): void {\n for (const value of getSetCookieHeaders()) {\n headers.append('Set-Cookie', value);\n }\n}\n\n/**\n * Merge framework-managed response headers onto a terminal response without\n * overwriting headers the terminal response already set itself.\n */\nexport function mergeMissingHeaders(target: Headers, source: Headers): void {\n const existingKeys = new Set([...target.keys()].map((key) => key.toLowerCase()));\n for (const [key, value] of source.entries()) {\n if (!existingKeys.has(key.toLowerCase())) {\n target.append(key, value);\n }\n }\n}\n\n// ─── Mutable Response Cloning ──────────────────────────────────────────────\n\n/**\n * Clone a Response into a fresh one whose header bag is guaranteed mutable.\n *\n * `Response.redirect()` and some platform-level passthrough responses (notably\n * on Cloudflare Workers) return objects with frozen header bags. Calling\n * `.set()` or `.append()` on them throws `TypeError: immutable`, which the\n * pipeline can hit when it appends Set-Cookie or Server-Timing entries.\n *\n * The pipeline calls this at the producer sites where user-controlled\n * responses enter the framework — `outcomeToResponse` for all phase outcomes,\n * and `handleRequest` for metadata-route and auto-sitemap user handlers — so\n * downstream code can write headers without runtime feature-detection.\n *\n * The clone is unconditional. This is a deliberate trade: we avoid a\n * try/catch + thrown `TypeError` on every request (the previous probe-based\n * approach paid that cost on the hot path) and accept one cheap Response\n * rewrap at the framework boundary instead.\n */\nexport function cloneWithMutableHeaders(response: Response): Response {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n}\n\n// ─── Redirect Builder ──────────────────────────────────────────────────────\n\n/**\n * Build a redirect Response from a RedirectSignal.\n *\n * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect\n * so the client router can perform a soft SPA redirect. A raw 302 would be\n * turned into an opaque redirect by fetch({redirect:'manual'}), crashing\n * createFromFetch. See design/19-client-navigation.md.\n */\nexport function buildRedirectResponse(\n signal: RedirectSignal,\n req: Request,\n headers: Headers\n): Response {\n if (isRscRequest(req)) {\n headers.set('X-Timber-Redirect', signal.location);\n return new Response(null, { status: 204, headers });\n }\n headers.set('Location', signal.location);\n return new Response(null, { status: signal.status, headers });\n}\n\n// ─── Instrumentation ───────────────────────────────────────────────────────\n\n/**\n * Fire the user's onRequestError hook with request context.\n * Extracts request info from the Request object and calls the instrumentation hook.\n */\nexport async function fireOnRequestError(\n error: unknown,\n req: Request,\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route'\n): Promise<void> {\n const url = new URL(req.url);\n const headersObj: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headersObj[k] = v;\n });\n\n await callOnRequestError(\n error,\n { method: req.method, path: url.pathname, headers: headersObj },\n { phase, routePath: url.pathname, routeType: 'page', traceId: getTraceId() }\n );\n}\n","/**\n * CSP nonce — per-request, generated on demand.\n *\n * timber emits several inline `<script>` tags per HTML response — React's\n * bootstrap script and Fizz runtime, the Flight init/chunk scripts, the\n * segment-tree script, and the post-flush noindex script. Under a strict\n * `Content-Security-Policy` (`script-src 'nonce-…' 'strict-dynamic'`) every\n * one of them needs the request's nonce or the browser refuses to run it.\n *\n * The app opts in by reading `ctx.nonce` in `middleware.ts` and putting it\n * in the CSP header it sets through `ctx.headers`. The first read generates\n * the value and stores it on the request context; the render pipeline reads\n * it back with `getCspNonce()` and stamps it on every inline script. A\n * request whose middleware never reads `ctx.nonce` has no nonce, and its\n * markup is byte-identical to a render without this feature. timber does\n * not parse the CSP header — the app owns the policy, timber owns the\n * markup, and `ctx.nonce` is the one value both are built from.\n *\n * `proxy.ts` cannot participate: it only sees the finished response from\n * `next()`, after the markup exists.\n *\n * See design/13-security.md §\"Content Security Policy\" (TIM-1414).\n */\n\nimport { requestContextAls } from './als-registry.ts';\n\n/**\n * Generate a CSP nonce: 128 random bits, base64. Matches CSP's\n * `base64-value` grammar, so it is valid in the header verbatim and safe\n * inside a double-quoted HTML attribute without escaping.\n */\nfunction generateNonce(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n let binary = '';\n for (const b of bytes) binary += String.fromCharCode(b);\n return btoa(binary);\n}\n\n/**\n * The request's CSP nonce, creating it on first call. Backs the\n * `ctx.nonce` getter on `MiddlewareContext`. Outside a request context\n * (tests, build-time rendering) a fresh value is returned but not\n * retained, since there is no request for it to belong to.\n */\nexport function requestCspNonce(): string {\n const store = requestContextAls.getStore();\n if (!store) return generateNonce();\n store.cspNonce ??= generateNonce();\n return store.cspNonce;\n}\n\n/**\n * The request's CSP nonce if middleware asked for one, else `undefined` —\n * in which case the pipeline emits bare `<script>` tags.\n */\nexport function getCspNonce(): string | undefined {\n return requestContextAls.getStore()?.cspNonce;\n}\n\n/**\n * The ` nonce=\"…\"` attribute fragment for a timber-generated `<script>` tag,\n * or `''` when the request has no nonce. Callers interpolate it directly\n * after the tag name: `` `<script${nonceAttr(nonce)}>` ``.\n *\n * The value comes from `generateNonce` and is base64, so no attribute\n * escaping is required.\n */\nexport function nonceAttr(nonce: string | undefined): string {\n return nonce ? ` nonce=\"${nonce}\"` : '';\n}\n"],"mappings":"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"}