@timber-js/app 0.2.0-alpha.185 → 0.2.0-alpha.187
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-O_LsyCE4.js → actions-35jnMdeJ.js} +4 -3
- package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-35jnMdeJ.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
- package/dist/_chunks/als-slots-BEEIPKYm.js +26 -0
- package/dist/_chunks/als-slots-BEEIPKYm.js.map +1 -0
- package/dist/_chunks/base-path-DaQrzbez.js +80 -0
- package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
- package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-DjNrIWRR.js} +8 -14
- package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-CGP6BZKO.js} +92 -14
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
- package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
- package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
- package/dist/_chunks/error-boundary-D-ODYX41.js +353 -0
- package/dist/_chunks/error-boundary-D-ODYX41.js.map +1 -0
- package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
- package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
- package/dist/_chunks/file-cache-DmX7OqZP.js +454 -0
- package/dist/_chunks/file-cache-DmX7OqZP.js.map +1 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
- package/dist/_chunks/{logger-AWfuX-KJ.js → logger-D8xJZXIN.js} +34 -51
- package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
- package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
- package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
- package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-rCinWLiE.js} +8 -2
- package/dist/_chunks/plugin-context-rCinWLiE.js.map +1 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
- package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
- package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
- package/dist/_chunks/rsc-media-type-DRqE_lD_.js +46 -0
- package/dist/_chunks/rsc-media-type-DRqE_lD_.js.map +1 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
- package/dist/_chunks/scanner-C8b0Gcw3.js +1564 -0
- package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js.map +1 -0
- package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -0
- package/dist/_chunks/segment-context-CjOlyB8Y.js +97 -0
- package/dist/_chunks/segment-context-CjOlyB8Y.js.map +1 -0
- package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
- package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
- package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
- package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
- package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
- package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
- package/dist/_chunks/use-segment-params-C4r4BD9T.js +398 -0
- package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
- package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
- package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +37 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/compress-module.d.ts +12 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/fs-identity.d.ts +64 -0
- package/dist/adapters/fs-identity.d.ts.map +1 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -5
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +57 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +13 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/fast-hash.d.ts +5 -0
- package/dist/cache/fast-hash.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cdn/cloudflare-purge.js +30 -0
- package/dist/cdn/cloudflare-purge.js.map +1 -0
- package/dist/cdn/fastly-purge.js +33 -0
- package/dist/cdn/fastly-purge.js.map +1 -0
- package/dist/cdn/index.js +94 -0
- package/dist/cdn/index.js.map +1 -0
- package/dist/cdn/workers-cache-purge.js +35 -0
- package/dist/cdn/workers-cache-purge.js.map +1 -0
- package/dist/cli-check.d.ts +153 -0
- package/dist/cli-check.d.ts.map +1 -0
- package/dist/cli.d.ts +34 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +46 -21
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts +20 -9
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +1 -1
- package/dist/client/browser-entry/index.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts +4 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +3 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +7 -0
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -162
- package/dist/client/history.d.ts +13 -6
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +42 -41
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +4 -3
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +877 -311
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +1 -7
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +2 -2
- package/dist/client/navigation-commit.d.ts +87 -0
- package/dist/client/navigation-commit.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +37 -20
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +63 -23
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/params-context.d.ts +81 -0
- package/dist/client/params-context.d.ts.map +1 -0
- package/dist/client/router-effects.d.ts +129 -0
- package/dist/client/router-effects.d.ts.map +1 -0
- package/dist/client/router-lifecycle.d.ts +55 -0
- package/dist/client/router-lifecycle.d.ts.map +1 -0
- package/dist/client/router-pipeline.d.ts +84 -0
- package/dist/client/router-pipeline.d.ts.map +1 -0
- package/dist/client/router-ref.d.ts +1 -1
- package/dist/client/router-ref.d.ts.map +1 -1
- package/dist/client/router-skew.d.ts +21 -0
- package/dist/client/router-skew.d.ts.map +1 -0
- package/dist/client/router-types.d.ts +207 -0
- package/dist/client/router-types.d.ts.map +1 -0
- package/dist/client/router.d.ts +1 -182
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +47 -26
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +87 -34
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +6 -0
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-client.d.ts +69 -0
- package/dist/client/stale-client.d.ts.map +1 -0
- package/dist/client/state.d.ts +10 -1
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +2 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/unload-guard.d.ts +5 -0
- package/dist/client/unload-guard.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +9 -3
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +1 -1
- package/dist/client/use-segment-params.d.ts +18 -8
- package/dist/client/use-segment-params.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +5 -5
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1009 -759
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/plugin-context.d.ts +46 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/request-dep/analysis.d.ts +106 -0
- package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
- package/dist/plugins/request-dep/index.d.ts +75 -0
- package/dist/plugins/request-dep/index.d.ts.map +1 -0
- package/dist/plugins/request-dep/origin.d.ts +61 -0
- package/dist/plugins/request-dep/origin.d.ts.map +1 -0
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +160 -8
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-shared.d.ts +3 -44
- package/dist/routing/codegen-shared.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +28 -31
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts +51 -0
- package/dist/routing/codegen-write.d.ts.map +1 -0
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/collision-probe.d.ts +104 -0
- package/dist/routing/collision-probe.d.ts.map +1 -0
- package/dist/routing/collision-spaces.d.ts +26 -0
- package/dist/routing/collision-spaces.d.ts.map +1 -0
- package/dist/routing/convention-lint.d.ts +18 -4
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +16 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +4 -3
- package/dist/routing/interception.d.ts +235 -6
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/link-codegen.d.ts +19 -4
- package/dist/routing/link-codegen.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts +1 -7
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +33 -0
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/segment-keys.d.ts +95 -0
- package/dist/routing/segment-keys.d.ts.map +1 -0
- package/dist/routing/slot-placement.d.ts +48 -0
- package/dist/routing/slot-placement.d.ts.map +1 -0
- package/dist/routing/types.d.ts +0 -6
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +6 -0
- package/dist/routing/walkers.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +60 -9
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +62 -14
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.d.ts +0 -1
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +221 -10
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/parse-total.d.ts +70 -0
- package/dist/search-params/parse-total.d.ts.map +1 -0
- package/dist/search-params/wrappers.d.ts +26 -3
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/server/access-gate.d.ts +19 -8
- 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 +6 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +18 -1
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +13 -1
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/chain-url-parts.d.ts +82 -0
- package/dist/server/chain-url-parts.d.ts.map +1 -0
- package/dist/server/children-interception.d.ts +49 -0
- package/dist/server/children-interception.d.ts.map +1 -0
- package/dist/server/compress.d.ts.map +1 -1
- package/dist/server/default-status-page.d.ts +45 -0
- package/dist/server/default-status-page.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +148 -15
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +15 -4
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +85 -15
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/head-response.d.ts +35 -0
- package/dist/server/head-response.d.ts.map +1 -0
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +3 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +712 -418
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +52 -0
- package/dist/server/metadata-collector.d.ts.map +1 -0
- package/dist/server/param-coercion.d.ts +22 -6
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +0 -18
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-interception.d.ts +59 -9
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +11 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +23 -0
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/publish-params.d.ts +33 -0
- package/dist/server/publish-params.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +48 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +22 -3
- 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 +13 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-cache-key-guard.d.ts +78 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +15 -3
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -17
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +0 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +1 -2
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +15 -2
- 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/skippable-prefix.d.ts +23 -0
- package/dist/server/skippable-prefix.d.ts.map +1 -0
- package/dist/server/slot-interception.d.ts +80 -0
- package/dist/server/slot-interception.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +70 -8
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +17 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +0 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +24 -13
- package/dist/server/ssr-wrappers.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +6 -27
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/static-generator.d.ts +6 -0
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/static-not-found.d.ts +52 -0
- package/dist/server/static-not-found.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +8 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +28 -36
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +12 -7
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +40 -0
- package/dist/server/utils/element-type.d.ts.map +1 -0
- package/dist/shared/als-slots.d.ts +41 -0
- package/dist/shared/als-slots.d.ts.map +1 -0
- package/dist/shared/base-path.d.ts +56 -0
- package/dist/shared/base-path.d.ts.map +1 -0
- package/dist/shared/param-value.d.ts +80 -0
- package/dist/shared/param-value.d.ts.map +1 -0
- package/dist/shared/payload-root.d.ts +108 -0
- package/dist/shared/payload-root.d.ts.map +1 -0
- package/dist/shared/rsc-cache-key.d.ts +163 -0
- package/dist/shared/rsc-cache-key.d.ts.map +1 -0
- package/dist/shared/rsc-error-envelope.d.ts +25 -0
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
- package/dist/shared/rsc-manifest.d.ts +28 -0
- package/dist/shared/rsc-manifest.d.ts.map +1 -0
- package/dist/shared/rsc-media-type.d.ts +40 -0
- package/dist/shared/rsc-media-type.d.ts.map +1 -0
- package/dist/shared/rsc-payload-path.d.ts +42 -0
- package/dist/shared/rsc-payload-path.d.ts.map +1 -0
- package/dist/shared/segment-info.d.ts +59 -0
- package/dist/shared/segment-info.d.ts.map +1 -0
- package/dist/shared/slot-params.d.ts +51 -0
- package/dist/shared/slot-params.d.ts.map +1 -0
- package/dist/shared/static-platform-files.d.ts +71 -0
- package/dist/shared/static-platform-files.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +1 -1
- package/docs/api/31-api-client.mdx +4 -2
- package/docs/api/33-api-search-params.mdx +38 -16
- package/docs/api/35-api-typescript.mdx +3 -3
- package/docs/api/36-cli.mdx +34 -7
- package/docs/learn/00-introduction.mdx +1 -1
- package/docs/learn/02-pages-and-layouts.mdx +2 -2
- package/docs/learn/05-typed-params.mdx +8 -8
- package/docs/learn/07-typed-routes.mdx +12 -7
- package/docs/learn/11-error-handling.mdx +18 -0
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +173 -3
- package/docs/more/03-coming-from-nextjs.mdx +2 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/docs/more/50-ai-agent-instructions.mdx +6 -4
- package/package.json +9 -6
- package/src/adapters/build-output-helper.ts +116 -3
- package/src/adapters/cloudflare.ts +90 -12
- package/src/adapters/compress-module.ts +79 -1
- package/src/adapters/fs-identity.ts +156 -0
- package/src/adapters/nitro.ts +6 -6
- package/src/adapters/shared.ts +196 -21
- package/src/adapters/types.ts +13 -1
- package/src/cache/fast-hash.ts +5 -0
- package/src/cli-check.ts +458 -0
- package/src/cli.ts +59 -24
- package/src/client/browser-entry/action-dispatch.ts +11 -5
- package/src/client/browser-entry/hydrate.ts +40 -29
- package/src/client/browser-entry/index.ts +15 -6
- package/src/client/browser-entry/post-hydration.ts +10 -9
- package/src/client/browser-entry/router-init.ts +104 -48
- package/src/client/browser-entry/rsc-stream.ts +11 -2
- package/src/client/error-boundary.tsx +85 -16
- package/src/client/history.ts +13 -6
- package/src/client/index.ts +14 -3
- package/src/client/internal.ts +4 -3
- package/src/client/link.tsx +120 -72
- package/src/client/navigation-api.ts +3 -3
- package/src/client/navigation-commit.ts +197 -0
- package/src/client/navigation-context.ts +40 -67
- package/src/client/navigation-root.tsx +326 -101
- package/src/client/params-context.ts +192 -0
- package/src/client/router-effects.ts +231 -0
- package/src/client/router-lifecycle.ts +288 -0
- package/src/client/router-pipeline.ts +393 -0
- package/src/client/router-ref.ts +1 -1
- package/src/client/router-skew.ts +30 -0
- package/src/client/router-types.ts +230 -0
- package/src/client/router.ts +243 -707
- package/src/client/rsc-fetch.ts +183 -106
- package/src/client/segment-cache.ts +129 -44
- package/src/client/ssr-data.ts +6 -0
- package/src/client/stale-client.ts +191 -0
- package/src/client/state.ts +14 -1
- package/src/client/top-loader.tsx +2 -1
- package/src/client/unload-guard.ts +34 -2
- package/src/client/use-cookie.ts +9 -2
- package/src/client/use-query-states.ts +102 -39
- package/src/client/use-router.ts +1 -1
- package/src/client/use-segment-params.ts +41 -18
- package/src/cookies/define-cookie.ts +6 -1
- package/src/index.ts +25 -4
- package/src/plugin-context.ts +58 -0
- package/src/plugins/adapter-build.ts +4 -0
- package/src/plugins/build-manifest.ts +34 -13
- package/src/plugins/entries.ts +4 -0
- package/src/plugins/fonts.ts +11 -8
- package/src/plugins/request-dep/analysis.ts +447 -0
- package/src/plugins/request-dep/index.ts +376 -0
- package/src/plugins/request-dep/origin.ts +310 -0
- package/src/plugins/routing.ts +97 -102
- package/src/plugins/shims.ts +0 -1
- package/src/plugins/static-build.ts +391 -95
- package/src/routing/codegen-shared.ts +3 -79
- package/src/routing/codegen-types.ts +28 -31
- package/src/routing/codegen-write.ts +139 -0
- package/src/routing/codegen.ts +86 -172
- package/src/routing/collision-probe.ts +438 -0
- package/src/routing/collision-spaces.ts +281 -0
- package/src/routing/convention-lint.ts +139 -40
- package/src/routing/export-detect.ts +151 -7
- package/src/routing/interception.ts +896 -50
- package/src/routing/link-codegen.ts +32 -65
- package/src/routing/manifest-codegen.ts +6 -62
- package/src/routing/scanner.ts +117 -92
- package/src/routing/segment-classify.ts +57 -0
- package/src/routing/segment-keys.ts +146 -0
- package/src/routing/slot-placement.ts +97 -0
- package/src/routing/types.ts +0 -6
- package/src/routing/walkers.ts +8 -0
- package/src/schema-bridge.ts +180 -58
- package/src/search-params/define.ts +108 -66
- package/src/search-params/index.ts +0 -1
- package/src/search-params/parse-total.ts +78 -0
- package/src/search-params/wrappers.ts +60 -11
- package/src/server/access-gate.tsx +60 -40
- package/src/server/action-handler.ts +4 -5
- package/src/server/actions.ts +6 -3
- package/src/server/als-registry.ts +18 -1
- package/src/server/build-manifest.ts +14 -1
- package/src/server/chain-url-parts.ts +163 -0
- package/src/server/children-interception.ts +115 -0
- package/src/server/compress.ts +9 -1
- package/src/server/default-status-page.ts +127 -0
- package/src/server/deny-boundary.ts +269 -41
- package/src/server/deny-renderer.ts +111 -39
- package/src/server/error-boundary-wrapper.ts +166 -79
- package/src/server/head-response.ts +54 -0
- package/src/server/index.ts +1 -3
- package/src/server/internal.ts +2 -2
- package/src/server/metadata-collector.ts +115 -0
- package/src/server/param-coercion.ts +51 -69
- package/src/server/pipeline-helpers.ts +2 -47
- package/src/server/pipeline-interception.ts +81 -32
- package/src/server/pipeline-outcome.ts +4 -5
- package/src/server/pipeline-phases.ts +77 -25
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-runtime.ts +4 -4
- package/src/server/primitives.ts +25 -0
- package/src/server/publish-params.ts +42 -0
- package/src/server/request-context.ts +97 -18
- package/src/server/route-element-builder.ts +241 -253
- package/src/server/route-handler.ts +8 -8
- package/src/server/route-matcher.ts +21 -9
- package/src/server/rsc-cache-key-guard.ts +164 -0
- package/src/server/rsc-entry/api-handler.ts +6 -8
- package/src/server/rsc-entry/deny-fallback.ts +3 -2
- package/src/server/rsc-entry/error-renderer.ts +94 -35
- package/src/server/rsc-entry/helpers.ts +33 -28
- package/src/server/rsc-entry/index.ts +36 -33
- package/src/server/rsc-entry/render-route.ts +18 -10
- package/src/server/rsc-entry/revalidate-renderer.ts +3 -2
- package/src/server/rsc-entry/rsc-payload.ts +113 -40
- package/src/server/rsc-entry/rsc-stream.ts +29 -5
- package/src/server/rsc-entry/ssr-renderer.ts +38 -22
- package/src/server/skippable-prefix.ts +92 -0
- package/src/server/slot-interception.ts +168 -0
- package/src/server/slot-resolver.ts +263 -158
- package/src/server/ssr-bridge-types.ts +18 -0
- package/src/server/ssr-entry.ts +20 -10
- package/src/server/ssr-wrappers.tsx +33 -32
- package/src/server/state-tree-diff.ts +14 -55
- package/src/server/static-generator.ts +68 -29
- package/src/server/static-not-found.ts +134 -0
- package/src/server/status-code-resolver.ts +28 -11
- package/src/server/tree-builder.ts +35 -218
- package/src/server/types.ts +12 -7
- package/src/server/utils/element-type.ts +72 -0
- package/src/shared/als-slots.ts +72 -0
- package/src/shared/base-path.ts +80 -0
- package/src/shared/param-value.ts +244 -0
- package/src/shared/payload-root.ts +151 -0
- package/src/shared/rsc-cache-key.ts +269 -0
- package/src/shared/rsc-error-envelope.ts +31 -0
- package/src/shared/rsc-manifest.ts +29 -0
- package/src/shared/rsc-media-type.ts +43 -0
- package/src/shared/rsc-payload-path.ts +45 -0
- package/src/shared/segment-info.ts +60 -0
- package/src/shared/slot-params.ts +111 -0
- package/src/shared/static-platform-files.ts +75 -0
- package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
- package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +0 -1
- package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js.map +0 -1
- package/dist/_chunks/define-COtkxMRT.js +0 -191
- package/dist/_chunks/define-COtkxMRT.js.map +0 -1
- package/dist/_chunks/define-c4au4I9R.js +0 -82
- package/dist/_chunks/define-c4au4I9R.js.map +0 -1
- package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
- package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
- package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
- package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
- package/dist/_chunks/registry-DbJPKoBp.js +0 -20
- package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
- package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
- package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
- package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
- package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
- package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
- package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
- package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
- package/dist/_chunks/walkers-_6zKFlch.js +0 -1002
- package/dist/_chunks/walkers-_6zKFlch.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/plugins/request-dep.d.ts +0 -32
- package/dist/plugins/request-dep.d.ts.map +0 -1
- package/dist/search-params/registry.d.ts +0 -20
- package/dist/search-params/registry.d.ts.map +0 -1
- package/dist/segment-params/define.d.ts +0 -89
- package/dist/segment-params/define.d.ts.map +0 -1
- package/dist/segment-params/index.d.ts +0 -3
- package/dist/segment-params/index.d.ts.map +0 -1
- package/dist/segment-params/index.js +0 -2
- package/src/plugins/request-dep.ts +0 -499
- package/src/search-params/registry.ts +0 -31
- package/src/segment-params/define.ts +0 -249
- package/src/segment-params/index.ts +0 -9
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type { RouteContext } from './types.js';
|
|
12
12
|
import { logRouteError } from './logger.js';
|
|
13
13
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
14
|
+
import { stripHeadBody } from './head-response.js';
|
|
14
15
|
|
|
15
16
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
16
17
|
|
|
@@ -89,18 +90,17 @@ export async function handleRouteRequest(mod: RouteModule, ctx: RouteContext): P
|
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
// HEAD fallback —
|
|
93
|
+
// HEAD fallback — dispatch to GET when the module exports no HEAD, so the
|
|
94
|
+
// headers are the ones a GET would produce. The body is stripped by the
|
|
95
|
+
// shared helper (also applied at the pipeline exit, where it is a no-op the
|
|
96
|
+
// second time) rather than open-coded here — one implementation means the
|
|
97
|
+
// discarded stream is cancelled on every path. See TIM-1324.
|
|
93
98
|
if (method === 'HEAD') {
|
|
94
99
|
if (mod.HEAD) {
|
|
95
|
-
return runHandler(mod.HEAD, ctx);
|
|
100
|
+
return stripHeadBody(await runHandler(mod.HEAD, ctx), method);
|
|
96
101
|
}
|
|
97
102
|
if (mod.GET) {
|
|
98
|
-
|
|
99
|
-
// Return headers + status but no body
|
|
100
|
-
return new Response(null, {
|
|
101
|
-
status: res.status,
|
|
102
|
-
headers: res.headers,
|
|
103
|
-
});
|
|
103
|
+
return stripHeadBody(await runHandler(mod.GET, ctx), method);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -83,10 +83,27 @@ function matchPathname(root: ManifestSegmentNode, pathname: string): RouteMatch
|
|
|
83
83
|
const result = matchUrlParts(root, parts);
|
|
84
84
|
if (!result) return null;
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
return {
|
|
87
|
+
segments: result.chain,
|
|
88
|
+
segmentParams: result.params,
|
|
89
|
+
middlewareChain: collectMiddlewareChain(result.chain),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Collect middleware from a matched segment chain, root to leaf.
|
|
95
|
+
*
|
|
96
|
+
* Each entry is lazy-loaded via dynamic import on first invocation.
|
|
97
|
+
*
|
|
98
|
+
* Shared with the children-path interception resolver
|
|
99
|
+
* (`children-interception.ts`), which assembles a chain from the tree rather
|
|
100
|
+
* than from a pathname but produces the same kind of `RouteMatch` — every
|
|
101
|
+
* `middleware.ts` above an intercepted page must run exactly as it does for
|
|
102
|
+
* the canonical route.
|
|
103
|
+
*/
|
|
104
|
+
export function collectMiddlewareChain(chain: ManifestSegmentNode[]): MiddlewareFn[] {
|
|
88
105
|
const middlewareChain: MiddlewareFn[] = [];
|
|
89
|
-
for (const segment of
|
|
106
|
+
for (const segment of chain) {
|
|
90
107
|
if (segment.middleware) {
|
|
91
108
|
const loader = segment.middleware.load;
|
|
92
109
|
middlewareChain.push(async (ctx) => {
|
|
@@ -97,12 +114,7 @@ function matchPathname(root: ManifestSegmentNode, pathname: string): RouteMatch
|
|
|
97
114
|
});
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
segments: result.chain,
|
|
103
|
-
segmentParams: result.params,
|
|
104
|
-
middlewareChain,
|
|
105
|
-
};
|
|
117
|
+
return middlewareChain;
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
// ─── Metadata Route Matcher ─────────────────────────────────────────────
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin-side binding of the `_rsc` cache key to the request that produced
|
|
3
|
+
* the response (TIM-1268).
|
|
4
|
+
*
|
|
5
|
+
* The key is deterministic, and deliberately so — that is what makes RSC
|
|
6
|
+
* payloads cacheable. But deterministic also means *predictable*: anyone can
|
|
7
|
+
* compute the key another client will request. Without this guard, a caller
|
|
8
|
+
* could request `/about?_rsc=<victim's key>` while sending its own
|
|
9
|
+
* `X-Timber-State-Tree`, and a shared cache that keys by URL — the exact
|
|
10
|
+
* environment this feature targets — would store that variant under the
|
|
11
|
+
* victim's key. Every subsequent legitimate navigation would then be served
|
|
12
|
+
* a payload rendered for someone else's segment tree or deployment.
|
|
13
|
+
*
|
|
14
|
+
* The guard is a *storability* rule, not a rejection: if the claimed key
|
|
15
|
+
* does not match the key derived from the received headers, the response
|
|
16
|
+
* loses its shared-cache directives. Mismatched requests are still served
|
|
17
|
+
* normally, they just cannot be stored for anyone else.
|
|
18
|
+
*
|
|
19
|
+
* Why not Next.js's 307 self-correction: a redirect is cacheable in its own
|
|
20
|
+
* right, costs a round trip on every legitimate miss, and would make the
|
|
21
|
+
* server's behaviour depend on a parameter it otherwise never reads. This
|
|
22
|
+
* rule closes the same hole by making the poisoned entry unstorable at the
|
|
23
|
+
* source, in the response the developer already has to opt into sharing.
|
|
24
|
+
*
|
|
25
|
+
* See design/19-client-navigation.md §"The `_rsc` cache key".
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { rscCacheKey, isRscCacheKeyShape, RSC_KEY_PARAM } from '../shared/rsc-cache-key.js';
|
|
29
|
+
import { RSC_CONTENT_TYPE } from '../shared/rsc-media-type.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Legacy shared-cache control fields that predate RFC 9213 and so do not
|
|
33
|
+
* follow the `<target>-Cache-Control` naming pattern — the one part of this
|
|
34
|
+
* rule that cannot be matched by shape. `X-Accel-Expires` is nginx
|
|
35
|
+
* `proxy_cache`, which gives it precedence over `Cache-Control`.
|
|
36
|
+
*
|
|
37
|
+
* A closed historical set, unlike the vendor `*-Cache-Control` names, which
|
|
38
|
+
* keep being minted and are handled by pattern below.
|
|
39
|
+
*/
|
|
40
|
+
const LEGACY_CDN_CACHE_HEADERS = ['Surrogate-Control', 'Edge-Control', 'X-Accel-Expires'];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether a response header overrides `Cache-Control` at a shared cache.
|
|
44
|
+
*
|
|
45
|
+
* Matched by *shape*, not by vendor name. RFC 9213 defines targeted fields
|
|
46
|
+
* as `<target>-Cache-Control`, and every CDN spells its own: `CDN-`,
|
|
47
|
+
* `Cloudflare-CDN-`, `Netlify-CDN-`, `Vercel-CDN-`, and whichever ships
|
|
48
|
+
* next. A vendor list here is a denylist with an unbounded tail — it was
|
|
49
|
+
* extended twice during review, each time by one name, which is the signal
|
|
50
|
+
* that enumeration is the wrong shape. The suffix rule is total.
|
|
51
|
+
*
|
|
52
|
+
* `Cache-Control` itself does not match (no `-` prefix) and is replaced
|
|
53
|
+
* separately.
|
|
54
|
+
*/
|
|
55
|
+
function isSharedCacheOverride(name: string): boolean {
|
|
56
|
+
const lowered = name.toLowerCase();
|
|
57
|
+
return (
|
|
58
|
+
lowered.endsWith('-cache-control') ||
|
|
59
|
+
LEGACY_CDN_CACHE_HEADERS.some((legacy) => legacy.toLowerCase() === lowered)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether the request's `_rsc` parameter matches the key derived from its
|
|
65
|
+
* own headers.
|
|
66
|
+
*
|
|
67
|
+
* Requests without the parameter are not claiming a key at all, so they are
|
|
68
|
+
* left alone — the bare document URL is the developer's declared territory
|
|
69
|
+
* (see design/32-response-caching.md, TIM-1264). Neither is a parameter
|
|
70
|
+
* whose value could not be one of our digests: `_rsc` is not reserved, and
|
|
71
|
+
* an application that already uses it for its own search state
|
|
72
|
+
* (`/search?_rsc=foo`) must not have its cache policy silently revoked by a
|
|
73
|
+
* guard that mistook that for a forgery.
|
|
74
|
+
*/
|
|
75
|
+
export async function hasValidRscCacheKey(req: Request): Promise<boolean> {
|
|
76
|
+
// Fast path: this runs on every request, and the overwhelming majority
|
|
77
|
+
// (document requests) have no query string at all. Keyed on `?` and not
|
|
78
|
+
// on the literal `_rsc=`, because a substring test is not a parse: a
|
|
79
|
+
// request for `?%5Frsc=<key>` carries no literal `_rsc=`, yet
|
|
80
|
+
// `URLSearchParams` decodes it to one — and a cache that canonicalizes
|
|
81
|
+
// percent-encoded unreserved characters in its key while forwarding the
|
|
82
|
+
// original URL would store the response under the decoded, claimed key.
|
|
83
|
+
// The only sound fast path is one that cannot answer "no key" wrongly.
|
|
84
|
+
if (!req.url.includes('?')) return true;
|
|
85
|
+
let values: string[];
|
|
86
|
+
try {
|
|
87
|
+
values = new URL(req.url).searchParams.getAll(RSC_KEY_PARAM);
|
|
88
|
+
} catch {
|
|
89
|
+
// Unparseable URL — nothing to validate, and nothing safe to assume.
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// Every value, not the first. An application that owns `_rsc` for its own
|
|
93
|
+
// search state makes the client emit `?_rsc=foo&_rsc=<key>` — reading only
|
|
94
|
+
// the first would see `foo`, exempt it as application data, and never
|
|
95
|
+
// check the real key sitting beside it.
|
|
96
|
+
const claims = values.filter(isRscCacheKeyShape);
|
|
97
|
+
if (claims.length === 0) return true;
|
|
98
|
+
const expected = await rscCacheKey((name) => req.headers.get(name));
|
|
99
|
+
// No digest available means the claim is unverifiable, which is not the
|
|
100
|
+
// same as verified. Every server runtime timber targets has
|
|
101
|
+
// `crypto.subtle`, so this is a guard against a future one that does not.
|
|
102
|
+
if (expected === null) return false;
|
|
103
|
+
// All of them, because at most one can be legitimate and a cache keys on
|
|
104
|
+
// the whole query string — a second, mismatched key would ride along
|
|
105
|
+
// inside an address that otherwise looks validated.
|
|
106
|
+
return claims.every((claim) => claim === expected);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Ensure a Flight payload can never be interpreted as a document.
|
|
111
|
+
*
|
|
112
|
+
* Applied here, at the pipeline's single exit, rather than where each
|
|
113
|
+
* payload response is built. Six places build one — the success path, three
|
|
114
|
+
* in `deny-renderer.ts`, the action paths — and hardening them one at a time
|
|
115
|
+
* is how you end up hardening five of six. The response is Flight if it says
|
|
116
|
+
* it is; that is the only test that covers every producer, including ones
|
|
117
|
+
* added later.
|
|
118
|
+
*
|
|
119
|
+
* Matters because the payload URL is observable and, on a shared-cache hit,
|
|
120
|
+
* the origin is never consulted — so a top-level navigation can be handed
|
|
121
|
+
* these bytes with no request-time check available. See design/13-security.md
|
|
122
|
+
* #36c.
|
|
123
|
+
*/
|
|
124
|
+
export function hardenRscPayloadResponse(response: Response): void {
|
|
125
|
+
const contentType = response.headers.get('content-type');
|
|
126
|
+
if (!contentType) return;
|
|
127
|
+
if (contentType.split(';')[0].trim().toLowerCase() !== RSC_CONTENT_TYPE) return;
|
|
128
|
+
response.headers.set('X-Content-Type-Options', 'nosniff');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Make a response unstorable by any shared cache when its request claimed an
|
|
133
|
+
* `_rsc` key that does not match its own headers.
|
|
134
|
+
*
|
|
135
|
+
* The end state is unconditional and therefore statable: **a mismatched
|
|
136
|
+
* response is never stored by any cache.** Every targeted cache-control
|
|
137
|
+
* field is removed and `Cache-Control` is replaced outright, rather than
|
|
138
|
+
* downgrading only when a shared directive is spotted. Conditional variants
|
|
139
|
+
* of this were tried and were wrong twice over — they missed the CDN-
|
|
140
|
+
* specific headers, and leaving `Cache-Control` alone when it looked
|
|
141
|
+
* harmless lets a CDN apply heuristic caching to a response that has none.
|
|
142
|
+
*
|
|
143
|
+
* The cost is that a mismatched request loses developer-chosen *browser*
|
|
144
|
+
* caching too. That is a request no timber client ever makes.
|
|
145
|
+
*
|
|
146
|
+
* Purge tags (`Surrogate-Key`, `Cache-Tag`) are deliberately left alone:
|
|
147
|
+
* they address stored entries, and this response is not stored.
|
|
148
|
+
*
|
|
149
|
+
* Mutates `response.headers` in place; the pipeline guarantees mutable
|
|
150
|
+
* headers at this point (see TIM-866).
|
|
151
|
+
*/
|
|
152
|
+
export async function enforceRscCacheKey(req: Request, response: Response): Promise<void> {
|
|
153
|
+
if (await hasValidRscCacheKey(req)) return;
|
|
154
|
+
// Collected before deleting — mutating Headers while iterating its live
|
|
155
|
+
// key iterator can skip entries.
|
|
156
|
+
const overrides: string[] = [];
|
|
157
|
+
for (const name of response.headers.keys()) {
|
|
158
|
+
if (isSharedCacheOverride(name)) overrides.push(name);
|
|
159
|
+
}
|
|
160
|
+
for (const name of overrides) {
|
|
161
|
+
response.headers.delete(name);
|
|
162
|
+
}
|
|
163
|
+
response.headers.set('Cache-Control', 'private, no-store');
|
|
164
|
+
}
|
|
@@ -11,6 +11,7 @@ import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
|
11
11
|
import { loadModule } from '../safe-load.js';
|
|
12
12
|
import { DenySignal, RedirectSignal } from '../primitives.js';
|
|
13
13
|
import { handleRouteRequest } from '../route-handler.js';
|
|
14
|
+
import { stripHeadBody } from '../head-response.js';
|
|
14
15
|
import type { RouteModule } from '../route-handler.js';
|
|
15
16
|
import type { RouteContext } from '../types.js';
|
|
16
17
|
|
|
@@ -89,9 +90,10 @@ export async function handleApiRoute(
|
|
|
89
90
|
* method handler into the proper API response: DenySignal → JSON status-file
|
|
90
91
|
* chain via `renderApiDeny`, RedirectSignal → bare 3xx with Location header.
|
|
91
92
|
*
|
|
92
|
-
* For HEAD requests, strip the deny body to preserve HEAD semantics —
|
|
93
|
-
*
|
|
94
|
-
*
|
|
93
|
+
* For HEAD requests, strip the deny body to preserve HEAD semantics — signals
|
|
94
|
+
* bypass the HEAD path in `handleRouteRequest`, so this exit needs its own
|
|
95
|
+
* call. Shares `stripHeadBody` with that path and with the pipeline exit; the
|
|
96
|
+
* pipeline applying it again downstream is a no-op (TIM-1324).
|
|
95
97
|
*
|
|
96
98
|
* Returns `null` for non-signal errors so the caller knows to rethrow.
|
|
97
99
|
*/
|
|
@@ -102,11 +104,7 @@ async function translateApiSignal(
|
|
|
102
104
|
responseHeaders: Headers
|
|
103
105
|
): Promise<Response | null> {
|
|
104
106
|
if (error instanceof DenySignal) {
|
|
105
|
-
|
|
106
|
-
if (req.method.toUpperCase() === 'HEAD') {
|
|
107
|
-
return new Response(null, { status: res.status, headers: res.headers });
|
|
108
|
-
}
|
|
109
|
-
return res;
|
|
107
|
+
return stripHeadBody(await renderApiDeny(error, segments, responseHeaders), req.method);
|
|
110
108
|
}
|
|
111
109
|
if (error instanceof RedirectSignal) {
|
|
112
110
|
responseHeaders.set('Location', error.location);
|
|
@@ -17,7 +17,8 @@ import { logRenderError } from '../logger.js';
|
|
|
17
17
|
import type { PipelineConfig, RouteMatch } from '../pipeline.js';
|
|
18
18
|
import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
19
19
|
import { loadModule } from '../safe-load.js';
|
|
20
|
-
import { createDebugChannelSink
|
|
20
|
+
import { createDebugChannelSink } from './helpers.js';
|
|
21
|
+
import { isRscRequest } from '../../shared/rsc-media-type.js';
|
|
21
22
|
import { callSsr } from './ssr-bridge.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -67,7 +68,7 @@ export function createDenyFallbackRenderer(options: {
|
|
|
67
68
|
// decoder breaks navigation, so dispatch to the RSC renderer when the
|
|
68
69
|
// request is an RSC payload request — mirroring the normal render path.
|
|
69
70
|
// See design/19-client-navigation.md §"RSC Payload Handling" and TIM-823.
|
|
70
|
-
if (
|
|
71
|
+
if (isRscRequest(req)) {
|
|
71
72
|
return renderDenyPageAsRsc(
|
|
72
73
|
deny,
|
|
73
74
|
chain,
|
|
@@ -17,14 +17,18 @@ import { renderToReadableStream } from '../../rsc-runtime/rsc.js';
|
|
|
17
17
|
|
|
18
18
|
import type { RouteMatch } from '../pipeline.js';
|
|
19
19
|
import { logRenderError } from '../logger.js';
|
|
20
|
+
import { appVisibleSearch } from '../../shared/rsc-cache-key.js';
|
|
21
|
+
import { getSlotParamsForClient } from '../request-context.js';
|
|
20
22
|
import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
21
23
|
import { DenySignal, RenderError } from '../primitives.js';
|
|
22
24
|
import type { ClientBootstrapConfig } from '../html-injectors.js';
|
|
23
25
|
import { flightInitScript, htmlEscapeJsonString } from '../flight-scripts.js';
|
|
24
26
|
import { renderDenyPage, renderDenyPageAsRsc } from '../deny-renderer.js';
|
|
25
|
-
import type { LayoutEntry } from '../deny-renderer.js';
|
|
27
|
+
import type { DefaultPageRenderer, LayoutEntry } from '../deny-renderer.js';
|
|
26
28
|
import type { NavContext } from '../ssr-bridge-types.js';
|
|
27
|
-
import { createDebugChannelSink
|
|
29
|
+
import { createDebugChannelSink } from './helpers.js';
|
|
30
|
+
import { isRscRequest } from '../../shared/rsc-media-type.js';
|
|
31
|
+
import { withPublishedParams } from '../publish-params.js';
|
|
28
32
|
import { getCookiesForSsr } from '../cookie-context.js';
|
|
29
33
|
import { callSsr } from './ssr-bridge.js';
|
|
30
34
|
import { teeWithErrorPropagation } from '../stream-utils.js';
|
|
@@ -34,6 +38,8 @@ import type { SerializableError } from '../../client/error-reconstituter.js';
|
|
|
34
38
|
import { loadModule } from '../safe-load.js';
|
|
35
39
|
import { sourceMapError } from '../dev-source-map.js';
|
|
36
40
|
import { isMdxFilePath as isMdxFile } from '../utils/mdx-file.js';
|
|
41
|
+
import { defaultStatusPageResponse } from '../default-status-page.js';
|
|
42
|
+
import { rscErrorEnvelope } from '../../shared/rsc-error-envelope.js';
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
45
|
* A manifest file reference with lazy import and path.
|
|
@@ -67,9 +73,12 @@ interface ErrorFileResolution {
|
|
|
67
73
|
*/
|
|
68
74
|
async function resolveErrorFile(
|
|
69
75
|
status: number,
|
|
70
|
-
segments: ManifestSegmentNode[]
|
|
76
|
+
segments: ManifestSegmentNode[],
|
|
77
|
+
maxIndex?: number
|
|
71
78
|
): Promise<ErrorFileResolution | null> {
|
|
72
|
-
|
|
79
|
+
const start =
|
|
80
|
+
maxIndex !== undefined ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
|
|
81
|
+
for (let i = start; i >= 0; i--) {
|
|
73
82
|
const segment = segments[i];
|
|
74
83
|
|
|
75
84
|
if (segment.statusFiles) {
|
|
@@ -126,9 +135,14 @@ async function resolveErrorFile(
|
|
|
126
135
|
function getLayoutsForErrorFile(
|
|
127
136
|
segments: ManifestSegmentNode[],
|
|
128
137
|
layoutComponents: LayoutEntry[],
|
|
129
|
-
foundSegmentIndex: number
|
|
138
|
+
foundSegmentIndex: number,
|
|
139
|
+
firstDeniedIndex = Infinity
|
|
130
140
|
): LayoutEntry[] {
|
|
131
|
-
|
|
141
|
+
// Clamp so a denied segment's layout never wraps the error page.
|
|
142
|
+
// The file resolution is unbounded (errors may use leaf files), but the
|
|
143
|
+
// layout wrapping must still respect the denial boundary. TIM-1358.
|
|
144
|
+
const ceiling = Math.min(foundSegmentIndex, firstDeniedIndex - 1);
|
|
145
|
+
const resolvedSegments = new Set(segments.slice(0, ceiling + 1));
|
|
132
146
|
return layoutComponents.filter((lc) => resolvedSegments.has(lc.segment));
|
|
133
147
|
}
|
|
134
148
|
|
|
@@ -155,6 +169,18 @@ function toSerializableError(error: unknown): SerializableError {
|
|
|
155
169
|
* All error pages (TSX and MDX) go through RSC → SSR:
|
|
156
170
|
* - TSX: ErrorReconstituter wrapper converts SerializableError → real Error
|
|
157
171
|
* - MDX: Plain props ({ status }), no wrapper needed
|
|
172
|
+
*
|
|
173
|
+
* ...except for a client-navigation request, which gets the hard-navigate
|
|
174
|
+
* envelope instead. Every tier below produces an HTML document, and the
|
|
175
|
+
* client's Flight decoder is waiting for `text/x-component`; handing it HTML
|
|
176
|
+
* crashes the decode. The check lives here rather than at the call sites
|
|
177
|
+
* because it holds for all five of them, and the deny path's equivalent split
|
|
178
|
+
* (`renderDenyPage` / `renderDenyPageAsRsc`) had already been forgotten at one
|
|
179
|
+
* of this function's — the synchronous render-error exit in `render-route.ts`
|
|
180
|
+
* (TIM-1322). Four of the five are unreachable for an RSC request today
|
|
181
|
+
* (`render-route.ts` returns the payload before it reaches SSR, and
|
|
182
|
+
* `outcomeToResponse` answers RSC errors itself), so this fires on one path
|
|
183
|
+
* and guards the rest against drift.
|
|
158
184
|
*/
|
|
159
185
|
export async function renderErrorPage(
|
|
160
186
|
error: unknown,
|
|
@@ -165,14 +191,27 @@ export async function renderErrorPage(
|
|
|
165
191
|
match: RouteMatch,
|
|
166
192
|
responseHeaders: Headers,
|
|
167
193
|
clientBootstrap: ClientBootstrapConfig,
|
|
168
|
-
globalError?: GlobalErrorFile
|
|
194
|
+
globalError?: GlobalErrorFile,
|
|
195
|
+
firstDeniedIndex = Infinity
|
|
169
196
|
): Promise<Response> {
|
|
197
|
+
// The error itself is not logged here — every producer of a `renderError`
|
|
198
|
+
// signal already logged it at the point it was caught (`rsc-stream.ts`'s
|
|
199
|
+
// `onError`, `outcomeToResponse`), so a log on this exit would double up.
|
|
200
|
+
if (isRscRequest(req)) {
|
|
201
|
+
return rscErrorEnvelope(status, responseHeaders);
|
|
202
|
+
}
|
|
203
|
+
|
|
170
204
|
// Source-map the error's stack trace in dev mode so the error page
|
|
171
205
|
// shows original source positions instead of transpiled/bundled ones.
|
|
172
206
|
// See TIM-811.
|
|
173
207
|
sourceMapError(error);
|
|
174
|
-
// Walk segments from leaf to root to find the error component
|
|
175
|
-
|
|
208
|
+
// Walk segments from leaf to root to find the error component.
|
|
209
|
+
// When firstDeniedIndex is finite, bound the walk so error files below
|
|
210
|
+
// the denied segment are not selected — matching the deny renderer's
|
|
211
|
+
// behavior. For plain errors (firstDeniedIndex = Infinity), the full
|
|
212
|
+
// chain is searched. See TIM-1358.
|
|
213
|
+
const errorMaxIndex = firstDeniedIndex < Infinity ? firstDeniedIndex : undefined;
|
|
214
|
+
const resolution = await resolveErrorFile(status, segments, errorMaxIndex);
|
|
176
215
|
|
|
177
216
|
// Tier 2: No segment-level error file — try global-error.tsx
|
|
178
217
|
if (!resolution && globalError) {
|
|
@@ -187,13 +226,22 @@ export async function renderErrorPage(
|
|
|
187
226
|
);
|
|
188
227
|
}
|
|
189
228
|
|
|
190
|
-
// Tier 3: No error component found at any level —
|
|
229
|
+
// Tier 3: No error component found at any level — framework default page.
|
|
230
|
+
// The deny chain's terminal is the same page, so an app with no error files
|
|
231
|
+
// gets a readable status page for both 4xx and 5xx rather than a blank
|
|
232
|
+
// document. It carries the status and nothing else — never the error
|
|
233
|
+
// (design/13-security.md principle 4). See TIM-1258.
|
|
191
234
|
if (!resolution) {
|
|
192
|
-
return
|
|
235
|
+
return defaultStatusPageResponse(status, responseHeaders);
|
|
193
236
|
}
|
|
194
237
|
|
|
195
238
|
const { component: errorComponent, segmentIndex, isMdx } = resolution;
|
|
196
|
-
const layoutsToWrap = getLayoutsForErrorFile(
|
|
239
|
+
const layoutsToWrap = getLayoutsForErrorFile(
|
|
240
|
+
segments,
|
|
241
|
+
layoutComponents,
|
|
242
|
+
segmentIndex,
|
|
243
|
+
firstDeniedIndex
|
|
244
|
+
);
|
|
197
245
|
|
|
198
246
|
return renderErrorPageViaRsc(
|
|
199
247
|
error,
|
|
@@ -257,9 +305,12 @@ async function renderErrorPageViaRsc(
|
|
|
257
305
|
element = h(component, null, element);
|
|
258
306
|
}
|
|
259
307
|
|
|
260
|
-
// Render through RSC → SSR (same path as deny pages)
|
|
308
|
+
// Render through RSC → SSR (same path as deny pages) — including the params
|
|
309
|
+
// wrap, which every payload the server sends needs. An error page on a
|
|
310
|
+
// dynamic route reads params like any other page, and SSR would get them
|
|
311
|
+
// from the request ALS while the hydrated client got an empty record.
|
|
261
312
|
try {
|
|
262
|
-
const rscStream = renderToReadableStream(element, {
|
|
313
|
+
const rscStream = renderToReadableStream(withPublishedParams(element), {
|
|
263
314
|
signal: req.signal,
|
|
264
315
|
onError(err: unknown) {
|
|
265
316
|
logRenderError({ method: req.method, path: url.pathname, error: err });
|
|
@@ -269,32 +320,35 @@ async function renderErrorPageViaRsc(
|
|
|
269
320
|
|
|
270
321
|
const [ssrStream, inlineStream] = teeWithErrorPropagation(rscStream);
|
|
271
322
|
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
const
|
|
275
|
-
Object.keys(match.segmentParams).length === 0
|
|
276
|
-
? ''
|
|
277
|
-
: `<script>self.__timber_params=${htmlEscapeJsonString(JSON.stringify(match.segmentParams))}</script>`;
|
|
323
|
+
// Params ride the payload root, beside the tree, so there is no
|
|
324
|
+
// bootstrap script. Read here for the SSR-side ALS context below.
|
|
325
|
+
const slotParams = getSlotParamsForClient();
|
|
278
326
|
|
|
279
327
|
const navContext: NavContext = {
|
|
280
328
|
pathname: url.pathname,
|
|
281
329
|
segmentParams: match.segmentParams,
|
|
282
|
-
searchParams: Object.fromEntries(url.
|
|
330
|
+
searchParams: Object.fromEntries(appVisibleSearch(url).params),
|
|
331
|
+
slotParams,
|
|
283
332
|
statusCode: status,
|
|
284
333
|
responseHeaders,
|
|
285
|
-
headHtml:
|
|
334
|
+
headHtml: flightInitScript(),
|
|
286
335
|
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
287
336
|
rscStream: inlineStream,
|
|
288
337
|
signal: req.signal,
|
|
289
338
|
cookies: getCookiesForSsr(),
|
|
290
339
|
};
|
|
291
340
|
|
|
292
|
-
|
|
341
|
+
// `await` inside the try on purpose: SSR of the error page fails
|
|
342
|
+
// asynchronously far more often than synchronously, and returning the
|
|
343
|
+
// promise bare let every such rejection escape this guard.
|
|
344
|
+
return await callSsr(ssrStream, navContext);
|
|
293
345
|
} catch (renderError) {
|
|
294
|
-
//
|
|
295
|
-
//
|
|
346
|
+
// The error page itself failed to render — guards against recursive
|
|
347
|
+
// errors in user error page code. Terminates in the framework default
|
|
348
|
+
// page, not a blank body: a crashing error page is precisely when the
|
|
349
|
+
// reader has nothing else to go on (TIM-1258).
|
|
296
350
|
logRenderError({ method: req.method, path: url.pathname, error: renderError });
|
|
297
|
-
return
|
|
351
|
+
return defaultStatusPageResponse(status, responseHeaders);
|
|
298
352
|
}
|
|
299
353
|
}
|
|
300
354
|
|
|
@@ -315,11 +369,12 @@ async function renderGlobalErrorPage(
|
|
|
315
369
|
responseHeaders: Headers,
|
|
316
370
|
clientBootstrap: ClientBootstrapConfig
|
|
317
371
|
): Promise<Response> {
|
|
318
|
-
// .catch: if global-error.tsx fails to load
|
|
319
|
-
//
|
|
372
|
+
// .catch: if global-error.tsx fails to load or exports no component, it is
|
|
373
|
+
// not a usable Tier 2 match — fall through to the framework default page
|
|
374
|
+
// rather than a blank body. See TIM-584, TIM-1258.
|
|
320
375
|
const mod = await loadModule(globalError).catch(() => null);
|
|
321
376
|
if (!mod?.default) {
|
|
322
|
-
return
|
|
377
|
+
return defaultStatusPageResponse(status, responseHeaders);
|
|
323
378
|
}
|
|
324
379
|
|
|
325
380
|
const component = mod.default as (...args: unknown[]) => unknown;
|
|
@@ -350,15 +405,18 @@ export async function renderNoMatchPage(
|
|
|
350
405
|
req: Request,
|
|
351
406
|
rootSegment: ManifestSegmentNode,
|
|
352
407
|
responseHeaders: Headers,
|
|
353
|
-
clientBootstrap: ClientBootstrapConfig
|
|
408
|
+
clientBootstrap: ClientBootstrapConfig,
|
|
409
|
+
defaultPage?: DefaultPageRenderer
|
|
354
410
|
): Promise<Response> {
|
|
355
411
|
const segments = [rootSegment];
|
|
356
412
|
|
|
357
|
-
// Load root layout if present
|
|
413
|
+
// Load root layout if present. A layout that fails to import or evaluate
|
|
414
|
+
// must not take the 404 down with it — render the status page unwrapped,
|
|
415
|
+
// as renderFallbackError does for the same reason.
|
|
358
416
|
const layoutComponents: LayoutEntry[] = [];
|
|
359
417
|
if (rootSegment.layout) {
|
|
360
|
-
const mod = await loadModule(rootSegment.layout);
|
|
361
|
-
if (mod
|
|
418
|
+
const mod = await loadModule(rootSegment.layout).catch(() => null);
|
|
419
|
+
if (mod?.default) {
|
|
362
420
|
layoutComponents.push({
|
|
363
421
|
component: mod.default as (...args: unknown[]) => unknown,
|
|
364
422
|
segment: rootSegment,
|
|
@@ -369,7 +427,7 @@ export async function renderNoMatchPage(
|
|
|
369
427
|
const deny = new DenySignal(404);
|
|
370
428
|
const match: RouteMatch = { segments, segmentParams: {}, middlewareChain: [] };
|
|
371
429
|
|
|
372
|
-
if (
|
|
430
|
+
if (isRscRequest(req)) {
|
|
373
431
|
return renderDenyPageAsRsc(
|
|
374
432
|
deny,
|
|
375
433
|
segments,
|
|
@@ -389,6 +447,7 @@ export async function renderNoMatchPage(
|
|
|
389
447
|
responseHeaders,
|
|
390
448
|
clientBootstrap,
|
|
391
449
|
createDebugChannelSink,
|
|
392
|
-
callSsr
|
|
450
|
+
callSsr,
|
|
451
|
+
defaultPage
|
|
393
452
|
);
|
|
394
453
|
}
|