@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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel slot placement.
|
|
3
|
+
*
|
|
4
|
+
* One rule: a slot is resolved only for a segment on the **matched route
|
|
5
|
+
* chain**, and a slot's own subtree is never on that chain. So a slot declared
|
|
6
|
+
* anywhere inside another slot's subtree can never render, and is rejected at
|
|
7
|
+
* build time rather than left as a directory the author will keep editing.
|
|
8
|
+
*
|
|
9
|
+
* See design/07-routing.md §"Parallel Routes".
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SegmentNode } from './types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reject every parallel slot that no renderer reaches.
|
|
16
|
+
*
|
|
17
|
+
* **Where slots are resolved.** Two places read `segment.slots`, and both walk
|
|
18
|
+
* the main route chain: `route-element-builder.ts` (the RSC render) and
|
|
19
|
+
* `tree-builder.ts`. Neither is reachable from inside a slot. The slot's own
|
|
20
|
+
* subtree is rendered by `slot-resolver.ts`, which walks the slot's matched
|
|
21
|
+
* chain to apply that chain's layouts, error boundaries and access gates
|
|
22
|
+
* (`wrapWithIntermediateSegments`, `wrapWithLayout`) and never reads `.slots`
|
|
23
|
+
* on any of those nodes. So `app/feed/@modal/photo/@side/page.tsx` and
|
|
24
|
+
* everything below it is dead: the file is scanned, typed, code-split and
|
|
25
|
+
* shipped, and no request can render it.
|
|
26
|
+
*
|
|
27
|
+
* That is the TIM-1278 failure — a directory that silently never renders —
|
|
28
|
+
* and the reason it is a build error rather than a documented limit is that
|
|
29
|
+
* nothing at request time can report it. A slot that does not match renders
|
|
30
|
+
* `default.tsx` or `null`, which is exactly what a slot the resolver never
|
|
31
|
+
* looked at also produces, so the author sees the *designed* behaviour of a
|
|
32
|
+
* non-matching slot and has no signal to distinguish it from a slot that
|
|
33
|
+
* cannot match at all.
|
|
34
|
+
*
|
|
35
|
+
* **Interception is incidental.** TIM-1311 arrived as
|
|
36
|
+
* `feed/@modal/(.)photo/@side`, and the interception contributes nothing: the
|
|
37
|
+
* identical `feed/@modal/photo/@side` never renders either, for the same
|
|
38
|
+
* reason and with no marker anywhere. Rejecting only the intercepting spelling
|
|
39
|
+
* would have drawn a line the author cannot see from the directory names.
|
|
40
|
+
*
|
|
41
|
+
* The children-path interception shape is **not** affected and must not be:
|
|
42
|
+
* its intercepting node joins the main chain (`server/children-interception.ts`
|
|
43
|
+
* splices `[…owner ancestors, interceptingNode, …descendants]` into an ordinary
|
|
44
|
+
* `RouteMatch`), so `route-element-builder.ts` resolves slots on it and below
|
|
45
|
+
* it exactly as it does for any other chain segment. `(browse)/(...)photo/[id]/@side`
|
|
46
|
+
* renders; `feed/@modal/(.)photo/@side` does not. The discriminator is the slot
|
|
47
|
+
* boundary, which is what this walk tracks — not the marker.
|
|
48
|
+
*/
|
|
49
|
+
export function validateSlotPlacement(root: SegmentNode): void {
|
|
50
|
+
walkForSlotPlacement(root, '', null);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `enclosingSlotPath` is the `app/`-relative path of the nearest slot at or
|
|
55
|
+
* above `node`, or null on the main chain. It is carried rather than recomputed
|
|
56
|
+
* so the error can name the slot that swallows the offender — "this is inside
|
|
57
|
+
* @modal" is the part of the diagnosis the author cannot see from the offending
|
|
58
|
+
* path alone.
|
|
59
|
+
*/
|
|
60
|
+
function walkForSlotPlacement(
|
|
61
|
+
node: SegmentNode,
|
|
62
|
+
path: string,
|
|
63
|
+
enclosingSlotPath: string | null
|
|
64
|
+
): void {
|
|
65
|
+
for (const [slotName, slotNode] of Object.entries(node.slots)) {
|
|
66
|
+
const slotPath = path ? `${path}/@${slotName}` : `@${slotName}`;
|
|
67
|
+
if (enclosingSlotPath !== null) {
|
|
68
|
+
throwNestedSlot(slotPath, enclosingSlotPath);
|
|
69
|
+
}
|
|
70
|
+
walkForSlotPlacement(slotNode, slotPath, slotPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const child of node.children) {
|
|
74
|
+
walkForSlotPlacement(
|
|
75
|
+
child,
|
|
76
|
+
path ? `${path}/${child.segmentName}` : child.segmentName,
|
|
77
|
+
enclosingSlotPath
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function throwNestedSlot(slotPath: string, enclosingSlotPath: string): never {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Build error: parallel slot nested inside another parallel slot.\n` +
|
|
85
|
+
` Directory: app/${slotPath}\n` +
|
|
86
|
+
` Inside: app/${enclosingSlotPath}\n` +
|
|
87
|
+
`A parallel slot is resolved only for a segment on the matched route chain, and a ` +
|
|
88
|
+
`slot's own subtree is never on that chain — the slot resolver walks the slot's ` +
|
|
89
|
+
`matched chain to apply its layouts, error boundaries and access gates, and never ` +
|
|
90
|
+
`looks at slots declared inside it. So app/${slotPath} and everything below it can ` +
|
|
91
|
+
`never render, and no request-time signal can tell you: an unreachable slot ` +
|
|
92
|
+
`produces exactly what a slot that simply did not match produces.\n` +
|
|
93
|
+
`Declare the slot on a segment of the main route chain instead, or move its pages ` +
|
|
94
|
+
`into app/${enclosingSlotPath}'s own subtree.\n` +
|
|
95
|
+
`See design/07-routing.md §"Parallel Routes".`
|
|
96
|
+
);
|
|
97
|
+
}
|
package/src/routing/types.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `SegmentNode` (Map-based) and `ManifestSegmentNode` (object-based).
|
|
15
15
|
*
|
|
16
16
|
* Keyed groups (`slots`, `statusFiles`, `jsonStatusFiles`,
|
|
17
|
-
* `
|
|
17
|
+
* `metadataRoutes`) are plain `Record<string, …>`
|
|
18
18
|
* objects rather than `Map`s so that the build-time tree can be
|
|
19
19
|
* serialized into the virtual route manifest with no shape transform.
|
|
20
20
|
*
|
|
@@ -110,8 +110,6 @@ export interface SegmentNode<TFile = RouteFile> {
|
|
|
110
110
|
jsonStatusFiles?: Record<string, TFile>;
|
|
111
111
|
/** denied.tsx — slot-only denial rendering */
|
|
112
112
|
denied?: TFile;
|
|
113
|
-
/** Legacy compat: not-found.tsx (maps to 404), forbidden.tsx (403), unauthorized.tsx (401) */
|
|
114
|
-
legacyStatusFiles?: Record<string, TFile>;
|
|
115
113
|
|
|
116
114
|
/** Metadata route files (sitemap.ts, robots.ts, icon.tsx, etc.) keyed by base name */
|
|
117
115
|
metadataRoutes?: Record<string, TFile>;
|
package/src/routing/walkers.ts
CHANGED
|
@@ -49,6 +49,12 @@ export interface CollectLeafRoutesOptions {
|
|
|
49
49
|
* entries (the build report deduplicates by URL afterward).
|
|
50
50
|
* - Parallel slots are skipped unless `includeSlots: true` (slots
|
|
51
51
|
* share their parent's URL and are not addressable on their own).
|
|
52
|
+
* - Intercepting subtrees are skipped unconditionally. They render only
|
|
53
|
+
* on soft navigation and add no URL depth, so their computed `urlPath`s
|
|
54
|
+
* are not addresses — listing them would advertise routes (`/[year]`)
|
|
55
|
+
* that no request can reach. This matches every other consumer of the
|
|
56
|
+
* tree that produces URLs: the sitemap generator, the static generator,
|
|
57
|
+
* prerendering and the prebuilt-payload builder all skip them too.
|
|
52
58
|
* - Result is sorted by `urlPath` for deterministic output.
|
|
53
59
|
*/
|
|
54
60
|
export function collectLeafRoutes<TFile>(
|
|
@@ -68,6 +74,8 @@ function walk<TFile>(
|
|
|
68
74
|
result: LeafRoute<TFile>[],
|
|
69
75
|
includeSlots: boolean
|
|
70
76
|
): void {
|
|
77
|
+
if (node.segmentType === 'intercepting') return;
|
|
78
|
+
|
|
71
79
|
const currentChain = [...chain, node];
|
|
72
80
|
const path = node.urlPath || '/';
|
|
73
81
|
|
|
@@ -14,35 +14,12 @@ import { useQueryStates as clientUseQueryStates } from '../client/use-query-stat
|
|
|
14
14
|
import { fromSchema, isStandardSchema, isCodec } from '../schema-bridge.js';
|
|
15
15
|
import type { StandardSchemaV1 } from '../schema-bridge.js';
|
|
16
16
|
import type { Codec } from '../codec.js';
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// dynamic `await import()` at call time because the async microtask from the
|
|
24
|
-
// dynamic import loses AsyncLocalStorage context in React's RSC Flight renderer,
|
|
25
|
-
// breaking getSearchParams() in parallel slot pages. See TIM-523.
|
|
26
|
-
let _getSearchParamsFn: (() => URLSearchParams) | undefined;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Register the getSearchParams function. Called once at module load time
|
|
30
|
-
* from request-context.ts to avoid dynamic import at call time.
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
export function _setGetSearchParamsFn(fn: () => URLSearchParams): void {
|
|
34
|
-
_getSearchParamsFn = fn;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getSearchParamsFromAls(): URLSearchParams {
|
|
38
|
-
if (!_getSearchParamsFn) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
'[timber] searchParams.get() is only available on the server. ' +
|
|
41
|
-
'Use searchParams.useQueryStates() on the client.'
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return _getSearchParamsFn();
|
|
45
|
-
}
|
|
17
|
+
// Server-only reference for .get() — avoids pulling server ALS into client
|
|
18
|
+
// bundles. In client environments, .get() throws before reaching this code
|
|
19
|
+
// path. The slot lives in its own leaf module so that `request-context.ts`
|
|
20
|
+
// can register the getter without importing this file, which would drag
|
|
21
|
+
// `nuqs` into every server entry (TIM-1298). See shared/als-slots.ts.
|
|
22
|
+
import { getSearchParamsFromAls } from '../shared/als-slots.js';
|
|
46
23
|
|
|
47
24
|
// ---------------------------------------------------------------------------
|
|
48
25
|
// Types
|
|
@@ -16,33 +16,10 @@
|
|
|
16
16
|
import type { Codec } from '../codec.js';
|
|
17
17
|
import { type StandardSchemaV1, resolveCodecOrSchema } from '../schema-bridge.js';
|
|
18
18
|
import { useSegmentParams as clientUseSegmentParams } from '../client/use-segment-params.js';
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Same pattern as search-params: eagerly registered at server startup
|
|
25
|
-
// to avoid dynamic imports that lose ALS context. See TIM-523.
|
|
26
|
-
let _getSegmentParamsFn: (() => Record<string, string | string[]>) | undefined;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Register the getSegmentParams function. Called once at module load time
|
|
30
|
-
* from request-context.ts to avoid dynamic import at call time.
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
export function _setGetSegmentParamsFn(fn: () => Record<string, string | string[]>): void {
|
|
34
|
-
_getSegmentParamsFn = fn;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getSegmentParamsFromAls(): Record<string, string | string[]> {
|
|
38
|
-
if (!_getSegmentParamsFn) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
'[timber] segmentParams.get() is only available on the server. ' +
|
|
41
|
-
'Use segmentParams.useSegmentParams() on the client.'
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return _getSegmentParamsFn();
|
|
45
|
-
}
|
|
19
|
+
// Server-only ALS reference for .get(), eagerly registered at server startup
|
|
20
|
+
// from a leaf module so this file stays out of the server graph. Same slot
|
|
21
|
+
// mechanism as search-params. See shared/als-slots.ts, TIM-523, TIM-1298.
|
|
22
|
+
import { getSegmentParamsFromAls } from '../shared/als-slots.js';
|
|
46
23
|
|
|
47
24
|
// ---------------------------------------------------------------------------
|
|
48
25
|
// Types
|
|
@@ -296,7 +296,9 @@ async function handleRscAction(
|
|
|
296
296
|
// serialized together so React Flight handles both in one stream.
|
|
297
297
|
payload = {
|
|
298
298
|
_action: result.actionResult,
|
|
299
|
-
|
|
299
|
+
// The payload root, not a bare tree: it carries the revalidated
|
|
300
|
+
// route's params too, and the client publishes them (TIM-1297).
|
|
301
|
+
_tree: result.revalidation.payload,
|
|
300
302
|
};
|
|
301
303
|
headers['X-Timber-Revalidation'] = '1';
|
|
302
304
|
// Metadata (<title>/<meta>/<link>) now rides the revalidation element
|
package/src/server/actions.ts
CHANGED
|
@@ -20,10 +20,13 @@ import { withSpan } from './tracing';
|
|
|
20
20
|
import { revalidationAls, type RevalidationState } from './als-registry.js';
|
|
21
21
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
22
22
|
|
|
23
|
-
/** Result of rendering a revalidation —
|
|
23
|
+
/** Result of rendering a revalidation — payload root before RSC serialization. */
|
|
24
24
|
export interface RevalidationResult {
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The payload root — tree plus the params it rendered with, exactly as an
|
|
27
|
+
* ordinary route payload. The client splits it in `applyRevalidation`.
|
|
28
|
+
*/
|
|
29
|
+
payload: unknown;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
/** Renderer function that builds a React element tree for a given path. */
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import '#server-only-guard';
|
|
35
|
+
import type { SlotParamsRecord } from '../shared/slot-params.js';
|
|
35
36
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
36
37
|
import type { DebugComponentEntry } from './rsc-entry/helpers.js';
|
|
37
38
|
|
|
@@ -113,7 +114,7 @@ export interface RequestContextStore {
|
|
|
113
114
|
*
|
|
114
115
|
* See design/07-routing.md §"Parallel Slot Params"
|
|
115
116
|
*/
|
|
116
|
-
|
|
117
|
+
slotParams?: SlotParamsRecord;
|
|
117
118
|
/** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */
|
|
118
119
|
cookieJar: Map<string, CookieEntry>;
|
|
119
120
|
/** Whether the response has flushed (headers committed). */
|
|
@@ -49,13 +49,26 @@ export interface BuildManifest {
|
|
|
49
49
|
requestDep?: Record<string, boolean>;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Empty build manifest used in dev mode, and the single source of truth for
|
|
54
|
+
* what an empty manifest contains.
|
|
55
|
+
*
|
|
56
|
+
* `virtual:timber-build-manifest` serializes this constant into its generated
|
|
57
|
+
* source rather than restating it — one literal for the dev module, one for the
|
|
58
|
+
* production module's `globalThis.__TIMBER_BUILD_MANIFEST__ ?? …` fallback.
|
|
59
|
+
* They were three hand-written objects that had already drifted: this one and
|
|
60
|
+
* the dev literal were missing `requestDep` while the production fallback had
|
|
61
|
+
* it, so a reader who grepped the built bundle for `requestDep` found the one
|
|
62
|
+
* copy that mentioned it and read the fallback as the shipped map (TIM-1300).
|
|
63
|
+
* A field added here now reaches every copy.
|
|
64
|
+
*/
|
|
53
65
|
export const EMPTY_BUILD_MANIFEST: BuildManifest = {
|
|
54
66
|
css: {},
|
|
55
67
|
js: {},
|
|
56
68
|
modulepreload: {},
|
|
57
69
|
fonts: {},
|
|
58
70
|
metadataRouteHashes: {},
|
|
71
|
+
requestDep: {},
|
|
59
72
|
};
|
|
60
73
|
|
|
61
74
|
/** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of the URL a matched segment chain has consumed.
|
|
3
|
+
*
|
|
4
|
+
* One question — "which URL parts had been consumed by the time segment `i`
|
|
5
|
+
* rendered?" — asked by four things that must all agree:
|
|
6
|
+
*
|
|
7
|
+
* - the layout depth handed to `SegmentProvider` (`route-element-builder.ts`),
|
|
8
|
+
* which `useSelectedLayoutSegment(s)` indexes the live pathname with;
|
|
9
|
+
* - `slotUrlParts`, which slices the destination pathname for slot matching;
|
|
10
|
+
* - `resolveSlotProps`, which slices the *departing* pathname the same way to
|
|
11
|
+
* decide whether a slot can be skipped;
|
|
12
|
+
* - `skippable-prefix.ts`, which does the same for the contiguous skip prefix.
|
|
13
|
+
*
|
|
14
|
+
* They were four separate `extractUrlParts(...).length` calls. That was fine
|
|
15
|
+
* while the answer was a plain walk, and stopped being fine when interception
|
|
16
|
+
* gave it a second term (TIM-1280): the fix landed on the layout depth alone
|
|
17
|
+
* and the other three kept the old walk, so a layout and the slots beside it
|
|
18
|
+
* disagreed about how deep they were. Hence one function, in its own module,
|
|
19
|
+
* rather than a helper on the side of whichever caller was fixed first.
|
|
20
|
+
*
|
|
21
|
+
* See design/07-routing.md §"Intercepting Routes".
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
25
|
+
import type { RouteMatch } from './pipeline.js';
|
|
26
|
+
import { effectiveUrlSegment } from '../routing/segment-classify.js';
|
|
27
|
+
import { computeInterceptedBase } from '../routing/interception.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract raw URL path parts from a segment chain.
|
|
31
|
+
*
|
|
32
|
+
* Each dynamic segment consumed one URL part (its param value). Static
|
|
33
|
+
* segments consumed their own name. Groups are transparent. Catch-all segments
|
|
34
|
+
* consumed multiple parts (their param value is `string[]`).
|
|
35
|
+
*
|
|
36
|
+
* The root segment is named '' and consumes nothing, so it is skipped here
|
|
37
|
+
* rather than by each caller — callers that pass a chain including the root
|
|
38
|
+
* (interception anchoring, slot-skip slicing) would otherwise have to remember
|
|
39
|
+
* to filter it, and one that forgot would push an empty part and shift every
|
|
40
|
+
* subsequent index by one.
|
|
41
|
+
*
|
|
42
|
+
* Uses rawSegmentParams (pre-coercion) to get actual URL strings. Values are
|
|
43
|
+
* coerced to `String()` defensively in case a codec leaked a non-string value
|
|
44
|
+
* into the params map.
|
|
45
|
+
*
|
|
46
|
+
* Each node is read through `effectiveUrlSegment`, so an intercepting
|
|
47
|
+
* directory contributes the URL part it intercepts (`(.)item` → `item`,
|
|
48
|
+
* `(.)[id]` → the bound value). A children-path interception puts such a node
|
|
49
|
+
* in the *main* chain, and reading it directly would classify it as neither
|
|
50
|
+
* static nor param-bearing — it would contribute nothing, and every part after
|
|
51
|
+
* it would shift by one.
|
|
52
|
+
*/
|
|
53
|
+
export function extractUrlParts(
|
|
54
|
+
segments: ManifestSegmentNode[],
|
|
55
|
+
mainParams: Record<string, string | string[]>
|
|
56
|
+
): string[] {
|
|
57
|
+
const parts: string[] = [];
|
|
58
|
+
for (const node of segments) {
|
|
59
|
+
const seg = effectiveUrlSegment(node);
|
|
60
|
+
// The root's *directory* name is empty; read it off the node, since an
|
|
61
|
+
// intercepting node's effective name is the one it intercepts.
|
|
62
|
+
if (seg.segmentType === 'group') continue;
|
|
63
|
+
if (node.segmentName === '') continue;
|
|
64
|
+
if (seg.segmentType === 'static') {
|
|
65
|
+
parts.push(seg.segmentName);
|
|
66
|
+
} else if (seg.paramName) {
|
|
67
|
+
const value = mainParams[seg.paramName];
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
parts.push(...value.map(String));
|
|
70
|
+
} else if (value !== undefined) {
|
|
71
|
+
// Reconstruct the original URL part for affixed segments.
|
|
72
|
+
// The param value is the stripped middle (e.g. "123" for [id].png
|
|
73
|
+
// matching "123.png"), so we re-attach prefix/suffix so that slot
|
|
74
|
+
// matching through matchUrlParts sees the original part.
|
|
75
|
+
const prefix = seg.paramPrefix ?? '';
|
|
76
|
+
const suffix = seg.paramSuffix ?? '';
|
|
77
|
+
parts.push(prefix + String(value) + suffix);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return parts;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Where a children-path interception sits in the target pathname, or null when
|
|
86
|
+
* the chain contains none.
|
|
87
|
+
*
|
|
88
|
+
* `anchor` is the intercepted base's length — the URL index the intercepting
|
|
89
|
+
* node occupies — from the same `computeInterceptedBase` that rewrite
|
|
90
|
+
* generation and the slot resolver both go through, so the three cannot
|
|
91
|
+
* disagree about which part a marker points at.
|
|
92
|
+
*/
|
|
93
|
+
function interceptionAnchor(
|
|
94
|
+
segments: ManifestSegmentNode[]
|
|
95
|
+
): { index: number; anchor: number } | null {
|
|
96
|
+
const index = segments.findIndex((s) => s.segmentType === 'intercepting');
|
|
97
|
+
if (index <= 0) return null;
|
|
98
|
+
|
|
99
|
+
const owner = segments[index - 1];
|
|
100
|
+
const base = computeInterceptedBase(owner.urlPath, segments[index].interceptionMarker ?? '(.)');
|
|
101
|
+
return { index, anchor: base === '/' ? 0 : base.slice(1).split('/').length };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The URL parts the chain has consumed through `index`, inclusive.
|
|
106
|
+
*
|
|
107
|
+
* For an ordinary chain this is just the walk. Two things make it more than
|
|
108
|
+
* that, and both are cases where a node stands for a different number of URL
|
|
109
|
+
* parts than itself:
|
|
110
|
+
*
|
|
111
|
+
* - A **catch-all** consumes as many parts as the request supplies, which the
|
|
112
|
+
* walk already handles (its param value is an array).
|
|
113
|
+
* - An **interception with a climbing marker** intercepts a URL that starts
|
|
114
|
+
* *above* its owner, so the owner's own URL segment — and any static
|
|
115
|
+
* ancestor between the intercepted base and the owner — is not in the target
|
|
116
|
+
* at all. `feed/(..)photo/[id]` at `/photo/42` still has `feed` in the
|
|
117
|
+
* chain, and counting it puts everything after it off by one.
|
|
118
|
+
*
|
|
119
|
+
* So everything above the interception clamps to the anchor, and everything
|
|
120
|
+
* from it down counts forward from there. The owner chain is param-free by
|
|
121
|
+
* build-time rule and the intercepted base is a prefix of the owner's
|
|
122
|
+
* `urlPath`, so the target's leading `anchor` parts *are* the owner chain's
|
|
123
|
+
* leading `anchor` parts — which is what makes the clamped prefix real values
|
|
124
|
+
* rather than placeholders. For `(.)` the base is the owner's own path, so the
|
|
125
|
+
* clamp is a no-op and this reduces to the plain walk.
|
|
126
|
+
*/
|
|
127
|
+
export function consumedUrlParts(
|
|
128
|
+
segments: ManifestSegmentNode[],
|
|
129
|
+
index: number,
|
|
130
|
+
match: RouteMatch
|
|
131
|
+
): string[] {
|
|
132
|
+
const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
|
|
133
|
+
const partsThrough = (from: number, to: number) =>
|
|
134
|
+
extractUrlParts(segments.slice(from, to + 1), rawParams);
|
|
135
|
+
|
|
136
|
+
const interception = interceptionAnchor(segments);
|
|
137
|
+
if (!interception) return partsThrough(0, index);
|
|
138
|
+
|
|
139
|
+
const targetPrefix = partsThrough(0, interception.index - 1).slice(0, interception.anchor);
|
|
140
|
+
|
|
141
|
+
if (index < interception.index) {
|
|
142
|
+
// Above the interception: an ancestor is never deeper than the anchor.
|
|
143
|
+
return targetPrefix.slice(0, Math.min(partsThrough(0, index).length, interception.anchor));
|
|
144
|
+
}
|
|
145
|
+
// At or below it: the intercepting subtree's parts ARE the target's.
|
|
146
|
+
return [...targetPrefix, ...partsThrough(interception.index, index)];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The index in `match.segments` of a segment, or -1.
|
|
151
|
+
*
|
|
152
|
+
* Split out so the three slot call sites and the layout-depth one derive their
|
|
153
|
+
* slice point identically — `indexOf` plus `consumedUrlParts` — instead of
|
|
154
|
+
* each inlining the pair.
|
|
155
|
+
*/
|
|
156
|
+
export function consumedPartsThroughSegment(
|
|
157
|
+
segment: ManifestSegmentNode,
|
|
158
|
+
match: RouteMatch
|
|
159
|
+
): string[] {
|
|
160
|
+
const index = match.segments.indexOf(segment);
|
|
161
|
+
if (index < 0) return [];
|
|
162
|
+
return consumedUrlParts(match.segments as ManifestSegmentNode[], index, match);
|
|
163
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Children-path interception — the sibling layout swap (TIM-1280).
|
|
3
|
+
*
|
|
4
|
+
* The second shape interception has. An intercepting directory that is a
|
|
5
|
+
* direct child of an ordinary segment or route group renders *in place of*
|
|
6
|
+
* `children` for that directory's own layout chain:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* app/(landing)/layout.tsx
|
|
10
|
+
* app/(landing)/[artistSlug]/[year]/[month]/[day]/page.tsx ← hard nav
|
|
11
|
+
* app/(browse)/layout.tsx
|
|
12
|
+
* app/(browse)/(...)[artistSlug]/[year]/[month]/[day]/page.tsx ← soft nav from (browse)
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* There is no overlay, no slot, and no `default.tsx`. Nothing is re-matched
|
|
16
|
+
* either — and that is the point. The modal shape re-matches
|
|
17
|
+
* `interceptingPrefix`, a URL, and route groups contribute nothing to a URL,
|
|
18
|
+
* so an owner like `app/(browse)/` serializes to "/" and re-matching it
|
|
19
|
+
* resolves through whichever group owns the canonical page. This resolver
|
|
20
|
+
* addresses the owner by **tree path** instead, which never collapses.
|
|
21
|
+
*
|
|
22
|
+
* The result is an ordinary `RouteMatch` whose chain runs
|
|
23
|
+
* `[…owner ancestors, interceptingNode, …its matched descendants]`. Every
|
|
24
|
+
* consumer downstream — `tree-builder.ts`, the element builder, access gates,
|
|
25
|
+
* middleware — sees a segment chain and needs to know nothing about
|
|
26
|
+
* interception. That is why this feature adds no rendering path: the chain
|
|
27
|
+
* *is* the feature.
|
|
28
|
+
*
|
|
29
|
+
* See design/07-routing.md §"Intercepting Routes".
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type { RouteMatch } from './pipeline.js';
|
|
33
|
+
import { collectMiddlewareChain, type ManifestSegmentNode } from './route-matcher.js';
|
|
34
|
+
import { findInterceptingMatch } from './slot-interception.js';
|
|
35
|
+
import { treePathNames } from '../routing/segment-keys.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a children-path interception to a complete route match.
|
|
39
|
+
*
|
|
40
|
+
* `interceptingScope` is the rewrite's own build-time field — the tree path of
|
|
41
|
+
* the directory that owns the interception. The pipeline has already
|
|
42
|
+
* established that the client's rendered tree passes through it; this
|
|
43
|
+
* translates it into the chain to render.
|
|
44
|
+
*
|
|
45
|
+
* Returns null when the scope names no directory in this manifest, or when no
|
|
46
|
+
* intercepting child of that directory matches the target. Both are fail-safe:
|
|
47
|
+
* the caller keeps the canonical target match, so the page the user gets is
|
|
48
|
+
* the one a hard navigation would have produced.
|
|
49
|
+
*/
|
|
50
|
+
export type InterceptedRouteMatcher = (
|
|
51
|
+
targetPathname: string,
|
|
52
|
+
interceptingScope: string
|
|
53
|
+
) => RouteMatch | null;
|
|
54
|
+
|
|
55
|
+
/** Build a children-path interception resolver over a runtime manifest. */
|
|
56
|
+
export function createInterceptedRouteMatcher(root: ManifestSegmentNode): InterceptedRouteMatcher {
|
|
57
|
+
return (targetPathname, interceptingScope) => {
|
|
58
|
+
const ancestors = findChainByTreePath(root, interceptingScope);
|
|
59
|
+
if (!ancestors) return null;
|
|
60
|
+
|
|
61
|
+
const owner = ancestors[ancestors.length - 1];
|
|
62
|
+
// The same resolver the slot path uses. It takes an arbitrary node, buckets
|
|
63
|
+
// its intercepting children by the URL index their marker anchors them to,
|
|
64
|
+
// and walks each bucket with the shared tree walker — so marker depth,
|
|
65
|
+
// static-before-dynamic sibling ordering and catch-all handling are one
|
|
66
|
+
// implementation across both shapes rather than two that must agree.
|
|
67
|
+
const result = findInterceptingMatch(owner, targetPathname);
|
|
68
|
+
if (!result) return null;
|
|
69
|
+
|
|
70
|
+
// `result.chain` is [owner, interceptingNode, …descendants]; splice the
|
|
71
|
+
// owner's own ancestry in front of it to get a root-anchored chain.
|
|
72
|
+
const segments = [...ancestors, ...result.chain.slice(1)];
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
segments,
|
|
76
|
+
// Every segment above the intercepting node is static or a route group —
|
|
77
|
+
// `validateInterceptionPlacement` rejects a dynamic one at build time,
|
|
78
|
+
// precisely because the intercepted URL carries no value to bind it
|
|
79
|
+
// with. So the intercepting subtree's params are the whole record.
|
|
80
|
+
segmentParams: result.slotParams,
|
|
81
|
+
middlewareChain: collectMiddlewareChain(segments),
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find the chain from the root to the segment with this tree path.
|
|
88
|
+
*
|
|
89
|
+
* Tree paths are `computeSegmentTreePaths` — the directory path with slots
|
|
90
|
+
* elided — so this walks `children` only, which is also exactly the set of
|
|
91
|
+
* positions a children-path interception can own: an intercepting directory
|
|
92
|
+
* inside a slot subtree is rejected at build time unless it is a direct slot
|
|
93
|
+
* child, and that shape is the modal one.
|
|
94
|
+
*
|
|
95
|
+
* Sibling directories cannot share a name, so a tree path names at most one
|
|
96
|
+
* node and the walk needs no tie-break. This is the property `urlPath` and
|
|
97
|
+
* `computeSegmentKeys` both lack — see the note on `computeSegmentTreePaths`.
|
|
98
|
+
*/
|
|
99
|
+
function findChainByTreePath(
|
|
100
|
+
root: ManifestSegmentNode,
|
|
101
|
+
treePath: string
|
|
102
|
+
): ManifestSegmentNode[] | null {
|
|
103
|
+
const names = treePathNames(treePath);
|
|
104
|
+
const chain: ManifestSegmentNode[] = [root];
|
|
105
|
+
let node = root;
|
|
106
|
+
|
|
107
|
+
for (const name of names) {
|
|
108
|
+
const next = node.children.find((child) => child.segmentName === name);
|
|
109
|
+
if (!next) return null;
|
|
110
|
+
chain.push(next);
|
|
111
|
+
node = next;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return chain;
|
|
115
|
+
}
|