@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
|
@@ -20,115 +20,36 @@ import { createElement, Fragment } from 'react';
|
|
|
20
20
|
import { withSpan } from './tracing.js';
|
|
21
21
|
import type { RouteMatch } from './pipeline.js';
|
|
22
22
|
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
23
|
-
import { resolveMetadata, renderMetadataToElements } from './metadata.js';
|
|
24
23
|
import type { Metadata } from './types.js';
|
|
25
|
-
import { collectMetadataRouteHeadElements } from './metadata-routes.js';
|
|
26
24
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
25
|
+
import { MetadataCollector, MetadataHead } from './metadata-collector.js';
|
|
27
26
|
import { AccessGate } from './access-gate.js';
|
|
28
27
|
import { requestContextAls } from './als-registry.js';
|
|
29
|
-
import {
|
|
30
|
-
PageDenyBoundary,
|
|
31
|
-
buildDenyPageChain,
|
|
32
|
-
renderMatchingDenyPage,
|
|
33
|
-
setDenyStatus,
|
|
34
|
-
} from './deny-boundary.js';
|
|
28
|
+
import { PageDenyBoundary, buildDenyPageChain, handleCaughtDeny } from './deny-boundary.js';
|
|
35
29
|
import type { DenyPageEntry } from './deny-boundary.js';
|
|
36
30
|
import { LAYOUT_CHILDREN_SLOT } from './prebuilt/slots.js';
|
|
37
|
-
import { resolveSlotProps,
|
|
31
|
+
import { resolveSlotProps, publishSlotSegmentParams, type SlotSkipEntry } from './slot-resolver.js';
|
|
32
|
+
import { consumedUrlParts } from './chain-url-parts.js';
|
|
33
|
+
import { withPublishedParams } from './publish-params.js';
|
|
34
|
+
import type { RscPayloadRoot } from '../shared/payload-root.js';
|
|
38
35
|
import { SegmentProvider } from '../client/segment-context.js';
|
|
39
36
|
import { SegmentOutlet } from '../client/segment-outlet.js';
|
|
40
37
|
import { SlotOutlet } from '../client/slot-outlet.js';
|
|
41
38
|
import { SlotsProvider } from '../client/slot-provider.js';
|
|
42
39
|
|
|
40
|
+
import { TimberErrorBoundary } from '../client/error-boundary.js';
|
|
43
41
|
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
|
|
44
42
|
import type { InterceptionContext } from './pipeline.js';
|
|
43
|
+
import { shouldSkipSegment, type ClientStateTree } from './state-tree-diff.js';
|
|
44
|
+
import { truncateSkippableForChangedSlots } from './skippable-prefix.js';
|
|
45
45
|
import {
|
|
46
|
-
shouldSkipSegment,
|
|
47
|
-
shouldSkipSlot,
|
|
48
46
|
computeSegmentKeys,
|
|
47
|
+
computeSegmentTreePaths,
|
|
49
48
|
computeSlotKey,
|
|
50
|
-
|
|
51
|
-
} from './
|
|
52
|
-
import { headElementsToReact } from './metadata.js';
|
|
49
|
+
} from '../routing/segment-keys.js';
|
|
50
|
+
import { setFirstDeniedSegmentIndex } from './deny-boundary.js';
|
|
53
51
|
import { loadModule } from './safe-load.js';
|
|
54
52
|
|
|
55
|
-
/**
|
|
56
|
-
* Truncate the skippable prefix when a skippable layout has slots whose
|
|
57
|
-
* content changed between the departing and destination URLs (TIM-1232).
|
|
58
|
-
*
|
|
59
|
-
* Without this, changed slots at skipped layouts are never sent to the
|
|
60
|
-
* client — the `continue` in the main loop skips slot resolution entirely.
|
|
61
|
-
* Removing the segment from the skippable set forces it to render with
|
|
62
|
-
* its slots. The prefix must stay contiguous, so all segments after the
|
|
63
|
-
* first changed-slot segment are also removed.
|
|
64
|
-
*/
|
|
65
|
-
function truncateSkippableForChangedSlots(
|
|
66
|
-
skippableSet: Set<number>,
|
|
67
|
-
segments: ManifestSegmentNode[],
|
|
68
|
-
segmentKeys: string[],
|
|
69
|
-
match: RouteMatch,
|
|
70
|
-
clientStateTree: ClientStateTree,
|
|
71
|
-
req: Request
|
|
72
|
-
): void {
|
|
73
|
-
const departingUrl = req.headers.get('X-Timber-URL');
|
|
74
|
-
let depParsed: URL | null = null;
|
|
75
|
-
if (departingUrl) {
|
|
76
|
-
try {
|
|
77
|
-
depParsed = new URL(departingUrl, 'http://localhost');
|
|
78
|
-
} catch {
|
|
79
|
-
skippableSet.clear();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const destPathname = new URL(req.url, 'http://localhost').pathname;
|
|
84
|
-
const depPathname = depParsed?.pathname ?? null;
|
|
85
|
-
const split = (p: string) => (p === '/' ? [] : p.slice(1).split('/'));
|
|
86
|
-
const destAll = split(destPathname);
|
|
87
|
-
const depAll = depPathname ? split(depPathname) : null;
|
|
88
|
-
const clientSlots = clientStateTree.slots ?? null;
|
|
89
|
-
const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
|
|
90
|
-
|
|
91
|
-
for (let i = 0; i < segments.length; i++) {
|
|
92
|
-
if (!skippableSet.has(i)) continue;
|
|
93
|
-
const segment = segments[i];
|
|
94
|
-
const slotEntries = Object.entries(segment.slots ?? {});
|
|
95
|
-
if (slotEntries.length === 0) continue;
|
|
96
|
-
|
|
97
|
-
const segIdx = match.segments.indexOf(segment);
|
|
98
|
-
const parents =
|
|
99
|
-
segIdx >= 0 ? match.segments.slice(0, segIdx + 1).filter((s) => s.segmentName !== '') : [];
|
|
100
|
-
const sliceAt = extractUrlParts(parents, rawParams).length;
|
|
101
|
-
const destParts = destAll.slice(sliceAt);
|
|
102
|
-
const depParts = depAll ? depAll.slice(sliceAt) : null;
|
|
103
|
-
let parentChanged = false;
|
|
104
|
-
if (depAll) {
|
|
105
|
-
const dp = depAll.slice(0, sliceAt),
|
|
106
|
-
dd = destAll.slice(0, sliceAt);
|
|
107
|
-
parentChanged = dp.length !== dd.length || dp.some((p, j) => p !== dd[j]);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
for (const [slotName, slotNode] of slotEntries) {
|
|
111
|
-
const m = slotNode as ManifestSegmentNode;
|
|
112
|
-
const k = computeSlotKey(segmentKeys[i], `@${slotName}`);
|
|
113
|
-
if (
|
|
114
|
-
m.children.some((c) => c.segmentType === 'intercepting') ||
|
|
115
|
-
parentChanged ||
|
|
116
|
-
depParts === null ||
|
|
117
|
-
!shouldSkipSlot({
|
|
118
|
-
slotKey: k,
|
|
119
|
-
clientSlots,
|
|
120
|
-
slotNode: m,
|
|
121
|
-
departingUrlParts: depParts,
|
|
122
|
-
destinationUrlParts: destParts,
|
|
123
|
-
})
|
|
124
|
-
) {
|
|
125
|
-
for (let j = i; j < segments.length; j++) skippableSet.delete(j);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
53
|
/**
|
|
133
54
|
* Replace the outermost SegmentOutlet in a partial payload with its
|
|
134
55
|
* SegmentProvider child. Walks through AccessGate wrappers (which sit
|
|
@@ -141,9 +62,9 @@ function replaceOutermostSegmentOutlet(
|
|
|
141
62
|
if (element.type === SegmentOutlet) {
|
|
142
63
|
return replacement;
|
|
143
64
|
}
|
|
144
|
-
// AccessGate
|
|
65
|
+
// AccessGate and MetadataCollector wrap outside SegmentOutlet — recurse through them.
|
|
145
66
|
const props = element.props as Record<string, unknown>;
|
|
146
|
-
if (element.type === AccessGate && props.children) {
|
|
67
|
+
if ((element.type === AccessGate || element.type === MetadataCollector) && props.children) {
|
|
147
68
|
const inner = replaceOutermostSegmentOutlet(props.children as React.ReactElement, replacement);
|
|
148
69
|
/* eslint-disable react/no-children-prop -- createElement API */
|
|
149
70
|
return createElement(element.type as React.FunctionComponent<Record<string, unknown>>, {
|
|
@@ -175,6 +96,28 @@ const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
|
|
|
175
96
|
* function. Client components must go through createElement only — they are
|
|
176
97
|
* serialized as references in the RSC Flight stream, not executed on the server.
|
|
177
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* The `segments` array handed to a layout's `SegmentProvider` — the value
|
|
101
|
+
* `useSelectedLayoutSegment(s)` reads.
|
|
102
|
+
*
|
|
103
|
+
* Only its **length** matters: both hooks take `segments.length` as a depth and
|
|
104
|
+
* index the live pathname with it. So this is really "how many URL parts had
|
|
105
|
+
* been consumed by the time this layout rendered", which is
|
|
106
|
+
* `consumedUrlParts` — the same derivation slot matching and the slot-skip
|
|
107
|
+
* slicing use, so a layout and the slots beside it cannot disagree about how
|
|
108
|
+
* deep they are. The root consumes nothing → `['']`, length 1.
|
|
109
|
+
*
|
|
110
|
+
* @internal — exported for tests; production has exactly one caller.
|
|
111
|
+
*/
|
|
112
|
+
export function computeLayoutSegmentPath(
|
|
113
|
+
segments: ManifestSegmentNode[],
|
|
114
|
+
index: number,
|
|
115
|
+
match: RouteMatch
|
|
116
|
+
): string[] {
|
|
117
|
+
const parts = consumedUrlParts(segments, index, match);
|
|
118
|
+
return parts.length === 0 ? [''] : ['', ...parts];
|
|
119
|
+
}
|
|
120
|
+
|
|
178
121
|
export function isClientReference(component: unknown): boolean {
|
|
179
122
|
return (
|
|
180
123
|
component != null &&
|
|
@@ -186,7 +129,7 @@ export function isClientReference(component: unknown): boolean {
|
|
|
186
129
|
// ─── Typed Errors ────────────────────────────────────────────────────────
|
|
187
130
|
|
|
188
131
|
/**
|
|
189
|
-
* Thrown when
|
|
132
|
+
* Thrown when an app/schema.ts segment-param codec's parse() fails.
|
|
190
133
|
* The pipeline catches this and responds with 404.
|
|
191
134
|
*/
|
|
192
135
|
export class ParamCoercionError extends Error {
|
|
@@ -217,8 +160,12 @@ export interface LayoutComponentEntry {
|
|
|
217
160
|
|
|
218
161
|
/** Result of building a route element tree. */
|
|
219
162
|
export interface RouteElementResult {
|
|
220
|
-
/**
|
|
221
|
-
|
|
163
|
+
/**
|
|
164
|
+
* The payload root: the React element tree (page wrapped in layouts, access
|
|
165
|
+
* gates, error boundaries) plus the params it rendered with, as siblings.
|
|
166
|
+
* See shared/payload-root.ts for why params are not a wrapper around it.
|
|
167
|
+
*/
|
|
168
|
+
payload: RscPayloadRoot;
|
|
222
169
|
/** Layout components loaded along the segment chain. */
|
|
223
170
|
layoutComponents: LayoutComponentEntry[];
|
|
224
171
|
/** Segments from the route match. */
|
|
@@ -334,47 +281,19 @@ export async function buildRouteElement(
|
|
|
334
281
|
): Promise<RouteElementResult> {
|
|
335
282
|
const segments = match.segments;
|
|
336
283
|
|
|
337
|
-
// ──
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// See TIM-1027, design/04-authorization.md, design/13-security.md.
|
|
349
|
-
const accessVerdicts = new Map<number, DenySignal | RedirectSignal>();
|
|
350
|
-
let firstDeniedIndex = Infinity;
|
|
351
|
-
|
|
352
|
-
for (let i = 0; i < segments.length; i++) {
|
|
353
|
-
const segment = segments[i];
|
|
354
|
-
if (!segment.access || i >= firstDeniedIndex) continue;
|
|
355
|
-
|
|
356
|
-
try {
|
|
357
|
-
const accessMod = await loadModule(segment.access);
|
|
358
|
-
const accessFn = accessMod.default as (() => Promise<void>) | undefined;
|
|
359
|
-
if (accessFn) {
|
|
360
|
-
await withSpan(
|
|
361
|
-
'timber.access.pre',
|
|
362
|
-
{ 'timber.segment': segment.segmentName ?? 'unknown' },
|
|
363
|
-
() => accessFn()
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
} catch (error: unknown) {
|
|
367
|
-
if (error instanceof DenySignal || error instanceof RedirectSignal) {
|
|
368
|
-
accessVerdicts.set(i, error);
|
|
369
|
-
firstDeniedIndex = i;
|
|
370
|
-
} else {
|
|
371
|
-
throw error;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
284
|
+
// ── Module loading ──────────────────────────────────────────────────
|
|
285
|
+
// Load layout/page modules and collect metadata sources. Metadata
|
|
286
|
+
// resolution is deferred to render time via MetadataCollector components
|
|
287
|
+
// placed below each AccessGate — denied segments' metadata() never
|
|
288
|
+
// executes because AccessGate prevents MetadataCollector from rendering.
|
|
289
|
+
// No pre-render access pass needed. See TIM-1363.
|
|
290
|
+
interface MetadataSource {
|
|
291
|
+
metadataExport: Metadata | (() => Promise<Metadata>);
|
|
292
|
+
segmentName: string;
|
|
293
|
+
isPage: boolean;
|
|
294
|
+
segmentIndex: number;
|
|
374
295
|
}
|
|
375
|
-
|
|
376
|
-
// ── Module loading + metadata resolution ─────────────────────────────
|
|
377
|
-
const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];
|
|
296
|
+
const metadataSources: MetadataSource[] = [];
|
|
378
297
|
const layoutComponents: LayoutComponentEntry[] = [];
|
|
379
298
|
let PageComponent: ((...args: unknown[]) => unknown) | null = null;
|
|
380
299
|
let deferSuspenseFor = 0;
|
|
@@ -382,7 +301,6 @@ export async function buildRouteElement(
|
|
|
382
301
|
for (let i = 0; i < segments.length; i++) {
|
|
383
302
|
const segment = segments[i];
|
|
384
303
|
const isLeaf = i === segments.length - 1;
|
|
385
|
-
const isDenied = i >= firstDeniedIndex;
|
|
386
304
|
|
|
387
305
|
// Load layout
|
|
388
306
|
if (segment.layout) {
|
|
@@ -394,15 +312,14 @@ export async function buildRouteElement(
|
|
|
394
312
|
});
|
|
395
313
|
}
|
|
396
314
|
|
|
397
|
-
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
398
|
-
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
399
|
-
|
|
400
315
|
rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
316
|
+
if (mod.metadata) {
|
|
317
|
+
metadataSources.push({
|
|
318
|
+
metadataExport: mod.metadata as Metadata | (() => Promise<Metadata>),
|
|
319
|
+
segmentName: segment.segmentName ?? segment.urlPath,
|
|
320
|
+
isPage: false,
|
|
321
|
+
segmentIndex: i,
|
|
322
|
+
});
|
|
406
323
|
}
|
|
407
324
|
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
408
325
|
}
|
|
@@ -411,18 +328,17 @@ export async function buildRouteElement(
|
|
|
411
328
|
if (isLeaf && segment.page) {
|
|
412
329
|
const mod = await loadModule(segment.page);
|
|
413
330
|
|
|
414
|
-
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
415
|
-
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
416
|
-
|
|
417
331
|
if (mod.default) {
|
|
418
332
|
PageComponent = mod.default as (...args: unknown[]) => unknown;
|
|
419
333
|
}
|
|
420
334
|
rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
335
|
+
if (mod.metadata) {
|
|
336
|
+
metadataSources.push({
|
|
337
|
+
metadataExport: mod.metadata as Metadata | (() => Promise<Metadata>),
|
|
338
|
+
segmentName: segment.segmentName ?? segment.urlPath,
|
|
339
|
+
isPage: true,
|
|
340
|
+
segmentIndex: i,
|
|
341
|
+
});
|
|
426
342
|
}
|
|
427
343
|
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
428
344
|
}
|
|
@@ -440,45 +356,36 @@ export async function buildRouteElement(
|
|
|
440
356
|
);
|
|
441
357
|
}
|
|
442
358
|
|
|
359
|
+
// Compute state-tree keys for all segments. Route groups accumulate
|
|
360
|
+
// ancestor group names for globally unique keys (e.g., "/(a)/(shared)"
|
|
361
|
+
// instead of just "/(shared)"). This is the single key computation —
|
|
362
|
+
// the same keys are used for skip decisions, SegmentOutlet props, the
|
|
363
|
+
// X-Timber-Segments header (via buildSegmentInfo), the error boundaries,
|
|
364
|
+
// and deny page ownership.
|
|
365
|
+
//
|
|
366
|
+
// Computed here, before the deny chains below, because each chain entry
|
|
367
|
+
// records the key of the segment that owns it.
|
|
368
|
+
const segmentKeys = computeSegmentKeys(segments);
|
|
369
|
+
// The directory path of each segment — the key `setSlotParams` and codegen
|
|
370
|
+
// both use for a slot beneath it, and the identity interception scoping
|
|
371
|
+
// compares against. One derivation, so a slot cannot be keyed two ways.
|
|
372
|
+
const segmentTreePaths = computeSegmentTreePaths(segments);
|
|
373
|
+
|
|
443
374
|
// Build deny page fallback chains for each segment position.
|
|
444
375
|
// When AccessGate or PageDenyBoundary catches a DenySignal, they render
|
|
445
376
|
// the matching deny page in-tree instead of throwing into React Flight.
|
|
446
377
|
// The chain walks from the current segment outward to root, collecting
|
|
447
|
-
// status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order
|
|
448
|
-
//
|
|
378
|
+
// status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order,
|
|
379
|
+
// each tagged with the key of the segment it came from so the catch site
|
|
380
|
+
// can tell a file it owns from an ancestor's. See TIM-666, TIM-1356.
|
|
449
381
|
const denyPageChains = new Map<number, DenyPageEntry[]>();
|
|
450
382
|
for (let i = 0; i < segments.length; i++) {
|
|
451
|
-
const chain = await buildDenyPageChain(segments, i);
|
|
383
|
+
const chain = await buildDenyPageChain(segments, i, segmentKeys);
|
|
452
384
|
if (chain.length > 0) {
|
|
453
385
|
denyPageChains.set(i, chain);
|
|
454
386
|
}
|
|
455
387
|
}
|
|
456
388
|
|
|
457
|
-
// Resolve metadata and convert to React elements that React 19 Float
|
|
458
|
-
// hoists into <head> during both SSR and client rendering. This replaces
|
|
459
|
-
// the previous HeadElement descriptor system (string splice for SSR,
|
|
460
|
-
// X-Timber-Head header for RSC nav, imperative DOM updates on client).
|
|
461
|
-
// React manages <title>/<meta>/<link> lifecycle across navigations.
|
|
462
|
-
// See design/16-metadata.md, TIM-1151.
|
|
463
|
-
const resolvedMetadata = resolveMetadata(metadataEntries);
|
|
464
|
-
const headElements = renderMetadataToElements(resolvedMetadata);
|
|
465
|
-
|
|
466
|
-
// Auto-link metadata route files (icon, apple-icon, manifest, opengraph-image).
|
|
467
|
-
// See design/16-metadata.md §"Auto-Linking"
|
|
468
|
-
const autoLinkedElements = collectMetadataRouteHeadElements(
|
|
469
|
-
segments,
|
|
470
|
-
firstDeniedIndex,
|
|
471
|
-
resolvedMetadata,
|
|
472
|
-
new URL(req.url),
|
|
473
|
-
metadataRouteHashes
|
|
474
|
-
);
|
|
475
|
-
headElements.push(...autoLinkedElements);
|
|
476
|
-
|
|
477
|
-
// Convert HeadElement descriptors to React elements for Float hoisting.
|
|
478
|
-
// Placed at the page segment level so they're always re-rendered on
|
|
479
|
-
// SPA navigation (sync layouts may be skipped by segment tree diffing).
|
|
480
|
-
const metadataElement = headElementsToReact(headElements);
|
|
481
|
-
|
|
482
389
|
// Build element tree: page wrapped in layouts (innermost to outermost)
|
|
483
390
|
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
484
391
|
|
|
@@ -519,6 +426,7 @@ export async function buildRouteElement(
|
|
|
519
426
|
Page: PageComponent,
|
|
520
427
|
route: match.segments[leafIndex]?.urlPath ?? '/',
|
|
521
428
|
denyPages: leafDenyPages,
|
|
429
|
+
segmentKey: segmentKeys[leafIndex],
|
|
522
430
|
onSettled,
|
|
523
431
|
});
|
|
524
432
|
} else {
|
|
@@ -533,11 +441,18 @@ export async function buildRouteElement(
|
|
|
533
441
|
element = h(TracedPage, {});
|
|
534
442
|
}
|
|
535
443
|
|
|
536
|
-
// Wrap page
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
|
|
444
|
+
// Wrap page in MetadataHead — a server component that reads all
|
|
445
|
+
// metadata entries collected by MetadataCollector components above it,
|
|
446
|
+
// merges them, and renders Float-hoisted head elements as siblings of
|
|
447
|
+
// the page. Placed at the page level so metadata is always re-rendered
|
|
448
|
+
// on SPA navigation (never skipped by segment tree diffing).
|
|
449
|
+
// See TIM-1363, design/16-metadata.md.
|
|
450
|
+
element = h(MetadataHead, {
|
|
451
|
+
segments,
|
|
452
|
+
requestUrl: req.url,
|
|
453
|
+
metadataRouteHashes,
|
|
454
|
+
children: element,
|
|
455
|
+
});
|
|
541
456
|
|
|
542
457
|
// Build a lookup of layout components by segment for O(1) access.
|
|
543
458
|
const layoutBySegment = new Map(
|
|
@@ -548,13 +463,6 @@ export async function buildRouteElement(
|
|
|
548
463
|
// The client uses this to merge the partial payload with its cached segments.
|
|
549
464
|
const skippedSegments: string[] = [];
|
|
550
465
|
|
|
551
|
-
// Compute state-tree keys for all segments. Route groups accumulate
|
|
552
|
-
// ancestor group names for globally unique keys (e.g., "/(a)/(shared)"
|
|
553
|
-
// instead of just "/(shared)"). This is the single key computation —
|
|
554
|
-
// the same keys are used for skip decisions, SegmentOutlet props, and
|
|
555
|
-
// the X-Timber-Segments header (via buildSegmentInfo).
|
|
556
|
-
const segmentKeys = computeSegmentKeys(segments);
|
|
557
|
-
|
|
558
466
|
// Pre-compute the contiguous skippable prefix (top-down).
|
|
559
467
|
//
|
|
560
468
|
// Skipped segments must form a contiguous prefix from root to avoid
|
|
@@ -572,10 +480,6 @@ export async function buildRouteElement(
|
|
|
572
480
|
const isLeaf = i === segments.length - 1;
|
|
573
481
|
const layoutComponent = layoutBySegment.get(segments[i]);
|
|
574
482
|
|
|
575
|
-
// Never skip denied segments — their AccessGate must render the
|
|
576
|
-
// deny page, not reuse the client's cached (passing) layout.
|
|
577
|
-
if (i >= firstDeniedIndex) break;
|
|
578
|
-
|
|
579
483
|
if (shouldSkipSegment(segmentKeys[i], layoutComponent, isLeaf, clientStateTree)) {
|
|
580
484
|
skippableSet.add(i);
|
|
581
485
|
} else if (layoutComponent) {
|
|
@@ -645,8 +549,27 @@ export async function buildRouteElement(
|
|
|
645
549
|
|
|
646
550
|
// Slot caching: the departing URL (X-Timber-URL header) and the
|
|
647
551
|
// destination URL (request URL) are compared to determine which
|
|
648
|
-
// slots' content has changed.
|
|
649
|
-
//
|
|
552
|
+
// slots' content has changed.
|
|
553
|
+
//
|
|
554
|
+
// Pathnames only. The client sends `X-Timber-URL` as a pathname
|
|
555
|
+
// (`client/router.ts` takes `.pathname` on both branches) and
|
|
556
|
+
// `slot-resolver.ts` reads `.pathname` from both sides, so search
|
|
557
|
+
// params take no part in the skip decision.
|
|
558
|
+
//
|
|
559
|
+
// Nothing here can tell whether a slot's content depends on the query.
|
|
560
|
+
// What is supposed to make that safe lives elsewhere: a slot whose server
|
|
561
|
+
// render varies with the query must reach it through a request accessor
|
|
562
|
+
// (page components are built with no props), which the request-dep analysis
|
|
563
|
+
// is meant to catch — and that analysis is incomplete for accessors reached
|
|
564
|
+
// through indirection, so this is currently a real staleness hole. See
|
|
565
|
+
// TIM-1291 and design/19-client-navigation.md. A slot whose *client* subtree
|
|
566
|
+
// varies with the query reads useSearchParams(), which re-renders from
|
|
567
|
+
// NavigationContext whether or not the slot was skipped; that half is
|
|
568
|
+
// unconditional.
|
|
569
|
+
//
|
|
570
|
+
// Do not add search to the comparison by sending the full departing URL:
|
|
571
|
+
// the header is hashed into the public `_rsc` cache key (TIM-1268),
|
|
572
|
+
// and a digest does not anonymize a guessable query string.
|
|
650
573
|
const departingUrl = req.headers.get('X-Timber-URL');
|
|
651
574
|
const destinationUrl = req.url;
|
|
652
575
|
const slotSkipInfo: SlotSkipEntry[] = [];
|
|
@@ -661,20 +584,63 @@ export async function buildRouteElement(
|
|
|
661
584
|
// Record for X-Timber-Skipped-Segments header (outermost first, so prepend).
|
|
662
585
|
skippedSegments.unshift(segmentKeys[i]);
|
|
663
586
|
|
|
587
|
+
// A skipped layout never reaches resolveSlotProps below, but its slots
|
|
588
|
+
// stay mounted on the client — so their params still have to be
|
|
589
|
+
// published, or useSegmentParams(slotPath) falls back to the main
|
|
590
|
+
// route's record after any navigation that skipped their parent
|
|
591
|
+
// (TIM-1285). Params only: nothing renders and no access chain runs,
|
|
592
|
+
// because an unchanged slot is what made this layout skippable.
|
|
593
|
+
publishSlotSegmentParams(segment, match, destinationUrl, segmentTreePaths[i]);
|
|
594
|
+
|
|
595
|
+
// No error boundaries are wrapped for a skipped segment — the `continue`
|
|
596
|
+
// below returns before that walk. A deny addressed to this segment
|
|
597
|
+
// therefore finds no boundary in the payload, and since TIM-1356 the
|
|
598
|
+
// payload path no longer falls back to a re-render for an addressed
|
|
599
|
+
// deny. What makes that sound is the precondition for skipping at all:
|
|
600
|
+
// `shouldSkipSegment` only returns true when this exact segment key is
|
|
601
|
+
// in the client's state tree, which means the client rendered this
|
|
602
|
+
// segment on an earlier navigation and still has its boundaries mounted,
|
|
603
|
+
// carrying the same key this build would have stamped. The payload
|
|
604
|
+
// splices in below them, so the throw passes through them on its way up.
|
|
605
|
+
//
|
|
606
|
+
// Metadata from skipped segments still needs to be collected (e.g.,
|
|
607
|
+
// root layout title templates). MetadataCollector wraps the element
|
|
608
|
+
// inside AccessGate — if access denies, metadata is excluded.
|
|
609
|
+
const skippedMeta = metadataSources.filter((s) => s.segmentIndex === i).reverse();
|
|
610
|
+
for (const meta of skippedMeta) {
|
|
611
|
+
const isDynamic = typeof meta.metadataExport === 'function';
|
|
612
|
+
element = h(MetadataCollector, {
|
|
613
|
+
metadataExport: meta.metadataExport,
|
|
614
|
+
segmentName: meta.segmentName,
|
|
615
|
+
isPage: meta.isPage,
|
|
616
|
+
...(isDynamic ? { onSettled: trackShellComponent() } : {}),
|
|
617
|
+
children: element,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
664
621
|
// SECURITY: Even though the layout is skipped, AccessGate MUST still
|
|
665
622
|
// wrap the element tree. access.ts runs on every navigation regardless
|
|
666
623
|
// of cached layouts or state tree content.
|
|
667
624
|
// See design/13-security.md §"Auth always runs" (test #11).
|
|
625
|
+
//
|
|
626
|
+
// Skipped segments omit denyPages and set skippedSegment=true. If
|
|
627
|
+
// access denies at a skipped segment, AccessGate records the deny in
|
|
628
|
+
// ALS and rethrows — the response path returns rscErrorEnvelope so the
|
|
629
|
+
// client hard-navigates for a clean full render with correct deny page
|
|
630
|
+
// placement. Rendering the deny page in-tree here would appear inside
|
|
631
|
+
// the client's cached layout chrome (TIM-1074). See TIM-1363.
|
|
668
632
|
if (segment.access) {
|
|
669
633
|
const accessMod = await loadModule(segment.access);
|
|
670
634
|
const accessFn = accessMod.default as (() => unknown) | undefined;
|
|
671
635
|
if (accessFn) {
|
|
672
|
-
const
|
|
636
|
+
const onGateSettled = trackShellComponent();
|
|
673
637
|
element = h(AccessGate, {
|
|
674
638
|
accessFn,
|
|
675
639
|
segmentName: segment.segmentName,
|
|
676
|
-
|
|
677
|
-
|
|
640
|
+
segmentKey: segmentKeys[i],
|
|
641
|
+
segmentIndex: i,
|
|
642
|
+
skippedSegment: true,
|
|
643
|
+
onSettled: onGateSettled,
|
|
678
644
|
children: element,
|
|
679
645
|
});
|
|
680
646
|
}
|
|
@@ -683,12 +649,42 @@ export async function buildRouteElement(
|
|
|
683
649
|
continue;
|
|
684
650
|
}
|
|
685
651
|
|
|
652
|
+
// Wrap with MetadataCollector(s) INSIDE error boundaries so a
|
|
653
|
+
// metadata() throw is caught by this segment's status-file boundaries,
|
|
654
|
+
// not an ancestor's. Still inside AccessGate (placed below) — when
|
|
655
|
+
// access denies, neither MetadataCollector nor error boundaries render.
|
|
656
|
+
//
|
|
657
|
+
// Reversed: the loop wraps innermost-to-outermost, so the last-wrapped
|
|
658
|
+
// collector renders first. Reversing ensures layout metadata is pushed
|
|
659
|
+
// before page metadata, preserving the root→page merge order required
|
|
660
|
+
// by resolveMetadata (page-authoritative). See TIM-1363.
|
|
661
|
+
const segmentMetadata = metadataSources.filter((s) => s.segmentIndex === i).reverse();
|
|
662
|
+
for (const meta of segmentMetadata) {
|
|
663
|
+
const isDynamic = typeof meta.metadataExport === 'function';
|
|
664
|
+
element = h(MetadataCollector, {
|
|
665
|
+
metadataExport: meta.metadataExport,
|
|
666
|
+
segmentName: meta.segmentName,
|
|
667
|
+
isPage: meta.isPage,
|
|
668
|
+
...(isDynamic ? { onSettled: trackShellComponent() } : {}),
|
|
669
|
+
children: element,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
686
673
|
// Wrap with error boundaries from this segment (inside layout).
|
|
687
674
|
// Keep ALL error boundaries (including 4xx) — they're the safety net for
|
|
688
675
|
// DenySignal from nested server components that escape AccessGate/PageDenyBoundary
|
|
689
676
|
// try/catch. Status-code files must be 'use client' TSX or MDX to serialize
|
|
690
677
|
// as error boundary fallbacks. See TIM-666.
|
|
691
|
-
|
|
678
|
+
// `segmentKey` makes each boundary addressable: a deny hoisted to this
|
|
679
|
+
// segment carries its owner's key, and every other boundary declines it
|
|
680
|
+
// rather than catching it first and rendering at the wrong depth
|
|
681
|
+
// (TIM-1356).
|
|
682
|
+
element = await wrapSegmentWithErrorBoundaries(segment, element, {
|
|
683
|
+
loadModule,
|
|
684
|
+
createElement: h,
|
|
685
|
+
errorBoundaryComponent: TimberErrorBoundary,
|
|
686
|
+
segmentKey: segmentKeys[i],
|
|
687
|
+
});
|
|
692
688
|
|
|
693
689
|
// Wrap with layout BEFORE AccessGate — AccessGate is OUTSIDE the layout.
|
|
694
690
|
// When AccessGate denies, the layout never renders. The deny page appears
|
|
@@ -696,42 +692,26 @@ export async function buildRouteElement(
|
|
|
696
692
|
// This prevents leaking layout UI (sidebars, nav) on denied pages.
|
|
697
693
|
// See design/04-authorization.md §"Access Failure".
|
|
698
694
|
if (layoutComponent) {
|
|
699
|
-
// Resolve parallel slots for this layout
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
// Skip slot resolution for denied segments (TIM-1229): when a
|
|
704
|
-
// parent segment's AccessGate denies, the layout never renders,
|
|
705
|
-
// so slot props are never consumed. Resolving them is wasted work
|
|
706
|
-
// and could trigger side effects in slot access.ts functions.
|
|
695
|
+
// Resolve parallel slots for this layout. If a parent AccessGate
|
|
696
|
+
// denies during render, the layout never renders and slot props
|
|
697
|
+
// are never consumed. Slot access functions may still fire eagerly
|
|
698
|
+
// for skip-candidate slots — a follow-up will make that lazy.
|
|
707
699
|
const segmentId = segmentKeys[i];
|
|
708
|
-
const parentTreePath =
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
interception,
|
|
724
|
-
parentTreePath,
|
|
725
|
-
departingUrl,
|
|
726
|
-
destinationUrl,
|
|
727
|
-
clientStateTree: clientStateTree as ClientStateTree | null,
|
|
728
|
-
slotSkipInfo,
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
// '/'.split('/') → ['', ''] (length 2), but root should be [''] (length 1).
|
|
732
|
-
// The extra empty string makes useSelectedLayoutSegment skip the first URL segment.
|
|
733
|
-
const rawPath = segment.urlPath.split('/');
|
|
734
|
-
const segmentPath = rawPath.length === 2 && rawPath[1] === '' ? [''] : rawPath;
|
|
700
|
+
const parentTreePath = segmentTreePaths[i];
|
|
701
|
+
const slotProps = await resolveSlotProps({
|
|
702
|
+
segment,
|
|
703
|
+
segmentId,
|
|
704
|
+
match,
|
|
705
|
+
h,
|
|
706
|
+
interception,
|
|
707
|
+
parentTreePath,
|
|
708
|
+
departingUrl,
|
|
709
|
+
destinationUrl,
|
|
710
|
+
clientStateTree: clientStateTree as ClientStateTree | null,
|
|
711
|
+
slotSkipInfo,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const segmentPath = computeLayoutSegmentPath(segments, i, match);
|
|
735
715
|
const parallelRouteKeys = Object.keys(segment.slots ?? {});
|
|
736
716
|
|
|
737
717
|
// Build the layout element.
|
|
@@ -765,11 +745,14 @@ export async function buildRouteElement(
|
|
|
765
745
|
(layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)
|
|
766
746
|
);
|
|
767
747
|
} catch (error: unknown) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
748
|
+
// A layout that denied never renders its own chrome — it threw
|
|
749
|
+
// instead of returning it — so an entry this segment owns is
|
|
750
|
+
// already correctly placed here. Anything owned further up is
|
|
751
|
+
// hoisted (handleCaughtDeny throws). See TIM-1356.
|
|
752
|
+
if (error instanceof DenySignal) {
|
|
753
|
+
setFirstDeniedSegmentIndex(i);
|
|
754
|
+
if (layoutDenyPages) {
|
|
755
|
+
return handleCaughtDeny(layoutDenyPages, error, segmentId);
|
|
773
756
|
}
|
|
774
757
|
}
|
|
775
758
|
throw error;
|
|
@@ -810,23 +793,23 @@ export async function buildRouteElement(
|
|
|
810
793
|
outermostSegmentProvider = segmentProviderElement;
|
|
811
794
|
}
|
|
812
795
|
|
|
813
|
-
// Wrap in AccessGate OUTSIDE the layout.
|
|
814
|
-
// If access denies, the
|
|
815
|
-
//
|
|
816
|
-
//
|
|
796
|
+
// Wrap in AccessGate OUTSIDE the layout, error boundaries, and MetadataCollector.
|
|
797
|
+
// If access denies, neither the layout nor MetadataCollector renders.
|
|
798
|
+
// Parent layouts (from outer iterations) form the shell. Access runs
|
|
799
|
+
// exactly once, during render, inside React.cache scope — layouts
|
|
800
|
+
// share the cache. See TIM-1363, design/04-authorization.md.
|
|
817
801
|
if (segment.access) {
|
|
818
802
|
const accessMod = await loadModule(segment.access);
|
|
819
803
|
const accessFn = accessMod.default as (() => unknown) | undefined;
|
|
820
804
|
if (accessFn) {
|
|
821
|
-
|
|
822
|
-
// without re-execution. Passing segments omit verdict so AccessGate
|
|
823
|
-
// re-runs access during render for React.cache population.
|
|
824
|
-
const verdict = accessVerdicts.get(i);
|
|
805
|
+
const onGateSettled = trackShellComponent();
|
|
825
806
|
element = h(AccessGate, {
|
|
826
807
|
accessFn,
|
|
827
808
|
segmentName: segment.segmentName,
|
|
809
|
+
segmentKey: segmentKeys[i],
|
|
810
|
+
segmentIndex: i,
|
|
828
811
|
denyPages: denyPageChains.get(i),
|
|
829
|
-
|
|
812
|
+
onSettled: onGateSettled,
|
|
830
813
|
children: element,
|
|
831
814
|
});
|
|
832
815
|
}
|
|
@@ -854,8 +837,13 @@ export async function buildRouteElement(
|
|
|
854
837
|
shellSettled = Promise.race([Promise.all(shellPromises).then(() => {}), shortCircuit]);
|
|
855
838
|
}
|
|
856
839
|
|
|
840
|
+
// Publish the request's params beside the finished tree, as the payload's
|
|
841
|
+
// root row. Last, because the partial-navigation rewrite above rebuilds the
|
|
842
|
+
// tree and this takes it as it finally stands.
|
|
843
|
+
const payload = withPublishedParams(element);
|
|
844
|
+
|
|
857
845
|
return {
|
|
858
|
-
|
|
846
|
+
payload,
|
|
859
847
|
layoutComponents,
|
|
860
848
|
segments,
|
|
861
849
|
deferSuspenseFor,
|