@timber-js/app 0.2.0-alpha.197 → 0.2.0-alpha.199
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/dist/_chunks/{actions-BS-m5SLv.js → actions-d1hCqnU3.js} +35 -8
- package/dist/_chunks/actions-d1hCqnU3.js.map +1 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{cache-api-DqzgTEqk.js → cache-api-ByagcC-J.js} +2 -2
- package/dist/_chunks/{cache-api-DqzgTEqk.js.map → cache-api-ByagcC-J.js.map} +1 -1
- package/dist/_chunks/canonicalize-CgHoscYO.js +66 -0
- package/dist/_chunks/canonicalize-CgHoscYO.js.map +1 -0
- package/dist/_chunks/{chains-CZG7E5zg.js → chains-Bpb0W4ax.js} +3 -3
- package/dist/_chunks/{chains-CZG7E5zg.js.map → chains-Bpb0W4ax.js.map} +1 -1
- package/dist/_chunks/{cli-check-bE3H5Bjr.js → cli-check-D6VolrDV.js} +3 -3
- package/dist/_chunks/{cli-check-bE3H5Bjr.js.map → cli-check-D6VolrDV.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js → cli-schema-sync-D6rO-VcS.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js.map → cli-schema-sync-D6rO-VcS.js.map} +1 -1
- package/dist/_chunks/{convention-lint-n3RJLgww.js → convention-lint-fRkwVwEH.js} +27 -4
- package/dist/_chunks/convention-lint-fRkwVwEH.js.map +1 -0
- package/dist/_chunks/error-boundary-BfPHZjm0.js +1050 -0
- package/dist/_chunks/error-boundary-BfPHZjm0.js.map +1 -0
- package/dist/_chunks/{live-graph-BXDsdzBv.js → live-graph-D_2D32Ad.js} +3 -3
- package/dist/_chunks/{live-graph-BXDsdzBv.js.map → live-graph-D_2D32Ad.js.map} +1 -1
- package/dist/_chunks/{logger-DDirEsn7.js → logger-uLBuGKDI.js} +471 -440
- package/dist/_chunks/logger-uLBuGKDI.js.map +1 -0
- package/dist/_chunks/{navigation-root-B00jjGd5.js → navigation-context-D0TU0Jog.js} +3 -101
- package/dist/_chunks/navigation-context-D0TU0Jog.js.map +1 -0
- package/dist/_chunks/navigation-root-mHSK9psY.js +126 -0
- package/dist/_chunks/{navigation-root-B00jjGd5.js.map → navigation-root-mHSK9psY.js.map} +1 -1
- package/dist/_chunks/{poison-scan-BoDLgbix.js → poison-scan-Bm9Yyqk9.js} +2 -2
- package/dist/_chunks/{poison-scan-BoDLgbix.js.map → poison-scan-Bm9Yyqk9.js.map} +1 -1
- package/dist/_chunks/{scanner-tdFPvDYi.js → scanner-AiazgH_f.js} +6 -5
- package/dist/_chunks/scanner-AiazgH_f.js.map +1 -0
- package/dist/_chunks/{segment-keys-BhqoHiLc.js → segment-keys-lqtdookO.js} +2 -65
- package/dist/_chunks/segment-keys-lqtdookO.js.map +1 -0
- package/dist/_chunks/{ssr-data-BQGhTPAK.js → ssr-data-D6T6Y3ef.js} +4 -26
- package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +1 -0
- package/dist/_chunks/state-FippDgxN.js +52 -0
- package/dist/_chunks/state-FippDgxN.js.map +1 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js +496 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +1 -0
- package/dist/_chunks/{walkers-DNX05dC0.js → walkers-B6XUtmqK.js} +2 -2
- package/dist/_chunks/{walkers-DNX05dC0.js.map → walkers-B6XUtmqK.js.map} +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts +6 -4
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/action-queue.d.ts +44 -0
- package/dist/client/browser-entry/action-queue.d.ts.map +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/deny-last-resort.d.ts +29 -0
- package/dist/client/deny-last-resort.d.ts.map +1 -0
- package/dist/client/error-boundary.d.ts +48 -2
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -2
- package/dist/client/history.d.ts +21 -2
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +34 -14
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +1 -0
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +272 -1225
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-commit.d.ts +12 -19
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +62 -11
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +9 -8
- package/dist/client/router-effects.d.ts.map +1 -1
- package/dist/client/router-lifecycle.d.ts +60 -17
- package/dist/client/router-lifecycle.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +7 -4
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +63 -8
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +0 -9
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +23 -8
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/state.d.ts +16 -0
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/status-page-marker.d.ts +25 -0
- package/dist/client/status-page-marker.d.ts.map +1 -0
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/holding-server.d.ts +4 -17
- package/dist/dev-tools/holding-server.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -175
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception.d.ts +2 -2
- package/dist/routing/slot-placement.d.ts +2 -2
- package/dist/server/access-gate.d.ts +73 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +16 -1
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +3 -9
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/children-interception.d.ts +1 -1
- package/dist/server/default-status-page.d.ts +2 -2
- package/dist/server/default-status-page.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +15 -9
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +21 -4
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/error-response-headers.d.ts +3 -0
- package/dist/server/error-response-headers.d.ts.map +1 -0
- package/dist/server/index.js +3 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +1 -2
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +2339 -2506
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +2 -5
- package/dist/server/metadata-collector.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +10 -3
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +11 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +3 -89
- package/dist/server/port-resolution.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +38 -10
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/response-cache-policy.d.ts +3 -0
- package/dist/server/response-cache-policy.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +11 -41
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-element-helpers.d.ts +12 -0
- package/dist/server/route-element-helpers.d.ts.map +1 -0
- package/dist/server/route-module-loader.d.ts +37 -0
- package/dist/server/route-module-loader.d.ts.map +1 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +22 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +4 -11
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/rsc-error-envelope.d.ts +11 -0
- package/dist/server/rsc-error-envelope.d.ts.map +1 -0
- package/dist/server/skippable-prefix.d.ts +18 -15
- package/dist/server/skippable-prefix.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts +54 -0
- package/dist/server/slot-subtree-contain.d.ts.map +1 -0
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +10 -0
- package/dist/server/utils/element-type.d.ts.map +1 -1
- package/dist/shared/rsc-error-envelope.d.ts +0 -9
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -1
- package/dist/shared/status-reason-phrase.d.ts +26 -0
- package/dist/shared/status-reason-phrase.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +4 -2
- package/docs/api/31-api-client.mdx +5 -1
- package/docs/api/36-cli.mdx +5 -3
- package/docs/learn/12-error-handling.mdx +5 -1
- package/package.json +10 -10
- package/src/client/browser-entry/action-dispatch.ts +166 -99
- package/src/client/browser-entry/action-queue.ts +90 -0
- package/src/client/browser-entry/router-init.ts +60 -35
- package/src/client/deny-last-resort.tsx +54 -0
- package/src/client/error-boundary.tsx +144 -42
- package/src/client/history.ts +52 -3
- package/src/client/internal.ts +1 -0
- package/src/client/link.tsx +70 -35
- package/src/client/navigation-commit.ts +79 -27
- package/src/client/navigation-transition.ts +176 -127
- package/src/client/router-effects.ts +14 -17
- package/src/client/router-lifecycle.ts +181 -115
- package/src/client/router-pipeline.ts +94 -71
- package/src/client/router-types.ts +61 -7
- package/src/client/router.ts +147 -74
- package/src/client/rsc-fetch.ts +0 -13
- package/src/client/segment-cache.ts +43 -10
- package/src/client/state.ts +26 -0
- package/src/client/status-page-marker.tsx +32 -0
- package/src/dev-tools/holding-server.ts +4 -17
- package/src/index.ts +18 -34
- package/src/plugins/dev-server.ts +2 -1
- package/src/react-canary.d.ts +2 -0
- package/src/routing/codegen-write.ts +2 -0
- package/src/routing/interception.ts +2 -2
- package/src/routing/slot-placement.ts +2 -2
- package/src/server/access-gate.tsx +89 -21
- package/src/server/action-client.ts +2 -2
- package/src/server/action-handler.ts +23 -10
- package/src/server/actions.ts +81 -34
- package/src/server/als-registry.ts +3 -9
- package/src/server/children-interception.ts +1 -1
- package/src/server/default-status-page.ts +7 -47
- package/src/server/deny-boundary.ts +45 -28
- package/src/server/deny-renderer.ts +6 -2
- package/src/server/error-boundary-wrapper.ts +23 -4
- package/src/server/error-response-headers.ts +18 -0
- package/src/server/internal.ts +2 -10
- package/src/server/metadata-collector.ts +3 -18
- package/src/server/param-coercion.ts +13 -4
- package/src/server/pipeline-outcome.ts +35 -13
- package/src/server/pipeline-phases.ts +22 -16
- package/src/server/port-resolution.ts +3 -165
- package/src/server/prebuilt-builder.ts +4 -4
- package/src/server/primitives.ts +75 -11
- package/src/server/response-cache-policy.ts +45 -0
- package/src/server/route-element-builder.ts +149 -412
- package/src/server/route-element-helpers.ts +37 -0
- package/src/server/route-handler.ts +2 -2
- package/src/server/route-module-loader.ts +161 -0
- package/src/server/rsc-cache-key-guard.ts +2 -42
- package/src/server/rsc-entry/action-middleware-runner.ts +4 -4
- package/src/server/rsc-entry/api-handler.ts +5 -5
- package/src/server/rsc-entry/error-renderer.ts +3 -4
- package/src/server/rsc-entry/helpers.ts +1 -1
- package/src/server/rsc-entry/index.ts +3 -3
- package/src/server/rsc-entry/render-route.ts +4 -8
- package/src/server/rsc-entry/rsc-payload.ts +59 -42
- package/src/server/rsc-entry/rsc-stream.ts +48 -27
- package/src/server/rsc-entry/ssr-renderer.ts +6 -10
- package/src/server/rsc-error-envelope.ts +18 -0
- package/src/server/skippable-prefix.ts +105 -7
- package/src/server/slot-resolver.ts +43 -12
- package/src/server/slot-subtree-contain.ts +255 -0
- package/src/server/stream-utils.ts +12 -8
- package/src/server/utils/element-type.ts +18 -2
- package/src/shared/rsc-error-envelope.ts +0 -15
- package/src/shared/status-reason-phrase.ts +61 -0
- package/dist/_chunks/actions-BS-m5SLv.js.map +0 -1
- package/dist/_chunks/convention-lint-n3RJLgww.js.map +0 -1
- package/dist/_chunks/error-boundary-BvRCCmbN.js +0 -353
- package/dist/_chunks/error-boundary-BvRCCmbN.js.map +0 -1
- package/dist/_chunks/logger-DDirEsn7.js.map +0 -1
- package/dist/_chunks/mdx-file-CXyHGUpS.js +0 -25
- package/dist/_chunks/mdx-file-CXyHGUpS.js.map +0 -1
- package/dist/_chunks/router-ref-8gr8qsxN.js +0 -28
- package/dist/_chunks/router-ref-8gr8qsxN.js.map +0 -1
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +0 -40
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +0 -1
- package/dist/_chunks/scanner-tdFPvDYi.js.map +0 -1
- package/dist/_chunks/segment-keys-BhqoHiLc.js.map +0 -1
- package/dist/_chunks/ssr-data-BQGhTPAK.js.map +0 -1
- package/dist/server/tree-builder.d.ts +0 -150
- package/dist/server/tree-builder.d.ts.map +0 -1
- package/src/server/tree-builder.ts +0 -313
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { i as revalidationAls } from "./als-registry-C6kcfprT.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { A as isDenySignal, T as withSpan, j as isRedirectSignal } from "./logger-uLBuGKDI.js";
|
|
3
|
+
import { r as canonicalize } from "./canonicalize-CgHoscYO.js";
|
|
4
|
+
import { t as cache } from "./cache-api-ByagcC-J.js";
|
|
4
5
|
//#region src/server/form-data.ts
|
|
5
6
|
/**
|
|
6
7
|
* FormData preprocessing — schema-agnostic conversion of FormData to typed objects.
|
|
@@ -252,6 +253,10 @@ function revalidateTag(tag) {
|
|
|
252
253
|
const state = getRevalidationState();
|
|
253
254
|
if (!state.tags.includes(tag)) state.tags.push(tag);
|
|
254
255
|
}
|
|
256
|
+
function stripFragment(path) {
|
|
257
|
+
const idx = path.indexOf("#");
|
|
258
|
+
return idx >= 0 ? path.slice(0, idx) : path;
|
|
259
|
+
}
|
|
255
260
|
/**
|
|
256
261
|
* Execute a server action and process revalidation.
|
|
257
262
|
*
|
|
@@ -280,7 +285,7 @@ async function executeAction(actionFn, args, config = {}, spanMeta) {
|
|
|
280
285
|
...spanMeta?.actionName ? { "timber.action_name": spanMeta.actionName } : {}
|
|
281
286
|
}, () => actionFn(...args));
|
|
282
287
|
} catch (error) {
|
|
283
|
-
if (error
|
|
288
|
+
if (isRedirectSignal(error)) {
|
|
284
289
|
redirectTo = error.location;
|
|
285
290
|
redirectStatus = error.status;
|
|
286
291
|
} else throw error;
|
|
@@ -291,28 +296,50 @@ async function executeAction(actionFn, args, config = {}, spanMeta) {
|
|
|
291
296
|
for (const r of results) if (r.status === "rejected") console.error("[timber] revalidateTag invalidation failed:", r.reason);
|
|
292
297
|
}
|
|
293
298
|
let revalidation;
|
|
294
|
-
|
|
299
|
+
let invalidatedPaths;
|
|
300
|
+
if (state.paths.length > 0) {
|
|
295
301
|
const path = state.paths[0];
|
|
296
|
-
|
|
302
|
+
if (config.requestPathname && !revalidationPathMatches(path, config.requestPathname, config.requestSearch)) invalidatedPaths = [stripFragment(path)];
|
|
303
|
+
else if (config.renderer) try {
|
|
297
304
|
revalidation = await config.renderer(path);
|
|
298
305
|
} catch (renderError) {
|
|
299
|
-
if (renderError
|
|
306
|
+
if (isRedirectSignal(renderError)) {
|
|
300
307
|
redirectTo = renderError.location;
|
|
301
308
|
redirectStatus = renderError.status;
|
|
302
|
-
} else if (renderError
|
|
309
|
+
} else if (isDenySignal(renderError)) console.error(`[timber] revalidatePath dropped — target middleware denied (status ${renderError.status})`);
|
|
303
310
|
else console.error("[timber] revalidatePath render failed:", renderError);
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
return {
|
|
307
314
|
actionResult,
|
|
308
315
|
revalidation,
|
|
316
|
+
invalidatedPaths,
|
|
309
317
|
...redirectTo ? {
|
|
310
318
|
redirectTo,
|
|
311
319
|
redirectStatus
|
|
312
320
|
} : {}
|
|
313
321
|
};
|
|
314
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Check if a `revalidatePath(path)` argument matches the request pathname.
|
|
325
|
+
/**
|
|
326
|
+
* Canonicalizes the revalidated path the same way the renderer does so
|
|
327
|
+
* equivalent paths compare equal. Returns true when the paths match or
|
|
328
|
+
* when comparison is inconclusive (validation failure → render to be safe).
|
|
329
|
+
*/
|
|
330
|
+
function revalidationPathMatches(revalidatedPath, requestPathname, requestSearch) {
|
|
331
|
+
const hashIdx = revalidatedPath.indexOf("#");
|
|
332
|
+
const noFragment = hashIdx >= 0 ? revalidatedPath.slice(0, hashIdx) : revalidatedPath;
|
|
333
|
+
const queryIdx = noFragment.indexOf("?");
|
|
334
|
+
const pathnameOnly = queryIdx >= 0 ? noFragment.slice(0, queryIdx) : noFragment;
|
|
335
|
+
const search = queryIdx >= 0 ? noFragment.slice(queryIdx) : "";
|
|
336
|
+
const canonical = canonicalize(pathnameOnly, true);
|
|
337
|
+
if (!canonical.ok) return true;
|
|
338
|
+
if (canonical.pathname !== requestPathname) return false;
|
|
339
|
+
if (search && search !== (requestSearch ?? "")) return false;
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
315
342
|
//#endregion
|
|
316
343
|
export { parseFormData as a, coerce as i, revalidatePath as n, revalidateTag as r, executeAction as t };
|
|
317
344
|
|
|
318
|
-
//# sourceMappingURL=actions-
|
|
345
|
+
//# sourceMappingURL=actions-d1hCqnU3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions-d1hCqnU3.js","names":[],"sources":["../../src/server/form-data.ts","../../src/server/actions.ts"],"sourcesContent":["/**\n * FormData preprocessing — schema-agnostic conversion of FormData to typed objects.\n *\n * FormData is all strings. Schema validation expects typed values. This module\n * bridges the gap with intelligent coercion that runs *before* schema validation.\n *\n * Inspired by zod-form-data, but schema-agnostic — works with any Standard Schema\n * library (Zod, Valibot, ArkType).\n *\n * See design/08-forms-and-actions.md §\"parseFormData() and coerce helpers\"\n */\n\n// ─── parseFormData ───────────────────────────────────────────────────────\n\n/**\n * Convert FormData into a plain object with intelligent coercion.\n *\n * Handles:\n * - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: [\"js\", \"ts\"] }`\n * - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: \"Alice\" } }`\n * - **Empty strings → undefined**: Enables `.optional()` semantics in schemas\n * - **Empty Files → undefined**: File inputs with no selection become `undefined`\n * - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded\n */\nexport function parseFormData(formData: FormData): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n\n for (const key of new Set(formData.keys())) {\n // Skip React internal fields\n if (key.startsWith('$ACTION_')) continue;\n\n // Skip prototype-pollution keys at the top level — assigning to\n // `flat['__proto__']` invokes the prototype setter and can splice\n // attacker-controlled data into `flat`'s prototype chain.\n // The dot-path case is filtered separately in `expandDotPaths`.\n if (DANGEROUS_KEYS.has(key)) continue;\n\n const values = formData.getAll(key);\n const processed = values.map(normalizeValue);\n\n if (processed.length === 1) {\n flat[key] = processed[0];\n } else {\n // Filter out undefined (empty Files) and empty strings from multi-value fields.\n // Multi-value fields (e.g. tags=js&tags=&tags=ts) should not include blanks.\n flat[key] = processed.filter((v) => v !== undefined && v !== '');\n }\n }\n\n // Expand dot-notation paths into nested objects\n return expandDotPaths(flat);\n}\n\n/**\n * Normalize a single FormData entry value.\n * - Empty File objects (no selection) → undefined\n * - Strings pass through as-is (empty strings stay as empty strings so\n * schema validators like `z.string().min(1, 'Required')` can produce\n * their custom error messages instead of a generic type mismatch)\n * - Everything else passes through as-is\n */\nfunction normalizeValue(value: FormDataEntryValue): unknown {\n // File input with no selection: browsers submit a File with name=\"\" and size=0\n if (value instanceof File && value.size === 0 && value.name === '') {\n return undefined;\n }\n\n return value;\n}\n\n/**\n * Expand dot-notation keys into nested objects.\n * `{ \"user.name\": \"Alice\", \"user.age\": \"30\" }` → `{ user: { name: \"Alice\", age: \"30\" } }`\n *\n * Keys without dots are left as-is. Bracket notation (e.g. `items[0]`) is NOT\n * supported — use dot notation (`items.0`) instead.\n */\nfunction expandDotPaths(flat: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let hasDotPaths = false;\n\n // First pass: check if any keys have dots\n for (const key of Object.keys(flat)) {\n if (key.includes('.')) {\n hasDotPaths = true;\n break;\n }\n }\n\n // Fast path: no dot-notation keys, return as-is.\n // Top-level dangerous keys are already filtered in `parseFormData` above.\n if (!hasDotPaths) return flat;\n\n for (const [key, value] of Object.entries(flat)) {\n // Reject any path that contains __proto__ / constructor / prototype.\n // Without this, `__proto__.x=1` writes to Object.prototype because\n // `result['__proto__']` resolves to the prototype object itself.\n const parts = key.split('.');\n if (parts.some((p) => DANGEROUS_KEYS.has(p))) continue;\n\n if (parts.length === 1) {\n result[parts[0]] = value;\n continue;\n }\n\n let current: Record<string, unknown> = result;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] === undefined || current[part] === null) {\n current[part] = {};\n }\n // If current[part] is not an object (e.g., a string from a non-dotted key),\n // the dot-path takes precedence\n if (typeof current[part] !== 'object' || current[part] instanceof File) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n// `__proto__` is the only key in this parser that escapes the dot-path\n// walk's `typeof !== 'object'` reset: accessing `obj['__proto__']` returns\n// `Object.prototype` (itself an object), so the walk steps into the global\n// prototype and the leaf write mutates it. `constructor` resolves to a\n// function (reset fires) and `prototype` is undefined on fresh objects\n// (reset fires), so neither escapes the walk — they parse as legit nested\n// keys and do not mutate anything global.\nconst DANGEROUS_KEYS = new Set(['__proto__']);\n\n// ─── Coercion Helpers ────────────────────────────────────────────────────\n\n/**\n * Schema-agnostic coercion primitives for common FormData patterns.\n *\n * These are plain transform functions — they compose with any schema library's\n * `transform`/`preprocess` pipeline:\n *\n * ```ts\n * // Zod\n * z.preprocess(coerce.number, z.number())\n * // Valibot\n * v.pipe(v.unknown(), v.transform(coerce.number), v.number())\n * ```\n */\nexport const coerce = {\n /**\n * Coerce an empty string to undefined for optional text fields.\n * Use with `.optional()` schemas where an empty input means \"not provided\".\n *\n * ```ts\n * // Zod\n * z.unknown().transform(coerce.text).pipe(z.string().optional())\n * // Valibot\n * v.pipe(v.unknown(), v.transform(coerce.text), v.optional(v.string()))\n * ```\n */\n text(value: unknown): string | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value === 'string') return value;\n return undefined;\n },\n\n /**\n * Coerce a string to a number.\n * - `\"42\"` → `42`\n * - `\"3.14\"` → `3.14`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Non-numeric strings → `undefined` (schema validation will catch this)\n */\n number(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return undefined;\n const num = Number(value);\n if (Number.isNaN(num)) return undefined;\n return num;\n },\n\n /**\n * Coerce a checkbox value to a boolean.\n * HTML checkboxes submit \"on\" when checked and are absent when unchecked.\n * - `\"on\"` / any truthy string → `true`\n * - `undefined` / `null` / `\"\"` → `false`\n */\n checkbox(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return false;\n if (typeof value === 'boolean') return value;\n // Any non-empty string (typically \"on\") is true\n return typeof value === 'string' && value.length > 0;\n },\n\n /**\n * Parse a JSON string into an object.\n * - Valid JSON string → parsed object\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid JSON → `undefined` (schema validation will catch this)\n */\n json(value: unknown): unknown {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n },\n\n /**\n * Coerce a date string to a Date object.\n * Handles `<input type=\"date\">` (`\"2024-01-15\"`), `<input type=\"datetime-local\">`\n * (`\"2024-01-15T10:30\"`), and full ISO 8601 strings.\n * - Valid date string → `Date`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid date strings → `undefined` (schema validation will catch this)\n * - Impossible dates that `new Date()` silently normalizes (e.g. Feb 31) → `undefined`\n */\n date(value: unknown): Date | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (value instanceof Date) return value;\n if (typeof value !== 'string') return undefined;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return undefined;\n\n // Overflow detection: extract Y/M/D from the input string and verify\n // they match the parsed Date components. new Date('2024-02-31') silently\n // normalizes to March 2nd — we reject such inputs.\n const ymdMatch = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (ymdMatch) {\n const inputYear = Number(ymdMatch[1]);\n const inputMonth = Number(ymdMatch[2]);\n const inputDay = Number(ymdMatch[3]);\n\n // Use UTC methods for date-only and Z-suffixed strings to avoid\n // timezone offset shifting the day. For datetime-local (no Z suffix),\n // the Date constructor parses in local time, so use local methods.\n const isUTC = value.length === 10 || value.endsWith('Z');\n const parsedYear = isUTC ? date.getUTCFullYear() : date.getFullYear();\n const parsedMonth = isUTC ? date.getUTCMonth() + 1 : date.getMonth() + 1;\n const parsedDay = isUTC ? date.getUTCDate() : date.getDate();\n\n if (inputYear !== parsedYear || inputMonth !== parsedMonth || inputDay !== parsedDay) {\n return undefined;\n }\n }\n\n return date;\n },\n\n /**\n * Create a File coercion function with optional size and mime type validation.\n * Returns the File if valid, `undefined` otherwise.\n *\n * ```ts\n * // Basic — just checks it's a real File\n * z.preprocess(coerce.file(), z.instanceof(File))\n *\n * // With constraints\n * z.preprocess(\n * coerce.file({ maxSize: 5 * 1024 * 1024, accept: ['image/png', 'image/jpeg'] }),\n * z.instanceof(File)\n * )\n * ```\n */\n file(options?: { maxSize?: number; accept?: string[] }): (value: unknown) => File | undefined {\n return (value: unknown): File | undefined => {\n if (value === undefined || value === null || value === '') return undefined;\n if (!(value instanceof File)) return undefined;\n\n // Empty file input (no selection): browsers submit File with name=\"\" and size=0\n if (value.size === 0 && value.name === '') return undefined;\n\n if (options?.maxSize !== undefined && value.size > options.maxSize) {\n return undefined;\n }\n\n if (options?.accept !== undefined && !options.accept.includes(value.type)) {\n return undefined;\n }\n\n return value;\n };\n },\n};\n","/**\n * Server action primitives: revalidatePath, revalidateTag, and the action handler.\n *\n * - revalidatePath(path) re-renders the route at that path and returns the RSC\n * flight payload for inline reconciliation.\n * - revalidateTag(tag) invalidates timber.cache entries by tag.\n *\n * Both are callable from anywhere on the server — actions, API routes, handlers.\n *\n * The action handler processes incoming action requests, validates CSRF,\n * enforces body limits, executes the action, and returns the response\n * (with piggybacked RSC payload if revalidatePath was called).\n *\n * See design/08-forms-and-actions.md\n */\n\nimport { cache } from '../cache/cache-api.ts';\nimport { canonicalize } from './canonicalize.ts';\nimport { isDenySignal, isRedirectSignal } from './primitives.ts';\nimport { withSpan } from './tracing.ts';\nimport { revalidationAls, type RevalidationState } from './als-registry.ts';\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** Result of rendering a revalidation — payload root before RSC serialization. */\nexport interface RevalidationResult {\n /**\n * The payload root — tree plus the params it rendered with, exactly as an\n * ordinary route payload. The client splits it in `applyActionResult`.\n */\n payload: unknown;\n}\n\n/** Renderer function that builds a React element tree for a given path. */\nexport type RevalidateRenderer = (path: string) => Promise<RevalidationResult>;\n\n// Re-export the type from the registry for public API consumers.\nexport type { RevalidationState } from './als-registry.ts';\n\n/** Options for creating the action handler. */\nexport interface ActionHandlerConfig {\n /** Renderer for producing RSC payloads during revalidation. */\n renderer?: RevalidateRenderer;\n /**\n * Pathname of the page the action was submitted from. When set,\n * revalidatePath renders are skipped for non-matching paths — the\n * server cache is already invalidated, so the next navigation fetches\n * fresh data. See TIM-1454.\n */\n requestPathname?: string;\n /** Search string of the action request URL (e.g. `?page=1`). */\n requestSearch?: string;\n}\n\n/** Result of handling a server action request. */\nexport interface ActionHandlerResult {\n /** The action's return value (serialized). */\n actionResult: unknown;\n /** Revalidation result if revalidatePath was called (element tree, not yet serialized). */\n revalidation?: RevalidationResult;\n /**\n * Paths passed to `revalidatePath()` that don't match the current page\n * (TIM-1454). The client invalidates its caches for these so the next\n * navigation fetches fresh data.\n */\n invalidatedPaths?: string[];\n /** Redirect location if a RedirectSignal was thrown during revalidation. */\n redirectTo?: string;\n /** Redirect status code. */\n redirectStatus?: number;\n}\n\n// ─── Revalidation State ──────────────────────────────────────────────────\n\n// Per-request revalidation state stored in AsyncLocalStorage (from als-registry.ts).\n// This ensures concurrent requests never share or overwrite each other's state\n// (the previous module-level global was vulnerable to cross-request pollution).\n\n/**\n * Run `fn` inside a revalidation ALS scope.\n * @internal — for tests that call revalidatePath/Tag directly without executeAction().\n */\nexport function _runWithRevalidationState<T>(state: RevalidationState, fn: () => T): T {\n return revalidationAls.run(state, fn);\n}\n\n/**\n * Get the current revalidation state. Throws if called outside an action context.\n * @internal\n */\nfunction getRevalidationState(): RevalidationState {\n const state = revalidationAls.getStore();\n if (!state) {\n throw new Error(\n 'revalidatePath/revalidateTag called outside of a server action context. ' +\n 'These functions can only be called during action execution.'\n );\n }\n return state;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Re-render the route at `path` and include the RSC flight payload in the\n * action response. The client reconciles inline — no separate fetch needed.\n *\n * Can be called from server actions, API routes, or any server-side context.\n *\n * @param path - The path to re-render (e.g. '/dashboard', '/todos').\n */\nexport function revalidatePath(path: string): void {\n const state = getRevalidationState();\n if (!state.paths.includes(path)) {\n state.paths.push(path);\n }\n}\n\n/**\n * Invalidate all timber.cache entries tagged with `tag`.\n * Does not return a payload — the next request for an invalidated entry re-executes.\n *\n * @param tag - The cache tag to invalidate (e.g. 'products', 'user:123').\n */\nexport function revalidateTag(tag: string): void {\n const state = getRevalidationState();\n if (!state.tags.includes(tag)) {\n state.tags.push(tag);\n }\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction stripFragment(path: string): string {\n const idx = path.indexOf('#');\n return idx >= 0 ? path.slice(0, idx) : path;\n}\n\n// ─── Action Handler ──────────────────────────────────────────────────────\n\n/**\n * Execute a server action and process revalidation.\n *\n * 1. Sets up revalidation state\n * 2. Calls the action function\n * 3. Processes revalidateTag calls (invalidates cache entries)\n * 4. Processes revalidatePath calls (re-renders and captures RSC payload)\n * 5. Returns the action result + optional RSC payload\n *\n * @param actionFn - The server action function to execute.\n * @param args - Arguments to pass to the action.\n * @param config - Handler configuration (cache handler, renderer).\n */\nexport async function executeAction(\n actionFn: (...args: unknown[]) => Promise<unknown>,\n args: unknown[],\n config: ActionHandlerConfig = {},\n spanMeta?: { actionFile?: string; actionName?: string }\n): Promise<ActionHandlerResult> {\n const state: RevalidationState = { paths: [], tags: [] };\n let actionResult: unknown;\n let redirectTo: string | undefined;\n let redirectStatus: number | undefined;\n\n // Run the action inside ALS scope so revalidatePath/Tag resolve to this\n // request's state object — concurrent requests each get their own scope.\n await revalidationAls.run(state, async () => {\n try {\n actionResult = await withSpan(\n 'timber.action',\n {\n ...(spanMeta?.actionFile ? { 'timber.action_file': spanMeta.actionFile } : {}),\n ...(spanMeta?.actionName ? { 'timber.action_name': spanMeta.actionName } : {}),\n },\n () => actionFn(...args)\n );\n } catch (error) {\n if (isRedirectSignal(error)) {\n redirectTo = error.location;\n redirectStatus = error.status;\n } else {\n throw error;\n }\n }\n });\n\n // Process tag invalidation through cache.invalidate so it records the\n // invalidation epoch — an in-flight cached fn must not re-store data this\n // mutation just invalidated (TIM-1028). cache.invalidate resolves the\n // module-level handler singleton: setCacheHandler() is called at boot from\n // rsc-entry when timber.config.ts provides a cacheHandler; otherwise falls\n // back to in-memory LRU (TIM-599).\n if (state.tags.length > 0) {\n // cache.invalidate handles both data-cache clearing AND component-seed\n // tombstone writes (design/45 §ISR mechanics) — no separate tombstone\n // step needed here.\n // Best-effort: the action body already executed and its mutation committed.\n // Use allSettled so all tags settle before proceeding — Promise.all would\n // short-circuit on the first rejection, leaving other invalidations (including\n // component-seed tombstone writes) in-flight during revalidatePath rendering.\n const results = await Promise.allSettled(state.tags.map((tag) => cache.invalidate({ tag })));\n for (const r of results) {\n if (r.status === 'rejected') {\n console.error('[timber] revalidateTag invalidation failed:', r.reason);\n }\n }\n }\n\n // Process path revalidation — build element tree (not yet serialized).\n //\n // TIM-1454: Only render the path if it matches the current page. Non-matching\n // paths are collected as `invalidatedPaths` — the client evicts them from its\n // caches so the next navigation fetches fresh data.\n let revalidation: RevalidationResult | undefined;\n let invalidatedPaths: string[] | undefined;\n\n if (state.paths.length > 0) {\n const path = state.paths[0];\n\n if (\n config.requestPathname &&\n !revalidationPathMatches(path, config.requestPathname, config.requestSearch)\n ) {\n invalidatedPaths = [stripFragment(path)];\n } else if (config.renderer) {\n try {\n revalidation = await config.renderer(path);\n } catch (renderError) {\n if (isRedirectSignal(renderError)) {\n redirectTo = renderError.location;\n redirectStatus = renderError.status;\n } else if (isDenySignal(renderError)) {\n console.error(\n `[timber] revalidatePath dropped — target middleware denied (status ${renderError.status})`\n );\n } else {\n console.error('[timber] revalidatePath render failed:', renderError);\n }\n }\n }\n }\n\n return {\n actionResult,\n revalidation,\n invalidatedPaths,\n ...(redirectTo ? { redirectTo, redirectStatus } : {}),\n };\n}\n\n/**\n * Check if a `revalidatePath(path)` argument matches the request pathname.\n/**\n * Canonicalizes the revalidated path the same way the renderer does so\n * equivalent paths compare equal. Returns true when the paths match or\n * when comparison is inconclusive (validation failure → render to be safe).\n */\nfunction revalidationPathMatches(\n revalidatedPath: string,\n requestPathname: string,\n requestSearch?: string\n): boolean {\n const hashIdx = revalidatedPath.indexOf('#');\n const noFragment = hashIdx >= 0 ? revalidatedPath.slice(0, hashIdx) : revalidatedPath;\n const queryIdx = noFragment.indexOf('?');\n const pathnameOnly = queryIdx >= 0 ? noFragment.slice(0, queryIdx) : noFragment;\n const search = queryIdx >= 0 ? noFragment.slice(queryIdx) : '';\n const canonical = canonicalize(pathnameOnly, true);\n if (!canonical.ok) return true;\n if (canonical.pathname !== requestPathname) return false;\n // revalidatePath('/dashboard') (no search) matches any query variant.\n // revalidatePath('/dashboard?tab=x') only matches that exact search.\n if (search && search !== (requestSearch ?? '')) return false;\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"als-registry-C6kcfprT.js","names":[],"sources":["../../src/server/als-registry.ts"],"sourcesContent":["/**\n * Centralized AsyncLocalStorage registry for server-side per-request state.\n *\n * ALL ALS instances used by the server framework live here. Individual\n * modules (request-context.ts, tracing.ts, actions.ts, etc.) import from\n * this registry and re-export public accessor functions.\n *\n * Why: ALS instances require singleton semantics — if two copies of the\n * same ALS exist (one from a relative import, one from a barrel import,\n * or the same file re-evaluated by Vite after an HMR invalidation), one\n * module writes to its copy and another reads from an empty copy.\n * Centralizing ALS creation in a single module + anchoring each instance\n * on `globalThis` under a `Symbol.for(...)` key eliminates this class of\n * bug, including the Vite-dev case where module instance split would\n * otherwise hand two copies of this file out of a single evaluation cycle.\n *\n * The `timber-shims` plugin remaps `@timber-js/app/server` to src/ in\n * RSC and SSR environments so import-specifier paths converge here, and\n * the `globalThis[SYMBOL_FOR]` anchor below closes the remaining gap: any\n * second evaluation of this module picks up the ALS the first evaluation\n * already stored rather than instantiating a fresh one. Same pattern the\n * Cloudflare bindings ALS uses in `adapters/cloudflare.ts`.\n *\n * DO NOT create ALS instances outside this file. If you need a new ALS,\n * add it here and import from `./als-registry.js` in the consuming module.\n * Inside this file, always route through `getOrCreateAls(symbol)` — a raw\n * `new AsyncLocalStorage()` reintroduces the instance-split hazard.\n *\n * See design/18-build-system.md §\"Module Singleton Strategy\" and\n * §\"Singleton State Registry\". Regression test:\n * `tests/als-registry-singleton.test.ts`.\n */\n\nimport '#server-only-guard';\nimport type { CoercedParams } from '../shared/param-value.ts';\nimport type { SlotParamsRecord } from '../shared/slot-params.ts';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { DebugComponentEntry } from './rsc-entry/helpers.ts';\n\n/**\n * Return a process-wide singleton `AsyncLocalStorage` keyed by `symbol`.\n *\n * `Symbol.for(...)` keys live in the cross-realm symbol registry, and\n * `globalThis` is shared across every module evaluation inside the same\n * Node.js process. Together they give a stable anchor that survives\n * re-evaluation of this file — the second evaluation observes the\n * `globalThis[symbol]` slot already populated by the first and hands\n * back the identical ALS instance.\n */\nfunction getOrCreateAls<T>(symbol: symbol): AsyncLocalStorage<T> {\n const g = globalThis as unknown as Record<symbol, unknown>;\n const existing = g[symbol];\n if (existing instanceof AsyncLocalStorage) {\n return existing as AsyncLocalStorage<T>;\n }\n const created = new AsyncLocalStorage<T>();\n g[symbol] = created;\n return created;\n}\n\n// ─── Request Context ──────────────────────────────────────────────────────\n// Used by: request-context.ts (getHeaders(), getCookies(), getSearchParams())\n// Design doc: design/04-authorization.md\n\n/** @internal — import via request-context.ts public API */\nexport const requestContextAls = getOrCreateAls<RequestContextStore>(\n Symbol.for('timber:request-context-als')\n);\n\nexport interface RequestContextStore {\n /** Incoming request headers (read-only view). */\n headers: Headers;\n /** Raw cookie header string, parsed lazily into a Map on first access. */\n cookieHeader: string;\n /** Lazily-parsed cookie map (mutable — reflects write-overlay from set()). */\n parsedCookies?: Map<string, string>;\n /** Original (pre-overlay) frozen headers, kept for overlay merging. */\n originalHeaders: Headers;\n /**\n * CSP nonce, created the first time middleware reads `ctx.nonce`. Read by\n * the renderers via `getCspNonce()` and stamped on every inline script.\n * Absent when no middleware asked for one. See server/csp-nonce.ts.\n */\n cspNonce?: string;\n /**\n * Raw URLSearchParams for the current request.\n * To get typed parsed params, import a search params definition and\n * call `.parse(searchParams())`.\n */\n searchParams: URLSearchParams;\n /**\n * Raw search string from the request URL (e.g. \"?foo=bar&baz=1\").\n * Available synchronously for use in `redirect()` with `preserveSearchParams`.\n */\n searchString: string;\n /**\n * Coerced segment params for the current request.\n * Set by the pipeline after route matching and param coercion, before\n * middleware and rendering. Pages and layouts read params via\n * `getSegmentParams()` instead of receiving them as a prop.\n *\n * See design/07-routing.md §\"params.ts — Convention File for Typed Params\"\n */\n segmentParams?: CoercedParams;\n /**\n * The matched segment path (e.g. '/(browse)/[artistSlug]/[year]').\n * Includes route groups and parallel slots — uniquely identifies the\n * segment in the route tree. Set by the pipeline alongside segmentParams.\n * Used by getSegmentParams() for dev-mode validation.\n * See design/41-global-params.md §Runtime Validation\n */\n matchedSegmentPath?: string;\n /**\n * Per-slot coerced segment params, keyed by the slot's full tree path\n * (e.g. '/(browse)/@shows/[artistSlug]/[...year]').\n *\n * Each slot walks its own segment tree and may interpret URL parts\n * differently from the main route (e.g. catch-all vs dynamic). This\n * map stores slot-specific params so getSegmentParams(segmentPath)\n * returns the correct types for slot pages.\n *\n * See design/07-routing.md §\"Parallel Slot Params\"\n */\n slotParams?: SlotParamsRecord;\n /** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */\n cookieJar: Map<string, CookieEntry>;\n /** Whether the response has flushed (headers committed). */\n flushed: boolean;\n /** Whether the current context allows cookie mutation. */\n mutableContext: boolean;\n /**\n * Set by AccessGate or PageDenyBoundary when a DenySignal is caught\n * server-side (inside the React tree, before React Flight sees it).\n * The pipeline reads this after render to set the HTTP status code.\n * See TIM-666.\n */\n denyStatus?: number;\n /**\n * Callback fired when setDenyStatus() is called. Used by shellSettled\n * to short-circuit when any component catches a DenySignal in-tree,\n * regardless of which component caught it (AccessGate, TracedLayout,\n * or PageDenyBoundary). See TIM-1208.\n */\n onDenyStatus?: () => void;\n /**\n * Dev-only: getter for the current request's RSC debug components.\n * Set by renderRoute() so onPipelineError can include component tree\n * context for render-phase errors without module-level shared state.\n */\n debugComponentsGetter?: () => DebugComponentEntry[];\n /**\n * Index of the first segment whose AccessGate denied during render.\n * Recorded by AccessGate when it catches a DenySignal so post-render\n * consumers (buildSegmentInfo, X-Timber-Segments header) can bound\n * the segment list without a pre-render access pass. Infinity when\n * no segment denied. See TIM-1363.\n */\n _firstDeniedSegmentIndex?: number;\n /**\n * Metadata entries collected during render by MetadataCollector.\n * Populated per-segment in tree order (root→page) during\n * renderToReadableStream. Entries may contain promises for dynamic\n * metadata (resolved concurrently with page rendering, TIM-1367).\n * MetadataHead awaits all entries before merging.\n * See TIM-1363, TIM-1367, design/16-metadata.md.\n */\n _metadataEntries?: Array<{\n metadata: import('./types.ts').Metadata | Promise<import('./types.ts').Metadata | null>;\n isPage: boolean;\n }>;\n}\n\n/** A single outgoing cookie entry in the cookie jar. */\nexport interface CookieEntry {\n name: string;\n value: string;\n options: import('./cookie-context.ts').CookieOptions;\n}\n\n// ─── Tracing ──────────────────────────────────────────────────────────────\n// Used by: tracing.ts (getTraceId(), getSpanId())\n// Design doc: design/17-logging.md\n\nexport interface TraceStore {\n /** 32-char lowercase hex trace ID (OTEL or UUID fallback). */\n traceId: string;\n /** OTEL span ID if available, undefined otherwise. */\n spanId?: string;\n /**\n * Innermost active native platform span (e.g. Cloudflare's `Span` from\n * tracing.enterSpan()). Tracked so setSpanAttribute() can set attributes\n * after span creation — the platform API has no getActiveSpan() equivalent.\n * Saved/restored by withSpan() around each callback. See TIM-1135.\n */\n platformSpan?: import('./tracing.ts').PlatformSpan;\n}\n\n/** @internal — import via tracing.ts public API */\nexport const traceAls = getOrCreateAls<TraceStore>(Symbol.for('timber:trace-als'));\n\n// ─── Server-Timing ────────────────────────────────────────────────────────\n// Used by: server-timing.ts (recordTiming(), withTiming())\n// Design doc: (dev-only performance instrumentation)\n\nexport interface TimingStore {\n entries: import('./server-timing.ts').TimingEntry[];\n}\n\n/** @internal — import via server-timing.ts public API */\nexport const timingAls = getOrCreateAls<TimingStore>(Symbol.for('timber:timing-als'));\n\n// ─── Revalidation ─────────────────────────────────────────────────────────\n// Used by: actions.ts (revalidatePath(), revalidateTag())\n// Design doc: design/08-forms-and-actions.md\n\nexport interface RevalidationState {\n /** Paths to re-render (populated by revalidatePath calls). */\n paths: string[];\n /** Tags to invalidate (populated by revalidateTag calls). */\n tags: string[];\n}\n\n/** @internal — import via actions.ts public API */\nexport const revalidationAls = getOrCreateAls<RevalidationState>(\n Symbol.for('timber:revalidation-als')\n);\n\n// ─── Form Flash ───────────────────────────────────────────────────────────\n// Used by: form-flash.ts (getFormFlash())\n// Design doc: design/08-forms-and-actions.md §\"No-JS Error Round-Trip\"\n\n/** @internal — import via form-flash.ts public API */\nexport const formFlashAls = getOrCreateAls<import('./form-flash.ts').FormFlashData>(\n Symbol.for('timber:form-flash-als')\n);\n\n// ─── Early Hints Sender ──────────────────────────────────────────────────\n// Used by: early-hints-sender.ts (sendEarlyHints103())\n// Design doc: design/02-rendering-pipeline.md §\"Early Hints (103)\"\n\n/** Function that sends Link header values as a 103 Early Hints response. */\nexport type EarlyHintsSenderFn = (links: string[]) => void;\n\n/** @internal — import via early-hints-sender.ts public API */\nexport const earlyHintsSenderAls = getOrCreateAls<EarlyHintsSenderFn>(\n Symbol.for('timber:early-hints-sender-als')\n);\n\n// ─── waitUntil Bridge ────────────────────────────────────────────────────\n// Used by: waituntil-bridge.ts (waitUntil())\n// Design doc: design/11-platform.md §\"waitUntil()\"\n\n/** Function that extends the request lifecycle with a background promise. */\nexport type WaitUntilFn = (promise: Promise<unknown>) => void;\n\n/** @internal — import via waituntil-bridge.ts public API */\nexport const waitUntilAls = getOrCreateAls<WaitUntilFn>(Symbol.for('timber:wait-until-als'));\n\n// ─── Workers Cache Purge Bridge ─────────────────────────────────────────\n// Used by: workers-cache-bridge.ts (runWithWorkersCachePurge())\n// Design doc: design/06-caching.md, design/25-production-deployments.md\n\n/** Workers Cache purge function from Cloudflare ExecutionContext.cache. */\nexport interface WorkersCachePurgeCtx {\n purge(opts: { tags: string[] }): Promise<void>;\n}\n\n/** @internal — import via workers-cache-bridge.ts public API */\nexport const workersCachePurgeAls = getOrCreateAls<WorkersCachePurgeCtx>(\n Symbol.for('timber:workers-cache-purge-als')\n);\n"],"mappings":";;;;;;;;;;;;;AAiDA,SAAS,eAAkB,QAAsC;CAC/D,MAAM,IAAI;CACV,MAAM,WAAW,EAAE;CACnB,IAAI,oBAAoB,mBACtB,OAAO;CAET,MAAM,UAAU,IAAI,kBAAqB;CACzC,EAAE,UAAU;CACZ,OAAO;AACT;;AAOA,IAAa,oBAAoB,eAC/B,OAAO,IAAI,4BAA4B,CACzC;;AAmIA,IAAa,WAAW,eAA2B,OAAO,IAAI,kBAAkB,CAAC;;AAWjF,IAAa,YAAY,eAA4B,OAAO,IAAI,mBAAmB,CAAC;;AAcpF,IAAa,kBAAkB,eAC7B,OAAO,IAAI,yBAAyB,CACtC;;AAOA,IAAa,eAAe,eAC1B,OAAO,IAAI,uBAAuB,CACpC;;AAUA,IAAa,sBAAsB,eACjC,OAAO,IAAI,+BAA+B,CAC5C;;AAUA,IAAa,eAAe,eAA4B,OAAO,IAAI,uBAAuB,CAAC;;AAY3F,IAAa,uBAAuB,eAClC,OAAO,IAAI,gCAAgC,CAC7C"}
|
|
1
|
+
{"version":3,"file":"als-registry-C6kcfprT.js","names":[],"sources":["../../src/server/als-registry.ts"],"sourcesContent":["/**\n * Centralized AsyncLocalStorage registry for server-side per-request state.\n *\n * ALL ALS instances used by the server framework live here. Individual\n * modules (request-context.ts, tracing.ts, actions.ts, etc.) import from\n * this registry and re-export public accessor functions.\n *\n * Why: ALS instances require singleton semantics — if two copies of the\n * same ALS exist (one from a relative import, one from a barrel import,\n * or the same file re-evaluated by Vite after an HMR invalidation), one\n * module writes to its copy and another reads from an empty copy.\n * Centralizing ALS creation in a single module + anchoring each instance\n * on `globalThis` under a `Symbol.for(...)` key eliminates this class of\n * bug, including the Vite-dev case where module instance split would\n * otherwise hand two copies of this file out of a single evaluation cycle.\n *\n * The `timber-shims` plugin remaps `@timber-js/app/server` to src/ in\n * RSC and SSR environments so import-specifier paths converge here, and\n * the `globalThis[SYMBOL_FOR]` anchor below closes the remaining gap: any\n * second evaluation of this module picks up the ALS the first evaluation\n * already stored rather than instantiating a fresh one. Same pattern the\n * Cloudflare bindings ALS uses in `adapters/cloudflare.ts`.\n *\n * DO NOT create ALS instances outside this file. If you need a new ALS,\n * add it here and import from `./als-registry.js` in the consuming module.\n * Inside this file, always route through `getOrCreateAls(symbol)` — a raw\n * `new AsyncLocalStorage()` reintroduces the instance-split hazard.\n *\n * See design/18-build-system.md §\"Module Singleton Strategy\" and\n * §\"Singleton State Registry\". Regression test:\n * `tests/als-registry-singleton.test.ts`.\n */\n\nimport '#server-only-guard';\nimport type { CoercedParams } from '../shared/param-value.ts';\nimport type { SlotParamsRecord } from '../shared/slot-params.ts';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { DebugComponentEntry } from './rsc-entry/helpers.ts';\n\n/**\n * Return a process-wide singleton `AsyncLocalStorage` keyed by `symbol`.\n *\n * `Symbol.for(...)` keys live in the cross-realm symbol registry, and\n * `globalThis` is shared across every module evaluation inside the same\n * Node.js process. Together they give a stable anchor that survives\n * re-evaluation of this file — the second evaluation observes the\n * `globalThis[symbol]` slot already populated by the first and hands\n * back the identical ALS instance.\n */\nfunction getOrCreateAls<T>(symbol: symbol): AsyncLocalStorage<T> {\n const g = globalThis as unknown as Record<symbol, unknown>;\n const existing = g[symbol];\n if (existing instanceof AsyncLocalStorage) {\n return existing as AsyncLocalStorage<T>;\n }\n const created = new AsyncLocalStorage<T>();\n g[symbol] = created;\n return created;\n}\n\n// ─── Request Context ──────────────────────────────────────────────────────\n// Used by: request-context.ts (getHeaders(), getCookies(), getSearchParams())\n// Design doc: design/04-authorization.md\n\n/** @internal — import via request-context.ts public API */\nexport const requestContextAls = getOrCreateAls<RequestContextStore>(\n Symbol.for('timber:request-context-als')\n);\n\nexport interface RequestContextStore {\n /** Incoming request headers (read-only view). */\n headers: Headers;\n /** Raw cookie header string, parsed lazily into a Map on first access. */\n cookieHeader: string;\n /** Lazily-parsed cookie map (mutable — reflects write-overlay from set()). */\n parsedCookies?: Map<string, string>;\n /** Original (pre-overlay) frozen headers, kept for overlay merging. */\n originalHeaders: Headers;\n /**\n * CSP nonce, created the first time middleware reads `ctx.nonce`. Read by\n * the renderers via `getCspNonce()` and stamped on every inline script.\n * Absent when no middleware asked for one. See server/csp-nonce.ts.\n */\n cspNonce?: string;\n /**\n * Raw URLSearchParams for the current request.\n * To get typed parsed params, import a search params definition and\n * call `.parse(searchParams())`.\n */\n searchParams: URLSearchParams;\n /**\n * Raw search string from the request URL (e.g. \"?foo=bar&baz=1\").\n * Available synchronously for use in `redirect()` with `preserveSearchParams`.\n */\n searchString: string;\n /**\n * Coerced segment params for the current request.\n * Set by the pipeline after route matching and param coercion, before\n * middleware and rendering. Pages and layouts read params via\n * `getSegmentParams()` instead of receiving them as a prop.\n *\n * See design/07-routing.md §\"params.ts — Convention File for Typed Params\"\n */\n segmentParams?: CoercedParams;\n /**\n * The matched segment path (e.g. '/(browse)/[artistSlug]/[year]').\n * Includes route groups and parallel slots — uniquely identifies the\n * segment in the route tree. Set by the pipeline alongside segmentParams.\n * Used by getSegmentParams() for dev-mode validation.\n * See design/41-global-params.md §Runtime Validation\n */\n matchedSegmentPath?: string;\n /**\n * Per-slot coerced segment params, keyed by the slot's full tree path\n * (e.g. '/(browse)/@shows/[artistSlug]/[...year]').\n *\n * Each slot walks its own segment tree and may interpret URL parts\n * differently from the main route (e.g. catch-all vs dynamic). This\n * map stores slot-specific params so getSegmentParams(segmentPath)\n * returns the correct types for slot pages.\n *\n * See design/07-routing.md §\"Parallel Slot Params\"\n */\n slotParams?: SlotParamsRecord;\n /** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */\n cookieJar: Map<string, CookieEntry>;\n /** Whether the response has flushed (headers committed). */\n flushed: boolean;\n /** Whether the current context allows cookie mutation. */\n mutableContext: boolean;\n /**\n * Set by AccessGate or PageDenyBoundary when a DenySignal is caught\n * server-side (inside the React tree, before React Flight sees it).\n * The document path reads this after render to set the HTTP status code;\n * the RSC payload path does not read it (a Flight payload response is always 200).\n * See TIM-666, TIM-1495.\n */\n denyStatus?: number;\n /**\n * Dev-only: getter for the current request's RSC debug components.\n * Set by renderRoute() so onPipelineError can include component tree\n * context for render-phase errors without module-level shared state.\n */\n debugComponentsGetter?: () => DebugComponentEntry[];\n /**\n * Index of the first segment whose AccessGate denied during render.\n * Recorded by AccessGate when it catches a DenySignal so post-render\n * consumers (buildSegmentInfo, X-Timber-Segments header) can bound\n * the segment list without a pre-render access pass. Infinity when\n * no segment denied. See TIM-1363.\n */\n _firstDeniedSegmentIndex?: number;\n /**\n * Metadata entries collected during render by MetadataCollector.\n * Populated per-segment in tree order (root→page) during\n * renderToReadableStream. Entries may contain promises for dynamic\n * metadata (resolved concurrently with page rendering, TIM-1367).\n * MetadataHead awaits all entries before merging.\n * See TIM-1363, TIM-1367, design/16-metadata.md.\n */\n _metadataEntries?: Array<{\n metadata: import('./types.ts').Metadata | Promise<import('./types.ts').Metadata | null>;\n isPage: boolean;\n }>;\n}\n\n/** A single outgoing cookie entry in the cookie jar. */\nexport interface CookieEntry {\n name: string;\n value: string;\n options: import('./cookie-context.ts').CookieOptions;\n}\n\n// ─── Tracing ──────────────────────────────────────────────────────────────\n// Used by: tracing.ts (getTraceId(), getSpanId())\n// Design doc: design/17-logging.md\n\nexport interface TraceStore {\n /** 32-char lowercase hex trace ID (OTEL or UUID fallback). */\n traceId: string;\n /** OTEL span ID if available, undefined otherwise. */\n spanId?: string;\n /**\n * Innermost active native platform span (e.g. Cloudflare's `Span` from\n * tracing.enterSpan()). Tracked so setSpanAttribute() can set attributes\n * after span creation — the platform API has no getActiveSpan() equivalent.\n * Saved/restored by withSpan() around each callback. See TIM-1135.\n */\n platformSpan?: import('./tracing.ts').PlatformSpan;\n}\n\n/** @internal — import via tracing.ts public API */\nexport const traceAls = getOrCreateAls<TraceStore>(Symbol.for('timber:trace-als'));\n\n// ─── Server-Timing ────────────────────────────────────────────────────────\n// Used by: server-timing.ts (recordTiming(), withTiming())\n// Design doc: (dev-only performance instrumentation)\n\nexport interface TimingStore {\n entries: import('./server-timing.ts').TimingEntry[];\n}\n\n/** @internal — import via server-timing.ts public API */\nexport const timingAls = getOrCreateAls<TimingStore>(Symbol.for('timber:timing-als'));\n\n// ─── Revalidation ─────────────────────────────────────────────────────────\n// Used by: actions.ts (revalidatePath(), revalidateTag())\n// Design doc: design/08-forms-and-actions.md\n\nexport interface RevalidationState {\n /** Paths to re-render (populated by revalidatePath calls). */\n paths: string[];\n /** Tags to invalidate (populated by revalidateTag calls). */\n tags: string[];\n}\n\n/** @internal — import via actions.ts public API */\nexport const revalidationAls = getOrCreateAls<RevalidationState>(\n Symbol.for('timber:revalidation-als')\n);\n\n// ─── Form Flash ───────────────────────────────────────────────────────────\n// Used by: form-flash.ts (getFormFlash())\n// Design doc: design/08-forms-and-actions.md §\"No-JS Error Round-Trip\"\n\n/** @internal — import via form-flash.ts public API */\nexport const formFlashAls = getOrCreateAls<import('./form-flash.ts').FormFlashData>(\n Symbol.for('timber:form-flash-als')\n);\n\n// ─── Early Hints Sender ──────────────────────────────────────────────────\n// Used by: early-hints-sender.ts (sendEarlyHints103())\n// Design doc: design/02-rendering-pipeline.md §\"Early Hints (103)\"\n\n/** Function that sends Link header values as a 103 Early Hints response. */\nexport type EarlyHintsSenderFn = (links: string[]) => void;\n\n/** @internal — import via early-hints-sender.ts public API */\nexport const earlyHintsSenderAls = getOrCreateAls<EarlyHintsSenderFn>(\n Symbol.for('timber:early-hints-sender-als')\n);\n\n// ─── waitUntil Bridge ────────────────────────────────────────────────────\n// Used by: waituntil-bridge.ts (waitUntil())\n// Design doc: design/11-platform.md §\"waitUntil()\"\n\n/** Function that extends the request lifecycle with a background promise. */\nexport type WaitUntilFn = (promise: Promise<unknown>) => void;\n\n/** @internal — import via waituntil-bridge.ts public API */\nexport const waitUntilAls = getOrCreateAls<WaitUntilFn>(Symbol.for('timber:wait-until-als'));\n\n// ─── Workers Cache Purge Bridge ─────────────────────────────────────────\n// Used by: workers-cache-bridge.ts (runWithWorkersCachePurge())\n// Design doc: design/06-caching.md, design/25-production-deployments.md\n\n/** Workers Cache purge function from Cloudflare ExecutionContext.cache. */\nexport interface WorkersCachePurgeCtx {\n purge(opts: { tags: string[] }): Promise<void>;\n}\n\n/** @internal — import via workers-cache-bridge.ts public API */\nexport const workersCachePurgeAls = getOrCreateAls<WorkersCachePurgeCtx>(\n Symbol.for('timber:workers-cache-purge-als')\n);\n"],"mappings":";;;;;;;;;;;;;AAiDA,SAAS,eAAkB,QAAsC;CAC/D,MAAM,IAAI;CACV,MAAM,WAAW,EAAE;CACnB,IAAI,oBAAoB,mBACtB,OAAO;CAET,MAAM,UAAU,IAAI,kBAAqB;CACzC,EAAE,UAAU;CACZ,OAAO;AACT;;AAOA,IAAa,oBAAoB,eAC/B,OAAO,IAAI,4BAA4B,CACzC;;AA6HA,IAAa,WAAW,eAA2B,OAAO,IAAI,kBAAkB,CAAC;;AAWjF,IAAa,YAAY,eAA4B,OAAO,IAAI,mBAAmB,CAAC;;AAcpF,IAAa,kBAAkB,eAC7B,OAAO,IAAI,yBAAyB,CACtC;;AAOA,IAAa,eAAe,eAC1B,OAAO,IAAI,uBAAuB,CACpC;;AAUA,IAAa,sBAAsB,eACjC,OAAO,IAAI,+BAA+B,CAC5C;;AAUA,IAAa,eAAe,eAA4B,OAAO,IAAI,uBAAuB,CAAC;;AAY3F,IAAa,uBAAuB,eAClC,OAAO,IAAI,gCAAgC,CAC7C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Z as getWaitUntil, _ as addSpanEventSync, d as logSwrRefetchFailed, h as swallow } from "./logger-uLBuGKDI.js";
|
|
2
2
|
import { t as fnv1aHash } from "./fast-hash-C-tVYrLb.js";
|
|
3
3
|
import { n as createSingleflight } from "./singleflight-2lUWfcAk.js";
|
|
4
4
|
import { t as estimateByteSize } from "./sizeof-lXffQ1Vr.js";
|
|
@@ -1146,4 +1146,4 @@ var cache = {
|
|
|
1146
1146
|
//#endregion
|
|
1147
1147
|
export { setDefaultSingleflightTimeout as _, getManifestEntry as a, wasInvalidatedSince as b, lookupPrebuiltPayload as c, checkVersionSkew as d, MemoryCacheHandler as f, setCacheHandler as g, getCacheHandler as h, storeOverlayEntry as i, setPrebuiltPayloadSource as l, RedisCacheHandler as m, lookupOverlay as n, hasPrebuiltPayloadSource as o, TagAwareCacheHandler as p, overlayKey as r, isParamIndependent as s, cache as t, applyReloadHeaders as u, currentInvalidationEpoch as v, lastInvalidationEpochFor as y };
|
|
1148
1148
|
|
|
1149
|
-
//# sourceMappingURL=cache-api-
|
|
1149
|
+
//# sourceMappingURL=cache-api-ByagcC-J.js.map
|