@timber-js/app 0.2.0-alpha.185 → 0.2.0-alpha.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{actions-O_LsyCE4.js → actions-35jnMdeJ.js} +4 -3
- package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-35jnMdeJ.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
- package/dist/_chunks/als-slots-BEEIPKYm.js +26 -0
- package/dist/_chunks/als-slots-BEEIPKYm.js.map +1 -0
- package/dist/_chunks/base-path-DaQrzbez.js +80 -0
- package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
- package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-DjNrIWRR.js} +8 -14
- package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-CGP6BZKO.js} +92 -14
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
- package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
- package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
- package/dist/_chunks/error-boundary-D-ODYX41.js +353 -0
- package/dist/_chunks/error-boundary-D-ODYX41.js.map +1 -0
- package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
- package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
- package/dist/_chunks/file-cache-DmX7OqZP.js +454 -0
- package/dist/_chunks/file-cache-DmX7OqZP.js.map +1 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
- package/dist/_chunks/{logger-AWfuX-KJ.js → logger-D8xJZXIN.js} +34 -51
- package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
- package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
- package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
- package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-rCinWLiE.js} +8 -2
- package/dist/_chunks/plugin-context-rCinWLiE.js.map +1 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
- package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
- package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
- package/dist/_chunks/rsc-media-type-DRqE_lD_.js +46 -0
- package/dist/_chunks/rsc-media-type-DRqE_lD_.js.map +1 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
- package/dist/_chunks/scanner-C8b0Gcw3.js +1564 -0
- package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js.map +1 -0
- package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -0
- package/dist/_chunks/segment-context-CjOlyB8Y.js +97 -0
- package/dist/_chunks/segment-context-CjOlyB8Y.js.map +1 -0
- package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
- package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
- package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
- package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
- package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
- package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
- package/dist/_chunks/use-segment-params-C4r4BD9T.js +398 -0
- package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
- package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
- package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +37 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/compress-module.d.ts +12 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/fs-identity.d.ts +64 -0
- package/dist/adapters/fs-identity.d.ts.map +1 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -5
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +57 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +13 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/fast-hash.d.ts +5 -0
- package/dist/cache/fast-hash.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cdn/cloudflare-purge.js +30 -0
- package/dist/cdn/cloudflare-purge.js.map +1 -0
- package/dist/cdn/fastly-purge.js +33 -0
- package/dist/cdn/fastly-purge.js.map +1 -0
- package/dist/cdn/index.js +94 -0
- package/dist/cdn/index.js.map +1 -0
- package/dist/cdn/workers-cache-purge.js +35 -0
- package/dist/cdn/workers-cache-purge.js.map +1 -0
- package/dist/cli-check.d.ts +153 -0
- package/dist/cli-check.d.ts.map +1 -0
- package/dist/cli.d.ts +34 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +46 -21
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts +20 -9
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +1 -1
- package/dist/client/browser-entry/index.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts +4 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +3 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +7 -0
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -162
- package/dist/client/history.d.ts +13 -6
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +42 -41
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +4 -3
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +877 -311
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +1 -7
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +2 -2
- package/dist/client/navigation-commit.d.ts +87 -0
- package/dist/client/navigation-commit.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +37 -20
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +63 -23
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/params-context.d.ts +81 -0
- package/dist/client/params-context.d.ts.map +1 -0
- package/dist/client/router-effects.d.ts +129 -0
- package/dist/client/router-effects.d.ts.map +1 -0
- package/dist/client/router-lifecycle.d.ts +55 -0
- package/dist/client/router-lifecycle.d.ts.map +1 -0
- package/dist/client/router-pipeline.d.ts +84 -0
- package/dist/client/router-pipeline.d.ts.map +1 -0
- package/dist/client/router-ref.d.ts +1 -1
- package/dist/client/router-ref.d.ts.map +1 -1
- package/dist/client/router-skew.d.ts +21 -0
- package/dist/client/router-skew.d.ts.map +1 -0
- package/dist/client/router-types.d.ts +207 -0
- package/dist/client/router-types.d.ts.map +1 -0
- package/dist/client/router.d.ts +1 -182
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +47 -26
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +87 -34
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +6 -0
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-client.d.ts +69 -0
- package/dist/client/stale-client.d.ts.map +1 -0
- package/dist/client/state.d.ts +10 -1
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +2 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/unload-guard.d.ts +5 -0
- package/dist/client/unload-guard.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +9 -3
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +1 -1
- package/dist/client/use-segment-params.d.ts +18 -8
- package/dist/client/use-segment-params.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +5 -5
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1009 -759
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/plugin-context.d.ts +46 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/request-dep/analysis.d.ts +106 -0
- package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
- package/dist/plugins/request-dep/index.d.ts +75 -0
- package/dist/plugins/request-dep/index.d.ts.map +1 -0
- package/dist/plugins/request-dep/origin.d.ts +61 -0
- package/dist/plugins/request-dep/origin.d.ts.map +1 -0
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +160 -8
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-shared.d.ts +3 -44
- package/dist/routing/codegen-shared.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +28 -31
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts +51 -0
- package/dist/routing/codegen-write.d.ts.map +1 -0
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/collision-probe.d.ts +104 -0
- package/dist/routing/collision-probe.d.ts.map +1 -0
- package/dist/routing/collision-spaces.d.ts +26 -0
- package/dist/routing/collision-spaces.d.ts.map +1 -0
- package/dist/routing/convention-lint.d.ts +18 -4
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +16 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +4 -3
- package/dist/routing/interception.d.ts +235 -6
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/link-codegen.d.ts +19 -4
- package/dist/routing/link-codegen.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts +1 -7
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +33 -0
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/segment-keys.d.ts +95 -0
- package/dist/routing/segment-keys.d.ts.map +1 -0
- package/dist/routing/slot-placement.d.ts +48 -0
- package/dist/routing/slot-placement.d.ts.map +1 -0
- package/dist/routing/types.d.ts +0 -6
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +6 -0
- package/dist/routing/walkers.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +60 -9
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +62 -14
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.d.ts +0 -1
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +221 -10
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/parse-total.d.ts +70 -0
- package/dist/search-params/parse-total.d.ts.map +1 -0
- package/dist/search-params/wrappers.d.ts +26 -3
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/server/access-gate.d.ts +19 -8
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +6 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +18 -1
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +13 -1
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/chain-url-parts.d.ts +82 -0
- package/dist/server/chain-url-parts.d.ts.map +1 -0
- package/dist/server/children-interception.d.ts +49 -0
- package/dist/server/children-interception.d.ts.map +1 -0
- package/dist/server/compress.d.ts.map +1 -1
- package/dist/server/default-status-page.d.ts +45 -0
- package/dist/server/default-status-page.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +148 -15
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +15 -4
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +85 -15
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/head-response.d.ts +35 -0
- package/dist/server/head-response.d.ts.map +1 -0
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +3 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +712 -418
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +52 -0
- package/dist/server/metadata-collector.d.ts.map +1 -0
- package/dist/server/param-coercion.d.ts +22 -6
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +0 -18
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-interception.d.ts +59 -9
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +11 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +23 -0
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/publish-params.d.ts +33 -0
- package/dist/server/publish-params.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +48 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +22 -3
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +13 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-cache-key-guard.d.ts +78 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +15 -3
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -17
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +0 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +1 -2
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +15 -2
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/skippable-prefix.d.ts +23 -0
- package/dist/server/skippable-prefix.d.ts.map +1 -0
- package/dist/server/slot-interception.d.ts +80 -0
- package/dist/server/slot-interception.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +70 -8
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +17 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +0 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +24 -13
- package/dist/server/ssr-wrappers.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +6 -27
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/static-generator.d.ts +6 -0
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/static-not-found.d.ts +52 -0
- package/dist/server/static-not-found.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +8 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +28 -36
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +12 -7
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +40 -0
- package/dist/server/utils/element-type.d.ts.map +1 -0
- package/dist/shared/als-slots.d.ts +41 -0
- package/dist/shared/als-slots.d.ts.map +1 -0
- package/dist/shared/base-path.d.ts +56 -0
- package/dist/shared/base-path.d.ts.map +1 -0
- package/dist/shared/param-value.d.ts +80 -0
- package/dist/shared/param-value.d.ts.map +1 -0
- package/dist/shared/payload-root.d.ts +108 -0
- package/dist/shared/payload-root.d.ts.map +1 -0
- package/dist/shared/rsc-cache-key.d.ts +163 -0
- package/dist/shared/rsc-cache-key.d.ts.map +1 -0
- package/dist/shared/rsc-error-envelope.d.ts +25 -0
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
- package/dist/shared/rsc-manifest.d.ts +28 -0
- package/dist/shared/rsc-manifest.d.ts.map +1 -0
- package/dist/shared/rsc-media-type.d.ts +40 -0
- package/dist/shared/rsc-media-type.d.ts.map +1 -0
- package/dist/shared/rsc-payload-path.d.ts +42 -0
- package/dist/shared/rsc-payload-path.d.ts.map +1 -0
- package/dist/shared/segment-info.d.ts +59 -0
- package/dist/shared/segment-info.d.ts.map +1 -0
- package/dist/shared/slot-params.d.ts +51 -0
- package/dist/shared/slot-params.d.ts.map +1 -0
- package/dist/shared/static-platform-files.d.ts +71 -0
- package/dist/shared/static-platform-files.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +1 -1
- package/docs/api/31-api-client.mdx +4 -2
- package/docs/api/33-api-search-params.mdx +38 -16
- package/docs/api/35-api-typescript.mdx +3 -3
- package/docs/api/36-cli.mdx +34 -7
- package/docs/learn/00-introduction.mdx +1 -1
- package/docs/learn/02-pages-and-layouts.mdx +2 -2
- package/docs/learn/05-typed-params.mdx +8 -8
- package/docs/learn/07-typed-routes.mdx +12 -7
- package/docs/learn/11-error-handling.mdx +18 -0
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +173 -3
- package/docs/more/03-coming-from-nextjs.mdx +2 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/docs/more/50-ai-agent-instructions.mdx +6 -4
- package/package.json +9 -6
- package/src/adapters/build-output-helper.ts +116 -3
- package/src/adapters/cloudflare.ts +90 -12
- package/src/adapters/compress-module.ts +79 -1
- package/src/adapters/fs-identity.ts +156 -0
- package/src/adapters/nitro.ts +6 -6
- package/src/adapters/shared.ts +196 -21
- package/src/adapters/types.ts +13 -1
- package/src/cache/fast-hash.ts +5 -0
- package/src/cli-check.ts +458 -0
- package/src/cli.ts +59 -24
- package/src/client/browser-entry/action-dispatch.ts +11 -5
- package/src/client/browser-entry/hydrate.ts +40 -29
- package/src/client/browser-entry/index.ts +15 -6
- package/src/client/browser-entry/post-hydration.ts +10 -9
- package/src/client/browser-entry/router-init.ts +104 -48
- package/src/client/browser-entry/rsc-stream.ts +11 -2
- package/src/client/error-boundary.tsx +85 -16
- package/src/client/history.ts +13 -6
- package/src/client/index.ts +14 -3
- package/src/client/internal.ts +4 -3
- package/src/client/link.tsx +120 -72
- package/src/client/navigation-api.ts +3 -3
- package/src/client/navigation-commit.ts +197 -0
- package/src/client/navigation-context.ts +40 -67
- package/src/client/navigation-root.tsx +326 -101
- package/src/client/params-context.ts +192 -0
- package/src/client/router-effects.ts +231 -0
- package/src/client/router-lifecycle.ts +288 -0
- package/src/client/router-pipeline.ts +393 -0
- package/src/client/router-ref.ts +1 -1
- package/src/client/router-skew.ts +30 -0
- package/src/client/router-types.ts +230 -0
- package/src/client/router.ts +243 -707
- package/src/client/rsc-fetch.ts +183 -106
- package/src/client/segment-cache.ts +129 -44
- package/src/client/ssr-data.ts +6 -0
- package/src/client/stale-client.ts +191 -0
- package/src/client/state.ts +14 -1
- package/src/client/top-loader.tsx +2 -1
- package/src/client/unload-guard.ts +34 -2
- package/src/client/use-cookie.ts +9 -2
- package/src/client/use-query-states.ts +102 -39
- package/src/client/use-router.ts +1 -1
- package/src/client/use-segment-params.ts +41 -18
- package/src/cookies/define-cookie.ts +6 -1
- package/src/index.ts +25 -4
- package/src/plugin-context.ts +58 -0
- package/src/plugins/adapter-build.ts +4 -0
- package/src/plugins/build-manifest.ts +34 -13
- package/src/plugins/entries.ts +4 -0
- package/src/plugins/fonts.ts +11 -8
- package/src/plugins/request-dep/analysis.ts +447 -0
- package/src/plugins/request-dep/index.ts +376 -0
- package/src/plugins/request-dep/origin.ts +310 -0
- package/src/plugins/routing.ts +97 -102
- package/src/plugins/shims.ts +0 -1
- package/src/plugins/static-build.ts +391 -95
- package/src/routing/codegen-shared.ts +3 -79
- package/src/routing/codegen-types.ts +28 -31
- package/src/routing/codegen-write.ts +139 -0
- package/src/routing/codegen.ts +86 -172
- package/src/routing/collision-probe.ts +438 -0
- package/src/routing/collision-spaces.ts +281 -0
- package/src/routing/convention-lint.ts +139 -40
- package/src/routing/export-detect.ts +151 -7
- package/src/routing/interception.ts +896 -50
- package/src/routing/link-codegen.ts +32 -65
- package/src/routing/manifest-codegen.ts +6 -62
- package/src/routing/scanner.ts +117 -92
- package/src/routing/segment-classify.ts +57 -0
- package/src/routing/segment-keys.ts +146 -0
- package/src/routing/slot-placement.ts +97 -0
- package/src/routing/types.ts +0 -6
- package/src/routing/walkers.ts +8 -0
- package/src/schema-bridge.ts +180 -58
- package/src/search-params/define.ts +108 -66
- package/src/search-params/index.ts +0 -1
- package/src/search-params/parse-total.ts +78 -0
- package/src/search-params/wrappers.ts +60 -11
- package/src/server/access-gate.tsx +60 -40
- package/src/server/action-handler.ts +4 -5
- package/src/server/actions.ts +6 -3
- package/src/server/als-registry.ts +18 -1
- package/src/server/build-manifest.ts +14 -1
- package/src/server/chain-url-parts.ts +163 -0
- package/src/server/children-interception.ts +115 -0
- package/src/server/compress.ts +9 -1
- package/src/server/default-status-page.ts +127 -0
- package/src/server/deny-boundary.ts +269 -41
- package/src/server/deny-renderer.ts +111 -39
- package/src/server/error-boundary-wrapper.ts +166 -79
- package/src/server/head-response.ts +54 -0
- package/src/server/index.ts +1 -3
- package/src/server/internal.ts +2 -2
- package/src/server/metadata-collector.ts +115 -0
- package/src/server/param-coercion.ts +51 -69
- package/src/server/pipeline-helpers.ts +2 -47
- package/src/server/pipeline-interception.ts +81 -32
- package/src/server/pipeline-outcome.ts +4 -5
- package/src/server/pipeline-phases.ts +77 -25
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-runtime.ts +4 -4
- package/src/server/primitives.ts +25 -0
- package/src/server/publish-params.ts +42 -0
- package/src/server/request-context.ts +97 -18
- package/src/server/route-element-builder.ts +241 -253
- package/src/server/route-handler.ts +8 -8
- package/src/server/route-matcher.ts +21 -9
- package/src/server/rsc-cache-key-guard.ts +164 -0
- package/src/server/rsc-entry/api-handler.ts +6 -8
- package/src/server/rsc-entry/deny-fallback.ts +3 -2
- package/src/server/rsc-entry/error-renderer.ts +94 -35
- package/src/server/rsc-entry/helpers.ts +33 -28
- package/src/server/rsc-entry/index.ts +36 -33
- package/src/server/rsc-entry/render-route.ts +18 -10
- package/src/server/rsc-entry/revalidate-renderer.ts +3 -2
- package/src/server/rsc-entry/rsc-payload.ts +113 -40
- package/src/server/rsc-entry/rsc-stream.ts +29 -5
- package/src/server/rsc-entry/ssr-renderer.ts +38 -22
- package/src/server/skippable-prefix.ts +92 -0
- package/src/server/slot-interception.ts +168 -0
- package/src/server/slot-resolver.ts +263 -158
- package/src/server/ssr-bridge-types.ts +18 -0
- package/src/server/ssr-entry.ts +20 -10
- package/src/server/ssr-wrappers.tsx +33 -32
- package/src/server/state-tree-diff.ts +14 -55
- package/src/server/static-generator.ts +68 -29
- package/src/server/static-not-found.ts +134 -0
- package/src/server/status-code-resolver.ts +28 -11
- package/src/server/tree-builder.ts +35 -218
- package/src/server/types.ts +12 -7
- package/src/server/utils/element-type.ts +72 -0
- package/src/shared/als-slots.ts +72 -0
- package/src/shared/base-path.ts +80 -0
- package/src/shared/param-value.ts +244 -0
- package/src/shared/payload-root.ts +151 -0
- package/src/shared/rsc-cache-key.ts +269 -0
- package/src/shared/rsc-error-envelope.ts +31 -0
- package/src/shared/rsc-manifest.ts +29 -0
- package/src/shared/rsc-media-type.ts +43 -0
- package/src/shared/rsc-payload-path.ts +45 -0
- package/src/shared/segment-info.ts +60 -0
- package/src/shared/slot-params.ts +111 -0
- package/src/shared/static-platform-files.ts +75 -0
- package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
- package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +0 -1
- package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js.map +0 -1
- package/dist/_chunks/define-COtkxMRT.js +0 -191
- package/dist/_chunks/define-COtkxMRT.js.map +0 -1
- package/dist/_chunks/define-c4au4I9R.js +0 -82
- package/dist/_chunks/define-c4au4I9R.js.map +0 -1
- package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
- package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
- package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
- package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
- package/dist/_chunks/registry-DbJPKoBp.js +0 -20
- package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
- package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
- package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
- package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
- package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
- package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
- package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
- package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
- package/dist/_chunks/walkers-_6zKFlch.js +0 -1002
- package/dist/_chunks/walkers-_6zKFlch.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/plugins/request-dep.d.ts +0 -32
- package/dist/plugins/request-dep.d.ts.map +0 -1
- package/dist/search-params/registry.d.ts +0 -20
- package/dist/search-params/registry.d.ts.map +0 -1
- package/dist/segment-params/define.d.ts +0 -89
- package/dist/segment-params/define.d.ts.map +0 -1
- package/dist/segment-params/index.d.ts +0 -3
- package/dist/segment-params/index.d.ts.map +0 -1
- package/dist/segment-params/index.js +0 -2
- package/src/plugins/request-dep.ts +0 -499
- package/src/search-params/registry.ts +0 -31
- package/src/segment-params/define.ts +0 -249
- package/src/segment-params/index.ts +0 -9
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-time metadata resolution components.
|
|
3
|
+
*
|
|
4
|
+
* MetadataCollector runs below AccessGate for each segment, collecting
|
|
5
|
+
* metadata entries into a request-scoped array. MetadataHead runs at
|
|
6
|
+
* the page level, merging all collected entries and rendering Float-
|
|
7
|
+
* hoisted head elements.
|
|
8
|
+
*
|
|
9
|
+
* This structural approach replaces the eager pre-render metadata
|
|
10
|
+
* resolution: a denied segment's AccessGate prevents MetadataCollector
|
|
11
|
+
* from rendering, so metadata() never executes for denied segments.
|
|
12
|
+
* No `firstDeniedIndex` bookkeeping needed.
|
|
13
|
+
*
|
|
14
|
+
* See TIM-1363, design/16-metadata.md.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createElement, Fragment } from 'react';
|
|
18
|
+
import type { ReactNode } from 'react';
|
|
19
|
+
|
|
20
|
+
import { requestContextAls } from './als-registry.js';
|
|
21
|
+
import type { Metadata } from './types.js';
|
|
22
|
+
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
23
|
+
import { withSpan } from './tracing.js';
|
|
24
|
+
import { resolveMetadata, renderMetadataToElements, headElementsToReact } from './metadata.js';
|
|
25
|
+
import type { SegmentMetadataEntry } from './metadata.js';
|
|
26
|
+
import { collectMetadataRouteHeadElements } from './metadata-routes.js';
|
|
27
|
+
|
|
28
|
+
// ─── Request-scoped metadata entry store ────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function getMetadataEntries(): SegmentMetadataEntry[] {
|
|
31
|
+
const store = requestContextAls.getStore();
|
|
32
|
+
if (!store) return [];
|
|
33
|
+
store._metadataEntries ??= [];
|
|
34
|
+
return store._metadataEntries;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── MetadataCollector ──────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export interface MetadataCollectorProps {
|
|
40
|
+
metadataExport: Metadata | (() => Promise<Metadata>);
|
|
41
|
+
segmentName: string;
|
|
42
|
+
isPage: boolean;
|
|
43
|
+
onSettled?: () => void;
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Async server component that collects one segment's metadata during
|
|
49
|
+
* render. Placed between AccessGate and the layout/page. When
|
|
50
|
+
* AccessGate denies, this component never renders, so dynamic
|
|
51
|
+
* metadata() never executes for denied segments (TIM-1027).
|
|
52
|
+
*
|
|
53
|
+
* Static metadata objects are pushed synchronously (no side effects).
|
|
54
|
+
* Dynamic metadata functions are awaited with OTEL tracing.
|
|
55
|
+
*/
|
|
56
|
+
export async function MetadataCollector(props: MetadataCollectorProps): Promise<ReactNode> {
|
|
57
|
+
const { metadataExport, segmentName, isPage, onSettled, children } = props;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
let metadata: Metadata | null = null;
|
|
61
|
+
if (typeof metadataExport === 'function') {
|
|
62
|
+
metadata =
|
|
63
|
+
(await withSpan('timber.metadata', { 'timber.segment': segmentName }, () =>
|
|
64
|
+
(metadataExport as () => Promise<Metadata>)()
|
|
65
|
+
)) ?? null;
|
|
66
|
+
} else {
|
|
67
|
+
metadata = metadataExport;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (metadata) {
|
|
71
|
+
getMetadataEntries().push({ metadata, isPage });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return children;
|
|
75
|
+
} finally {
|
|
76
|
+
onSettled?.();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── MetadataHead ───────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
export interface MetadataHeadProps {
|
|
83
|
+
segments: ManifestSegmentNode[];
|
|
84
|
+
requestUrl: string;
|
|
85
|
+
metadataRouteHashes?: Record<string, string>;
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Server component at the page level. Reads all metadata entries
|
|
91
|
+
* collected by MetadataCollector components above it, merges them,
|
|
92
|
+
* auto-links metadata routes, and renders Float-hoisted head elements.
|
|
93
|
+
*
|
|
94
|
+
* Placed as a sibling of the page element inside a Fragment so the
|
|
95
|
+
* Float elements are always at the page segment level (re-rendered
|
|
96
|
+
* on every SPA navigation, never skipped by segment tree diffing).
|
|
97
|
+
*/
|
|
98
|
+
export function MetadataHead(props: MetadataHeadProps): ReactNode {
|
|
99
|
+
const { segments, requestUrl, metadataRouteHashes, children } = props;
|
|
100
|
+
const entries = getMetadataEntries();
|
|
101
|
+
const resolved = resolveMetadata(entries);
|
|
102
|
+
const headElements = renderMetadataToElements(resolved);
|
|
103
|
+
|
|
104
|
+
const autoLinked = collectMetadataRouteHeadElements(
|
|
105
|
+
segments,
|
|
106
|
+
Infinity,
|
|
107
|
+
resolved,
|
|
108
|
+
new URL(requestUrl),
|
|
109
|
+
metadataRouteHashes
|
|
110
|
+
);
|
|
111
|
+
headElements.push(...autoLinked);
|
|
112
|
+
|
|
113
|
+
const metadataElement = headElementsToReact(headElements);
|
|
114
|
+
return createElement(Fragment, null, metadataElement, children);
|
|
115
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Segment param coercion — runs the
|
|
3
|
-
*
|
|
2
|
+
* Segment param coercion — runs the app/schema.ts codecs over the raw
|
|
3
|
+
* matcher output before middleware and rendering.
|
|
4
4
|
*
|
|
5
5
|
* Lifted out of `pipeline-phases.ts` (TIM-853) so the coercer can be
|
|
6
6
|
* imported directly by other entry points (the action-dispatch wrapper,
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { Codec } from '../codec.js';
|
|
16
|
+
import type { SegmentType } from '../routing/types.js';
|
|
17
|
+
import { effectiveUrlSegment } from '../routing/segment-classify.js';
|
|
16
18
|
import { toBracketKey } from '../params/resolve-schema.js';
|
|
17
19
|
import type { RouteMatch } from './pipeline.js';
|
|
18
|
-
import {
|
|
19
|
-
import { loadModule } from './safe-load.js';
|
|
20
|
+
import { normalizeParamValue } from '../shared/param-value.js';
|
|
20
21
|
import { ParamCoercionError } from './route-element-builder.js';
|
|
21
22
|
import { isDebug } from './debug.js';
|
|
22
23
|
|
|
@@ -44,6 +45,12 @@ export function setGlobalSchemaCodecs(codecs: Record<string, Codec<unknown>> | n
|
|
|
44
45
|
* exists, the raw value is parsed through it. If no codec exists or no
|
|
45
46
|
* global codecs are registered, the raw value passes through unchanged.
|
|
46
47
|
*
|
|
48
|
+
* Segments are read through `effectiveUrlSegment` because an intercepting
|
|
49
|
+
* node's own `segmentType` is `'intercepting'` and its `paramName` is unset
|
|
50
|
+
* — the param it binds lives in the intercepted name (`(.)[id]` → `[id]`).
|
|
51
|
+
* The slot resolver matches through the same helper, so the key a param is
|
|
52
|
+
* coerced under is always the key it was matched under (TIM-1281).
|
|
53
|
+
*
|
|
47
54
|
* Unlike main route coercion, this does NOT throw ParamCoercionError on
|
|
48
55
|
* failure — slots degrade gracefully. A failed coercion logs a warning
|
|
49
56
|
* in dev mode and keeps the raw value.
|
|
@@ -51,7 +58,12 @@ export function setGlobalSchemaCodecs(codecs: Record<string, Codec<unknown>> | n
|
|
|
51
58
|
* @internal — framework use only
|
|
52
59
|
*/
|
|
53
60
|
export function coerceSlotParams(
|
|
54
|
-
slotChain: Array<{
|
|
61
|
+
slotChain: Array<{
|
|
62
|
+
segmentName: string;
|
|
63
|
+
segmentType: SegmentType;
|
|
64
|
+
paramName?: string;
|
|
65
|
+
interceptedSegmentName?: string;
|
|
66
|
+
}>,
|
|
55
67
|
rawParams: Record<string, string | string[]>
|
|
56
68
|
): Record<string, string | string[]> {
|
|
57
69
|
const globalCodecs = _globalCodecs;
|
|
@@ -62,7 +74,8 @@ export function coerceSlotParams(
|
|
|
62
74
|
result[key] = rawParams[key];
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
for (const
|
|
77
|
+
for (const node of slotChain) {
|
|
78
|
+
const segment = effectiveUrlSegment(node);
|
|
66
79
|
if (!segment.paramName) continue;
|
|
67
80
|
const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
|
|
68
81
|
const codec = globalCodecs[bracketKey];
|
|
@@ -71,10 +84,9 @@ export function coerceSlotParams(
|
|
|
71
84
|
const key = segment.paramName;
|
|
72
85
|
if (!(key in result)) continue;
|
|
73
86
|
|
|
87
|
+
let parsed: unknown;
|
|
74
88
|
try {
|
|
75
|
-
|
|
76
|
-
| string
|
|
77
|
-
| string[];
|
|
89
|
+
parsed = codec.parse(result[key] as string | string[]);
|
|
78
90
|
} catch (err) {
|
|
79
91
|
// Slot param coercion failures are non-fatal — keep the raw value.
|
|
80
92
|
// The main route already validated the URL; the slot just interprets
|
|
@@ -88,7 +100,9 @@ export function coerceSlotParams(
|
|
|
88
100
|
` Keeping raw value.`
|
|
89
101
|
);
|
|
90
102
|
}
|
|
103
|
+
continue;
|
|
91
104
|
}
|
|
105
|
+
result[key] = normalizeParamValue(parsed, key) as string | string[];
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
return result as Record<string, string | string[]>;
|
|
@@ -97,16 +111,24 @@ export function coerceSlotParams(
|
|
|
97
111
|
/**
|
|
98
112
|
* Run segment param coercion on the matched route's segments.
|
|
99
113
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
114
|
+
* Codecs come from `app/schema.ts` and nowhere else (TIM-1342 completed
|
|
115
|
+
* design/41-global-params.md migration step 9). A route with no schema, or
|
|
116
|
+
* a param the schema does not declare, keeps its raw string — which is the
|
|
117
|
+
* honest type for an uninterpreted URL segment.
|
|
103
118
|
*
|
|
104
119
|
* Throws ParamCoercionError if any codec fails (→ 404).
|
|
105
120
|
*
|
|
106
121
|
* This runs BEFORE middleware, so ctx.segmentParams is already typed.
|
|
107
122
|
* See design/07-routing.md §"Where Coercion Runs"
|
|
108
123
|
* See design/41-global-params.md §"Pipeline Integration"
|
|
124
|
+
*
|
|
125
|
+
* Stays `async` with nothing to await: it is injected as a
|
|
126
|
+
* `CoerceSegmentParamsFn` into the revalidation renderer and the action
|
|
127
|
+
* middleware runner, both of which await it. Narrowing the return type to
|
|
128
|
+
* `void` would break those contracts and remove a microtask boundary the
|
|
129
|
+
* pipeline's ordering has always had, for no gain.
|
|
109
130
|
*/
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- see above
|
|
110
132
|
export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
111
133
|
const globalCodecs = _globalCodecs;
|
|
112
134
|
// Unconditionally install a null-prototype target so the invariant
|
|
@@ -125,7 +147,17 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
|
125
147
|
// by bracket name (e.g. '[id]', '[...slug]') to avoid collisions between
|
|
126
148
|
// distinct segment types that share a param name.
|
|
127
149
|
if (globalCodecs) {
|
|
128
|
-
for (const
|
|
150
|
+
for (const node of match.segments) {
|
|
151
|
+
// Read through `effectiveUrlSegment` for the same reason
|
|
152
|
+
// `coerceSlotParams` does: an intercepting node's own `segmentType` is
|
|
153
|
+
// 'intercepting' and its `paramName` is unset — the param it binds lives
|
|
154
|
+
// in the intercepted name (`(.)[id]` → `[id]`). Until TIM-1280 an
|
|
155
|
+
// intercepting node could only appear in a *slot* chain, so only that
|
|
156
|
+
// path needed it; a children-path interception puts one in the main
|
|
157
|
+
// chain, and reading the node directly would silently skip coercion for
|
|
158
|
+
// exactly the param the intercepting directory declares — so the swapped
|
|
159
|
+
// route and the canonical one would disagree about the param's type.
|
|
160
|
+
const segment = effectiveUrlSegment(node);
|
|
129
161
|
if (!segment.paramName) continue;
|
|
130
162
|
const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
|
|
131
163
|
const codec = globalCodecs[bracketKey];
|
|
@@ -133,8 +165,9 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
|
133
165
|
|
|
134
166
|
const key = segment.paramName;
|
|
135
167
|
if (!(key in mergeTarget)) continue;
|
|
168
|
+
let parsed: unknown;
|
|
136
169
|
try {
|
|
137
|
-
|
|
170
|
+
parsed = codec.parse(mergeTarget[key] as string | string[]);
|
|
138
171
|
} catch (err) {
|
|
139
172
|
const message = err instanceof Error ? err.message : String(err);
|
|
140
173
|
if (isDebug()) {
|
|
@@ -147,61 +180,10 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
|
147
180
|
}
|
|
148
181
|
throw new ParamCoercionError(message);
|
|
149
182
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// Legacy path: per-segment params.ts coercion
|
|
155
|
-
for (const segment of match.segments) {
|
|
156
|
-
// Only process segments that have a params.ts convention file
|
|
157
|
-
if (!segment.params) continue;
|
|
158
|
-
|
|
159
|
-
let mod: Record<string, unknown>;
|
|
160
|
-
try {
|
|
161
|
-
mod = await loadModule(segment.params);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
const message = `Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`;
|
|
164
|
-
if (isDebug()) {
|
|
165
|
-
console.warn(
|
|
166
|
-
`[timber] Param coercion error: ${message}\n` +
|
|
167
|
-
` Segment: ${segment.segmentName}\n` +
|
|
168
|
-
` Params file: ${segment.params}`
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
throw new ParamCoercionError(message);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const segmentParamsDef = mod.segmentParams as
|
|
175
|
-
| { parse(raw: Record<string, string | string[]>): Record<string, unknown> }
|
|
176
|
-
| undefined;
|
|
177
|
-
|
|
178
|
-
if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const coerced = segmentParamsDef.parse(match.segmentParams);
|
|
182
|
-
|
|
183
|
-
// Deep-sanitize codec output: every nested plain object becomes
|
|
184
|
-
// null-prototype with dangerous keys stripped at every depth.
|
|
185
|
-
// See TIM-873, design/13-security.md
|
|
186
|
-
for (const key of Object.keys(coerced as Record<string, unknown>)) {
|
|
187
|
-
if (key !== '__proto__') {
|
|
188
|
-
mergeTarget[key] = sanitizeParamValue((coerced as Record<string, unknown>)[key]);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} catch (err) {
|
|
192
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
193
|
-
if (isDebug()) {
|
|
194
|
-
const rawKeys = Object.keys(match.segmentParams).join(', ');
|
|
195
|
-
console.warn(
|
|
196
|
-
`[timber] Param codec rejected values for segment "${segment.segmentName}"\n` +
|
|
197
|
-
` Error: ${message}\n` +
|
|
198
|
-
` Available raw params: { ${rawKeys} }\n` +
|
|
199
|
-
` Params file: ${segment.params}\n` +
|
|
200
|
-
` Hint: this usually means a codec threw for the raw URL value.\n` +
|
|
201
|
-
` Check that the regex/schema accepts the actual path segment string.`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
throw new ParamCoercionError(message);
|
|
183
|
+
// Outside the catch: a codec that rejects a URL value is a 404, a codec
|
|
184
|
+
// that returns an unrepresentable one is a bug in the app. Normalizing
|
|
185
|
+
// inside would report the second as the first.
|
|
186
|
+
mergeTarget[key] = normalizeParamValue(parsed, key);
|
|
205
187
|
}
|
|
206
188
|
}
|
|
207
189
|
}
|
|
@@ -15,54 +15,10 @@ import { callOnRequestError } from './instrumentation.js';
|
|
|
15
15
|
import { getTraceId } from './tracing.js';
|
|
16
16
|
import { RedirectSignal } from './primitives.js';
|
|
17
17
|
import type { ProxyConfig } from './pipeline.js';
|
|
18
|
+
import { isRscRequest } from '../shared/rsc-media-type.js';
|
|
18
19
|
|
|
19
20
|
// ─── Prototype-Pollution-Safe Sanitizer ────────────────────────────────────
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
* Only __proto__ needs stripping — it has a language-level setter that
|
|
23
|
-
* changes the prototype chain of spread copies. constructor and prototype
|
|
24
|
-
* are harmless own properties on null-prototype objects.
|
|
25
|
-
*/
|
|
26
|
-
const DANGEROUS_KEYS = new Set(['__proto__']);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Deep-walk a value returned by a segment param codec, producing a
|
|
30
|
-
* sanitized copy where every plain object is null-prototype and
|
|
31
|
-
* dangerous keys (__proto__, constructor, prototype) are stripped at
|
|
32
|
-
* every depth.
|
|
33
|
-
*
|
|
34
|
-
* Non-plain objects (Date, Map, class instances, etc.) are returned
|
|
35
|
-
* as-is — they cannot be poisoned by `{...x}` spread and may carry
|
|
36
|
-
* author-intended prototype methods.
|
|
37
|
-
*
|
|
38
|
-
* Arrays are walked element-wise.
|
|
39
|
-
*
|
|
40
|
-
* Performance: URL params are bounded by URL length (~8 KB). Realistic
|
|
41
|
-
* trees are <1 KB. The recursive walk is sub-microsecond.
|
|
42
|
-
*
|
|
43
|
-
* See TIM-655, TIM-855, TIM-873, design/13-security.md
|
|
44
|
-
*/
|
|
45
|
-
export function sanitizeParamValue(value: unknown): unknown {
|
|
46
|
-
if (value === null || typeof value !== 'object') return value;
|
|
47
|
-
|
|
48
|
-
if (Array.isArray(value)) {
|
|
49
|
-
return value.map(sanitizeParamValue);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Only walk plain objects — anything with a custom prototype (Date, Map,
|
|
53
|
-
// class instances) is left untouched.
|
|
54
|
-
const proto = Object.getPrototypeOf(value);
|
|
55
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
56
|
-
|
|
57
|
-
const out: Record<string, unknown> = Object.create(null);
|
|
58
|
-
for (const key of Object.keys(value as Record<string, unknown>)) {
|
|
59
|
-
if (!DANGEROUS_KEYS.has(key)) {
|
|
60
|
-
out[key] = sanitizeParamValue((value as Record<string, unknown>)[key]);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return out;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
22
|
// ─── Proxy Resolver ────────────────────────────────────────────────────────
|
|
67
23
|
|
|
68
24
|
/**
|
|
@@ -170,8 +126,7 @@ export function buildRedirectResponse(
|
|
|
170
126
|
req: Request,
|
|
171
127
|
headers: Headers
|
|
172
128
|
): Response {
|
|
173
|
-
|
|
174
|
-
if (isRsc) {
|
|
129
|
+
if (isRscRequest(req)) {
|
|
175
130
|
headers.set('X-Timber-Redirect', signal.location);
|
|
176
131
|
return new Response(null, { status: 204, headers });
|
|
177
132
|
}
|
|
@@ -10,47 +10,96 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { classifyUrlSegment } from '../routing/segment-classify.js';
|
|
13
|
-
|
|
14
|
-
/** Result of a successful interception match. */
|
|
15
|
-
export interface InterceptionMatchResult {
|
|
16
|
-
/** The pathname to re-match (the source/intercepting route's parent). */
|
|
17
|
-
sourcePathname: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Check if a pathname starts with a prefix on a segment boundary.
|
|
22
|
-
*
|
|
23
|
-
* Prevents /feed from matching /feed-private — the prefix must be
|
|
24
|
-
* followed by '/' or be an exact match.
|
|
25
|
-
*/
|
|
26
|
-
function hasSegmentPrefix(pathname: string, prefix: string): boolean {
|
|
27
|
-
if (prefix === '/') return true;
|
|
28
|
-
if (!pathname.startsWith(prefix)) return false;
|
|
29
|
-
return pathname.length === prefix.length || pathname[prefix.length] === '/';
|
|
30
|
-
}
|
|
13
|
+
import type { InterceptionRewrite } from '../routing/interception.js';
|
|
31
14
|
|
|
32
15
|
/**
|
|
33
16
|
* Check if an intercepting route applies for this soft navigation.
|
|
34
17
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
18
|
+
* Two conditions must hold:
|
|
19
|
+
*
|
|
20
|
+
* 1. The **target** pathname matches the rewrite's intercepted pattern.
|
|
21
|
+
* 2. The **source** route — the page the user is navigating FROM, per the
|
|
22
|
+
* validated X-Timber-URL header — renders the segment that owns the slot
|
|
23
|
+
* holding the intercepting page (`interceptingScope`). Only then does that
|
|
24
|
+
* slot exist in the rendered tree, so only then can the intercepting page
|
|
25
|
+
* render.
|
|
37
26
|
*
|
|
38
|
-
*
|
|
27
|
+
* Condition 2 replaced a URL-prefix check (TIM-1279). A prefix cannot
|
|
28
|
+
* distinguish a route-group-scoped interception from a genuinely root-level
|
|
29
|
+
* one — route groups are invisible in `urlPath`, so both collapse to "/" and
|
|
30
|
+
* the guard degenerates to always-true. Tree paths carry group identity.
|
|
31
|
+
* The scope check also subsumes the old segment-boundary rule: a source route
|
|
32
|
+
* under /feed-private has no "/feed" in its chain.
|
|
33
|
+
*
|
|
34
|
+
* A third condition lives with the caller, which owns route matching, and it
|
|
35
|
+
* applies to the `'slot'` shape only: the route produced by re-matching
|
|
36
|
+
* `interceptingPrefix` must pass through the scope too. Conditions 1–2 are
|
|
37
|
+
* about the route the user is leaving; the slot has to exist in the tree that
|
|
38
|
+
* actually renders. The `'children'` shape resolves its owner from the scope
|
|
39
|
+
* directly rather than from a pathname, so it satisfies that by construction.
|
|
40
|
+
*
|
|
41
|
+
* `resolveSourceScopes` is a thunk so its inputs are only resolved when some
|
|
42
|
+
* rewrite's pattern actually matches the target — the common case is no match
|
|
43
|
+
* at all. It returns the two client-supplied answers to "what is on screen?"
|
|
44
|
+
* as **separate sets, most authoritative first**: the tree paths the client
|
|
45
|
+
* says it has mounted, then those of the route the departing URL matches. It
|
|
46
|
+
* returns null only when neither yields anything, which is not a soft
|
|
47
|
+
* navigation this can reason about.
|
|
48
|
+
*
|
|
49
|
+
* They are two sets rather than one union because ranking needs a chain
|
|
50
|
+
* (TIM-1318). Each channel describes a single rendered chain, so within one the
|
|
51
|
+
* scopes are strictly nested and the deepest is the innermost owner. Unioned
|
|
52
|
+
* they are not: once a modal is open the address bar holds the *intercepted*
|
|
53
|
+
* URL, so the second channel describes the canonical branch — possibly an
|
|
54
|
+
* exclusive route group the client never mounted — and a deeper scope from
|
|
55
|
+
* there would outrank the owner actually on screen. Trying the mounted channel
|
|
56
|
+
* to exhaustion first keeps the fallback the union was there to provide while
|
|
57
|
+
* ranking only within one chain.
|
|
58
|
+
*
|
|
59
|
+
* Returns the winning build-time rewrite, or null. Only build-time values ever
|
|
60
|
+
* reach the renderer: the client-supplied header selects a rewrite but never
|
|
61
|
+
* becomes a rendered route or pathname.
|
|
62
|
+
*
|
|
63
|
+
* The **first** matching rewrite wins, and the caller does not fall back to the
|
|
64
|
+
* next one when the winner fails to resolve. That is deliberate and pre-dates
|
|
65
|
+
* the two shapes: a rewrite that matched the target and the scope described the
|
|
66
|
+
* author's intent, and quietly rendering some other author's interception
|
|
67
|
+
* instead would be harder to explain than rendering the canonical route.
|
|
68
|
+
*
|
|
69
|
+
* "First" is meaningful because the list arrives **deepest scope first** —
|
|
70
|
+
* `collectInterceptionRewrites` sorts it at build time, so the deepest owner a
|
|
71
|
+
* single chain passes through wins and nothing here has to score (TIM-1318).
|
|
72
|
+
* Tree-walk order used to decide it, which meant renaming a route group flipped
|
|
73
|
+
* which modal opened. The precondition is a property of the manifest this reads
|
|
74
|
+
* rather than something it can re-derive, so it is asserted at its source:
|
|
75
|
+
* `tests/interception.test.ts` §"rewrite order is decided by scope depth".
|
|
39
76
|
*/
|
|
40
77
|
export function findInterceptionMatch(
|
|
41
78
|
targetPathname: string,
|
|
42
|
-
|
|
43
|
-
rewrites:
|
|
44
|
-
):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
79
|
+
resolveSourceScopes: () => ReadonlySet<string>[] | null,
|
|
80
|
+
rewrites: InterceptionRewrite[]
|
|
81
|
+
): InterceptionRewrite | null {
|
|
82
|
+
// Dynamic segments in the pattern match any single URL segment. Collected up
|
|
83
|
+
// front rather than inside the channel loop so a pattern is matched once
|
|
84
|
+
// however many channels are tried — and the common case, no rewrite matching
|
|
85
|
+
// at all, costs exactly the same scan it always did and resolves no scope.
|
|
86
|
+
const candidates = rewrites.filter((rewrite) =>
|
|
87
|
+
pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)
|
|
88
|
+
);
|
|
89
|
+
if (candidates.length === 0) return null;
|
|
90
|
+
|
|
91
|
+
// Nothing at all is known about the source tree — no scope can contain
|
|
92
|
+
// anything, so no rewrite can apply.
|
|
93
|
+
const scopes = resolveSourceScopes();
|
|
94
|
+
if (!scopes) return null;
|
|
49
95
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
96
|
+
// `candidates` is deepest scope first (`collectInterceptionRewrites`), so
|
|
97
|
+
// within one channel the first hit is the innermost owner that channel
|
|
98
|
+
// reports. Channels are tried in order, so a scope the client says it has
|
|
99
|
+
// mounted always beats one only the address bar implies.
|
|
100
|
+
for (const scope of scopes) {
|
|
101
|
+
for (const rewrite of candidates) {
|
|
102
|
+
if (scope.has(rewrite.interceptingScope)) return rewrite;
|
|
54
103
|
}
|
|
55
104
|
}
|
|
56
105
|
return null;
|
|
@@ -26,16 +26,15 @@ import {
|
|
|
26
26
|
import { markResponseFlushed } from './request-context.js';
|
|
27
27
|
import { RedirectSignal, DenySignal } from './primitives.js';
|
|
28
28
|
import { isDebug } from './debug.js';
|
|
29
|
+
import { rscErrorEnvelope } from '../shared/rsc-error-envelope.js';
|
|
30
|
+
import { isRscRequest } from '../shared/rsc-media-type.js';
|
|
29
31
|
import type { PipelineConfig, RouteMatch } from './pipeline.js';
|
|
30
32
|
|
|
31
33
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
32
34
|
|
|
33
35
|
function rscErrorResponse(isRsc: boolean, status: number, headers?: Headers): Response {
|
|
34
36
|
if (!isRsc) return new Response(null, { status });
|
|
35
|
-
|
|
36
|
-
h.set('X-Timber-Error', '1');
|
|
37
|
-
h.set('content-type', 'application/json; charset=utf-8');
|
|
38
|
-
return new Response(JSON.stringify({ error: true, status }), { status, headers: h });
|
|
37
|
+
return rscErrorEnvelope(status, headers);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
// ─── Phase Outcome ─────────────────────────────────────────────────────────
|
|
@@ -143,7 +142,7 @@ export async function outcomeToResponse(
|
|
|
143
142
|
// Signal the error via X-Timber-Error so the client hard-navigates
|
|
144
143
|
// to the server-rendered error page instead of feeding HTML to the
|
|
145
144
|
// Flight decoder (which crashes with "enqueueModel is not a function").
|
|
146
|
-
const isRsc = (ctx.req
|
|
145
|
+
const isRsc = isRscRequest(ctx.req);
|
|
147
146
|
|
|
148
147
|
if (outcome.phase === 'proxy') {
|
|
149
148
|
logProxyError({ error: outcome.error });
|