@timber-js/app 0.2.0-alpha.207 → 0.2.0-alpha.209
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/{primitives-DLAnvsrk.js → actions-BerlqoXA.js} +640 -410
- package/dist/_chunks/actions-BerlqoXA.js.map +1 -0
- package/dist/_chunks/{als-registry-C6kcfprT.js → als-registry-DaxkVjt5.js} +6 -2
- package/dist/_chunks/als-registry-DaxkVjt5.js.map +1 -0
- package/dist/_chunks/{build-manifest-DTmSGLRz.js → build-manifest-DWppEdLB.js} +2 -51
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
- package/dist/_chunks/{build-output-helper-9dqCv1pZ.js → build-output-helper-RV_BRcF1.js} +50 -53
- package/dist/_chunks/build-output-helper-RV_BRcF1.js.map +1 -0
- package/dist/_chunks/{cache-api-CUD9Ezaq.js → cache-api-CR23J_NC.js} +64 -32
- package/dist/_chunks/cache-api-CR23J_NC.js.map +1 -0
- package/dist/_chunks/{chains-B4bgOl2g.js → chains-CpFg56UB.js} +2 -2
- package/dist/_chunks/{chains-B4bgOl2g.js.map → chains-CpFg56UB.js.map} +1 -1
- package/dist/_chunks/{cli-check-G8o30ldg.js → cli-check-C6Ev6wBO.js} +11 -52
- package/dist/_chunks/cli-check-C6Ev6wBO.js.map +1 -0
- package/dist/_chunks/cli-context-By7k2xVo.js +70 -0
- package/dist/_chunks/cli-context-By7k2xVo.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync--LZKs8Zn.js → cli-schema-sync-CbT2AUUI.js} +3 -3
- package/dist/_chunks/{cli-schema-sync--LZKs8Zn.js.map → cli-schema-sync-CbT2AUUI.js.map} +1 -1
- package/dist/_chunks/{cloudflare-BFb__LYG.js → cloudflare-Byxl8FWP.js} +96 -53
- package/dist/_chunks/{cloudflare-BFb__LYG.js.map → cloudflare-Byxl8FWP.js.map} +1 -1
- package/dist/_chunks/{convention-lint-5KzoMg_A.js → convention-lint-jKTwKwPe.js} +21 -6
- package/dist/_chunks/convention-lint-jKTwKwPe.js.map +1 -0
- package/dist/_chunks/{dev-server-BzBxgYAV.js → dev-server-C4WZdB7L.js} +15 -167
- package/dist/_chunks/dev-server-C4WZdB7L.js.map +1 -0
- package/dist/_chunks/{error-boundary-BndF-3Td.js → error-boundary-tA7kVfs4.js} +84 -104
- package/dist/_chunks/error-boundary-tA7kVfs4.js.map +1 -0
- package/dist/_chunks/{fs-identity-D7vb6GdI.js → fs-identity-BRKuL215.js} +3 -4
- package/dist/_chunks/fs-identity-BRKuL215.js.map +1 -0
- package/dist/_chunks/{graph-cache-CP4GEmf9.js → graph-cache-CYiUS2aS.js} +4 -5
- package/dist/_chunks/graph-cache-CYiUS2aS.js.map +1 -0
- package/dist/_chunks/{json-lossy-check-ClNvBM_3.js → json-lossy-check-ip0Qi0MT.js} +6 -3
- package/dist/_chunks/json-lossy-check-ip0Qi0MT.js.map +1 -0
- package/dist/_chunks/{live-graph-Bm5WTvcr.js → live-graph-Dv-JJCZw.js} +3 -3
- package/dist/_chunks/{live-graph-Bm5WTvcr.js.map → live-graph-Dv-JJCZw.js.map} +1 -1
- package/dist/_chunks/{logger-k2DQ4EUf.js → logger-DiDt5ppH.js} +192 -51
- package/dist/_chunks/logger-DiDt5ppH.js.map +1 -0
- package/dist/_chunks/{navigation-root-mHSK9psY.js → navigation-root-BQfo1-kG.js} +2 -2
- package/dist/_chunks/{navigation-root-mHSK9psY.js.map → navigation-root-BQfo1-kG.js.map} +1 -1
- package/dist/_chunks/{plugin-context-8idfR3UZ.js → plugin-context-BPBPc3lL.js} +28 -4
- package/dist/_chunks/plugin-context-BPBPc3lL.js.map +1 -0
- package/dist/_chunks/{poison-scan-DG5eI2_z.js → poison-scan-CpeT6_OJ.js} +2 -2
- package/dist/_chunks/{poison-scan-DG5eI2_z.js.map → poison-scan-CpeT6_OJ.js.map} +1 -1
- package/dist/_chunks/render-timeout-DOf_BBcO.js +46 -0
- package/dist/_chunks/render-timeout-DOf_BBcO.js.map +1 -0
- package/dist/_chunks/{rsc-payload-path-B_LBodc2.js → rsc-payload-path-dffcs4jp.js} +9 -3
- package/dist/_chunks/{rsc-payload-path-B_LBodc2.js.map → rsc-payload-path-dffcs4jp.js.map} +1 -1
- package/dist/_chunks/{scanner-BQ5sPIJ7.js → scanner-Bw0oq1HB.js} +2 -2
- package/dist/_chunks/{scanner-BQ5sPIJ7.js.map → scanner-Bw0oq1HB.js.map} +1 -1
- package/dist/_chunks/{navigation-context-D0TU0Jog.js → segment-context-xtPUGfdq.js} +36 -34
- package/dist/_chunks/segment-context-xtPUGfdq.js.map +1 -0
- package/dist/_chunks/{singleflight-2lUWfcAk.js → singleflight-BeVMMKi0.js} +4 -3
- package/dist/_chunks/{singleflight-2lUWfcAk.js.map → singleflight-BeVMMKi0.js.map} +1 -1
- package/dist/_chunks/{sizeof-lXffQ1Vr.js → sizeof-UwzwB1uM.js} +3 -2
- package/dist/_chunks/sizeof-UwzwB1uM.js.map +1 -0
- package/dist/_chunks/ssr-data-nA3I70_n.js +49 -0
- package/dist/_chunks/ssr-data-nA3I70_n.js.map +1 -0
- package/dist/_chunks/{state-FippDgxN.js → state-BhNGPsdi.js} +6 -8
- package/dist/_chunks/state-BhNGPsdi.js.map +1 -0
- package/dist/_chunks/{status-page-marker-DwQBrLBz.js → status-page-marker-gaihi0KZ.js} +140 -28
- package/dist/_chunks/status-page-marker-gaihi0KZ.js.map +1 -0
- package/dist/_chunks/{walkers-CN2N_bA-.js → walkers-BXExhzzk.js} +2 -2
- package/dist/_chunks/{walkers-CN2N_bA-.js.map → walkers-BXExhzzk.js.map} +1 -1
- package/dist/_chunks/write-file-atomic-D1qWqHb4.js +32 -0
- package/dist/_chunks/write-file-atomic-D1qWqHb4.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +7 -7
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/canonicalize-module.d.ts +8 -0
- package/dist/adapters/canonicalize-module.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +2 -2
- package/dist/adapters/cloudflare.d.ts +30 -8
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +2 -2
- package/dist/adapters/compress-module.d.ts +8 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/fs-identity.d.ts +2 -3
- package/dist/adapters/fs-identity.d.ts.map +1 -1
- package/dist/adapters/nitro-presets.d.ts +0 -6
- package/dist/adapters/nitro-presets.d.ts.map +1 -1
- package/dist/adapters/nitro-preview.d.ts +43 -11
- package/dist/adapters/nitro-preview.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +1 -13
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +291 -176
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +7 -2
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +105 -21
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js +0 -0
- package/dist/analyze/crawl-entry.js +3 -3
- package/dist/analyze/graph-cache.d.ts.map +1 -1
- package/dist/analyze/graph-command.js +4 -4
- package/dist/cache/index.js +2 -2
- package/dist/cache/json-lossy-check.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/sizeof.d.ts.map +1 -1
- package/dist/cache/stores/memory.js +1 -1
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cdn/workers-cache-purge.js +1 -1
- package/dist/cli-check.d.ts +7 -37
- package/dist/cli-check.d.ts.map +1 -1
- package/dist/cli-context.d.ts +54 -0
- package/dist/cli-context.d.ts.map +1 -0
- package/dist/cli.d.ts +9 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -24
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-dev.d.ts +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts +19 -29
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +7 -7
- package/dist/client/browser-entry/post-hydration.d.ts +6 -9
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +6 -11
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +4 -3
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/error-reconstituter.d.ts +3 -9
- package/dist/client/error-reconstituter.d.ts.map +1 -1
- package/dist/client/history.d.ts +20 -11
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -6
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +3 -3
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +95 -108
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-commit.d.ts +20 -7
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +3 -5
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/react-root.d.ts +3 -2
- package/dist/client/react-root.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +3 -8
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +45 -39
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router-url.d.ts +24 -0
- package/dist/client/router-url.d.ts.map +1 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +8 -4
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +44 -4
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +18 -64
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/state.d.ts +2 -5
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts +1 -1
- package/dist/config-types.d.ts +2 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/dev-page-shell.d.ts +17 -0
- package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/instrumentation.d.ts +17 -2
- package/dist/dev-tools/instrumentation.d.ts.map +1 -1
- package/dist/dev-tools/overlay.d.ts +6 -27
- package/dist/dev-tools/overlay.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +632 -373
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +26 -40
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +15 -2
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +19 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/post-build.d.ts +110 -0
- package/dist/plugins/post-build.d.ts.map +1 -0
- package/dist/plugins/prebuilt-capture.d.ts +11 -4
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -1
- package/dist/plugins/prebuilt.d.ts +17 -1
- package/dist/plugins/prebuilt.d.ts.map +1 -1
- package/dist/plugins/prerender-sugar.d.ts.map +1 -1
- package/dist/plugins/react-resolve.d.ts +49 -0
- package/dist/plugins/react-resolve.d.ts.map +1 -0
- package/dist/plugins/rsc-assets-manifest.d.ts +33 -0
- package/dist/plugins/rsc-assets-manifest.d.ts.map +1 -0
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +32 -9
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts +0 -9
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/segment-codegen.d.ts.map +1 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +5 -4
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +46 -7
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +58 -5
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/children-interception.d.ts.map +1 -1
- package/dist/server/cookie-context.d.ts +2 -23
- package/dist/server/cookie-context.d.ts.map +1 -1
- package/dist/server/csp-nonce.d.ts +0 -9
- package/dist/server/csp-nonce.d.ts.map +1 -1
- package/dist/server/csrf.d.ts +27 -4
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/debug.d.ts +0 -1
- package/dist/server/debug.d.ts.map +1 -1
- package/dist/server/default-logger.d.ts +2 -1
- package/dist/server/default-logger.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +12 -6
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +2 -2
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +7 -3
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +9 -2
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/head-response.d.ts +4 -4
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts +10 -3
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/internal.d.ts +1 -4
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +961 -581
- package/dist/server/internal.js.map +1 -1
- package/dist/server/json-status-response.d.ts +34 -0
- package/dist/server/json-status-response.d.ts.map +1 -0
- package/dist/server/logger.d.ts +26 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +11 -6
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +95 -7
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-interception.d.ts +11 -0
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts +11 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +2 -2
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +23 -35
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt/capture-render.d.ts +25 -0
- package/dist/server/prebuilt/capture-render.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-routes.d.ts +37 -0
- package/dist/server/prebuilt/capture-routes.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +79 -35
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
- package/dist/server/prebuilt/lifetime.d.ts +38 -0
- package/dist/server/prebuilt/lifetime.d.ts.map +1 -0
- package/dist/server/prebuilt/slots.d.ts +7 -0
- package/dist/server/prebuilt/slots.d.ts.map +1 -1
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
- package/dist/server/prebuilt-builder.d.ts.map +1 -1
- package/dist/server/prebuilt-runtime.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +6 -81
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/react-cache-bridge.d.ts +59 -0
- package/dist/server/react-cache-bridge.d.ts.map +1 -0
- package/dist/server/react-cache-scope.d.ts +49 -0
- package/dist/server/react-cache-scope.d.ts.map +1 -0
- package/dist/server/render-timeout.d.ts +15 -0
- package/dist/server/render-timeout.d.ts.map +1 -1
- package/dist/server/render-utils.d.ts +9 -0
- package/dist/server/render-utils.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +26 -5
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +7 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +22 -22
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +7 -0
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts +32 -6
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +11 -18
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -1
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +5 -15
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +5 -6
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-signals.d.ts +65 -0
- package/dist/server/rsc-entry/render-signals.d.ts.map +1 -0
- package/dist/server/rsc-entry/revalidate-renderer.d.ts +6 -16
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +7 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +8 -53
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +2 -2
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +9 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/signal-identity.d.ts +43 -0
- package/dist/server/signal-identity.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +4 -3
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +91 -53
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +8 -5
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +26 -40
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +1 -1
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +12 -0
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +12 -16
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/version-skew.d.ts +3 -3
- package/dist/shared/csrf-reject.d.ts +16 -0
- package/dist/shared/csrf-reject.d.ts.map +1 -0
- package/dist/shared/error-digest.d.ts +28 -0
- package/dist/shared/error-digest.d.ts.map +1 -0
- package/dist/shared/payload-root.d.ts +11 -10
- package/dist/shared/payload-root.d.ts.map +1 -1
- package/dist/shared/rsc-payload-path.d.ts +6 -0
- package/dist/shared/rsc-payload-path.d.ts.map +1 -1
- package/dist/utils/write-file-atomic.d.ts +17 -0
- package/dist/utils/write-file-atomic.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +17 -14
- package/docs/api/32-api-cache.mdx +51 -23
- package/docs/api/34-api-config.mdx +34 -6
- package/docs/api/36-cli.mdx +11 -1
- package/docs/learn/03-fetching-data.mdx +17 -1
- package/docs/learn/04-access-control.mdx +1 -1
- package/docs/learn/10-caching.mdx +29 -11
- package/docs/learn/11-middleware.mdx +8 -2
- package/docs/learn/12-error-handling.mdx +6 -1
- package/package.json +7 -3
- package/src/adapters/build-output-helper.ts +55 -53
- package/src/adapters/canonicalize-module.ts +29 -0
- package/src/adapters/cloudflare.ts +105 -63
- package/src/adapters/compress-module.ts +16 -5
- package/src/adapters/fs-identity.ts +2 -3
- package/src/adapters/nitro-presets.ts +0 -15
- package/src/adapters/nitro-preview.ts +272 -136
- package/src/adapters/nitro.ts +119 -82
- package/src/adapters/shared.ts +23 -14
- package/src/adapters/types.ts +110 -21
- package/src/analyze/graph-cache.ts +3 -4
- package/src/cache/json-lossy-check.ts +3 -9
- package/src/cache/singleflight.ts +5 -2
- package/src/cache/sizeof.ts +3 -1
- package/src/cache/tag-aware-handler.ts +43 -26
- package/src/cli-check.ts +8 -98
- package/src/cli-context.ts +119 -0
- package/src/cli.ts +19 -28
- package/src/client/browser-dev.ts +1 -1
- package/src/client/browser-entry/action-dispatch.ts +26 -4
- package/src/client/browser-entry/hydrate.ts +53 -58
- package/src/client/browser-entry/index.ts +16 -27
- package/src/client/browser-entry/post-hydration.ts +6 -40
- package/src/client/browser-entry/router-init.ts +66 -84
- package/src/client/error-boundary.tsx +9 -41
- package/src/client/error-reconstituter.tsx +10 -5
- package/src/client/history.ts +35 -21
- package/src/client/index.ts +0 -1
- package/src/client/internal.ts +2 -7
- package/src/client/navigation-commit.ts +41 -18
- package/src/client/navigation-context.ts +3 -39
- package/src/client/react-root.ts +3 -2
- package/src/client/router-pipeline.ts +96 -132
- package/src/client/router-types.ts +46 -44
- package/src/client/router-url.ts +32 -0
- package/src/client/router.ts +62 -21
- package/src/client/rsc-fetch.ts +117 -155
- package/src/client/segment-cache.ts +110 -11
- package/src/client/ssr-data.ts +19 -80
- package/src/client/state.ts +2 -9
- package/src/client/use-pathname.ts +1 -1
- package/src/config-types.ts +2 -1
- package/src/config-validation.ts +2 -1
- package/src/dev-tools/dev-page-shell.ts +18 -0
- package/src/dev-tools/index.ts +0 -2
- package/src/dev-tools/instrumentation.ts +50 -2
- package/src/dev-tools/overlay.ts +8 -238
- package/src/index.ts +26 -14
- package/src/plugin-context.ts +39 -40
- package/src/plugins/build-manifest.ts +4 -11
- package/src/plugins/build-report.ts +70 -70
- package/src/plugins/dev-server.ts +24 -44
- package/src/plugins/entries.ts +2 -1
- package/src/plugins/fonts.ts +35 -7
- package/src/plugins/post-build.ts +327 -0
- package/src/plugins/prebuilt-capture.ts +28 -32
- package/src/plugins/prebuilt.ts +54 -39
- package/src/plugins/prerender-sugar.ts +0 -4
- package/src/plugins/react-resolve.ts +149 -0
- package/src/plugins/{adapter-build.ts → rsc-assets-manifest.ts} +20 -109
- package/src/plugins/shims.ts +1 -0
- package/src/plugins/static-build.ts +42 -74
- package/src/routing/codegen-write.ts +25 -3
- package/src/routing/segment-codegen.ts +3 -2
- package/src/rsc-runtime/vendor-types.d.ts +8 -0
- package/src/server/access-gate.tsx +2 -1
- package/src/server/action-client.ts +1 -1
- package/src/server/action-handler.ts +253 -110
- package/src/server/actions.ts +73 -25
- package/src/server/als-registry.ts +73 -5
- package/src/server/chain-url-parts.ts +1 -1
- package/src/server/children-interception.ts +2 -1
- package/src/server/cookie-context.ts +18 -48
- package/src/server/csp-nonce.ts +0 -12
- package/src/server/csrf.ts +140 -8
- package/src/server/debug.ts +0 -1
- package/src/server/default-logger.ts +2 -1
- package/src/server/deny-boundary.ts +25 -13
- package/src/server/deny-renderer.ts +30 -97
- package/src/server/error-boundary-wrapper.ts +7 -3
- package/src/server/fallback-error.ts +26 -11
- package/src/server/flight-scripts.ts +1 -1
- package/src/server/head-response.ts +4 -4
- package/src/server/html-injector-core.ts +2 -2
- package/src/server/html-injectors.ts +2 -4
- package/src/server/index.ts +0 -4
- package/src/server/instrumentation.ts +10 -3
- package/src/server/internal.ts +1 -6
- package/src/server/json-status-response.ts +105 -0
- package/src/server/logger.ts +39 -3
- package/src/server/param-coercion.ts +15 -12
- package/src/server/pipeline-helpers.ts +257 -12
- package/src/server/pipeline-interception.ts +133 -0
- package/src/server/pipeline-outcome.ts +103 -31
- package/src/server/pipeline-phases.ts +127 -237
- package/src/server/pipeline.ts +204 -207
- package/src/server/prebuilt/capture-render.ts +118 -0
- package/src/server/prebuilt/capture-routes.ts +104 -0
- package/src/server/prebuilt/capture-state.ts +155 -48
- package/src/server/prebuilt/lifetime.ts +151 -0
- package/src/server/prebuilt/slots.ts +14 -2
- package/src/server/prebuilt/synthetic-store.ts +3 -0
- package/src/server/prebuilt-builder.ts +30 -320
- package/src/server/prebuilt-runtime.ts +125 -129
- package/src/server/primitives.ts +8 -168
- package/src/server/react-cache-bridge.ts +124 -0
- package/src/server/react-cache-scope.ts +104 -0
- package/src/server/render-timeout.ts +20 -0
- package/src/server/render-utils.ts +11 -1
- package/src/server/request-context.ts +36 -9
- package/src/server/route-element-builder.ts +11 -6
- package/src/server/route-handler.ts +36 -23
- package/src/server/route-matcher.ts +15 -1
- package/src/server/rsc-entry/action-middleware-runner.ts +79 -123
- package/src/server/rsc-entry/api-handler.ts +27 -103
- package/src/server/rsc-entry/deny-fallback.ts +93 -26
- package/src/server/rsc-entry/error-renderer.ts +39 -91
- package/src/server/rsc-entry/helpers.ts +2 -4
- package/src/server/rsc-entry/index.ts +64 -58
- package/src/server/rsc-entry/render-route.ts +15 -80
- package/src/server/rsc-entry/render-signals.ts +108 -0
- package/src/server/rsc-entry/revalidate-renderer.ts +56 -57
- package/src/server/rsc-entry/rsc-payload.ts +28 -61
- package/src/server/rsc-entry/rsc-stream.ts +113 -213
- package/src/server/rsc-entry/ssr-bridge.ts +2 -2
- package/src/server/rsc-entry/ssr-renderer.ts +182 -263
- package/src/server/rsc-entry/wrap-action-dispatch.ts +12 -14
- package/src/server/signal-identity.ts +112 -0
- package/src/server/slot-resolver.ts +18 -18
- package/src/server/slot-subtree-contain.ts +2 -1
- package/src/server/ssr-bridge-types.ts +92 -56
- package/src/server/ssr-entry.ts +87 -111
- package/src/server/ssr-render.ts +55 -64
- package/src/server/ssr-wrappers.tsx +2 -2
- package/src/server/static-generator.ts +7 -42
- package/src/server/static-not-found.ts +3 -3
- package/src/server/stream-utils.ts +51 -0
- package/src/server/tracing.ts +58 -75
- package/src/server/types.ts +1 -1
- package/src/server/version-skew.ts +5 -5
- package/src/shared/csrf-reject.ts +15 -0
- package/src/shared/error-digest.ts +46 -0
- package/src/shared/payload-root.ts +11 -10
- package/src/shared/rsc-payload-path.ts +8 -1
- package/src/utils/write-file-atomic.ts +32 -0
- package/dist/_chunks/actions-jNrXdzgs.js +0 -346
- package/dist/_chunks/actions-jNrXdzgs.js.map +0 -1
- package/dist/_chunks/als-registry-C6kcfprT.js.map +0 -1
- package/dist/_chunks/build-manifest-DTmSGLRz.js.map +0 -1
- package/dist/_chunks/build-output-helper-9dqCv1pZ.js.map +0 -1
- package/dist/_chunks/cache-api-CUD9Ezaq.js.map +0 -1
- package/dist/_chunks/cli-check-G8o30ldg.js.map +0 -1
- package/dist/_chunks/convention-lint-5KzoMg_A.js.map +0 -1
- package/dist/_chunks/dev-server-BzBxgYAV.js.map +0 -1
- package/dist/_chunks/error-boundary-BndF-3Td.js.map +0 -1
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +0 -1
- package/dist/_chunks/graph-cache-CP4GEmf9.js.map +0 -1
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +0 -1
- package/dist/_chunks/logger-k2DQ4EUf.js.map +0 -1
- package/dist/_chunks/navigation-context-D0TU0Jog.js.map +0 -1
- package/dist/_chunks/plugin-context-8idfR3UZ.js.map +0 -1
- package/dist/_chunks/primitives-DLAnvsrk.js.map +0 -1
- package/dist/_chunks/segment-context-D9_89u34.js +0 -34
- package/dist/_chunks/segment-context-D9_89u34.js.map +0 -1
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +0 -1
- package/dist/_chunks/ssr-data-D6T6Y3ef.js +0 -60
- package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +0 -1
- package/dist/_chunks/state-FippDgxN.js.map +0 -1
- package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +0 -1
- package/dist/client/types.d.ts +0 -14
- package/dist/client/types.d.ts.map +0 -1
- package/dist/plugins/adapter-build.d.ts +0 -38
- package/dist/plugins/adapter-build.d.ts.map +0 -1
- package/dist/server/asset-headers.d.ts +0 -32
- package/dist/server/asset-headers.d.ts.map +0 -1
- package/dist/server/dev-source-map.d.ts +0 -22
- package/dist/server/dev-source-map.d.ts.map +0 -1
- package/src/client/types.ts +0 -21
- package/src/server/asset-headers.ts +0 -59
- package/src/server/dev-source-map.ts +0 -31
|
@@ -1,134 +1,294 @@
|
|
|
1
|
-
import { r as
|
|
1
|
+
import { r as canonicalize } from "./canonicalize-CgHoscYO.js";
|
|
2
|
+
import { a as requestContextAls, i as reactCacheScopeAls, o as revalidationAls } from "./als-registry-DaxkVjt5.js";
|
|
3
|
+
import { C as isDebug, P as withSpan, S as isRedirectSignal, v as DENY_BRAND, w as isDevMode, x as isDenySignal, y as REDIRECT_BRAND } from "./logger-DiDt5ppH.js";
|
|
2
4
|
import { n as _setGetSegmentParamsFn, t as _setGetSearchParamsFn } from "./als-slots-BEEIPKYm.js";
|
|
3
|
-
import { a as assertValidCookieOptions, i as assertValidCookieName, n as parseSetCookie$1, o as assertValidCookieValue, r as stringifySetCookie, t as parseCookie } from "./dist-DtrJyBbw.js";
|
|
4
5
|
import { r as appVisibleSearch } from "./rsc-cache-key-ClUiXQnK.js";
|
|
5
6
|
import { a as mergePreservedSearchParams, o as resolveSegmentParams, r as validateExternalRedirectUrl, t as assertRelativeRedirectPath } from "./href-validation-BIrxavIy.js";
|
|
6
|
-
|
|
7
|
+
import { a as assertValidCookieOptions, i as assertValidCookieName, n as parseSetCookie$1, o as assertValidCookieValue, r as stringifySetCookie, t as parseCookie } from "./dist-DtrJyBbw.js";
|
|
8
|
+
import { t as cache, x as getWaitUntil } from "./cache-api-CR23J_NC.js";
|
|
9
|
+
//#region src/server/react-cache-scope.ts
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* React.cache scopes — opening, resuming, and leaving the per-request
|
|
12
|
+
* `React.cache` scope that `react-cache-bridge.ts` makes Flight read.
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
* Kept apart from the bridge so request-context and cookie code can use
|
|
15
|
+
* it without importing `react`. See design/spike-TIM-1529-react-cache-bridge.md.
|
|
16
|
+
*/
|
|
17
|
+
/** A new, empty `React.cache` scope. */
|
|
18
|
+
function createReactCacheScope() {
|
|
19
|
+
return /* @__PURE__ */ new Map();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run `fn` in a `React.cache` scope — a fresh one unless `scope` is given.
|
|
23
|
+
* Every `React.cache` call made from `fn` — synchronously, in its async
|
|
24
|
+
* continuations, and in any Flight render it starts — shares one memo table.
|
|
25
|
+
*
|
|
26
|
+
* Opened by `runWithRequestContext`, so each request context gets its own
|
|
27
|
+
* scope. Pass `scope` to resume one across two ALS runs: the post-action
|
|
28
|
+
* revalidation opens a fresh scope for the target route's middleware and
|
|
29
|
+
* renders the revalidated tree in that same scope later.
|
|
17
30
|
*/
|
|
31
|
+
function runWithReactCacheScope(fn, scope = createReactCacheScope()) {
|
|
32
|
+
return reactCacheScopeAls.run(scope, fn);
|
|
33
|
+
}
|
|
18
34
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
35
|
+
* Run `fn` with no timber scope, so `React.cache` behaves as stock React:
|
|
36
|
+
* no dedupe outside a render, Flight's own per-render cache inside one.
|
|
37
|
+
*
|
|
38
|
+
* Used for server action bodies — an action reads, writes cookies, and
|
|
39
|
+
* mutates, so memoized reads would go stale mid-action (the same choice
|
|
40
|
+
* Next.js makes) — and for renders whose output outlives the request
|
|
41
|
+
* (`cache.component` capture), which must not bake request-memoized values
|
|
42
|
+
* into a cross-request cache entry.
|
|
24
43
|
*/
|
|
25
|
-
function
|
|
26
|
-
return
|
|
44
|
+
function runOutsideReactCacheScope(fn) {
|
|
45
|
+
return reactCacheScopeAls.exit(fn);
|
|
46
|
+
}
|
|
47
|
+
var cookieReads = /* @__PURE__ */ new WeakMap();
|
|
48
|
+
var warnedScopes = /* @__PURE__ */ new WeakSet();
|
|
49
|
+
/**
|
|
50
|
+
* Dev-only: record that cookie `name` (or {@link ANY_COOKIE}) was read
|
|
51
|
+
* while `React.cache` held values in this request — the read may have
|
|
52
|
+
* happened inside a cached function.
|
|
53
|
+
*/
|
|
54
|
+
function noteCookieReadForReactCache(name) {
|
|
55
|
+
if (!isDevMode()) return;
|
|
56
|
+
const scope = reactCacheScopeAls.getStore();
|
|
57
|
+
if (!scope || scope.size === 0) return;
|
|
58
|
+
let reads = cookieReads.get(scope);
|
|
59
|
+
if (!reads) {
|
|
60
|
+
reads = /* @__PURE__ */ new Set();
|
|
61
|
+
cookieReads.set(scope, reads);
|
|
62
|
+
}
|
|
63
|
+
reads.add(name);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Dev-only: warn (once per request) when a cookie recorded by
|
|
67
|
+
* {@link noteCookieReadForReactCache} is written. `name` is the written
|
|
68
|
+
* cookie, or {@link ANY_COOKIE} for `clear()`. Dev-only because reads are
|
|
69
|
+
* only recorded in dev.
|
|
70
|
+
*/
|
|
71
|
+
function warnIfCachedCookieIsWritten(name) {
|
|
72
|
+
const scope = reactCacheScopeAls.getStore();
|
|
73
|
+
if (!scope || warnedScopes.has(scope)) return;
|
|
74
|
+
const reads = cookieReads.get(scope);
|
|
75
|
+
if (!reads) return;
|
|
76
|
+
if (!(name === "*" ? reads.size > 0 : reads.has(name) || reads.has("*"))) return;
|
|
77
|
+
warnedScopes.add(scope);
|
|
78
|
+
const which = name === "*" ? "Cookies were cleared" : `Cookie "${name}" was written`;
|
|
79
|
+
console.warn(`[timber] ${which} after being read while React.cache held values for this request. A cached function that read it keeps returning the old result — pass the cookie value in as an argument (e.g. getUser(sessionToken)) so a change is a new cache key.`);
|
|
27
80
|
}
|
|
28
81
|
//#endregion
|
|
29
|
-
//#region src/server/
|
|
82
|
+
//#region src/server/request-context.ts
|
|
30
83
|
/**
|
|
31
|
-
*
|
|
84
|
+
* Returns a read-only view of the current request's headers.
|
|
32
85
|
*
|
|
33
|
-
*
|
|
86
|
+
* Available in middleware, access checks, server components, and server actions.
|
|
87
|
+
* Throws if called outside a request context (security principle #2: no global fallback).
|
|
88
|
+
*/
|
|
89
|
+
function getHeaders() {
|
|
90
|
+
const store = requestContextAls.getStore();
|
|
91
|
+
if (!store) throw new Error("[timber] getHeaders() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
92
|
+
return store.headers;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Returns the value of a single request header, or undefined if absent.
|
|
34
96
|
*
|
|
35
|
-
*
|
|
97
|
+
* Thin wrapper over `getHeaders().get(name)` for the common case where
|
|
98
|
+
* you need exactly one header.
|
|
36
99
|
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
100
|
+
* @internal — not part of the public API. Use `getHeaders().get(name)` instead.
|
|
101
|
+
*/
|
|
102
|
+
function getHeader(name) {
|
|
103
|
+
return getHeaders().get(name) ?? void 0;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Returns the current request's raw URLSearchParams.
|
|
40
107
|
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
108
|
+
* @internal — not part of the public API. Use `defineSearchParams().get()` instead.
|
|
109
|
+
*/
|
|
110
|
+
function getSearchParams() {
|
|
111
|
+
const store = requestContextAls.getStore();
|
|
112
|
+
if (!store) throw new Error("[timber] getSearchParams() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
113
|
+
return store.searchParams;
|
|
114
|
+
}
|
|
115
|
+
_setGetSearchParamsFn(getSearchParams);
|
|
116
|
+
_setGetSegmentParamsFn(getSegmentParams);
|
|
117
|
+
/**
|
|
118
|
+
* Returns the current request's coerced segment params.
|
|
45
119
|
*
|
|
46
|
-
*
|
|
120
|
+
* The optional `segmentPath` argument exists only for TypeScript narrowing —
|
|
121
|
+
* it does not affect the runtime return value. Pass a segment path
|
|
122
|
+
* (e.g. `'/(browse)/[artistSlug]/[year]'`) to narrow the return type to the
|
|
123
|
+
* exact params shape for that segment.
|
|
47
124
|
*
|
|
48
|
-
*
|
|
49
|
-
* that changes what clients can observe:
|
|
50
|
-
* - Dev error pages with stack traces (fallback-error.ts)
|
|
51
|
-
* - Detailed Server-Timing headers (pipeline.ts)
|
|
52
|
-
* - Error messages in action INTERNAL_ERROR payloads (action-client.ts)
|
|
53
|
-
* - Pipeline error handler wiring (Vite overlay)
|
|
125
|
+
* Without an argument, returns all coerced params with optional types.
|
|
54
126
|
*
|
|
55
|
-
*
|
|
56
|
-
|
|
127
|
+
* See design/41-global-params.md §Function Signatures
|
|
128
|
+
*/
|
|
129
|
+
function getSegmentParams(segmentPath) {
|
|
130
|
+
const store = requestContextAls.getStore();
|
|
131
|
+
if (!store) throw new Error("[timber] getSegmentParams() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
132
|
+
if (!store.segmentParams) throw new Error("[timber] getSegmentParams() called before route matching completed. Segment params are not available until after the route is matched.");
|
|
133
|
+
const slotResolved = resolveSegmentParams(store.segmentParams, store.slotParams, segmentPath);
|
|
134
|
+
if (slotResolved !== store.segmentParams) return slotResolved;
|
|
135
|
+
if (segmentPath && isDebug() && store.matchedSegmentPath) {
|
|
136
|
+
const expected = extractDynamicSegments(segmentPath);
|
|
137
|
+
const actual = extractDynamicSegments(store.matchedSegmentPath);
|
|
138
|
+
const missing = expected.filter((s) => !actual.includes(s));
|
|
139
|
+
if (missing.length > 0) console.warn(`[timber] getSegmentParams('${segmentPath}') called but current segment is '${store.matchedSegmentPath}'\n Missing params: ${missing.join(", ")}\n These will be undefined at runtime despite the type annotation.`);
|
|
140
|
+
}
|
|
141
|
+
return store.segmentParams;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Set the segment params on the current request context.
|
|
145
|
+
* Called by the pipeline after route matching and param coercion.
|
|
57
146
|
*
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
147
|
+
* @internal — framework use only
|
|
148
|
+
*/
|
|
149
|
+
function setSegmentParams(params) {
|
|
150
|
+
const store = requestContextAls.getStore();
|
|
151
|
+
if (!store) throw new Error("[timber] setSegmentParams() called outside of a request context.");
|
|
152
|
+
store.segmentParams = params;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Store the matched segment path (e.g. '/(browse)/[artistSlug]/[year]') for
|
|
156
|
+
* dev-mode validation in getSegmentParams().
|
|
62
157
|
*
|
|
63
|
-
*
|
|
64
|
-
|
|
158
|
+
* @internal — framework use only
|
|
159
|
+
*/
|
|
160
|
+
function setMatchedSegmentPath(segmentPath) {
|
|
161
|
+
const store = requestContextAls.getStore();
|
|
162
|
+
if (!store) return;
|
|
163
|
+
store.matchedSegmentPath = segmentPath;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Extract dynamic segment names from a route pattern.
|
|
167
|
+
* '/[artistSlug]/[year]' → ['artistSlug', 'year']
|
|
168
|
+
* '/docs/[...slug]' → ['slug']
|
|
169
|
+
*/
|
|
170
|
+
function extractDynamicSegments(route) {
|
|
171
|
+
const segments = [];
|
|
172
|
+
const re = /\[{1,2}\.{0,3}(\w+)\]{1,2}/g;
|
|
173
|
+
let m;
|
|
174
|
+
while ((m = re.exec(route)) !== null) segments.push(m[1]);
|
|
175
|
+
return segments;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Returns the raw search string from the current request URL (e.g. "?foo=bar").
|
|
179
|
+
* Synchronous — safe for use in `redirect()` which throws synchronously.
|
|
65
180
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
181
|
+
* Returns empty string if called outside a request context (non-throwing for
|
|
182
|
+
* use in redirect's optional preserveSearchParams path).
|
|
68
183
|
*
|
|
69
|
-
*
|
|
70
|
-
* See design/18-build-system.md for build pipeline details.
|
|
184
|
+
* @internal — used by redirect() for preserveSearchParams support.
|
|
71
185
|
*/
|
|
186
|
+
function getRequestSearchString() {
|
|
187
|
+
return requestContextAls.getStore()?.searchString ?? "";
|
|
188
|
+
}
|
|
72
189
|
/**
|
|
73
|
-
*
|
|
190
|
+
* Run a callback within a request context. Used by the pipeline to establish
|
|
191
|
+
* per-request ALS scope so that `getHeaders()` and `getCookies()` work.
|
|
74
192
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* -
|
|
79
|
-
* - Pipeline error handler wiring (Vite overlay)
|
|
193
|
+
* When `options.cookies` is given, the context's `parsedCookies` map is a
|
|
194
|
+
* copy of it and the raw `cookieHeader` is left empty, so
|
|
195
|
+
* `parseCookieHeader` never runs for that request. Re-entry callers pass the
|
|
196
|
+
* action's post-mutation cookie state this way (TIM-868).
|
|
80
197
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
198
|
+
* Each request context also opens its own `React.cache` scope, shared by
|
|
199
|
+
* everything that runs inside it — proxy.ts, middleware.ts, route handlers,
|
|
200
|
+
* and the render (design/02-rendering-pipeline.md §"Cache Scoping Model").
|
|
84
201
|
*
|
|
85
|
-
*
|
|
86
|
-
* to
|
|
202
|
+
* @param req - The incoming Request object.
|
|
203
|
+
* @param fn - The function to run within the request context.
|
|
87
204
|
*/
|
|
88
|
-
function
|
|
89
|
-
|
|
205
|
+
function runWithRequestContext(req, fn, { reactCacheScope, cookies } = {}) {
|
|
206
|
+
const originalCopy = new Headers(req.headers);
|
|
207
|
+
const appVisible = appVisibleSearch(new URL(req.url));
|
|
208
|
+
const seed = cookies ? new Map(cookies) : void 0;
|
|
209
|
+
const store = {
|
|
210
|
+
request: req,
|
|
211
|
+
headers: freezeHeaders(req.headers),
|
|
212
|
+
originalHeaders: originalCopy,
|
|
213
|
+
cookieHeader: seed ? "" : req.headers.get("cookie") ?? "",
|
|
214
|
+
parsedCookies: seed,
|
|
215
|
+
searchParams: appVisible.params,
|
|
216
|
+
searchString: appVisible.search,
|
|
217
|
+
cookieJar: /* @__PURE__ */ new Map(),
|
|
218
|
+
flushed: false,
|
|
219
|
+
mutableContext: false
|
|
220
|
+
};
|
|
221
|
+
return requestContextAls.run(store, () => runWithReactCacheScope(fn, reactCacheScope));
|
|
90
222
|
}
|
|
91
223
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
224
|
+
* Enable cookie mutation for the current context. Called by the framework
|
|
225
|
+
* when entering middleware.ts, server actions, or route.ts handlers.
|
|
226
|
+
*
|
|
227
|
+
* See design/29-cookies.md §"Context Tracking"
|
|
228
|
+
*/
|
|
229
|
+
function setMutableCookieContext(mutable) {
|
|
230
|
+
const store = requestContextAls.getStore();
|
|
231
|
+
if (store) store.mutableContext = mutable;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Mark the response as flushed (headers committed). After this point,
|
|
235
|
+
* cookie mutations log a warning instead of throwing.
|
|
236
|
+
*
|
|
237
|
+
* See design/29-cookies.md §"Streaming Constraint: Post-Flush Cookie Warning"
|
|
94
238
|
*/
|
|
95
|
-
|
|
239
|
+
function markResponseFlushed() {
|
|
240
|
+
const store = requestContextAls.getStore();
|
|
241
|
+
if (store) store.flushed = true;
|
|
242
|
+
}
|
|
96
243
|
/**
|
|
97
|
-
*
|
|
244
|
+
* Apply middleware-injected request headers to the current request context.
|
|
98
245
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* - timber.config.ts has `debug: true`
|
|
246
|
+
* Called by the pipeline after middleware.ts runs. Merges overlay headers
|
|
247
|
+
* on top of the original request headers so downstream code (access.ts,
|
|
248
|
+
* server components, server actions) sees them via `getHeaders()`.
|
|
103
249
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* (error pages, response headers, action payloads). For client-visible
|
|
107
|
-
* behavior, use `isDevMode()`.
|
|
250
|
+
* The original request headers are never mutated — a new frozen Headers
|
|
251
|
+
* object is created with the overlay applied on top.
|
|
108
252
|
*
|
|
109
|
-
*
|
|
110
|
-
* access so bundlers cannot statically replace it.
|
|
253
|
+
* See design/07-routing.md §"Request Header Injection"
|
|
111
254
|
*/
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
255
|
+
function applyRequestHeaderOverlay(overlay) {
|
|
256
|
+
const store = requestContextAls.getStore();
|
|
257
|
+
if (!store) throw new Error("[timber] applyRequestHeaderOverlay() called outside of a request context.");
|
|
258
|
+
let hasOverlay = false;
|
|
259
|
+
overlay.forEach(() => {
|
|
260
|
+
hasOverlay = true;
|
|
261
|
+
});
|
|
262
|
+
if (!hasOverlay) return;
|
|
263
|
+
const merged = new Headers(store.originalHeaders);
|
|
264
|
+
overlay.forEach((value, key) => {
|
|
265
|
+
merged.set(key, value);
|
|
266
|
+
});
|
|
267
|
+
store.headers = freezeHeaders(merged);
|
|
116
268
|
}
|
|
269
|
+
var MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
270
|
+
"set",
|
|
271
|
+
"append",
|
|
272
|
+
"delete"
|
|
273
|
+
]);
|
|
117
274
|
/**
|
|
118
|
-
*
|
|
275
|
+
* Wrap a Headers object in a Proxy that throws on mutating methods.
|
|
276
|
+
* Object.freeze doesn't work on Headers (native internal slots), so we
|
|
277
|
+
* intercept property access and reject set/append/delete at runtime.
|
|
119
278
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* 2. Handle platforms where `process` may not exist (Cloudflare Workers)
|
|
123
|
-
* 3. Support globalThis.__TIMBER_DEBUG for programmatic control
|
|
279
|
+
* Read methods (get, has, entries, etc.) must be bound to the underlying
|
|
280
|
+
* Headers instance because they access private #headersList slots.
|
|
124
281
|
*/
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
282
|
+
function freezeHeaders(source) {
|
|
283
|
+
const copy = new Headers(source);
|
|
284
|
+
return new Proxy(copy, { get(target, prop) {
|
|
285
|
+
if (typeof prop === "string" && MUTATING_METHODS.has(prop)) return () => {
|
|
286
|
+
throw new Error(`[timber] getHeaders() returns a read-only Headers object. Calling .${prop}() is not allowed. Use ctx.requestHeaders in middleware to inject headers for downstream components.`);
|
|
287
|
+
};
|
|
288
|
+
const value = Reflect.get(target, prop);
|
|
289
|
+
if (typeof value === "function") return value.bind(target);
|
|
290
|
+
return value;
|
|
291
|
+
} });
|
|
132
292
|
}
|
|
133
293
|
//#endregion
|
|
134
294
|
//#region src/server/cookie-parsing.ts
|
|
@@ -261,8 +421,8 @@ function parseSetCookie(header) {
|
|
|
261
421
|
* Cookie Context — per-request cookie API and on-the-wire helpers.
|
|
262
422
|
*
|
|
263
423
|
* Split out of `request-context.ts` (TIM-853) so the cookie subsystem
|
|
264
|
-
* — encoding contract, options grammar, parser, serializer, RYW map
|
|
265
|
-
*
|
|
424
|
+
* — encoding contract, options grammar, parser, serializer, and RYW map
|
|
425
|
+
* — lives in one file. The headers/scope/params APIs
|
|
266
426
|
* stay in `request-context.ts` and call into this module via the
|
|
267
427
|
* exported helpers.
|
|
268
428
|
*
|
|
@@ -295,18 +455,22 @@ function getCookieJar() {
|
|
|
295
455
|
const map = store.parsedCookies;
|
|
296
456
|
return {
|
|
297
457
|
get(name) {
|
|
458
|
+
noteCookieReadForReactCache(name);
|
|
298
459
|
return map.get(name);
|
|
299
460
|
},
|
|
300
461
|
has(name) {
|
|
462
|
+
noteCookieReadForReactCache(name);
|
|
301
463
|
return map.has(name);
|
|
302
464
|
},
|
|
303
465
|
getAll() {
|
|
466
|
+
noteCookieReadForReactCache("*");
|
|
304
467
|
return Array.from(map.entries()).map(([name, value]) => ({
|
|
305
468
|
name,
|
|
306
469
|
value
|
|
307
470
|
}));
|
|
308
471
|
},
|
|
309
472
|
get size() {
|
|
473
|
+
noteCookieReadForReactCache("*");
|
|
310
474
|
return map.size;
|
|
311
475
|
},
|
|
312
476
|
set(name, value, options) {
|
|
@@ -332,6 +496,7 @@ function getCookieJar() {
|
|
|
332
496
|
options: opts
|
|
333
497
|
});
|
|
334
498
|
map.set(name, raw ? wireValue : value);
|
|
499
|
+
warnIfCachedCookieIsWritten(name);
|
|
335
500
|
},
|
|
336
501
|
setFromHeaders(headers) {
|
|
337
502
|
assertMutable(store, "setFromHeaders");
|
|
@@ -364,6 +529,7 @@ function getCookieJar() {
|
|
|
364
529
|
options: opts
|
|
365
530
|
});
|
|
366
531
|
map.delete(name);
|
|
532
|
+
warnIfCachedCookieIsWritten(name);
|
|
367
533
|
},
|
|
368
534
|
clear() {
|
|
369
535
|
assertMutable(store, "clear");
|
|
@@ -378,8 +544,10 @@ function getCookieJar() {
|
|
|
378
544
|
}
|
|
379
545
|
});
|
|
380
546
|
map.clear();
|
|
547
|
+
warnIfCachedCookieIsWritten("*");
|
|
381
548
|
},
|
|
382
549
|
toString() {
|
|
550
|
+
noteCookieReadForReactCache("*");
|
|
383
551
|
return Array.from(map.entries()).map(([name, value]) => `${name}=${encodeURIComponent(value)}`).join("; ");
|
|
384
552
|
}
|
|
385
553
|
};
|
|
@@ -399,32 +567,6 @@ var DEFAULT_COOKIE_OPTIONS = {
|
|
|
399
567
|
sameSite: "lax"
|
|
400
568
|
};
|
|
401
569
|
/**
|
|
402
|
-
* Per-request seed map of cookie name → value, registered out-of-band so the
|
|
403
|
-
* pipeline's `runWithRequestContext` call can pick it up without changing
|
|
404
|
-
* the function's calling convention. Stored in a WeakMap keyed by the
|
|
405
|
-
* synthetic Request object built for the rerender path, so the seed lives
|
|
406
|
-
* exactly as long as the request and is collected with it.
|
|
407
|
-
*
|
|
408
|
-
* The seed exists to eliminate the parse/serialize round-trip on the no-JS
|
|
409
|
-
* form-rerender path — see ONGOING_SECURITY.md H-3 (TIM-868). The action's
|
|
410
|
-
* post-mutation RYW snapshot is threaded directly into the rerender scope's
|
|
411
|
-
* `parsedCookies` map, bypassing `parseCookieHeader` entirely.
|
|
412
|
-
*/
|
|
413
|
-
var seededRequestCookies = /* @__PURE__ */ new WeakMap();
|
|
414
|
-
/**
|
|
415
|
-
* Pop the seed (if any) for `req` and return it. Called from
|
|
416
|
-
* `runWithRequestContext` exactly once per request — the seed is consumed
|
|
417
|
-
* eagerly so it cannot leak into a hypothetical future re-use of the same
|
|
418
|
-
* Request reference.
|
|
419
|
-
*
|
|
420
|
-
* @internal — framework use only.
|
|
421
|
-
*/
|
|
422
|
-
function consumeSeededCookies(req) {
|
|
423
|
-
const seed = seededRequestCookies.get(req);
|
|
424
|
-
if (seed) seededRequestCookies.delete(req);
|
|
425
|
-
return seed;
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
570
|
* Collect all Set-Cookie headers from the cookie jar.
|
|
429
571
|
* Called by the framework at flush time to apply cookies to the response.
|
|
430
572
|
*
|
|
@@ -456,217 +598,11 @@ function setRaw(store, readMap, name, value, options) {
|
|
|
456
598
|
value,
|
|
457
599
|
options
|
|
458
600
|
});
|
|
601
|
+
warnIfCachedCookieIsWritten(name);
|
|
459
602
|
if (options.maxAge === 0) readMap.delete(name);
|
|
460
603
|
else readMap.set(name, safeDecodeCookieValue(value));
|
|
461
604
|
}
|
|
462
605
|
//#endregion
|
|
463
|
-
//#region src/server/request-context.ts
|
|
464
|
-
/**
|
|
465
|
-
* Returns a read-only view of the current request's headers.
|
|
466
|
-
*
|
|
467
|
-
* Available in middleware, access checks, server components, and server actions.
|
|
468
|
-
* Throws if called outside a request context (security principle #2: no global fallback).
|
|
469
|
-
*/
|
|
470
|
-
function getHeaders() {
|
|
471
|
-
const store = requestContextAls.getStore();
|
|
472
|
-
if (!store) throw new Error("[timber] getHeaders() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
473
|
-
return store.headers;
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Returns the value of a single request header, or undefined if absent.
|
|
477
|
-
*
|
|
478
|
-
* Thin wrapper over `getHeaders().get(name)` for the common case where
|
|
479
|
-
* you need exactly one header.
|
|
480
|
-
*
|
|
481
|
-
* @internal — not part of the public API. Use `getHeaders().get(name)` instead.
|
|
482
|
-
*/
|
|
483
|
-
function getHeader(name) {
|
|
484
|
-
return getHeaders().get(name) ?? void 0;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Returns the current request's raw URLSearchParams.
|
|
488
|
-
*
|
|
489
|
-
* @internal — not part of the public API. Use `defineSearchParams().get()` instead.
|
|
490
|
-
*/
|
|
491
|
-
function getSearchParams() {
|
|
492
|
-
const store = requestContextAls.getStore();
|
|
493
|
-
if (!store) throw new Error("[timber] getSearchParams() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
494
|
-
return store.searchParams;
|
|
495
|
-
}
|
|
496
|
-
_setGetSearchParamsFn(getSearchParams);
|
|
497
|
-
_setGetSegmentParamsFn(getSegmentParams);
|
|
498
|
-
/**
|
|
499
|
-
* Returns the current request's coerced segment params.
|
|
500
|
-
*
|
|
501
|
-
* The optional `segmentPath` argument exists only for TypeScript narrowing —
|
|
502
|
-
* it does not affect the runtime return value. Pass a segment path
|
|
503
|
-
* (e.g. `'/(browse)/[artistSlug]/[year]'`) to narrow the return type to the
|
|
504
|
-
* exact params shape for that segment.
|
|
505
|
-
*
|
|
506
|
-
* Without an argument, returns all coerced params with optional types.
|
|
507
|
-
*
|
|
508
|
-
* See design/41-global-params.md §Function Signatures
|
|
509
|
-
*/
|
|
510
|
-
function getSegmentParams(segmentPath) {
|
|
511
|
-
const store = requestContextAls.getStore();
|
|
512
|
-
if (!store) throw new Error("[timber] getSegmentParams() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
513
|
-
if (!store.segmentParams) throw new Error("[timber] getSegmentParams() called before route matching completed. Segment params are not available until after the route is matched.");
|
|
514
|
-
const slotResolved = resolveSegmentParams(store.segmentParams, store.slotParams, segmentPath);
|
|
515
|
-
if (slotResolved !== store.segmentParams) return slotResolved;
|
|
516
|
-
if (segmentPath && isDebug() && store.matchedSegmentPath) {
|
|
517
|
-
const expected = extractDynamicSegments(segmentPath);
|
|
518
|
-
const actual = extractDynamicSegments(store.matchedSegmentPath);
|
|
519
|
-
const missing = expected.filter((s) => !actual.includes(s));
|
|
520
|
-
if (missing.length > 0) console.warn(`[timber] getSegmentParams('${segmentPath}') called but current segment is '${store.matchedSegmentPath}'\n Missing params: ${missing.join(", ")}\n These will be undefined at runtime despite the type annotation.`);
|
|
521
|
-
}
|
|
522
|
-
return store.segmentParams;
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Set the segment params on the current request context.
|
|
526
|
-
* Called by the pipeline after route matching and param coercion.
|
|
527
|
-
*
|
|
528
|
-
* @internal — framework use only
|
|
529
|
-
*/
|
|
530
|
-
function setSegmentParams(params) {
|
|
531
|
-
const store = requestContextAls.getStore();
|
|
532
|
-
if (!store) throw new Error("[timber] setSegmentParams() called outside of a request context.");
|
|
533
|
-
store.segmentParams = params;
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Store the matched segment path (e.g. '/(browse)/[artistSlug]/[year]') for
|
|
537
|
-
* dev-mode validation in getSegmentParams().
|
|
538
|
-
*
|
|
539
|
-
* @internal — framework use only
|
|
540
|
-
*/
|
|
541
|
-
function setMatchedSegmentPath(segmentPath) {
|
|
542
|
-
const store = requestContextAls.getStore();
|
|
543
|
-
if (!store) return;
|
|
544
|
-
store.matchedSegmentPath = segmentPath;
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Extract dynamic segment names from a route pattern.
|
|
548
|
-
* '/[artistSlug]/[year]' → ['artistSlug', 'year']
|
|
549
|
-
* '/docs/[...slug]' → ['slug']
|
|
550
|
-
*/
|
|
551
|
-
function extractDynamicSegments(route) {
|
|
552
|
-
const segments = [];
|
|
553
|
-
const re = /\[{1,2}\.{0,3}(\w+)\]{1,2}/g;
|
|
554
|
-
let m;
|
|
555
|
-
while ((m = re.exec(route)) !== null) segments.push(m[1]);
|
|
556
|
-
return segments;
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Returns the raw search string from the current request URL (e.g. "?foo=bar").
|
|
560
|
-
* Synchronous — safe for use in `redirect()` which throws synchronously.
|
|
561
|
-
*
|
|
562
|
-
* Returns empty string if called outside a request context (non-throwing for
|
|
563
|
-
* use in redirect's optional preserveSearchParams path).
|
|
564
|
-
*
|
|
565
|
-
* @internal — used by redirect() for preserveSearchParams support.
|
|
566
|
-
*/
|
|
567
|
-
function getRequestSearchString() {
|
|
568
|
-
return requestContextAls.getStore()?.searchString ?? "";
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Run a callback within a request context. Used by the pipeline to establish
|
|
572
|
-
* per-request ALS scope so that `getHeaders()` and `getCookies()` work.
|
|
573
|
-
*
|
|
574
|
-
* If the request was previously registered via `seedRequestCookies`, the
|
|
575
|
-
* resulting context's `parsedCookies` map is initialized from the seed and
|
|
576
|
-
* the raw `cookieHeader` is left empty — `parseCookieHeader` is never called
|
|
577
|
-
* for that request. This is the no-JS form-rerender path. See TIM-868.
|
|
578
|
-
*
|
|
579
|
-
* @param req - The incoming Request object.
|
|
580
|
-
* @param fn - The function to run within the request context.
|
|
581
|
-
*/
|
|
582
|
-
function runWithRequestContext(req, fn) {
|
|
583
|
-
const originalCopy = new Headers(req.headers);
|
|
584
|
-
const appVisible = appVisibleSearch(new URL(req.url));
|
|
585
|
-
const seed = consumeSeededCookies(req);
|
|
586
|
-
const store = {
|
|
587
|
-
headers: freezeHeaders(req.headers),
|
|
588
|
-
originalHeaders: originalCopy,
|
|
589
|
-
cookieHeader: seed ? "" : req.headers.get("cookie") ?? "",
|
|
590
|
-
parsedCookies: seed,
|
|
591
|
-
searchParams: appVisible.params,
|
|
592
|
-
searchString: appVisible.search,
|
|
593
|
-
cookieJar: /* @__PURE__ */ new Map(),
|
|
594
|
-
flushed: false,
|
|
595
|
-
mutableContext: false
|
|
596
|
-
};
|
|
597
|
-
return requestContextAls.run(store, fn);
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Enable cookie mutation for the current context. Called by the framework
|
|
601
|
-
* when entering middleware.ts, server actions, or route.ts handlers.
|
|
602
|
-
*
|
|
603
|
-
* See design/29-cookies.md §"Context Tracking"
|
|
604
|
-
*/
|
|
605
|
-
function setMutableCookieContext(mutable) {
|
|
606
|
-
const store = requestContextAls.getStore();
|
|
607
|
-
if (store) store.mutableContext = mutable;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Mark the response as flushed (headers committed). After this point,
|
|
611
|
-
* cookie mutations log a warning instead of throwing.
|
|
612
|
-
*
|
|
613
|
-
* See design/29-cookies.md §"Streaming Constraint: Post-Flush Cookie Warning"
|
|
614
|
-
*/
|
|
615
|
-
function markResponseFlushed() {
|
|
616
|
-
const store = requestContextAls.getStore();
|
|
617
|
-
if (store) store.flushed = true;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Apply middleware-injected request headers to the current request context.
|
|
621
|
-
*
|
|
622
|
-
* Called by the pipeline after middleware.ts runs. Merges overlay headers
|
|
623
|
-
* on top of the original request headers so downstream code (access.ts,
|
|
624
|
-
* server components, server actions) sees them via `getHeaders()`.
|
|
625
|
-
*
|
|
626
|
-
* The original request headers are never mutated — a new frozen Headers
|
|
627
|
-
* object is created with the overlay applied on top.
|
|
628
|
-
*
|
|
629
|
-
* See design/07-routing.md §"Request Header Injection"
|
|
630
|
-
*/
|
|
631
|
-
function applyRequestHeaderOverlay(overlay) {
|
|
632
|
-
const store = requestContextAls.getStore();
|
|
633
|
-
if (!store) throw new Error("[timber] applyRequestHeaderOverlay() called outside of a request context.");
|
|
634
|
-
let hasOverlay = false;
|
|
635
|
-
overlay.forEach(() => {
|
|
636
|
-
hasOverlay = true;
|
|
637
|
-
});
|
|
638
|
-
if (!hasOverlay) return;
|
|
639
|
-
const merged = new Headers(store.originalHeaders);
|
|
640
|
-
overlay.forEach((value, key) => {
|
|
641
|
-
merged.set(key, value);
|
|
642
|
-
});
|
|
643
|
-
store.headers = freezeHeaders(merged);
|
|
644
|
-
}
|
|
645
|
-
var MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
646
|
-
"set",
|
|
647
|
-
"append",
|
|
648
|
-
"delete"
|
|
649
|
-
]);
|
|
650
|
-
/**
|
|
651
|
-
* Wrap a Headers object in a Proxy that throws on mutating methods.
|
|
652
|
-
* Object.freeze doesn't work on Headers (native internal slots), so we
|
|
653
|
-
* intercept property access and reject set/append/delete at runtime.
|
|
654
|
-
*
|
|
655
|
-
* Read methods (get, has, entries, etc.) must be bound to the underlying
|
|
656
|
-
* Headers instance because they access private #headersList slots.
|
|
657
|
-
*/
|
|
658
|
-
function freezeHeaders(source) {
|
|
659
|
-
const copy = new Headers(source);
|
|
660
|
-
return new Proxy(copy, { get(target, prop) {
|
|
661
|
-
if (typeof prop === "string" && MUTATING_METHODS.has(prop)) return () => {
|
|
662
|
-
throw new Error(`[timber] getHeaders() returns a read-only Headers object. Calling .${prop}() is not allowed. Use ctx.requestHeaders in middleware to inject headers for downstream components.`);
|
|
663
|
-
};
|
|
664
|
-
const value = Reflect.get(target, prop);
|
|
665
|
-
if (typeof value === "function") return value.bind(target);
|
|
666
|
-
return value;
|
|
667
|
-
} });
|
|
668
|
-
}
|
|
669
|
-
//#endregion
|
|
670
606
|
//#region src/server/primitives.ts
|
|
671
607
|
/**
|
|
672
608
|
* Check if a value is JSON-serializable without data loss.
|
|
@@ -715,36 +651,13 @@ function warnIfNotSerializable(data, callerName) {
|
|
|
715
651
|
if (!isDebug()) return;
|
|
716
652
|
if (data === void 0) return;
|
|
717
653
|
const issue = findNonSerializable(data);
|
|
718
|
-
if (issue) console.warn(`[timber] ${callerName}: ${issue}. Data passed to deny()
|
|
719
|
-
}
|
|
720
|
-
var DENY_BRAND = Symbol.for("timber:signal.deny");
|
|
721
|
-
var REDIRECT_BRAND = Symbol.for("timber:signal.redirect");
|
|
722
|
-
var RENDER_ERROR_BRAND = Symbol.for("timber:signal.render-error");
|
|
723
|
-
var SSR_STREAM_ERROR_BRAND = Symbol.for("timber:signal.ssr-stream-error");
|
|
724
|
-
function hasBrand(value, brand) {
|
|
725
|
-
return typeof value === "object" && value !== null && brand in value;
|
|
726
|
-
}
|
|
727
|
-
/** True for any `DenySignal`, regardless of which module evaluation created it. */
|
|
728
|
-
function isDenySignal(error) {
|
|
729
|
-
return hasBrand(error, DENY_BRAND);
|
|
730
|
-
}
|
|
731
|
-
/** True for any `RedirectSignal`, regardless of which module evaluation created it. */
|
|
732
|
-
function isRedirectSignal(error) {
|
|
733
|
-
return hasBrand(error, REDIRECT_BRAND);
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* True for any `SsrStreamError`. This one also crosses Vite environments:
|
|
737
|
-
* it is thrown in SSR and caught in RSC, which are separate module graphs in
|
|
738
|
-
* the same process — the brand is the only identity the two share.
|
|
739
|
-
*/
|
|
740
|
-
function isSsrStreamError(error) {
|
|
741
|
-
return hasBrand(error, SSR_STREAM_ERROR_BRAND);
|
|
654
|
+
if (issue) console.warn(`[timber] ${callerName}: ${issue}. Data passed to deny() must be JSON-serializable because the post-flush path uses JSON.stringify, not React Flight.`);
|
|
742
655
|
}
|
|
743
656
|
/**
|
|
744
657
|
* Render-phase signal thrown by `deny()`. Caught by the framework to produce
|
|
745
658
|
* the correct HTTP status code (segment context) or graceful degradation (slot context).
|
|
746
659
|
*
|
|
747
|
-
* Detect with `isDenySignal()`, never `instanceof` — see "Signal branding"
|
|
660
|
+
* Detect with `isDenySignal()`, never `instanceof` — see "Signal branding" in signal-identity.ts.
|
|
748
661
|
*/
|
|
749
662
|
var DenySignal = class extends Error {
|
|
750
663
|
[DENY_BRAND] = true;
|
|
@@ -814,9 +727,9 @@ var DenySignal = class extends Error {
|
|
|
814
727
|
* deny(404, { dangerouslyPassData: { resourceId: params.id } }) // explicit client opt-in
|
|
815
728
|
* ```
|
|
816
729
|
*
|
|
817
|
-
* Accepts any 4xx or 5xx status code
|
|
818
|
-
*
|
|
819
|
-
*
|
|
730
|
+
* Accepts any 4xx or 5xx status code — it is also how a page renders a 5xx
|
|
731
|
+
* status file with data. An unplanned throw needs no primitive: it renders
|
|
732
|
+
* the error page with a 500.
|
|
820
733
|
*
|
|
821
734
|
* @param status - HTTP status code (4xx or 5xx). Default: 403.
|
|
822
735
|
* @param options - Optional message and/or data to pass to the client.
|
|
@@ -832,7 +745,7 @@ function deny(status, options) {
|
|
|
832
745
|
* Render-phase signal thrown by `redirect()` and `redirectExternal()`.
|
|
833
746
|
* Caught by the framework to produce a 3xx response or client-side navigation.
|
|
834
747
|
*
|
|
835
|
-
* Detect with `isRedirectSignal()`, never `instanceof` — see "Signal branding"
|
|
748
|
+
* Detect with `isRedirectSignal()`, never `instanceof` — see "Signal branding" in signal-identity.ts.
|
|
836
749
|
*/
|
|
837
750
|
var RedirectSignal = class extends Error {
|
|
838
751
|
[REDIRECT_BRAND] = true;
|
|
@@ -846,24 +759,6 @@ var RedirectSignal = class extends Error {
|
|
|
846
759
|
}
|
|
847
760
|
};
|
|
848
761
|
/**
|
|
849
|
-
* Returns true if the error is a framework control-flow signal (RedirectSignal
|
|
850
|
-
* or DenySignal) rather than a genuine application error. Works both for direct
|
|
851
|
-
* instances (RSC-side) and deserialized digest errors (SSR cross-boundary).
|
|
852
|
-
*
|
|
853
|
-
* See also: isFrameworkSignalError() in client/browser-dev.ts (client-only).
|
|
854
|
-
*/
|
|
855
|
-
function isControlFlowSignal(error) {
|
|
856
|
-
if (isDenySignal(error) || isRedirectSignal(error)) return true;
|
|
857
|
-
if (error && typeof error === "object") {
|
|
858
|
-
const digest = error.digest;
|
|
859
|
-
if (typeof digest === "string") try {
|
|
860
|
-
const parsed = JSON.parse(digest);
|
|
861
|
-
if (parsed && typeof parsed === "object") return parsed.type === "redirect" || parsed.type === "deny";
|
|
862
|
-
} catch {}
|
|
863
|
-
}
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
762
|
* Redirect to a relative path. Rejects absolute and protocol-relative URLs.
|
|
868
763
|
* Use `redirectExternal()` for external redirects with an allow-list.
|
|
869
764
|
*
|
|
@@ -911,41 +806,6 @@ function redirectExternal(url, allowList, status = 302) {
|
|
|
911
806
|
if (status < 300 || status > 399) throw new Error(`redirectExternal() requires a 3xx status code, got ${status}.`);
|
|
912
807
|
throw new RedirectSignal(validateExternalRedirectUrl(url, allowList), status);
|
|
913
808
|
}
|
|
914
|
-
/**
|
|
915
|
-
* Typed throw for render-phase errors that carry structured context to error boundaries.
|
|
916
|
-
*
|
|
917
|
-
* The `digest` (code + data) is serialized into the RSC stream separately from the
|
|
918
|
-
* Error instance — only the digest crosses the RSC → client boundary.
|
|
919
|
-
*
|
|
920
|
-
* @example
|
|
921
|
-
* ```ts
|
|
922
|
-
* throw new RenderError('PRODUCT_NOT_FOUND', {
|
|
923
|
-
* title: 'Product not found',
|
|
924
|
-
* resourceId: params.id,
|
|
925
|
-
* })
|
|
926
|
-
* ```
|
|
927
|
-
*
|
|
928
|
-
* Detect with `isRenderError()`, never `instanceof` — see "Signal branding" above.
|
|
929
|
-
*/
|
|
930
|
-
var RenderError = class extends Error {
|
|
931
|
-
[RENDER_ERROR_BRAND] = true;
|
|
932
|
-
code;
|
|
933
|
-
digest;
|
|
934
|
-
status;
|
|
935
|
-
constructor(code, data, options) {
|
|
936
|
-
super(`RenderError: ${code}`);
|
|
937
|
-
this.name = "RenderError";
|
|
938
|
-
this.code = code;
|
|
939
|
-
this.digest = {
|
|
940
|
-
code,
|
|
941
|
-
data
|
|
942
|
-
};
|
|
943
|
-
warnIfNotSerializable(data, "RenderError");
|
|
944
|
-
const status = options?.status ?? 500;
|
|
945
|
-
if (status < 400 || status > 599) throw new Error(`RenderError status must be 4xx or 5xx, got ${status}.`);
|
|
946
|
-
this.status = status;
|
|
947
|
-
}
|
|
948
|
-
};
|
|
949
809
|
var _waitUntilWarned = false;
|
|
950
810
|
/**
|
|
951
811
|
* Register a promise to be kept alive after the response is sent.
|
|
@@ -970,6 +830,376 @@ function waitUntil(promise) {
|
|
|
970
830
|
}
|
|
971
831
|
}
|
|
972
832
|
//#endregion
|
|
973
|
-
|
|
833
|
+
//#region src/server/form-data.ts
|
|
834
|
+
/**
|
|
835
|
+
* FormData preprocessing — schema-agnostic conversion of FormData to typed objects.
|
|
836
|
+
*
|
|
837
|
+
* FormData is all strings. Schema validation expects typed values. This module
|
|
838
|
+
* bridges the gap with intelligent coercion that runs *before* schema validation.
|
|
839
|
+
*
|
|
840
|
+
* Inspired by zod-form-data, but schema-agnostic — works with any Standard Schema
|
|
841
|
+
* library (Zod, Valibot, ArkType).
|
|
842
|
+
*
|
|
843
|
+
* See design/08-forms-and-actions.md §"parseFormData() and coerce helpers"
|
|
844
|
+
*/
|
|
845
|
+
/**
|
|
846
|
+
* Convert FormData into a plain object with intelligent coercion.
|
|
847
|
+
*
|
|
848
|
+
* Handles:
|
|
849
|
+
* - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: ["js", "ts"] }`
|
|
850
|
+
* - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: "Alice" } }`
|
|
851
|
+
* - **Empty strings → undefined**: Enables `.optional()` semantics in schemas
|
|
852
|
+
* - **Empty Files → undefined**: File inputs with no selection become `undefined`
|
|
853
|
+
* - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded
|
|
854
|
+
*/
|
|
855
|
+
function parseFormData(formData) {
|
|
856
|
+
const flat = {};
|
|
857
|
+
for (const key of new Set(formData.keys())) {
|
|
858
|
+
if (key.startsWith("$ACTION_")) continue;
|
|
859
|
+
if (DANGEROUS_KEYS.has(key)) continue;
|
|
860
|
+
const processed = formData.getAll(key).map(normalizeValue);
|
|
861
|
+
if (processed.length === 1) flat[key] = processed[0];
|
|
862
|
+
else flat[key] = processed.filter((v) => v !== void 0 && v !== "");
|
|
863
|
+
}
|
|
864
|
+
return expandDotPaths(flat);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Normalize a single FormData entry value.
|
|
868
|
+
* - Empty File objects (no selection) → undefined
|
|
869
|
+
* - Strings pass through as-is (empty strings stay as empty strings so
|
|
870
|
+
* schema validators like `z.string().min(1, 'Required')` can produce
|
|
871
|
+
* their custom error messages instead of a generic type mismatch)
|
|
872
|
+
* - Everything else passes through as-is
|
|
873
|
+
*/
|
|
874
|
+
function normalizeValue(value) {
|
|
875
|
+
if (value instanceof File && value.size === 0 && value.name === "") return;
|
|
876
|
+
return value;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Expand dot-notation keys into nested objects.
|
|
880
|
+
* `{ "user.name": "Alice", "user.age": "30" }` → `{ user: { name: "Alice", age: "30" } }`
|
|
881
|
+
*
|
|
882
|
+
* Keys without dots are left as-is. Bracket notation (e.g. `items[0]`) is NOT
|
|
883
|
+
* supported — use dot notation (`items.0`) instead.
|
|
884
|
+
*/
|
|
885
|
+
function expandDotPaths(flat) {
|
|
886
|
+
const result = {};
|
|
887
|
+
let hasDotPaths = false;
|
|
888
|
+
for (const key of Object.keys(flat)) if (key.includes(".")) {
|
|
889
|
+
hasDotPaths = true;
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
if (!hasDotPaths) return flat;
|
|
893
|
+
for (const [key, value] of Object.entries(flat)) {
|
|
894
|
+
const parts = key.split(".");
|
|
895
|
+
if (parts.some((p) => DANGEROUS_KEYS.has(p))) continue;
|
|
896
|
+
if (parts.length === 1) {
|
|
897
|
+
result[parts[0]] = value;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
let current = result;
|
|
901
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
902
|
+
const part = parts[i];
|
|
903
|
+
if (current[part] === void 0 || current[part] === null) current[part] = {};
|
|
904
|
+
if (typeof current[part] !== "object" || current[part] instanceof File) current[part] = {};
|
|
905
|
+
current = current[part];
|
|
906
|
+
}
|
|
907
|
+
current[parts[parts.length - 1]] = value;
|
|
908
|
+
}
|
|
909
|
+
return result;
|
|
910
|
+
}
|
|
911
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__"]);
|
|
912
|
+
/**
|
|
913
|
+
* Schema-agnostic coercion primitives for common FormData patterns.
|
|
914
|
+
*
|
|
915
|
+
* These are plain transform functions — they compose with any schema library's
|
|
916
|
+
* `transform`/`preprocess` pipeline:
|
|
917
|
+
*
|
|
918
|
+
* ```ts
|
|
919
|
+
* // Zod
|
|
920
|
+
* z.preprocess(coerce.number, z.number())
|
|
921
|
+
* // Valibot
|
|
922
|
+
* v.pipe(v.unknown(), v.transform(coerce.number), v.number())
|
|
923
|
+
* ```
|
|
924
|
+
*/
|
|
925
|
+
var coerce = {
|
|
926
|
+
/**
|
|
927
|
+
* Coerce an empty string to undefined for optional text fields.
|
|
928
|
+
* Use with `.optional()` schemas where an empty input means "not provided".
|
|
929
|
+
*
|
|
930
|
+
* ```ts
|
|
931
|
+
* // Zod
|
|
932
|
+
* z.unknown().transform(coerce.text).pipe(z.string().optional())
|
|
933
|
+
* // Valibot
|
|
934
|
+
* v.pipe(v.unknown(), v.transform(coerce.text), v.optional(v.string()))
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
text(value) {
|
|
938
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
939
|
+
if (typeof value === "string") return value;
|
|
940
|
+
},
|
|
941
|
+
/**
|
|
942
|
+
* Coerce a string to a number.
|
|
943
|
+
* - `"42"` → `42`
|
|
944
|
+
* - `"3.14"` → `3.14`
|
|
945
|
+
* - `""` / `undefined` / `null` → `undefined`
|
|
946
|
+
* - Non-numeric strings → `undefined` (schema validation will catch this)
|
|
947
|
+
*/
|
|
948
|
+
number(value) {
|
|
949
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
950
|
+
if (typeof value === "number") return value;
|
|
951
|
+
if (typeof value !== "string") return void 0;
|
|
952
|
+
const num = Number(value);
|
|
953
|
+
if (Number.isNaN(num)) return void 0;
|
|
954
|
+
return num;
|
|
955
|
+
},
|
|
956
|
+
/**
|
|
957
|
+
* Coerce a checkbox value to a boolean.
|
|
958
|
+
* HTML checkboxes submit "on" when checked and are absent when unchecked.
|
|
959
|
+
* - `"on"` / any truthy string → `true`
|
|
960
|
+
* - `undefined` / `null` / `""` → `false`
|
|
961
|
+
*/
|
|
962
|
+
checkbox(value) {
|
|
963
|
+
if (value === void 0 || value === null || value === "") return false;
|
|
964
|
+
if (typeof value === "boolean") return value;
|
|
965
|
+
return typeof value === "string" && value.length > 0;
|
|
966
|
+
},
|
|
967
|
+
/**
|
|
968
|
+
* Parse a JSON string into an object.
|
|
969
|
+
* - Valid JSON string → parsed object
|
|
970
|
+
* - `""` / `undefined` / `null` → `undefined`
|
|
971
|
+
* - Invalid JSON → `undefined` (schema validation will catch this)
|
|
972
|
+
*/
|
|
973
|
+
json(value) {
|
|
974
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
975
|
+
if (typeof value !== "string") return value;
|
|
976
|
+
try {
|
|
977
|
+
return JSON.parse(value);
|
|
978
|
+
} catch {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
/**
|
|
983
|
+
* Coerce a date string to a Date object.
|
|
984
|
+
* Handles `<input type="date">` (`"2024-01-15"`), `<input type="datetime-local">`
|
|
985
|
+
* (`"2024-01-15T10:30"`), and full ISO 8601 strings.
|
|
986
|
+
* - Valid date string → `Date`
|
|
987
|
+
* - `""` / `undefined` / `null` → `undefined`
|
|
988
|
+
* - Invalid date strings → `undefined` (schema validation will catch this)
|
|
989
|
+
* - Impossible dates that `new Date()` silently normalizes (e.g. Feb 31) → `undefined`
|
|
990
|
+
*/
|
|
991
|
+
date(value) {
|
|
992
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
993
|
+
if (value instanceof Date) return value;
|
|
994
|
+
if (typeof value !== "string") return void 0;
|
|
995
|
+
const date = new Date(value);
|
|
996
|
+
if (Number.isNaN(date.getTime())) return void 0;
|
|
997
|
+
const ymdMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
998
|
+
if (ymdMatch) {
|
|
999
|
+
const inputYear = Number(ymdMatch[1]);
|
|
1000
|
+
const inputMonth = Number(ymdMatch[2]);
|
|
1001
|
+
const inputDay = Number(ymdMatch[3]);
|
|
1002
|
+
const isUTC = value.length === 10 || value.endsWith("Z");
|
|
1003
|
+
const parsedYear = isUTC ? date.getUTCFullYear() : date.getFullYear();
|
|
1004
|
+
const parsedMonth = isUTC ? date.getUTCMonth() + 1 : date.getMonth() + 1;
|
|
1005
|
+
const parsedDay = isUTC ? date.getUTCDate() : date.getDate();
|
|
1006
|
+
if (inputYear !== parsedYear || inputMonth !== parsedMonth || inputDay !== parsedDay) return;
|
|
1007
|
+
}
|
|
1008
|
+
return date;
|
|
1009
|
+
},
|
|
1010
|
+
/**
|
|
1011
|
+
* Create a File coercion function with optional size and mime type validation.
|
|
1012
|
+
* Returns the File if valid, `undefined` otherwise.
|
|
1013
|
+
*
|
|
1014
|
+
* ```ts
|
|
1015
|
+
* // Basic — just checks it's a real File
|
|
1016
|
+
* z.preprocess(coerce.file(), z.instanceof(File))
|
|
1017
|
+
*
|
|
1018
|
+
* // With constraints
|
|
1019
|
+
* z.preprocess(
|
|
1020
|
+
* coerce.file({ maxSize: 5 * 1024 * 1024, accept: ['image/png', 'image/jpeg'] }),
|
|
1021
|
+
* z.instanceof(File)
|
|
1022
|
+
* )
|
|
1023
|
+
* ```
|
|
1024
|
+
*/
|
|
1025
|
+
file(options) {
|
|
1026
|
+
return (value) => {
|
|
1027
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
1028
|
+
if (!(value instanceof File)) return void 0;
|
|
1029
|
+
if (value.size === 0 && value.name === "") return void 0;
|
|
1030
|
+
if (options?.maxSize !== void 0 && value.size > options.maxSize) return;
|
|
1031
|
+
if (options?.accept !== void 0 && !options.accept.includes(value.type)) return;
|
|
1032
|
+
return value;
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/server/actions.ts
|
|
1038
|
+
/**
|
|
1039
|
+
* Server action primitives: revalidatePath, revalidateTag, and the action handler.
|
|
1040
|
+
*
|
|
1041
|
+
* - revalidatePath(path) re-renders the route at that path and returns the RSC
|
|
1042
|
+
* flight payload for inline reconciliation. Server actions only.
|
|
1043
|
+
* - revalidateTag(tag) invalidates timber.cache entries by tag. Callable from
|
|
1044
|
+
* any server code: deferred to the end of the action inside one, immediate
|
|
1045
|
+
* anywhere else.
|
|
1046
|
+
*
|
|
1047
|
+
* The action handler processes incoming action requests, validates CSRF,
|
|
1048
|
+
* enforces body limits, executes the action, and returns the response
|
|
1049
|
+
* (with piggybacked RSC payload if revalidatePath was called).
|
|
1050
|
+
*
|
|
1051
|
+
* See design/08-forms-and-actions.md
|
|
1052
|
+
*/
|
|
1053
|
+
/**
|
|
1054
|
+
* Thrown by a `RevalidateRenderer` that decided not to render: the path
|
|
1055
|
+
* was rejected, its params failed coercion, or its middleware answered
|
|
1056
|
+
* with a `Response` that cannot become an element tree. `executeAction`
|
|
1057
|
+
* drops the revalidation — the action result is still delivered, with no
|
|
1058
|
+
* `_tree` piggybacked — and, because the drop was a decision, does not
|
|
1059
|
+
* return it as an error to report. Identified by `instanceof`, so the
|
|
1060
|
+
* message text never reaches clients.
|
|
1061
|
+
*/
|
|
1062
|
+
var RevalidationDropped = class extends Error {
|
|
1063
|
+
constructor(message) {
|
|
1064
|
+
super(message);
|
|
1065
|
+
this.name = "RevalidationDropped";
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* Re-render the route at `path` and include the RSC flight payload in the
|
|
1070
|
+
* action response. The client reconciles inline — no separate fetch needed.
|
|
1071
|
+
*
|
|
1072
|
+
* Only callable inside a server action: it attaches a render to the action
|
|
1073
|
+
* response, and nothing else has one. To refresh cached data from a
|
|
1074
|
+
* `route.ts` handler, a webhook, or a job, use `revalidateTag()` or
|
|
1075
|
+
* `cache.invalidate()`.
|
|
1076
|
+
*
|
|
1077
|
+
* @param path - The path to re-render (e.g. '/dashboard', '/todos').
|
|
1078
|
+
*/
|
|
1079
|
+
function revalidatePath(path) {
|
|
1080
|
+
const state = revalidationAls.getStore();
|
|
1081
|
+
if (!state || state.closed) throw new Error("revalidatePath() can only be called inside a server action: it re-renders the route into the action response, and there is no action response here. To refresh cached data from a route handler or other server code, call revalidateTag() or cache.invalidate() for the tags the route reads.");
|
|
1082
|
+
if (!state.paths.includes(path)) state.paths.push(path);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Invalidate all timber.cache entries tagged with `tag`, and tombstone the
|
|
1086
|
+
* pre-rendered component seeds that carry it.
|
|
1087
|
+
* Does not return a payload — the next request for an invalidated entry re-executes.
|
|
1088
|
+
*
|
|
1089
|
+
* Callable from any server code. Inside a server action the invalidation is
|
|
1090
|
+
* deferred until the action body returns, so a `revalidatePath()` render in
|
|
1091
|
+
* the same action reads fresh data; the returned promise resolves at once.
|
|
1092
|
+
* Anywhere else — a `route.ts` webhook, a job, or background work an action
|
|
1093
|
+
* started that runs after the action body returned — it is
|
|
1094
|
+
* `cache.invalidate({ tag })`, and the promise settles when the invalidation
|
|
1095
|
+
* has. Await it before responding.
|
|
1096
|
+
*
|
|
1097
|
+
* @param tag - The cache tag to invalidate (e.g. 'products', 'user:123').
|
|
1098
|
+
*/
|
|
1099
|
+
function revalidateTag(tag) {
|
|
1100
|
+
const state = revalidationAls.getStore();
|
|
1101
|
+
if (!state || state.closed) return cache.invalidate({ tag });
|
|
1102
|
+
if (!state.tags.includes(tag)) state.tags.push(tag);
|
|
1103
|
+
return Promise.resolve();
|
|
1104
|
+
}
|
|
1105
|
+
function stripFragment(path) {
|
|
1106
|
+
const idx = path.indexOf("#");
|
|
1107
|
+
return idx >= 0 ? path.slice(0, idx) : path;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Execute a server action and process revalidation.
|
|
1111
|
+
*
|
|
1112
|
+
* 1. Sets up revalidation state
|
|
1113
|
+
* 2. Calls the action function
|
|
1114
|
+
* 3. Processes revalidateTag calls (invalidates cache entries)
|
|
1115
|
+
* 4. Processes revalidatePath calls (re-renders and captures RSC payload)
|
|
1116
|
+
* 5. Returns the action result + optional RSC payload
|
|
1117
|
+
*
|
|
1118
|
+
* @param actionFn - The server action function to execute.
|
|
1119
|
+
* @param args - Arguments to pass to the action.
|
|
1120
|
+
* @param config - Handler configuration (cache handler, renderer).
|
|
1121
|
+
*/
|
|
1122
|
+
async function executeAction(actionFn, args, config = {}, spanMeta) {
|
|
1123
|
+
const state = {
|
|
1124
|
+
paths: [],
|
|
1125
|
+
tags: []
|
|
1126
|
+
};
|
|
1127
|
+
const revalidationErrors = [];
|
|
1128
|
+
let actionResult;
|
|
1129
|
+
let redirectTo;
|
|
1130
|
+
let redirectStatus;
|
|
1131
|
+
await revalidationAls.run(state, async () => {
|
|
1132
|
+
try {
|
|
1133
|
+
actionResult = await withSpan("timber.action", {
|
|
1134
|
+
...spanMeta?.actionFile ? { "timber.action_file": spanMeta.actionFile } : {},
|
|
1135
|
+
...spanMeta?.actionName ? { "timber.action_name": spanMeta.actionName } : {}
|
|
1136
|
+
}, () => actionFn(...args));
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
if (isRedirectSignal(error)) {
|
|
1139
|
+
redirectTo = error.location;
|
|
1140
|
+
redirectStatus = error.status;
|
|
1141
|
+
} else throw error;
|
|
1142
|
+
} finally {
|
|
1143
|
+
state.closed = true;
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
if (state.tags.length > 0) {
|
|
1147
|
+
const results = await Promise.allSettled(state.tags.map((tag) => cache.invalidate({ tag })));
|
|
1148
|
+
for (const r of results) if (r.status === "rejected") {
|
|
1149
|
+
console.error("[timber] revalidateTag invalidation failed:", r.reason);
|
|
1150
|
+
revalidationErrors.push(r.reason);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
let revalidation;
|
|
1154
|
+
let invalidatedPaths;
|
|
1155
|
+
if (state.paths.length > 0) {
|
|
1156
|
+
const path = state.paths[0];
|
|
1157
|
+
if (config.requestPathname && !revalidationPathMatches(path, config.requestPathname, config.requestSearch)) invalidatedPaths = [stripFragment(path)];
|
|
1158
|
+
else if (config.renderer) try {
|
|
1159
|
+
revalidation = await config.renderer(path);
|
|
1160
|
+
} catch (renderError) {
|
|
1161
|
+
if (isRedirectSignal(renderError)) {
|
|
1162
|
+
redirectTo = renderError.location;
|
|
1163
|
+
redirectStatus = renderError.status;
|
|
1164
|
+
} else if (isDenySignal(renderError)) console.error(`[timber] revalidatePath dropped — target middleware denied (status ${renderError.status})`);
|
|
1165
|
+
else if (renderError instanceof RevalidationDropped) console.error(`[timber] ${renderError.message}`);
|
|
1166
|
+
else {
|
|
1167
|
+
console.error("[timber] revalidatePath render failed:", renderError);
|
|
1168
|
+
revalidationErrors.push(renderError);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return {
|
|
1173
|
+
actionResult,
|
|
1174
|
+
revalidation,
|
|
1175
|
+
invalidatedPaths,
|
|
1176
|
+
revalidationErrors,
|
|
1177
|
+
...redirectTo ? {
|
|
1178
|
+
redirectTo,
|
|
1179
|
+
redirectStatus
|
|
1180
|
+
} : {}
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Check if a `revalidatePath(path)` argument matches the request pathname.
|
|
1185
|
+
/**
|
|
1186
|
+
* Canonicalizes the revalidated path the same way the renderer does so
|
|
1187
|
+
* equivalent paths compare equal. Returns true when the paths match or
|
|
1188
|
+
* when comparison is inconclusive (validation failure → render to be safe).
|
|
1189
|
+
*/
|
|
1190
|
+
function revalidationPathMatches(revalidatedPath, requestPathname, requestSearch) {
|
|
1191
|
+
const hashIdx = revalidatedPath.indexOf("#");
|
|
1192
|
+
const noFragment = hashIdx >= 0 ? revalidatedPath.slice(0, hashIdx) : revalidatedPath;
|
|
1193
|
+
const queryIdx = noFragment.indexOf("?");
|
|
1194
|
+
const pathnameOnly = queryIdx >= 0 ? noFragment.slice(0, queryIdx) : noFragment;
|
|
1195
|
+
const search = queryIdx >= 0 ? noFragment.slice(queryIdx) : "";
|
|
1196
|
+
const canonical = canonicalize(pathnameOnly, true);
|
|
1197
|
+
if (!canonical.ok) return true;
|
|
1198
|
+
if (canonical.pathname !== requestPathname) return false;
|
|
1199
|
+
if (search && search !== (requestSearch ?? "")) return false;
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
1202
|
+
//#endregion
|
|
1203
|
+
export { setMutableCookieContext as C, setMatchedSegmentPath as S, runOutsideReactCacheScope as T, getHeaders as _, parseFormData as a, markResponseFlushed as b, deny as c, waitUntil as d, getCookie as f, getHeader as g, applyRequestHeaderOverlay as h, coerce as i, redirect as l, getSetCookieHeaders as m, revalidatePath as n, DenySignal as o, getCookieJar as p, revalidateTag as r, RedirectSignal as s, executeAction as t, redirectExternal as u, getSearchParams as v, setSegmentParams as w, runWithRequestContext as x, getSegmentParams as y };
|
|
974
1204
|
|
|
975
|
-
//# sourceMappingURL=
|
|
1205
|
+
//# sourceMappingURL=actions-BerlqoXA.js.map
|