@timber-js/app 0.2.0-alpha.184 → 0.2.0-alpha.186
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-C-Rw9vPc.js} +3 -3
- package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-C-Rw9vPc.js.map} +1 -1
- package/dist/_chunks/als-slots-mFweg276.js +27 -0
- package/dist/_chunks/als-slots-mFweg276.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-Cd0VZ_Pd.js} +3 -3
- package/dist/_chunks/{cache-api-B-lhk9p4.js.map → cache-api-Cd0VZ_Pd.js.map} +1 -1
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js +1857 -0
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +1 -0
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-Cs0uZXea.js} +89 -12
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-Cs0uZXea.js.map} +1 -1
- package/dist/_chunks/error-boundary-DpYRI_I1.js +330 -0
- package/dist/_chunks/error-boundary-DpYRI_I1.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/fs-identity-D7vb6GdI.js +208 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
- package/dist/_chunks/{logger-AWfuX-KJ.js → logger-N7e5auP0.js} +9 -13
- package/dist/_chunks/logger-N7e5auP0.js.map +1 -0
- package/dist/_chunks/navigation-root-B29qg0_T.js +274 -0
- package/dist/_chunks/navigation-root-B29qg0_T.js.map +1 -0
- package/dist/_chunks/param-value-C8TNYchQ.js +66 -0
- package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
- package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-DEGLSJs3.js} +2 -1
- package/dist/_chunks/plugin-context-DEGLSJs3.js.map +1 -0
- 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-payload-path-B_LBodc2.js +48 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.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/{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-segment-params-ClyUNq4d.js +128 -0
- package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +1 -0
- package/dist/_chunks/{walkers-BtTlKviE.js → walkers-BhhwI9TD.js} +21 -87
- package/dist/_chunks/walkers-BhhwI9TD.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/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 +2 -3
- 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/cli.js +2 -2
- 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/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 +1 -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.js +18 -11
- 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 +872 -305
- package/dist/client/internal.js.map +1 -1
- 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 +71 -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 +39 -16
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +79 -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-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/cookies/index.js +3 -3
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +915 -320
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +33 -1
- 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 +9 -0
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +149 -8
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +18 -0
- package/dist/routing/codegen-types.d.ts.map +1 -1
- 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/index.js +3 -3
- package/dist/routing/interception.d.ts +235 -6
- package/dist/routing/interception.d.ts.map +1 -1
- 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 +1 -3
- 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/search-params/define.d.ts +0 -6
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +176 -2
- package/dist/search-params/index.js.map +1 -1
- package/dist/segment-params/define.d.ts +0 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +69 -1
- package/dist/segment-params/index.js.map +1 -0
- package/dist/server/actions.d.ts +6 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +2 -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/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 +4 -7
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +14 -3
- package/dist/server/deny-renderer.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.js +2 -2
- package/dist/server/internal.js +528 -373
- package/dist/server/internal.js.map +1 -1
- package/dist/server/param-coercion.d.ts +10 -1
- 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/prebuilt-builder.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +0 -12
- 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 +32 -2
- 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/error-renderer.d.ts +14 -2
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -10
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -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/rsc-payload.d.ts +3 -2
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/sitemap-generator.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 +6 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -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 +3 -10
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- 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-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/dist/shims/navigation-client.d.ts +8 -19
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +0 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/docs/api/31-api-client.mdx +4 -2
- package/docs/learn/02-pages-and-layouts.mdx +1 -1
- package/docs/learn/11-error-handling.mdx +4 -3
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +172 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/package.json +2 -2
- package/src/adapters/build-output-helper.ts +116 -3
- package/src/adapters/cloudflare.ts +86 -11
- 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/client/browser-entry/action-dispatch.ts +9 -4
- package/src/client/browser-entry/hydrate.ts +40 -29
- package/src/client/browser-entry/index.ts +15 -1
- 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 +20 -15
- package/src/client/history.ts +13 -6
- package/src/client/internal.ts +4 -3
- package/src/client/link.tsx +55 -8
- package/src/client/navigation-api.ts +3 -3
- package/src/client/navigation-commit.ts +174 -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 +389 -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 +242 -690
- package/src/client/rsc-fetch.ts +180 -122
- package/src/client/segment-cache.ts +121 -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-router.ts +1 -1
- package/src/client/use-segment-params.ts +41 -18
- package/src/index.ts +5 -1
- package/src/plugin-context.ts +34 -1
- 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 +60 -3
- package/src/plugins/static-build.ts +333 -63
- package/src/routing/codegen-types.ts +18 -0
- package/src/routing/codegen.ts +50 -10
- package/src/routing/collision-probe.ts +438 -0
- package/src/routing/collision-spaces.ts +281 -0
- package/src/routing/interception.ts +896 -50
- package/src/routing/manifest-codegen.ts +6 -9
- package/src/routing/scanner.ts +114 -113
- 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 +1 -3
- package/src/routing/walkers.ts +8 -0
- package/src/search-params/define.ts +6 -29
- package/src/segment-params/define.ts +4 -27
- package/src/server/action-handler.ts +3 -1
- package/src/server/actions.ts +6 -3
- package/src/server/als-registry.ts +2 -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/default-status-page.ts +127 -0
- package/src/server/deny-boundary.ts +5 -31
- package/src/server/deny-renderer.ts +83 -22
- package/src/server/head-response.ts +54 -0
- package/src/server/param-coercion.ts +48 -18
- package/src/server/pipeline-helpers.ts +0 -45
- package/src/server/pipeline-interception.ts +81 -32
- package/src/server/pipeline-outcome.ts +3 -5
- package/src/server/pipeline-phases.ts +68 -16
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-builder.ts +1 -7
- package/src/server/prebuilt-runtime.ts +4 -4
- package/src/server/primitives.ts +0 -18
- package/src/server/publish-params.ts +42 -0
- package/src/server/request-context.ts +97 -18
- package/src/server/route-element-builder.ts +89 -98
- package/src/server/route-handler.ts +8 -8
- package/src/server/route-matcher.ts +21 -9
- package/src/server/rsc-cache-key-guard.ts +170 -0
- package/src/server/rsc-entry/api-handler.ts +6 -8
- package/src/server/rsc-entry/error-renderer.ts +64 -25
- package/src/server/rsc-entry/helpers.ts +31 -16
- package/src/server/rsc-entry/index.ts +36 -28
- package/src/server/rsc-entry/render-route.ts +12 -7
- package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
- package/src/server/rsc-entry/rsc-payload.ts +14 -25
- package/src/server/rsc-entry/rsc-stream.ts +4 -3
- package/src/server/rsc-entry/ssr-renderer.ts +13 -9
- package/src/server/sitemap-generator.ts +1 -7
- package/src/server/skippable-prefix.ts +92 -0
- package/src/server/slot-interception.ts +168 -0
- package/src/server/slot-resolver.ts +253 -156
- package/src/server/ssr-bridge-types.ts +6 -0
- package/src/server/ssr-entry.ts +12 -4
- package/src/server/ssr-wrappers.tsx +33 -32
- package/src/server/state-tree-diff.ts +14 -55
- package/src/server/static-generator.ts +67 -45
- package/src/server/static-not-found.ts +134 -0
- package/src/server/status-code-resolver.ts +4 -47
- 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-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/src/shims/navigation-client.ts +8 -26
- package/src/shims/navigation.ts +0 -4
- 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/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-D3naS3eH.js +0 -688
- package/dist/_chunks/cli-schema-sync-D3naS3eH.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/router-ref-CtmF-aPv.js.map +0 -1
- package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
- package/dist/_chunks/ssr-data-BOWsq18U.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-BtTlKviE.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/src/plugins/request-dep.ts +0 -499
|
@@ -26,11 +26,14 @@ import type { InterceptionContext, RouteMatch } from './pipeline.js';
|
|
|
26
26
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
27
27
|
import { logRenderError } from './logger.js';
|
|
28
28
|
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
29
|
-
import { setSlotParams } from './request-context.js';
|
|
29
|
+
import { setSlotParams, runWithSlotSegmentParams } from './request-context.js';
|
|
30
30
|
import { coerceSlotParams } from './param-coercion.js';
|
|
31
31
|
import { matchUrlParts } from './tree-match.js';
|
|
32
|
+
import { extractUrlParts, consumedPartsThroughSegment } from './chain-url-parts.js';
|
|
33
|
+
import { findInterceptingMatch } from './slot-interception.js';
|
|
32
34
|
import { SegmentOutlet } from '../client/segment-outlet.js';
|
|
33
|
-
import {
|
|
35
|
+
import { shouldSkipSlot, type ClientStateTree } from './state-tree-diff.js';
|
|
36
|
+
import { computeSlotKey } from '../routing/segment-keys.js';
|
|
34
37
|
import { isStaticRequestDependent } from './request-dep.js';
|
|
35
38
|
|
|
36
39
|
type CreateElementFn = (...args: unknown[]) => React.ReactElement;
|
|
@@ -96,21 +99,27 @@ export async function resolveSlotElement(
|
|
|
96
99
|
: findSlotMatch(slotNode, match);
|
|
97
100
|
|
|
98
101
|
if (slotMatch) {
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
// Coerce the slot's own chain params through the global schema codecs
|
|
103
|
+
// (same as the main route) and expose them two ways:
|
|
104
|
+
//
|
|
105
|
+
// - keyed, in the ALS slot map, so `getSegmentParams(slotPath)` resolves
|
|
106
|
+
// from anywhere in the request;
|
|
107
|
+
// - scoped, around every component this resolver invokes from the slot's
|
|
108
|
+
// matched chain, so a no-arg `getSegmentParams()` inside the slot
|
|
109
|
+
// returns the slot's params instead of the main route's. An
|
|
110
|
+
// intercepting page has no other way to see its own params: the
|
|
111
|
+
// pipeline re-matched the SOURCE pathname, so the main params describe
|
|
112
|
+
// the page rendering *behind* the modal (TIM-1283).
|
|
113
|
+
//
|
|
114
|
+
// Both derive from the same coerced record, so the keyed overload and the
|
|
115
|
+
// no-arg call inside the slot can never disagree.
|
|
116
|
+
const coerced = coerceSlotParams(slotMatch.chain, slotMatch.slotParams);
|
|
117
|
+
const fullSlotPath = computeSlotSegmentPath(slotMatch.chain, parentTreePath);
|
|
118
|
+
|
|
119
|
+
if (fullSlotPath && Object.keys(coerced).length > 0) {
|
|
112
120
|
setSlotParams(fullSlotPath, coerced);
|
|
113
121
|
}
|
|
122
|
+
const scope = <T>(fn: () => T): T => runWithSlotSegmentParams(fullSlotPath, coerced, fn);
|
|
114
123
|
|
|
115
124
|
const SlotPage = await loadComponent(slotMatch.page);
|
|
116
125
|
if (SlotPage) {
|
|
@@ -137,7 +146,9 @@ export async function resolveSlotElement(
|
|
|
137
146
|
} else {
|
|
138
147
|
const SafeSlotPage = async (props: Record<string, unknown>) => {
|
|
139
148
|
try {
|
|
140
|
-
return await (
|
|
149
|
+
return await scope(() =>
|
|
150
|
+
(SlotPage as (props: Record<string, unknown>) => unknown)(props)
|
|
151
|
+
);
|
|
141
152
|
} catch (error) {
|
|
142
153
|
// RedirectSignal must propagate — the pipeline handles redirects
|
|
143
154
|
// at the top level. Swallowing it here would silently return
|
|
@@ -165,11 +176,17 @@ export async function resolveSlotElement(
|
|
|
165
176
|
// intermediate slot segments (everything between slot root and leaf).
|
|
166
177
|
// Process innermost-first, same order as route-element-builder.ts
|
|
167
178
|
// handles main segments. The slot root (index 0) is handled below.
|
|
168
|
-
element = await wrapWithIntermediateSegments(
|
|
179
|
+
element = await wrapWithIntermediateSegments(
|
|
180
|
+
slotMatch.chain,
|
|
181
|
+
element,
|
|
182
|
+
h,
|
|
183
|
+
accessVerdicts,
|
|
184
|
+
scope
|
|
185
|
+
);
|
|
169
186
|
|
|
170
187
|
// Wrap with slot root's layout — INSIDE the access gate, so the layout
|
|
171
188
|
// server component never executes when access.ts denies. See TIM-1074.
|
|
172
|
-
element = await wrapWithLayout(slotNode, element, h);
|
|
189
|
+
element = await wrapWithLayout(slotNode, element, h, scope);
|
|
173
190
|
|
|
174
191
|
// Wrap in SlotAccessGate if slot root has access.ts. The gate is
|
|
175
192
|
// OUTSIDE the layout, matching the segment model in
|
|
@@ -178,7 +195,7 @@ export async function resolveSlotElement(
|
|
|
178
195
|
// On denial: denied.tsx → default.tsx → null (graceful degradation),
|
|
179
196
|
// rendered WITHOUT the denied slot's own layout.
|
|
180
197
|
// See design/04-authorization.md §"Slot-Level Auth".
|
|
181
|
-
element = await wrapWithAccessGate(slotNode, element, h, accessVerdicts);
|
|
198
|
+
element = await wrapWithAccessGate(slotNode, element, h, accessVerdicts, scope);
|
|
182
199
|
|
|
183
200
|
// Wrap with slot root's error boundaries (outermost)
|
|
184
201
|
element = await wrapSegmentWithErrorBoundaries(slotNode, element, h);
|
|
@@ -207,6 +224,33 @@ export async function resolveSlotElement(
|
|
|
207
224
|
return renderDefaultFallback(slotNode, h);
|
|
208
225
|
}
|
|
209
226
|
|
|
227
|
+
/**
|
|
228
|
+
* The full tree path of a slot's matched chain — the key both the ALS slot
|
|
229
|
+
* params map and the generated `getSegmentParams` overloads use.
|
|
230
|
+
*
|
|
231
|
+
* Built from the parent's tree path plus each chain node's directory name,
|
|
232
|
+
* so an intercepting node contributes its marker
|
|
233
|
+
* (`/gallery/@modal/(.)photo/[id]`) and a route group contributes its
|
|
234
|
+
* parentheses. Returns undefined when the caller has no parent tree path,
|
|
235
|
+
* which is how the resolver's test callers and any path-less entry point opt
|
|
236
|
+
* out of keyed lookups.
|
|
237
|
+
*
|
|
238
|
+
* One definition for both the render path and the eager access path: they
|
|
239
|
+
* key the same map, and a divergence would silently strand one of them.
|
|
240
|
+
*/
|
|
241
|
+
function computeSlotSegmentPath(
|
|
242
|
+
chain: ManifestSegmentNode[],
|
|
243
|
+
parentTreePath: string | undefined
|
|
244
|
+
): string | undefined {
|
|
245
|
+
if (!parentTreePath) return undefined;
|
|
246
|
+
const slotSuffix = chain
|
|
247
|
+
.map((s) => s.segmentName)
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.join('/');
|
|
250
|
+
const prefix = parentTreePath === '/' ? '' : parentTreePath;
|
|
251
|
+
return `${prefix}/${slotSuffix}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
210
254
|
// ─── Element Wrapping Helpers ───────────────────────────────────────────────
|
|
211
255
|
|
|
212
256
|
/**
|
|
@@ -229,29 +273,76 @@ async function wrapWithIntermediateSegments(
|
|
|
229
273
|
chain: ManifestSegmentNode[],
|
|
230
274
|
element: React.ReactElement,
|
|
231
275
|
h: CreateElementFn,
|
|
232
|
-
accessVerdicts?: SlotAccessVerdict[]
|
|
276
|
+
accessVerdicts?: SlotAccessVerdict[],
|
|
277
|
+
scope?: SlotParamScope
|
|
233
278
|
): Promise<React.ReactElement> {
|
|
234
279
|
for (let i = chain.length - 1; i > 0; i--) {
|
|
235
280
|
const seg = chain[i];
|
|
236
281
|
element = await wrapSegmentWithErrorBoundaries(seg, element, h);
|
|
237
|
-
element = await wrapWithLayout(seg, element, h);
|
|
238
|
-
element = await wrapWithAccessGate(seg, element, h, accessVerdicts);
|
|
282
|
+
element = await wrapWithLayout(seg, element, h, scope);
|
|
283
|
+
element = await wrapWithAccessGate(seg, element, h, accessVerdicts, scope);
|
|
239
284
|
}
|
|
240
285
|
return element;
|
|
241
286
|
}
|
|
242
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Runs a function with the slot's own segment params in scope.
|
|
290
|
+
*
|
|
291
|
+
* Applied to every component this module invokes from a slot's matched chain
|
|
292
|
+
* — page, layouts, access functions — so all of them agree on what a no-arg
|
|
293
|
+
* `getSegmentParams()` means inside a slot. A partial application would be
|
|
294
|
+
* worse than none: a layout and the page under it would disagree about the
|
|
295
|
+
* params of the same render.
|
|
296
|
+
*/
|
|
297
|
+
type SlotParamScope = <T>(fn: () => T) => T;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Invokes a slot layout inside that slot's param scope.
|
|
301
|
+
*
|
|
302
|
+
* Declared at module scope, and given the layout and the scope as props,
|
|
303
|
+
* so its component identity is the same on every render. A closure created
|
|
304
|
+
* per render would be a new component type each time, and React would
|
|
305
|
+
* unmount and remount the whole layout subtree — including client
|
|
306
|
+
* components' state — on every navigation that re-renders the slot.
|
|
307
|
+
*
|
|
308
|
+
* Server component: the function-valued props are called here on the server
|
|
309
|
+
* and never reach the Flight stream, the same way `SlotAccessGate` takes
|
|
310
|
+
* `accessFn`.
|
|
311
|
+
*/
|
|
312
|
+
function ScopedSlotLayout(props: {
|
|
313
|
+
layout: (props: Record<string, unknown>) => unknown;
|
|
314
|
+
scope: SlotParamScope;
|
|
315
|
+
children: React.ReactElement;
|
|
316
|
+
}): unknown {
|
|
317
|
+
return props.scope(() => props.layout({ children: props.children }));
|
|
318
|
+
}
|
|
319
|
+
|
|
243
320
|
/**
|
|
244
321
|
* Wrap an element with a segment's layout component, if present.
|
|
322
|
+
*
|
|
323
|
+
* A server-component layout is invoked through the slot param scope, so it
|
|
324
|
+
* sees the slot's params rather than the main route's — a layout and the
|
|
325
|
+
* page beneath it must not disagree about the params of one render. A
|
|
326
|
+
* client-reference layout is passed through untouched: it never executes on
|
|
327
|
+
* the server, and its params arrive over the client channel
|
|
328
|
+
* (`useSegmentParams`).
|
|
245
329
|
*/
|
|
246
330
|
async function wrapWithLayout(
|
|
247
331
|
node: ManifestSegmentNode,
|
|
248
332
|
element: React.ReactElement,
|
|
249
|
-
h: CreateElementFn
|
|
333
|
+
h: CreateElementFn,
|
|
334
|
+
scope?: SlotParamScope
|
|
250
335
|
): Promise<React.ReactElement> {
|
|
251
336
|
if (!node.layout) return element;
|
|
252
337
|
const Layout = await loadComponent(node.layout);
|
|
253
338
|
if (!Layout) return element;
|
|
254
|
-
return h(Layout, { children: element });
|
|
339
|
+
if (!scope || isClientReference(Layout)) return h(Layout, { children: element });
|
|
340
|
+
|
|
341
|
+
return h(ScopedSlotLayout, {
|
|
342
|
+
layout: Layout as (props: Record<string, unknown>) => unknown,
|
|
343
|
+
scope,
|
|
344
|
+
children: element,
|
|
345
|
+
});
|
|
255
346
|
}
|
|
256
347
|
|
|
257
348
|
/**
|
|
@@ -262,12 +353,17 @@ async function wrapWithAccessGate(
|
|
|
262
353
|
slotNode: ManifestSegmentNode,
|
|
263
354
|
element: React.ReactElement,
|
|
264
355
|
h: CreateElementFn,
|
|
265
|
-
accessVerdicts?: SlotAccessVerdict[]
|
|
356
|
+
accessVerdicts?: SlotAccessVerdict[],
|
|
357
|
+
scope?: SlotParamScope
|
|
266
358
|
): Promise<React.ReactElement> {
|
|
267
359
|
if (!slotNode.access) return element;
|
|
268
360
|
|
|
269
|
-
const
|
|
270
|
-
if (!
|
|
361
|
+
const loaded = await loadComponent(slotNode.access);
|
|
362
|
+
if (!loaded) return element;
|
|
363
|
+
|
|
364
|
+
// The gate calls this during render, so the scope has to travel with the
|
|
365
|
+
// function rather than wrap the call site.
|
|
366
|
+
const accessFn = scope ? () => scope(() => loaded()) : loaded;
|
|
271
367
|
|
|
272
368
|
let DeniedComponent: ((...args: unknown[]) => unknown) | null = null;
|
|
273
369
|
if (slotNode.denied) {
|
|
@@ -302,7 +398,7 @@ async function wrapWithAccessGate(
|
|
|
302
398
|
// ─── Slot Matching ──────────────────────────────────────────────────────────
|
|
303
399
|
|
|
304
400
|
/** Result of matching a slot's sub-tree against the current route. */
|
|
305
|
-
interface SlotMatchResult {
|
|
401
|
+
export interface SlotMatchResult {
|
|
306
402
|
/** The page file at the matched leaf. */
|
|
307
403
|
page: NonNullable<ManifestSegmentNode['page']>;
|
|
308
404
|
/** The full chain of slot nodes traversed (slot root → … → leaf with page). */
|
|
@@ -326,25 +422,50 @@ interface SlotMatchResult {
|
|
|
326
422
|
* We compare the matched route's segment chain against the slot's children
|
|
327
423
|
* to find the deepest matching page.
|
|
328
424
|
*/
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
425
|
+
/**
|
|
426
|
+
* The index in the matched chain of the segment that owns this slot.
|
|
427
|
+
*
|
|
428
|
+
* **By identity**, because `urlPath` cannot answer it. Slots add no URL depth,
|
|
429
|
+
* so the owner shares the slot's `urlPath` — but so does every route group
|
|
430
|
+
* above it (`/` for both the root and `(browse)`), and since TIM-1280 so does
|
|
431
|
+
* an intercepting node on the children path, which sits *below* the owner.
|
|
432
|
+
* Picking the deepest urlPath match — the rule before this — therefore chose
|
|
433
|
+
* the intercepting node for a tree like `(browse)/@sidebar/` +
|
|
434
|
+
* `(browse)/(.)item/[id]/`, slicing one segment too many off the chain and
|
|
435
|
+
* handing `@sidebar` a shorter URL than it matches against. It would render
|
|
436
|
+
* its `default.tsx` where a page exists.
|
|
437
|
+
*
|
|
438
|
+
* The index must also agree with `slotUrlParts`, which slices the destination
|
|
439
|
+
* pathname at the same point and already used `indexOf(owner)`. Two derivations
|
|
440
|
+
* of one index is how they came apart; this is the shared one.
|
|
441
|
+
*
|
|
442
|
+
* The `urlPath` search survives only as a fallback for a `slotNode` that is not
|
|
443
|
+
* attached to any segment in the chain. Production never does that —
|
|
444
|
+
* `resolveSlotProps` iterates `segment.slots` — but unit tests construct a slot
|
|
445
|
+
* node standalone, and for those the old behaviour is still the right answer.
|
|
446
|
+
*/
|
|
447
|
+
export function findSlotOwnerIndex(
|
|
448
|
+
slotNode: ManifestSegmentNode,
|
|
449
|
+
segments: ManifestSegmentNode[]
|
|
450
|
+
): number {
|
|
341
451
|
for (let i = segments.length - 1; i >= 0; i--) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
break;
|
|
452
|
+
for (const candidate of Object.values(segments[i].slots ?? {})) {
|
|
453
|
+
if (candidate === slotNode) return i;
|
|
345
454
|
}
|
|
346
455
|
}
|
|
347
456
|
|
|
457
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
458
|
+
if (segments[i].urlPath === slotNode.urlPath) return i;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return -1;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMatchResult | null {
|
|
465
|
+
const segments = match.segments;
|
|
466
|
+
|
|
467
|
+
const parentIndex = findSlotOwnerIndex(slotNode, segments);
|
|
468
|
+
|
|
348
469
|
// The remaining segments after the parent are what we need to match
|
|
349
470
|
// against the slot's children.
|
|
350
471
|
const remainingSegments = parentIndex >= 0 ? segments.slice(parentIndex + 1) : [];
|
|
@@ -367,96 +488,6 @@ function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMa
|
|
|
367
488
|
return { page: leaf.page, chain: result.chain, slotParams: result.params };
|
|
368
489
|
}
|
|
369
490
|
|
|
370
|
-
/**
|
|
371
|
-
* Extract raw URL path parts from a main-route segment chain.
|
|
372
|
-
*
|
|
373
|
-
* Each dynamic segment consumed one URL part (its param value).
|
|
374
|
-
* Static segments consumed their own name. Groups are transparent.
|
|
375
|
-
* Catch-all segments consumed multiple parts (their param value is string[]).
|
|
376
|
-
*
|
|
377
|
-
* Uses rawSegmentParams (pre-coercion) to get actual URL strings.
|
|
378
|
-
* Values are coerced to String() defensively in case a codec leaked
|
|
379
|
-
* a non-string value into the params map.
|
|
380
|
-
*/
|
|
381
|
-
export function extractUrlParts(
|
|
382
|
-
segments: ManifestSegmentNode[],
|
|
383
|
-
mainParams: Record<string, string | string[]>
|
|
384
|
-
): string[] {
|
|
385
|
-
const parts: string[] = [];
|
|
386
|
-
for (const seg of segments) {
|
|
387
|
-
if (seg.segmentType === 'group') continue;
|
|
388
|
-
if (seg.segmentType === 'static') {
|
|
389
|
-
parts.push(seg.segmentName);
|
|
390
|
-
} else if (seg.paramName) {
|
|
391
|
-
const value = mainParams[seg.paramName];
|
|
392
|
-
if (Array.isArray(value)) {
|
|
393
|
-
parts.push(...value.map(String));
|
|
394
|
-
} else if (value !== undefined) {
|
|
395
|
-
// Reconstruct the original URL part for affixed segments.
|
|
396
|
-
// The param value is the stripped middle (e.g. "123" for [id].png
|
|
397
|
-
// matching "123.png"), so we re-attach prefix/suffix so that slot
|
|
398
|
-
// matching through matchUrlParts sees the original part.
|
|
399
|
-
const prefix = seg.paramPrefix ?? '';
|
|
400
|
-
const suffix = seg.paramSuffix ?? '';
|
|
401
|
-
parts.push(prefix + String(value) + suffix);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return parts;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Find a matching intercepting route in a slot's children for the target pathname.
|
|
410
|
-
*
|
|
411
|
-
* When interception is active, the pipeline has already re-matched the source URL.
|
|
412
|
-
* Here we check the slot's intercepting children (e.g. `(.)photo/[id]`) against
|
|
413
|
-
* the target pathname to find which intercepting page to render.
|
|
414
|
-
*
|
|
415
|
-
* The interceptedSegmentName tells us the first URL segment to look for in the
|
|
416
|
-
* target pathname. We then walk the intercepting child's sub-tree to match
|
|
417
|
-
* remaining segments.
|
|
418
|
-
*/
|
|
419
|
-
function findInterceptingMatch(
|
|
420
|
-
slotNode: ManifestSegmentNode,
|
|
421
|
-
targetPathname: string
|
|
422
|
-
): SlotMatchResult | null {
|
|
423
|
-
const targetParts = targetPathname === '/' ? [] : targetPathname.slice(1).split('/');
|
|
424
|
-
|
|
425
|
-
for (const child of slotNode.children) {
|
|
426
|
-
if (child.segmentType !== 'intercepting' || !child.interceptedSegmentName) continue;
|
|
427
|
-
|
|
428
|
-
const segName = child.interceptedSegmentName;
|
|
429
|
-
|
|
430
|
-
// Find where the intercepted segment name appears in the target parts.
|
|
431
|
-
// Search from the end since intercepted routes match the URL tail.
|
|
432
|
-
let matchIdx = -1;
|
|
433
|
-
for (let i = targetParts.length - 1; i >= 0; i--) {
|
|
434
|
-
if (targetParts[i] === segName) {
|
|
435
|
-
matchIdx = i;
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
if (matchIdx < 0) continue;
|
|
440
|
-
|
|
441
|
-
// Match remaining target parts against the intercepting child's sub-tree.
|
|
442
|
-
const remaining = targetParts.slice(matchIdx + 1);
|
|
443
|
-
const result = matchUrlParts(child, remaining);
|
|
444
|
-
if (result) {
|
|
445
|
-
const leaf = result.chain[result.chain.length - 1];
|
|
446
|
-
if (leaf.page) {
|
|
447
|
-
return {
|
|
448
|
-
page: leaf.page,
|
|
449
|
-
// Prepend slotNode — matchUrlParts starts from child, not slot root
|
|
450
|
-
chain: [slotNode, ...result.chain],
|
|
451
|
-
slotParams: result.params,
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
491
|
// ─── Slot Access Evaluation ─────────────────────────────────────────────────
|
|
461
492
|
|
|
462
493
|
/** Result of eagerly evaluating one segment's access.ts. */
|
|
@@ -483,7 +514,8 @@ export interface SlotAccessVerdict {
|
|
|
483
514
|
*/
|
|
484
515
|
async function evaluateSlotAccessChain(
|
|
485
516
|
slotRoot: ManifestSegmentNode,
|
|
486
|
-
chain: ManifestSegmentNode[]
|
|
517
|
+
chain: ManifestSegmentNode[],
|
|
518
|
+
scope?: SlotParamScope
|
|
487
519
|
): Promise<SlotAccessVerdict[]> {
|
|
488
520
|
// Collect all nodes with access.ts: slot root + intermediate chain segments
|
|
489
521
|
const nodesWithAccess: ManifestSegmentNode[] = [];
|
|
@@ -502,7 +534,7 @@ async function evaluateSlotAccessChain(
|
|
|
502
534
|
continue;
|
|
503
535
|
}
|
|
504
536
|
try {
|
|
505
|
-
await accessFn();
|
|
537
|
+
await (scope ? scope(() => accessFn()) : accessFn());
|
|
506
538
|
results.push({ node, verdict: 'pass' });
|
|
507
539
|
} catch (e) {
|
|
508
540
|
if (e instanceof DenySignal) {
|
|
@@ -541,6 +573,68 @@ export interface SlotSkipEntry {
|
|
|
541
573
|
skipped?: boolean;
|
|
542
574
|
}
|
|
543
575
|
|
|
576
|
+
/**
|
|
577
|
+
* The destination URL parts a segment's slots match against.
|
|
578
|
+
*
|
|
579
|
+
* A slot walks its own sub-tree starting where its owning segment stopped
|
|
580
|
+
* consuming the URL, so the parts are sliced at the length its ancestors
|
|
581
|
+
* consumed. Shared by `resolveSlotProps` and `publishSlotSegmentParams` — both
|
|
582
|
+
* have to slice identically or the two would disagree about which page a slot
|
|
583
|
+
* matched, and only one of them would be right.
|
|
584
|
+
*
|
|
585
|
+
* @internal
|
|
586
|
+
*/
|
|
587
|
+
export function slotUrlParts(
|
|
588
|
+
segment: ManifestSegmentNode,
|
|
589
|
+
match: RouteMatch,
|
|
590
|
+
url: string
|
|
591
|
+
): string[] {
|
|
592
|
+
const pathname = new URL(url, 'http://localhost').pathname;
|
|
593
|
+
// Slice through the owning segment inclusive, to match findSlotMatch, which
|
|
594
|
+
// walks the chain from parentIndex + 1. `consumedUrlParts` drops the root
|
|
595
|
+
// (it consumes no URL part) and honors the interception anchor, so a slot on
|
|
596
|
+
// a retained owner above a climbing marker is not handed parts the target
|
|
597
|
+
// does not contain.
|
|
598
|
+
const sliceAt = consumedPartsThroughSegment(segment, match).length;
|
|
599
|
+
const all = pathname === '/' ? [] : pathname.slice(1).split('/');
|
|
600
|
+
return all.slice(sliceAt);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Seed per-slot segment params for a segment whose layout is being SKIPPED.
|
|
605
|
+
*
|
|
606
|
+
* `resolveSlotProps` seeds these as part of resolving slot props, but a skipped
|
|
607
|
+
* layout never gets there — `route-element-builder` continues past it. Its
|
|
608
|
+
* slots are still mounted on the client, though, so leaving them out of the
|
|
609
|
+
* published map made `useSegmentParams(slotPath)` fall back to the main route's
|
|
610
|
+
* record after any navigation that skipped their parent (TIM-1285).
|
|
611
|
+
*
|
|
612
|
+
* Only the params are seeded. Nothing renders, no access chain runs — a skipped
|
|
613
|
+
* layout's slots are unchanged by definition, which is what made it skippable.
|
|
614
|
+
*
|
|
615
|
+
* @internal
|
|
616
|
+
*/
|
|
617
|
+
export function publishSlotSegmentParams(
|
|
618
|
+
segment: ManifestSegmentNode,
|
|
619
|
+
match: RouteMatch,
|
|
620
|
+
destinationUrl: string,
|
|
621
|
+
parentTreePath: string | undefined
|
|
622
|
+
): void {
|
|
623
|
+
const slotEntries = Object.entries(segment.slots ?? {});
|
|
624
|
+
if (slotEntries.length === 0) return;
|
|
625
|
+
const destinationParts = slotUrlParts(segment, match, destinationUrl);
|
|
626
|
+
|
|
627
|
+
for (const [, slotNode] of slotEntries) {
|
|
628
|
+
const destMatch = matchUrlParts(slotNode as ManifestSegmentNode, destinationParts);
|
|
629
|
+
if (!destMatch) continue;
|
|
630
|
+
const coerced = coerceSlotParams(destMatch.chain, destMatch.params);
|
|
631
|
+
const fullSlotPath = computeSlotSegmentPath(destMatch.chain, parentTreePath);
|
|
632
|
+
if (fullSlotPath && Object.keys(coerced).length > 0) {
|
|
633
|
+
setSlotParams(fullSlotPath, coerced);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
544
638
|
interface ResolveSlotPropsArgs {
|
|
545
639
|
segment: ManifestSegmentNode;
|
|
546
640
|
segmentId: string;
|
|
@@ -591,16 +685,7 @@ export async function resolveSlotProps({
|
|
|
591
685
|
const destinationPathname = destParsed.pathname;
|
|
592
686
|
const departingPathname = depParsed?.pathname ?? null;
|
|
593
687
|
|
|
594
|
-
|
|
595
|
-
// Include the owning segment (segIdx + 1) to match findSlotMatch,
|
|
596
|
-
// which slices at parentIndex + 1. Filter out the root segment
|
|
597
|
-
// (segmentName === '') — it maps to '/' and doesn't consume a URL part.
|
|
598
|
-
const segIdx = match.segments.indexOf(segment);
|
|
599
|
-
const parentSegments =
|
|
600
|
-
segIdx >= 0 ? match.segments.slice(0, segIdx + 1).filter((s) => s.segmentName !== '') : [];
|
|
601
|
-
const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
|
|
602
|
-
const parentConsumedParts = extractUrlParts(parentSegments, rawParams);
|
|
603
|
-
const sliceAt = parentConsumedParts.length;
|
|
688
|
+
const sliceAt = consumedPartsThroughSegment(segment, match).length;
|
|
604
689
|
|
|
605
690
|
function splitPathname(pathname: string): string[] {
|
|
606
691
|
return pathname === '/' ? [] : pathname.slice(1).split('/');
|
|
@@ -608,7 +693,7 @@ export async function resolveSlotProps({
|
|
|
608
693
|
|
|
609
694
|
const destinationAll = splitPathname(destinationPathname);
|
|
610
695
|
const departingAll = departingPathname ? splitPathname(departingPathname) : null;
|
|
611
|
-
const destinationParts =
|
|
696
|
+
const destinationParts = slotUrlParts(segment, match, destinationUrl);
|
|
612
697
|
const departingParts = departingAll ? departingAll.slice(sliceAt) : null;
|
|
613
698
|
const clientSlots = clientStateTree?.slots ?? null;
|
|
614
699
|
|
|
@@ -633,15 +718,18 @@ export async function resolveSlotProps({
|
|
|
633
718
|
// files calling getSegmentParams() see the slot's own coerced params,
|
|
634
719
|
// not the main route's. This is the same seeding that resolveSlotElement
|
|
635
720
|
// does, but we need it here for the eager path.
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
.map((s) => s.segmentName)
|
|
639
|
-
.filter(Boolean)
|
|
640
|
-
.join('/');
|
|
641
|
-
const prefix = parentTreePath === '/' ? '' : parentTreePath;
|
|
642
|
-
const fullSlotPath = `${prefix}/${slotSuffix}`;
|
|
721
|
+
let eagerScope: SlotParamScope | undefined;
|
|
722
|
+
if (destMatch) {
|
|
643
723
|
const coerced = coerceSlotParams(destMatch.chain, destMatch.params);
|
|
644
|
-
|
|
724
|
+
const fullSlotPath = computeSlotSegmentPath(destMatch.chain, parentTreePath);
|
|
725
|
+
if (fullSlotPath && Object.keys(coerced).length > 0) {
|
|
726
|
+
setSlotParams(fullSlotPath, coerced);
|
|
727
|
+
}
|
|
728
|
+
// The eager path calls access.ts outside the React tree, so the render
|
|
729
|
+
// scope in resolveSlotElement cannot cover it. Without this, the same
|
|
730
|
+
// access.ts would read the main route's params when the slot is a skip
|
|
731
|
+
// candidate and the slot's own params when it is not.
|
|
732
|
+
eagerScope = (fn) => runWithSlotSegmentParams(fullSlotPath, coerced, fn);
|
|
645
733
|
}
|
|
646
734
|
|
|
647
735
|
// Check non-access skip conditions first. If any of these fail,
|
|
@@ -674,7 +762,7 @@ export async function resolveSlotProps({
|
|
|
674
762
|
// "No access check for default.tsx" (destMatch null = unmatched slot).
|
|
675
763
|
const chainVerdicts =
|
|
676
764
|
isSkipCandidate && destMatch
|
|
677
|
-
? await evaluateSlotAccessChain(slotManifest, destMatch.chain)
|
|
765
|
+
? await evaluateSlotAccessChain(slotManifest, destMatch.chain, eagerScope)
|
|
678
766
|
: [];
|
|
679
767
|
const accessBlocked = chainVerdicts.some((v) => v.verdict !== 'pass');
|
|
680
768
|
const canSkip = isSkipCandidate && !accessBlocked;
|
|
@@ -713,7 +801,16 @@ export async function resolveSlotProps({
|
|
|
713
801
|
let slotRequestDep = false;
|
|
714
802
|
const destLeaf = destMatch?.chain[destMatch.chain.length - 1];
|
|
715
803
|
const hasRenderedPage = destMatch && destLeaf?.page;
|
|
716
|
-
if (
|
|
804
|
+
if (interception) {
|
|
805
|
+
// `destMatch` walks the slot's ordinary children against the
|
|
806
|
+
// destination URL; the interception resolver renders an intercepting
|
|
807
|
+
// child instead, which that walk never sees. So the files analyzed
|
|
808
|
+
// below are not the files that rendered — and `false` here tells the
|
|
809
|
+
// client the slot is reusable. Stay conservative: a needlessly
|
|
810
|
+
// request-dependent slot is a wasted refetch, a wrongly static one is
|
|
811
|
+
// a stale modal.
|
|
812
|
+
slotRequestDep = true;
|
|
813
|
+
} else if (hasRenderedPage) {
|
|
717
814
|
// Check slot root layout (rendered by wrapWithLayout)
|
|
718
815
|
if (
|
|
719
816
|
slotManifest.layout?.filePath &&
|
|
@@ -42,6 +42,12 @@ export interface NavContext {
|
|
|
42
42
|
segmentParams: Record<string, string | string[]>;
|
|
43
43
|
/** Search params from the URL */
|
|
44
44
|
searchParams: Record<string, string>;
|
|
45
|
+
/**
|
|
46
|
+
* Per-slot segment params, keyed by slot tree path. Absent when the request
|
|
47
|
+
* rendered no slot with params of its own. Feeds `useSegmentParams(path)`
|
|
48
|
+
* during SSR and, via `__timber_slot_params`, after hydration (TIM-1285).
|
|
49
|
+
*/
|
|
50
|
+
slotParams?: Record<string, Record<string, string | string[]>>;
|
|
45
51
|
/** The committed HTTP status code */
|
|
46
52
|
statusCode: number;
|
|
47
53
|
/** Response headers from middleware/proxy */
|
package/src/server/ssr-entry.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { wrapSsrElement } from './ssr-wrappers.js';
|
|
|
45
45
|
import { withSpan } from './tracing.js';
|
|
46
46
|
import { setCurrentParams } from '../client/use-segment-params.js';
|
|
47
47
|
import { registerSsrDataProvider, type SsrData } from '../client/ssr-data.js';
|
|
48
|
+
import { readPayloadTree } from '../shared/payload-root.js';
|
|
48
49
|
import type { NavContext } from './ssr-bridge-types.js';
|
|
49
50
|
|
|
50
51
|
// Pre-import Node.js stream modules at module load time — not per-request.
|
|
@@ -129,6 +130,7 @@ export async function handleSsr(
|
|
|
129
130
|
searchParams: navContext.searchParams,
|
|
130
131
|
cookies: navContext.cookies ?? new Map(),
|
|
131
132
|
params: navContext.segmentParams,
|
|
133
|
+
slotParams: navContext.slotParams,
|
|
132
134
|
_navContext: navContext,
|
|
133
135
|
};
|
|
134
136
|
|
|
@@ -154,19 +156,25 @@ export async function handleSsr(
|
|
|
154
156
|
// On Node.js: convert Web ReadableStream → Node Readable → createFromNodeStream
|
|
155
157
|
// (eliminates Promise-per-chunk overhead from Web Streams reader)
|
|
156
158
|
// On Workers: createFromReadableStream (Web Streams are V8-native C++ there)
|
|
157
|
-
let
|
|
159
|
+
let root: unknown;
|
|
158
160
|
if (hasNodeStreamDecode && _nodeStreamImports) {
|
|
159
161
|
const nodeRscStream = _nodeStreamImports.ReadableFromWeb(
|
|
160
162
|
rscStream as import('stream/web').ReadableStream
|
|
161
163
|
);
|
|
162
|
-
|
|
164
|
+
root = createFromNodeStream(nodeRscStream);
|
|
163
165
|
} else {
|
|
164
|
-
|
|
166
|
+
root = createFromReadableStream(rscStream);
|
|
165
167
|
}
|
|
168
|
+
// The payload root is `{ tree, params, slotParams }` — params ride
|
|
169
|
+
// beside the tree rather than in a provider around it (TIM-1297). SSR
|
|
170
|
+
// renders the tree; the params it needs are already in `ssrData` above,
|
|
171
|
+
// which is where `useSegmentParams()` reads them from during SSR. The
|
|
172
|
+
// client renders its own provider over the same records.
|
|
173
|
+
const element = Promise.resolve(root).then(readPayloadTree) as React.ReactNode;
|
|
166
174
|
const _decodeEnd = performance.now();
|
|
167
175
|
|
|
168
176
|
// Wrap with the same component tree structure as the client hydration
|
|
169
|
-
// tree (NavigationRoot →
|
|
177
|
+
// tree (NavigationRoot → TopLoader →
|
|
170
178
|
// TimberNuqsAdapter → NuqsAdapterProvider → NavigationProvider).
|
|
171
179
|
// This ensures useId() produces matching IDs on both sides, preventing
|
|
172
180
|
// hydration mismatches in libraries like Radix UI. See TIM-532.
|