@timber-js/app 0.2.0-alpha.185 → 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-Byy425ng.js.map → segment-classify-C539Pa2O.js.map} +1 -1
- 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-_6zKFlch.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 +865 -306
- 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 +38 -25
- 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 +906 -321
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +31 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/request-dep/analysis.d.ts +106 -0
- package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
- package/dist/plugins/request-dep/index.d.ts +75 -0
- package/dist/plugins/request-dep/index.d.ts.map +1 -0
- package/dist/plugins/request-dep/origin.d.ts +61 -0
- package/dist/plugins/request-dep/origin.d.ts.map +1 -0
- package/dist/plugins/routing.d.ts +9 -0
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +136 -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/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-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 +393 -212
- 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/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/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/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/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 +2 -0
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +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 -707
- package/src/client/rsc-fetch.ts +169 -102
- 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 +32 -0
- package/src/plugins/adapter-build.ts +4 -0
- package/src/plugins/build-manifest.ts +34 -13
- package/src/plugins/entries.ts +4 -0
- package/src/plugins/fonts.ts +11 -8
- package/src/plugins/request-dep/analysis.ts +447 -0
- package/src/plugins/request-dep/index.ts +376 -0
- package/src/plugins/request-dep/origin.ts +310 -0
- package/src/plugins/routing.ts +59 -2
- package/src/plugins/static-build.ts +315 -73
- 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 +5 -3
- package/src/routing/scanner.ts +114 -89
- 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/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-renderer.ts +81 -20
- 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 +2 -4
- package/src/server/pipeline-phases.ts +68 -16
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-runtime.ts +4 -4
- 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 +9 -4
- 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/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 +65 -27
- package/src/server/static-not-found.ts +134 -0
- package/src/shared/als-slots.ts +72 -0
- package/src/shared/base-path.ts +80 -0
- package/src/shared/param-value.ts +244 -0
- package/src/shared/payload-root.ts +151 -0
- package/src/shared/rsc-cache-key.ts +269 -0
- package/src/shared/rsc-error-envelope.ts +31 -0
- package/src/shared/rsc-manifest.ts +29 -0
- package/src/shared/rsc-payload-path.ts +45 -0
- package/src/shared/segment-info.ts +60 -0
- package/src/shared/slot-params.ts +111 -0
- package/src/shared/static-platform-files.ts +75 -0
- package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
- package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
- package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js.map +0 -1
- package/dist/_chunks/define-COtkxMRT.js +0 -191
- package/dist/_chunks/define-COtkxMRT.js.map +0 -1
- package/dist/_chunks/define-c4au4I9R.js +0 -82
- package/dist/_chunks/define-c4au4I9R.js.map +0 -1
- package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
- package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
- package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
- package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
- package/dist/_chunks/router-ref-CtmF-aPv.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-_6zKFlch.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/plugins/request-dep.d.ts +0 -32
- package/dist/plugins/request-dep.d.ts.map +0 -1
- package/src/plugins/request-dep.ts +0 -499
package/dist/server/internal.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { n as classifyUrlSegment } from "../_chunks/segment-classify-
|
|
3
|
-
import { $ as timingAls, A as setMatchedSegmentPath, C as applyRequestHeaderOverlay, D as getSegmentParams, E as getSearchParams, F as getSetCookieHeaders, G as replaceTraceId, H as getOtelTraceId, I as getWaitUntil, J as withSpan, K as runWithTraceId, L as isDebug, M as setSegmentParams, N as getCookie, O as markResponseFlushed, V as generateTraceId, W as getTraceId, Y as earlyHintsSenderAls, Z as requestContextAls, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMutableCookieContext, k as runWithRequestContext, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as setSpanAttribute, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as getHeader } from "../_chunks/logger-
|
|
4
|
-
import { i as
|
|
5
|
-
import {
|
|
6
|
-
import "../
|
|
1
|
+
import { a as canonicalize, c as getMetadataRouteAutoLink, l as getMetadataRouteServePath, o as METADATA_ROUTE_CONVENTIONS, s as classifyMetadataRoute, t as computeSegmentTreePaths } from "../_chunks/segment-keys-BawYuNFO.js";
|
|
2
|
+
import { n as classifyUrlSegment, r as effectiveUrlSegment } from "../_chunks/segment-classify-C539Pa2O.js";
|
|
3
|
+
import { $ as timingAls, A as setMatchedSegmentPath, C as applyRequestHeaderOverlay, D as getSegmentParams, E as getSearchParams, F as getSetCookieHeaders, G as replaceTraceId, H as getOtelTraceId, I as getWaitUntil, J as withSpan, K as runWithTraceId, L as isDebug, M as setSegmentParams, N as getCookie, O as markResponseFlushed, V as generateTraceId, W as getTraceId, Y as earlyHintsSenderAls, Z as requestContextAls, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMutableCookieContext, k as runWithRequestContext, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as setSpanAttribute, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as getHeader } from "../_chunks/logger-N7e5auP0.js";
|
|
4
|
+
import { i as isRscCacheKeyShape, n as RSC_KEY_PARAM, s as rscCacheKey, t as RSC_KEY_HEADERS } from "../_chunks/rsc-cache-key-DD0fl_-s.js";
|
|
5
|
+
import { i as collectRouteModulepreloads, n as collectRouteCss, r as collectRouteFonts } from "../_chunks/build-manifest-DWppEdLB.js";
|
|
6
|
+
import { i as coerce, t as executeAction } from "../_chunks/actions-C-Rw9vPc.js";
|
|
7
|
+
import { a as getManifestEntry, b as wasInvalidatedSince, c as lookupPrebuiltPayload, d as checkVersionSkew, i as storeOverlayEntry, l as setPrebuiltPayloadSource, n as lookupOverlay, o as hasPrebuiltPayloadSource, r as overlayKey, s as isParamIndependent, u as applyReloadHeaders, v as currentInvalidationEpoch, x as createSingleflight, y as lastInvalidationEpochFor } from "../_chunks/cache-api-Cd0VZ_Pd.js";
|
|
8
|
+
import "../_chunks/error-boundary-DpYRI_I1.js";
|
|
7
9
|
import { n as toBracketKey } from "../_chunks/resolve-schema-Dz3fcFUo.js";
|
|
10
|
+
import { t as normalizeParamValue } from "../_chunks/param-value-C8TNYchQ.js";
|
|
8
11
|
import "../_chunks/segment-context-ZDnXDkbz.js";
|
|
12
|
+
import { n as rscErrorEnvelope } from "../_chunks/rsc-error-envelope-tT5PJs4q.js";
|
|
9
13
|
import { createHash } from "node:crypto";
|
|
10
14
|
import { readFile } from "node:fs/promises";
|
|
11
15
|
import React, { createElement, useContext } from "react";
|
|
@@ -209,38 +213,6 @@ function hasOnRequestError() {
|
|
|
209
213
|
//#endregion
|
|
210
214
|
//#region src/server/pipeline-helpers.ts
|
|
211
215
|
/**
|
|
212
|
-
* Only __proto__ needs stripping — it has a language-level setter that
|
|
213
|
-
* changes the prototype chain of spread copies. constructor and prototype
|
|
214
|
-
* are harmless own properties on null-prototype objects.
|
|
215
|
-
*/
|
|
216
|
-
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__"]);
|
|
217
|
-
/**
|
|
218
|
-
* Deep-walk a value returned by a segment param codec, producing a
|
|
219
|
-
* sanitized copy where every plain object is null-prototype and
|
|
220
|
-
* dangerous keys (__proto__, constructor, prototype) are stripped at
|
|
221
|
-
* every depth.
|
|
222
|
-
*
|
|
223
|
-
* Non-plain objects (Date, Map, class instances, etc.) are returned
|
|
224
|
-
* as-is — they cannot be poisoned by `{...x}` spread and may carry
|
|
225
|
-
* author-intended prototype methods.
|
|
226
|
-
*
|
|
227
|
-
* Arrays are walked element-wise.
|
|
228
|
-
*
|
|
229
|
-
* Performance: URL params are bounded by URL length (~8 KB). Realistic
|
|
230
|
-
* trees are <1 KB. The recursive walk is sub-microsecond.
|
|
231
|
-
*
|
|
232
|
-
* See TIM-655, TIM-855, TIM-873, design/13-security.md
|
|
233
|
-
*/
|
|
234
|
-
function sanitizeParamValue(value) {
|
|
235
|
-
if (value === null || typeof value !== "object") return value;
|
|
236
|
-
if (Array.isArray(value)) return value.map(sanitizeParamValue);
|
|
237
|
-
const proto = Object.getPrototypeOf(value);
|
|
238
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
239
|
-
const out = Object.create(null);
|
|
240
|
-
for (const key of Object.keys(value)) if (!DANGEROUS_KEYS.has(key)) out[key] = sanitizeParamValue(value[key]);
|
|
241
|
-
return out;
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
216
|
* Build a proxy resolver closure from the declared source. Called exactly
|
|
245
217
|
* once at `createPipeline` setup time, so the hot path sees only the branch
|
|
246
218
|
* that corresponds to this pipeline's configured variant.
|
|
@@ -1766,20 +1738,23 @@ async function coerceSegmentParams(match) {
|
|
|
1766
1738
|
for (const key of Object.keys(match.segmentParams)) if (key !== "__proto__") mergeTarget[key] = match.segmentParams[key];
|
|
1767
1739
|
match.segmentParams = mergeTarget;
|
|
1768
1740
|
if (globalCodecs) {
|
|
1769
|
-
for (const
|
|
1741
|
+
for (const node of match.segments) {
|
|
1742
|
+
const segment = effectiveUrlSegment(node);
|
|
1770
1743
|
if (!segment.paramName) continue;
|
|
1771
1744
|
const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
|
|
1772
1745
|
const codec = globalCodecs[bracketKey];
|
|
1773
1746
|
if (!codec) continue;
|
|
1774
1747
|
const key = segment.paramName;
|
|
1775
1748
|
if (!(key in mergeTarget)) continue;
|
|
1749
|
+
let parsed;
|
|
1776
1750
|
try {
|
|
1777
|
-
|
|
1751
|
+
parsed = codec.parse(mergeTarget[key]);
|
|
1778
1752
|
} catch (err) {
|
|
1779
1753
|
const message = err instanceof Error ? err.message : String(err);
|
|
1780
1754
|
if (isDebug()) console.warn(`[timber] Global schema codec rejected value for param "${key}"\n Error: ${message}\n Raw value: ${JSON.stringify(mergeTarget[key])}\n Hint: check the codec in app/schema.ts for key '${bracketKey}'.`);
|
|
1781
1755
|
throw new ParamCoercionError(message);
|
|
1782
1756
|
}
|
|
1757
|
+
mergeTarget[key] = normalizeParamValue(parsed, key);
|
|
1783
1758
|
}
|
|
1784
1759
|
return;
|
|
1785
1760
|
}
|
|
@@ -1795,9 +1770,9 @@ async function coerceSegmentParams(match) {
|
|
|
1795
1770
|
}
|
|
1796
1771
|
const segmentParamsDef = mod.segmentParams;
|
|
1797
1772
|
if (!segmentParamsDef || typeof segmentParamsDef.parse !== "function") continue;
|
|
1773
|
+
let coerced;
|
|
1798
1774
|
try {
|
|
1799
|
-
|
|
1800
|
-
for (const key of Object.keys(coerced)) if (key !== "__proto__") mergeTarget[key] = sanitizeParamValue(coerced[key]);
|
|
1775
|
+
coerced = segmentParamsDef.parse(match.segmentParams);
|
|
1801
1776
|
} catch (err) {
|
|
1802
1777
|
const message = err instanceof Error ? err.message : String(err);
|
|
1803
1778
|
if (isDebug()) {
|
|
@@ -1806,9 +1781,129 @@ async function coerceSegmentParams(match) {
|
|
|
1806
1781
|
}
|
|
1807
1782
|
throw new ParamCoercionError(message);
|
|
1808
1783
|
}
|
|
1784
|
+
for (const key of Object.keys(coerced)) {
|
|
1785
|
+
if (key === "__proto__") continue;
|
|
1786
|
+
mergeTarget[key] = normalizeParamValue(coerced[key], key);
|
|
1787
|
+
}
|
|
1809
1788
|
}
|
|
1810
1789
|
}
|
|
1811
1790
|
//#endregion
|
|
1791
|
+
//#region src/server/pipeline-interception.ts
|
|
1792
|
+
/**
|
|
1793
|
+
* Interception route matching for the request pipeline.
|
|
1794
|
+
*
|
|
1795
|
+
* Matches target URLs against interception rewrites to support the
|
|
1796
|
+
* modal route pattern (soft navigation intercepts).
|
|
1797
|
+
*
|
|
1798
|
+
* Extracted from pipeline.ts to keep files under 500 lines.
|
|
1799
|
+
*
|
|
1800
|
+
* See design/07-routing.md §"Intercepting Routes"
|
|
1801
|
+
*/
|
|
1802
|
+
/**
|
|
1803
|
+
* Check if an intercepting route applies for this soft navigation.
|
|
1804
|
+
*
|
|
1805
|
+
* Two conditions must hold:
|
|
1806
|
+
*
|
|
1807
|
+
* 1. The **target** pathname matches the rewrite's intercepted pattern.
|
|
1808
|
+
* 2. The **source** route — the page the user is navigating FROM, per the
|
|
1809
|
+
* validated X-Timber-URL header — renders the segment that owns the slot
|
|
1810
|
+
* holding the intercepting page (`interceptingScope`). Only then does that
|
|
1811
|
+
* slot exist in the rendered tree, so only then can the intercepting page
|
|
1812
|
+
* render.
|
|
1813
|
+
*
|
|
1814
|
+
* Condition 2 replaced a URL-prefix check (TIM-1279). A prefix cannot
|
|
1815
|
+
* distinguish a route-group-scoped interception from a genuinely root-level
|
|
1816
|
+
* one — route groups are invisible in `urlPath`, so both collapse to "/" and
|
|
1817
|
+
* the guard degenerates to always-true. Tree paths carry group identity.
|
|
1818
|
+
* The scope check also subsumes the old segment-boundary rule: a source route
|
|
1819
|
+
* under /feed-private has no "/feed" in its chain.
|
|
1820
|
+
*
|
|
1821
|
+
* A third condition lives with the caller, which owns route matching, and it
|
|
1822
|
+
* applies to the `'slot'` shape only: the route produced by re-matching
|
|
1823
|
+
* `interceptingPrefix` must pass through the scope too. Conditions 1–2 are
|
|
1824
|
+
* about the route the user is leaving; the slot has to exist in the tree that
|
|
1825
|
+
* actually renders. The `'children'` shape resolves its owner from the scope
|
|
1826
|
+
* directly rather than from a pathname, so it satisfies that by construction.
|
|
1827
|
+
*
|
|
1828
|
+
* `resolveSourceScopes` is a thunk so its inputs are only resolved when some
|
|
1829
|
+
* rewrite's pattern actually matches the target — the common case is no match
|
|
1830
|
+
* at all. It returns the two client-supplied answers to "what is on screen?"
|
|
1831
|
+
* as **separate sets, most authoritative first**: the tree paths the client
|
|
1832
|
+
* says it has mounted, then those of the route the departing URL matches. It
|
|
1833
|
+
* returns null only when neither yields anything, which is not a soft
|
|
1834
|
+
* navigation this can reason about.
|
|
1835
|
+
*
|
|
1836
|
+
* They are two sets rather than one union because ranking needs a chain
|
|
1837
|
+
* (TIM-1318). Each channel describes a single rendered chain, so within one the
|
|
1838
|
+
* scopes are strictly nested and the deepest is the innermost owner. Unioned
|
|
1839
|
+
* they are not: once a modal is open the address bar holds the *intercepted*
|
|
1840
|
+
* URL, so the second channel describes the canonical branch — possibly an
|
|
1841
|
+
* exclusive route group the client never mounted — and a deeper scope from
|
|
1842
|
+
* there would outrank the owner actually on screen. Trying the mounted channel
|
|
1843
|
+
* to exhaustion first keeps the fallback the union was there to provide while
|
|
1844
|
+
* ranking only within one chain.
|
|
1845
|
+
*
|
|
1846
|
+
* Returns the winning build-time rewrite, or null. Only build-time values ever
|
|
1847
|
+
* reach the renderer: the client-supplied header selects a rewrite but never
|
|
1848
|
+
* becomes a rendered route or pathname.
|
|
1849
|
+
*
|
|
1850
|
+
* The **first** matching rewrite wins, and the caller does not fall back to the
|
|
1851
|
+
* next one when the winner fails to resolve. That is deliberate and pre-dates
|
|
1852
|
+
* the two shapes: a rewrite that matched the target and the scope described the
|
|
1853
|
+
* author's intent, and quietly rendering some other author's interception
|
|
1854
|
+
* instead would be harder to explain than rendering the canonical route.
|
|
1855
|
+
*
|
|
1856
|
+
* "First" is meaningful because the list arrives **deepest scope first** —
|
|
1857
|
+
* `collectInterceptionRewrites` sorts it at build time, so the deepest owner a
|
|
1858
|
+
* single chain passes through wins and nothing here has to score (TIM-1318).
|
|
1859
|
+
* Tree-walk order used to decide it, which meant renaming a route group flipped
|
|
1860
|
+
* which modal opened. The precondition is a property of the manifest this reads
|
|
1861
|
+
* rather than something it can re-derive, so it is asserted at its source:
|
|
1862
|
+
* `tests/interception.test.ts` §"rewrite order is decided by scope depth".
|
|
1863
|
+
*/
|
|
1864
|
+
function findInterceptionMatch(targetPathname, resolveSourceScopes, rewrites) {
|
|
1865
|
+
const candidates = rewrites.filter((rewrite) => pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern));
|
|
1866
|
+
if (candidates.length === 0) return null;
|
|
1867
|
+
const scopes = resolveSourceScopes();
|
|
1868
|
+
if (!scopes) return null;
|
|
1869
|
+
for (const scope of scopes) for (const rewrite of candidates) if (scope.has(rewrite.interceptingScope)) return rewrite;
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Check if a pathname matches a URL pattern with dynamic segments.
|
|
1874
|
+
*
|
|
1875
|
+
* Supports [param] (single segment) and [...param] (one or more segments).
|
|
1876
|
+
* Static segments must match exactly.
|
|
1877
|
+
*/
|
|
1878
|
+
function pathnameMatchesPattern(pathname, pattern) {
|
|
1879
|
+
const pathParts = pathname === "/" ? [] : pathname.slice(1).split("/");
|
|
1880
|
+
const patternParts = pattern === "/" ? [] : pattern.slice(1).split("/");
|
|
1881
|
+
let pi = 0;
|
|
1882
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
1883
|
+
const seg = classifyUrlSegment(patternParts[i]);
|
|
1884
|
+
switch (seg.kind) {
|
|
1885
|
+
case "catch-all": return pi < pathParts.length;
|
|
1886
|
+
case "optional-catch-all": return true;
|
|
1887
|
+
case "dynamic": {
|
|
1888
|
+
if (pi >= pathParts.length) return false;
|
|
1889
|
+
const prefix = seg.prefix ?? "";
|
|
1890
|
+
const suffix = seg.suffix ?? "";
|
|
1891
|
+
const pathPart = pathParts[pi];
|
|
1892
|
+
if (prefix || suffix) {
|
|
1893
|
+
if (pathPart.length <= prefix.length + suffix.length || prefix && !pathPart.startsWith(prefix) || suffix && !pathPart.endsWith(suffix)) return false;
|
|
1894
|
+
}
|
|
1895
|
+
pi++;
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
case "static":
|
|
1899
|
+
if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;
|
|
1900
|
+
pi++;
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
return pi === pathParts.length;
|
|
1905
|
+
}
|
|
1906
|
+
//#endregion
|
|
1812
1907
|
//#region src/client/segment-update-context.ts
|
|
1813
1908
|
/**
|
|
1814
1909
|
* SegmentUpdateContext — React context for partial navigation updates.
|
|
@@ -1865,6 +1960,51 @@ getOrCreateContext();
|
|
|
1865
1960
|
* See design/13-security.md §"State tree manipulation".
|
|
1866
1961
|
*/
|
|
1867
1962
|
//#endregion
|
|
1963
|
+
//#region src/server/state-tree-diff.ts
|
|
1964
|
+
/**
|
|
1965
|
+
* State Tree Diffing — Server-side parsing and diffing of X-Timber-State-Tree.
|
|
1966
|
+
*
|
|
1967
|
+
* The client sends X-Timber-State-Tree on navigation requests, listing
|
|
1968
|
+
* the sync segments it has cached. The server diffs this against the
|
|
1969
|
+
* target route's segments to skip re-rendering unchanged sync layouts.
|
|
1970
|
+
*
|
|
1971
|
+
* This is a performance optimization only — NOT a security boundary.
|
|
1972
|
+
* All access.ts files run regardless of the state tree content.
|
|
1973
|
+
* A fabricated state tree can only cause extra rendering work or stale
|
|
1974
|
+
* layouts — never auth bypass.
|
|
1975
|
+
*
|
|
1976
|
+
* See design/19-client-navigation.md §"X-Timber-State-Tree Header"
|
|
1977
|
+
* See design/13-security.md §"State tree manipulation"
|
|
1978
|
+
*/
|
|
1979
|
+
/**
|
|
1980
|
+
* Parse the X-Timber-State-Tree header from a request.
|
|
1981
|
+
*
|
|
1982
|
+
* Returns a Set of segment paths the client has cached (with an
|
|
1983
|
+
* additional `slots` property for cached slot paths), or null if
|
|
1984
|
+
* the header is missing, malformed, or empty. Parsing happens before
|
|
1985
|
+
* renderToReadableStream — not inside the React render pass.
|
|
1986
|
+
*
|
|
1987
|
+
* @returns ClientStateTree with segments and slots, or null if no valid state tree
|
|
1988
|
+
*/
|
|
1989
|
+
function parseClientStateTree(req) {
|
|
1990
|
+
const header = req.headers.get("X-Timber-State-Tree");
|
|
1991
|
+
if (!header) return null;
|
|
1992
|
+
try {
|
|
1993
|
+
const parsed = JSON.parse(header);
|
|
1994
|
+
const hasSegments = Array.isArray(parsed.segments) && parsed.segments.length > 0;
|
|
1995
|
+
const hasSlots = Array.isArray(parsed.slots) && parsed.slots.length > 0;
|
|
1996
|
+
const hasTreePaths = Array.isArray(parsed.treePaths) && parsed.treePaths.length > 0;
|
|
1997
|
+
if (!hasSegments && !hasSlots && !hasTreePaths) return null;
|
|
1998
|
+
const result = new Set(hasSegments ? parsed.segments : []);
|
|
1999
|
+
result.slots = hasSlots ? new Set(parsed.slots) : null;
|
|
2000
|
+
result.treePaths = hasTreePaths ? new Set(parsed.treePaths) : null;
|
|
2001
|
+
return result;
|
|
2002
|
+
} catch (err) {
|
|
2003
|
+
swallow(err, "malformed X-Timber-State-Tree header");
|
|
2004
|
+
return null;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
//#endregion
|
|
1868
2008
|
//#region src/server/route-element-builder.ts
|
|
1869
2009
|
/**
|
|
1870
2010
|
* Thrown when a defineSegmentParams codec's parse() fails.
|
|
@@ -1942,78 +2082,6 @@ function escapeXml(str) {
|
|
|
1942
2082
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1943
2083
|
}
|
|
1944
2084
|
//#endregion
|
|
1945
|
-
//#region src/server/pipeline-interception.ts
|
|
1946
|
-
/**
|
|
1947
|
-
* Interception route matching for the request pipeline.
|
|
1948
|
-
*
|
|
1949
|
-
* Matches target URLs against interception rewrites to support the
|
|
1950
|
-
* modal route pattern (soft navigation intercepts).
|
|
1951
|
-
*
|
|
1952
|
-
* Extracted from pipeline.ts to keep files under 500 lines.
|
|
1953
|
-
*
|
|
1954
|
-
* See design/07-routing.md §"Intercepting Routes"
|
|
1955
|
-
*/
|
|
1956
|
-
/**
|
|
1957
|
-
* Check if a pathname starts with a prefix on a segment boundary.
|
|
1958
|
-
*
|
|
1959
|
-
* Prevents /feed from matching /feed-private — the prefix must be
|
|
1960
|
-
* followed by '/' or be an exact match.
|
|
1961
|
-
*/
|
|
1962
|
-
function hasSegmentPrefix(pathname, prefix) {
|
|
1963
|
-
if (prefix === "/") return true;
|
|
1964
|
-
if (!pathname.startsWith(prefix)) return false;
|
|
1965
|
-
return pathname.length === prefix.length || pathname[prefix.length] === "/";
|
|
1966
|
-
}
|
|
1967
|
-
/**
|
|
1968
|
-
* Check if an intercepting route applies for this soft navigation.
|
|
1969
|
-
*
|
|
1970
|
-
* Matches the target pathname against interception rewrites, constrained
|
|
1971
|
-
* by the source URL (X-Timber-URL header — where the user navigates FROM).
|
|
1972
|
-
*
|
|
1973
|
-
* Returns the source pathname to re-match if interception applies, or null.
|
|
1974
|
-
*/
|
|
1975
|
-
function findInterceptionMatch(targetPathname, sourceUrl, rewrites) {
|
|
1976
|
-
for (const rewrite of rewrites) {
|
|
1977
|
-
if (!hasSegmentPrefix(sourceUrl, rewrite.interceptingPrefix)) continue;
|
|
1978
|
-
if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) return { sourcePathname: rewrite.interceptingPrefix };
|
|
1979
|
-
}
|
|
1980
|
-
return null;
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* Check if a pathname matches a URL pattern with dynamic segments.
|
|
1984
|
-
*
|
|
1985
|
-
* Supports [param] (single segment) and [...param] (one or more segments).
|
|
1986
|
-
* Static segments must match exactly.
|
|
1987
|
-
*/
|
|
1988
|
-
function pathnameMatchesPattern(pathname, pattern) {
|
|
1989
|
-
const pathParts = pathname === "/" ? [] : pathname.slice(1).split("/");
|
|
1990
|
-
const patternParts = pattern === "/" ? [] : pattern.slice(1).split("/");
|
|
1991
|
-
let pi = 0;
|
|
1992
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
1993
|
-
const seg = classifyUrlSegment(patternParts[i]);
|
|
1994
|
-
switch (seg.kind) {
|
|
1995
|
-
case "catch-all": return pi < pathParts.length;
|
|
1996
|
-
case "optional-catch-all": return true;
|
|
1997
|
-
case "dynamic": {
|
|
1998
|
-
if (pi >= pathParts.length) return false;
|
|
1999
|
-
const prefix = seg.prefix ?? "";
|
|
2000
|
-
const suffix = seg.suffix ?? "";
|
|
2001
|
-
const pathPart = pathParts[pi];
|
|
2002
|
-
if (prefix || suffix) {
|
|
2003
|
-
if (pathPart.length <= prefix.length + suffix.length || prefix && !pathPart.startsWith(prefix) || suffix && !pathPart.endsWith(suffix)) return false;
|
|
2004
|
-
}
|
|
2005
|
-
pi++;
|
|
2006
|
-
continue;
|
|
2007
|
-
}
|
|
2008
|
-
case "static":
|
|
2009
|
-
if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;
|
|
2010
|
-
pi++;
|
|
2011
|
-
continue;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
return pi === pathParts.length;
|
|
2015
|
-
}
|
|
2016
|
-
//#endregion
|
|
2017
2085
|
//#region src/server/pipeline-outcome.ts
|
|
2018
2086
|
/**
|
|
2019
2087
|
* Pipeline outcome translator — converts a `PhaseOutcome` (the value
|
|
@@ -2028,16 +2096,7 @@ function pathnameMatchesPattern(pathname, pattern) {
|
|
|
2028
2096
|
*/
|
|
2029
2097
|
function rscErrorResponse(isRsc, status, headers) {
|
|
2030
2098
|
if (!isRsc) return new Response(null, { status });
|
|
2031
|
-
|
|
2032
|
-
h.set("X-Timber-Error", "1");
|
|
2033
|
-
h.set("content-type", "application/json; charset=utf-8");
|
|
2034
|
-
return new Response(JSON.stringify({
|
|
2035
|
-
error: true,
|
|
2036
|
-
status
|
|
2037
|
-
}), {
|
|
2038
|
-
status,
|
|
2039
|
-
headers: h
|
|
2040
|
-
});
|
|
2099
|
+
return rscErrorEnvelope(status, headers);
|
|
2041
2100
|
}
|
|
2042
2101
|
/**
|
|
2043
2102
|
* Terminal outcome handler — converts a `PhaseOutcome` into a final
|
|
@@ -2334,15 +2393,6 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
|
|
|
2334
2393
|
}
|
|
2335
2394
|
const handlerResult = await mod.default();
|
|
2336
2395
|
if (handlerResult instanceof Response) {
|
|
2337
|
-
if (method === "HEAD") {
|
|
2338
|
-
const headHeaders = new Headers(handlerResult.headers);
|
|
2339
|
-
if (handlerResult.ok && !headHeaders.has("Cache-Control")) headHeaders.set("Cache-Control", "public, max-age=14400, must-revalidate");
|
|
2340
|
-
return new Response(null, {
|
|
2341
|
-
status: handlerResult.status,
|
|
2342
|
-
statusText: handlerResult.statusText,
|
|
2343
|
-
headers: headHeaders
|
|
2344
|
-
});
|
|
2345
|
-
}
|
|
2346
2396
|
const res = cloneWithMutableHeaders(handlerResult);
|
|
2347
2397
|
if (res.ok && !res.headers.has("Cache-Control")) res.headers.set("Cache-Control", "public, max-age=14400, must-revalidate");
|
|
2348
2398
|
return res;
|
|
@@ -2404,10 +2454,19 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
|
|
|
2404
2454
|
const rawSourceUrl = req.headers.get("X-Timber-URL");
|
|
2405
2455
|
const validatedSourceUrl = rawSourceUrl ? validateInterceptionHeader(rawSourceUrl, stripTrailingSlash) : null;
|
|
2406
2456
|
if (validatedSourceUrl) {
|
|
2407
|
-
const
|
|
2408
|
-
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2457
|
+
const resolveSourceScopes = () => {
|
|
2458
|
+
const mounted = new Set(parseClientStateTree(req)?.treePaths ?? []);
|
|
2459
|
+
const sourceRoute = config.matchRoute(validatedSourceUrl);
|
|
2460
|
+
const scopes = [mounted, new Set(sourceRoute ? computeSegmentTreePaths(sourceRoute.segments) : [])].filter((scope) => scope.size > 0);
|
|
2461
|
+
return scopes.length > 0 ? scopes : null;
|
|
2462
|
+
};
|
|
2463
|
+
const rewrite = findInterceptionMatch(canonicalPathname, resolveSourceScopes, config.interceptionRewrites);
|
|
2464
|
+
if (rewrite?.mode === "children") {
|
|
2465
|
+
const interceptedMatch = config.matchInterceptedRoute?.(canonicalPathname, rewrite.interceptingScope);
|
|
2466
|
+
if (interceptedMatch) match = interceptedMatch;
|
|
2467
|
+
} else if (rewrite) {
|
|
2468
|
+
const sourceMatch = config.matchRoute(rewrite.interceptingPrefix);
|
|
2469
|
+
if (sourceMatch !== null && computeSegmentTreePaths(sourceMatch.segments).includes(rewrite.interceptingScope)) {
|
|
2411
2470
|
match = sourceMatch;
|
|
2412
2471
|
interception = { targetPathname: canonicalPathname };
|
|
2413
2472
|
}
|
|
@@ -2466,6 +2525,194 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
|
|
|
2466
2525
|
});
|
|
2467
2526
|
}
|
|
2468
2527
|
//#endregion
|
|
2528
|
+
//#region src/server/rsc-cache-key-guard.ts
|
|
2529
|
+
/**
|
|
2530
|
+
* Origin-side binding of the `_rsc` cache key to the request that produced
|
|
2531
|
+
* the response (TIM-1268).
|
|
2532
|
+
*
|
|
2533
|
+
* The key is deterministic, and deliberately so — that is what makes RSC
|
|
2534
|
+
* payloads cacheable. But deterministic also means *predictable*: anyone can
|
|
2535
|
+
* compute the key another client will request. Without this guard, a caller
|
|
2536
|
+
* could request `/about?_rsc=<victim's key>` while sending its own
|
|
2537
|
+
* `X-Timber-State-Tree`, and a shared cache that keys by URL — the exact
|
|
2538
|
+
* environment this feature targets — would store that variant under the
|
|
2539
|
+
* victim's key. Every subsequent legitimate navigation would then be served
|
|
2540
|
+
* a payload rendered for someone else's segment tree or deployment.
|
|
2541
|
+
*
|
|
2542
|
+
* The guard is a *storability* rule, not a rejection: if the claimed key
|
|
2543
|
+
* does not match the key derived from the received headers, the response
|
|
2544
|
+
* loses its shared-cache directives. Mismatched requests are still served
|
|
2545
|
+
* normally, they just cannot be stored for anyone else.
|
|
2546
|
+
*
|
|
2547
|
+
* Why not Next.js's 307 self-correction: a redirect is cacheable in its own
|
|
2548
|
+
* right, costs a round trip on every legitimate miss, and would make the
|
|
2549
|
+
* server's behaviour depend on a parameter it otherwise never reads. This
|
|
2550
|
+
* rule closes the same hole by making the poisoned entry unstorable at the
|
|
2551
|
+
* source, in the response the developer already has to opt into sharing.
|
|
2552
|
+
*
|
|
2553
|
+
* See design/19-client-navigation.md §"The `_rsc` cache key".
|
|
2554
|
+
*/
|
|
2555
|
+
/**
|
|
2556
|
+
* Legacy shared-cache control fields that predate RFC 9213 and so do not
|
|
2557
|
+
* follow the `<target>-Cache-Control` naming pattern — the one part of this
|
|
2558
|
+
* rule that cannot be matched by shape. `X-Accel-Expires` is nginx
|
|
2559
|
+
* `proxy_cache`, which gives it precedence over `Cache-Control`.
|
|
2560
|
+
*
|
|
2561
|
+
* A closed historical set, unlike the vendor `*-Cache-Control` names, which
|
|
2562
|
+
* keep being minted and are handled by pattern below.
|
|
2563
|
+
*/
|
|
2564
|
+
var LEGACY_CDN_CACHE_HEADERS = [
|
|
2565
|
+
"Surrogate-Control",
|
|
2566
|
+
"Edge-Control",
|
|
2567
|
+
"X-Accel-Expires"
|
|
2568
|
+
];
|
|
2569
|
+
/**
|
|
2570
|
+
* Whether a response header overrides `Cache-Control` at a shared cache.
|
|
2571
|
+
*
|
|
2572
|
+
* Matched by *shape*, not by vendor name. RFC 9213 defines targeted fields
|
|
2573
|
+
* as `<target>-Cache-Control`, and every CDN spells its own: `CDN-`,
|
|
2574
|
+
* `Cloudflare-CDN-`, `Netlify-CDN-`, `Vercel-CDN-`, and whichever ships
|
|
2575
|
+
* next. A vendor list here is a denylist with an unbounded tail — it was
|
|
2576
|
+
* extended twice during review, each time by one name, which is the signal
|
|
2577
|
+
* that enumeration is the wrong shape. The suffix rule is total.
|
|
2578
|
+
*
|
|
2579
|
+
* `Cache-Control` itself does not match (no `-` prefix) and is replaced
|
|
2580
|
+
* separately.
|
|
2581
|
+
*/
|
|
2582
|
+
function isSharedCacheOverride(name) {
|
|
2583
|
+
const lowered = name.toLowerCase();
|
|
2584
|
+
return lowered.endsWith("-cache-control") || LEGACY_CDN_CACHE_HEADERS.some((legacy) => legacy.toLowerCase() === lowered);
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Whether the request's `_rsc` parameter matches the key derived from its
|
|
2588
|
+
* own headers.
|
|
2589
|
+
*
|
|
2590
|
+
* Requests without the parameter are not claiming a key at all, so they are
|
|
2591
|
+
* left alone — the bare document URL is the developer's declared territory
|
|
2592
|
+
* (see design/32-response-caching.md, TIM-1264). Neither is a parameter
|
|
2593
|
+
* whose value could not be one of our digests: `_rsc` is not reserved, and
|
|
2594
|
+
* an application that already uses it for its own search state
|
|
2595
|
+
* (`/search?_rsc=foo`) must not have its cache policy silently revoked by a
|
|
2596
|
+
* guard that mistook that for a forgery.
|
|
2597
|
+
*/
|
|
2598
|
+
async function hasValidRscCacheKey(req) {
|
|
2599
|
+
if (!req.url.includes("?")) return true;
|
|
2600
|
+
let values;
|
|
2601
|
+
try {
|
|
2602
|
+
values = new URL(req.url).searchParams.getAll(RSC_KEY_PARAM);
|
|
2603
|
+
} catch {
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
const claims = values.filter(isRscCacheKeyShape);
|
|
2607
|
+
if (claims.length === 0) return true;
|
|
2608
|
+
const expected = await rscCacheKey((name) => req.headers.get(name));
|
|
2609
|
+
if (expected === null) return false;
|
|
2610
|
+
return claims.every((claim) => claim === expected);
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Media type of a Flight payload. Also spelled in `rsc-entry/helpers.ts`,
|
|
2614
|
+
* `deny-renderer.ts`, `action-handler.ts`, and `client/rsc-fetch.ts` — that
|
|
2615
|
+
* duplication predates this module and is tracked separately.
|
|
2616
|
+
*/
|
|
2617
|
+
var RSC_MEDIA_TYPE = "text/x-component";
|
|
2618
|
+
/**
|
|
2619
|
+
* Ensure a Flight payload can never be interpreted as a document.
|
|
2620
|
+
*
|
|
2621
|
+
* Applied here, at the pipeline's single exit, rather than where each
|
|
2622
|
+
* payload response is built. Six places build one — the success path, three
|
|
2623
|
+
* in `deny-renderer.ts`, the action paths — and hardening them one at a time
|
|
2624
|
+
* is how you end up hardening five of six. The response is Flight if it says
|
|
2625
|
+
* it is; that is the only test that covers every producer, including ones
|
|
2626
|
+
* added later.
|
|
2627
|
+
*
|
|
2628
|
+
* Matters because the payload URL is observable and, on a shared-cache hit,
|
|
2629
|
+
* the origin is never consulted — so a top-level navigation can be handed
|
|
2630
|
+
* these bytes with no request-time check available. See design/13-security.md
|
|
2631
|
+
* #36c.
|
|
2632
|
+
*/
|
|
2633
|
+
function hardenRscPayloadResponse(response) {
|
|
2634
|
+
const contentType = response.headers.get("content-type");
|
|
2635
|
+
if (!contentType) return;
|
|
2636
|
+
if (contentType.split(";")[0].trim().toLowerCase() !== RSC_MEDIA_TYPE) return;
|
|
2637
|
+
response.headers.set("X-Content-Type-Options", "nosniff");
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* Make a response unstorable by any shared cache when its request claimed an
|
|
2641
|
+
* `_rsc` key that does not match its own headers.
|
|
2642
|
+
*
|
|
2643
|
+
* The end state is unconditional and therefore statable: **a mismatched
|
|
2644
|
+
* response is never stored by any cache.** Every targeted cache-control
|
|
2645
|
+
* field is removed and `Cache-Control` is replaced outright, rather than
|
|
2646
|
+
* downgrading only when a shared directive is spotted. Conditional variants
|
|
2647
|
+
* of this were tried and were wrong twice over — they missed the CDN-
|
|
2648
|
+
* specific headers, and leaving `Cache-Control` alone when it looked
|
|
2649
|
+
* harmless lets a CDN apply heuristic caching to a response that has none.
|
|
2650
|
+
*
|
|
2651
|
+
* The cost is that a mismatched request loses developer-chosen *browser*
|
|
2652
|
+
* caching too. That is a request no timber client ever makes.
|
|
2653
|
+
*
|
|
2654
|
+
* Purge tags (`Surrogate-Key`, `Cache-Tag`) are deliberately left alone:
|
|
2655
|
+
* they address stored entries, and this response is not stored.
|
|
2656
|
+
*
|
|
2657
|
+
* Mutates `response.headers` in place; the pipeline guarantees mutable
|
|
2658
|
+
* headers at this point (see TIM-866).
|
|
2659
|
+
*/
|
|
2660
|
+
async function enforceRscCacheKey(req, response) {
|
|
2661
|
+
if (await hasValidRscCacheKey(req)) return;
|
|
2662
|
+
const overrides = [];
|
|
2663
|
+
for (const name of response.headers.keys()) if (isSharedCacheOverride(name)) overrides.push(name);
|
|
2664
|
+
for (const name of overrides) response.headers.delete(name);
|
|
2665
|
+
response.headers.set("Cache-Control", "private, no-store");
|
|
2666
|
+
}
|
|
2667
|
+
//#endregion
|
|
2668
|
+
//#region src/server/head-response.ts
|
|
2669
|
+
/**
|
|
2670
|
+
* HEAD body stripping — RFC 9110 §9.3.2.
|
|
2671
|
+
*
|
|
2672
|
+
* "The HEAD method is identical to GET except that the server MUST NOT send
|
|
2673
|
+
* content in the response." The headers, however, must be the ones the
|
|
2674
|
+
* corresponding GET would have produced — which is why timber renders the
|
|
2675
|
+
* route in full and discards the body here rather than short-circuiting the
|
|
2676
|
+
* render. Content-Type, Content-Length, Set-Cookie, cache directives and
|
|
2677
|
+
* everything else ride through untouched.
|
|
2678
|
+
*
|
|
2679
|
+
* Node's `ServerResponse` happens to discard writes for HEAD, so the wire is
|
|
2680
|
+
* usually correct even without this. That is a property of one adapter, not of
|
|
2681
|
+
* the `Response` object: static generation, the Cloudflare and Nitro adapters,
|
|
2682
|
+
* tests, and anything calling the pipeline handler as a function all observe
|
|
2683
|
+
* the `Response` directly and saw the full body before TIM-1324.
|
|
2684
|
+
*
|
|
2685
|
+
* The pipeline applies this at its single exit so every response shape is
|
|
2686
|
+
* covered — page render, API route, metadata route, deny page, error page,
|
|
2687
|
+
* proxy short-circuit. The two unit-level producers that are also reachable as
|
|
2688
|
+
* standalone functions (`handleRouteRequest`, `handleApiRoute`) call it too, so
|
|
2689
|
+
* each honours HEAD on its own; they share this implementation rather than
|
|
2690
|
+
* repeating it, and applying it twice is a no-op.
|
|
2691
|
+
*/
|
|
2692
|
+
/**
|
|
2693
|
+
* Return a bodyless copy of `response` when `method` is HEAD.
|
|
2694
|
+
*
|
|
2695
|
+
* A no-op for every other method and for responses that already carry no body
|
|
2696
|
+
* (204, 304, redirects), so callers do not need to pre-check either.
|
|
2697
|
+
*
|
|
2698
|
+
* The discarded stream is cancelled: an SSR render that reached this point has
|
|
2699
|
+
* a live producer attached, and dropping the reference without cancelling
|
|
2700
|
+
* leaks it for the lifetime of the render (design/13-security.md A4).
|
|
2701
|
+
*/
|
|
2702
|
+
function stripHeadBody(response, method) {
|
|
2703
|
+
if (method.toUpperCase() !== "HEAD") return response;
|
|
2704
|
+
const body = response.body;
|
|
2705
|
+
if (body === null) return response;
|
|
2706
|
+
try {
|
|
2707
|
+
body.cancel().catch(() => {});
|
|
2708
|
+
} catch {}
|
|
2709
|
+
return new Response(null, {
|
|
2710
|
+
status: response.status,
|
|
2711
|
+
statusText: response.statusText,
|
|
2712
|
+
headers: response.headers
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
//#endregion
|
|
2469
2716
|
//#region src/server/pipeline.ts
|
|
2470
2717
|
/**
|
|
2471
2718
|
* Create the request handler from a pipeline configuration.
|
|
@@ -2529,12 +2776,14 @@ function createPipeline(config) {
|
|
|
2529
2776
|
});
|
|
2530
2777
|
else result = await innerHandler();
|
|
2531
2778
|
await setSpanAttribute("http.response.status_code", result.status);
|
|
2532
|
-
const varyTokens = [
|
|
2533
|
-
if (config.interceptionRewrites?.length) varyTokens.push("X-Timber-URL");
|
|
2779
|
+
const varyTokens = [...RSC_KEY_HEADERS];
|
|
2534
2780
|
const existingVary = result.headers.get("Vary");
|
|
2535
2781
|
const existingTokens = existingVary ? existingVary.toLowerCase().split(",").map((t) => t.trim()) : [];
|
|
2536
2782
|
const newTokens = varyTokens.filter((t) => !existingTokens.includes(t.toLowerCase()));
|
|
2537
2783
|
if (newTokens.length > 0) result.headers.set("Vary", existingVary ? `${existingVary}, ${newTokens.join(", ")}` : newTokens.join(", "));
|
|
2784
|
+
await enforceRscCacheKey(canonicalReq, result);
|
|
2785
|
+
hardenRscPayloadResponse(result);
|
|
2786
|
+
result = stripHeadBody(result, method);
|
|
2538
2787
|
if (serverTiming === "detailed") {
|
|
2539
2788
|
const timingHeader = getServerTimingHeader();
|
|
2540
2789
|
if (timingHeader) result.headers.set("Server-Timing", timingHeader);
|
|
@@ -2576,68 +2825,6 @@ function createPipeline(config) {
|
|
|
2576
2825
|
return pipelineFn;
|
|
2577
2826
|
}
|
|
2578
2827
|
//#endregion
|
|
2579
|
-
//#region src/server/build-manifest.ts
|
|
2580
|
-
/**
|
|
2581
|
-
* Collect all CSS files needed for a matched route's segment chain.
|
|
2582
|
-
*
|
|
2583
|
-
* Walks segments root → leaf, collecting CSS for each layout and page.
|
|
2584
|
-
* Deduplicates while preserving order (root layout CSS first).
|
|
2585
|
-
*/
|
|
2586
|
-
function collectRouteCss(segments, manifest) {
|
|
2587
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2588
|
-
const result = [];
|
|
2589
|
-
for (const segment of segments) for (const file of [segment.layout, segment.page]) {
|
|
2590
|
-
if (!file) continue;
|
|
2591
|
-
const cssFiles = manifest.css[file.filePath];
|
|
2592
|
-
if (!cssFiles) continue;
|
|
2593
|
-
for (const url of cssFiles) if (!seen.has(url)) {
|
|
2594
|
-
seen.add(url);
|
|
2595
|
-
result.push(url);
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
return result;
|
|
2599
|
-
}
|
|
2600
|
-
/**
|
|
2601
|
-
* Collect all font entries needed for a matched route's segment chain.
|
|
2602
|
-
*
|
|
2603
|
-
* Walks segments root → leaf, collecting fonts for each layout and page.
|
|
2604
|
-
* Deduplicates by href while preserving order.
|
|
2605
|
-
*/
|
|
2606
|
-
function collectRouteFonts(segments, manifest) {
|
|
2607
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2608
|
-
const result = [];
|
|
2609
|
-
for (const segment of segments) for (const file of [segment.layout, segment.page]) {
|
|
2610
|
-
if (!file) continue;
|
|
2611
|
-
const fonts = manifest.fonts[file.filePath];
|
|
2612
|
-
if (!fonts) continue;
|
|
2613
|
-
for (const entry of fonts) if (!seen.has(entry.href)) {
|
|
2614
|
-
seen.add(entry.href);
|
|
2615
|
-
result.push(entry);
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
return result;
|
|
2619
|
-
}
|
|
2620
|
-
/**
|
|
2621
|
-
* Collect modulepreload URLs for a matched route's segment chain.
|
|
2622
|
-
*
|
|
2623
|
-
* Walks segments root → leaf, collecting transitive JS dependencies
|
|
2624
|
-
* for each layout and page. Deduplicates across segments.
|
|
2625
|
-
*/
|
|
2626
|
-
function collectRouteModulepreloads(segments, manifest) {
|
|
2627
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2628
|
-
const result = [];
|
|
2629
|
-
for (const segment of segments) for (const file of [segment.layout, segment.page]) {
|
|
2630
|
-
if (!file) continue;
|
|
2631
|
-
const preloads = manifest.modulepreload[file.filePath];
|
|
2632
|
-
if (!preloads) continue;
|
|
2633
|
-
for (const url of preloads) if (!seen.has(url)) {
|
|
2634
|
-
seen.add(url);
|
|
2635
|
-
result.push(url);
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
return result;
|
|
2639
|
-
}
|
|
2640
|
-
//#endregion
|
|
2641
2828
|
//#region src/server/early-hints.ts
|
|
2642
2829
|
/**
|
|
2643
2830
|
* 103 Early Hints utilities.
|
|
@@ -3277,14 +3464,8 @@ async function handleRouteRequest(mod, ctx) {
|
|
|
3277
3464
|
});
|
|
3278
3465
|
}
|
|
3279
3466
|
if (method === "HEAD") {
|
|
3280
|
-
if (mod.HEAD) return runHandler(mod.HEAD, ctx);
|
|
3281
|
-
if (mod.GET)
|
|
3282
|
-
const res = await runHandler(mod.GET, ctx);
|
|
3283
|
-
return new Response(null, {
|
|
3284
|
-
status: res.status,
|
|
3285
|
-
headers: res.headers
|
|
3286
|
-
});
|
|
3287
|
-
}
|
|
3467
|
+
if (mod.HEAD) return stripHeadBody(await runHandler(mod.HEAD, ctx), method);
|
|
3468
|
+
if (mod.GET) return stripHeadBody(await runHandler(mod.GET, ctx), method);
|
|
3288
3469
|
}
|
|
3289
3470
|
const handler = mod[method];
|
|
3290
3471
|
if (!handler) return new Response(null, {
|
|
@@ -3511,11 +3692,11 @@ var registry = /* @__PURE__ */ new Map();
|
|
|
3511
3692
|
*/
|
|
3512
3693
|
function resolveKeyParams(store) {
|
|
3513
3694
|
const main = store.segmentParams ?? {};
|
|
3514
|
-
const
|
|
3515
|
-
if (!
|
|
3695
|
+
const slots = store.slotParams;
|
|
3696
|
+
if (!slots || Object.keys(slots).length === 0) return main;
|
|
3516
3697
|
const mainCanonical = tryCanonicalize({ ...main });
|
|
3517
3698
|
if (mainCanonical === null) return null;
|
|
3518
|
-
for (const slotParams of
|
|
3699
|
+
for (const slotParams of Object.values(slots)) {
|
|
3519
3700
|
const merged = tryCanonicalize({
|
|
3520
3701
|
...main,
|
|
3521
3702
|
...slotParams
|