@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
|
@@ -24,13 +24,24 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { ParamEntry, RouteEntry } from './codegen-types.js';
|
|
27
|
-
import {
|
|
28
|
-
buildCodecChainType,
|
|
29
|
-
buildSchemaParamType,
|
|
30
|
-
formatSearchParamsType,
|
|
31
|
-
} from './codegen-shared.js';
|
|
27
|
+
import { buildSchemaParamType } from './codegen-shared.js';
|
|
32
28
|
import { classifyUrlSegment } from './segment-classify.js';
|
|
33
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The `searchParams` prop type, emitted identically on every Link variant
|
|
32
|
+
* — per-route pattern, resolved template, and catch-all.
|
|
33
|
+
*
|
|
34
|
+
* TIM-1343: there is no per-route narrowing any more. `params.ts` is gone,
|
|
35
|
+
* so codegen has no definition to read a `SearchParamsDefinition<T>` from.
|
|
36
|
+
* Type safety moved to the value's construction site instead: callers write
|
|
37
|
+
* `definition.buildSearchParams({ ... })`, which checks `Partial<T>` there
|
|
38
|
+
* and returns a query STRING — the only shape that survives the RSC Flight
|
|
39
|
+
* boundary. A plain record is the uncodec'd escape hatch. See `client/link.tsx` §"searchParams prop shapes".
|
|
40
|
+
*
|
|
41
|
+
* Declared once so the three emission sites cannot drift apart.
|
|
42
|
+
*/
|
|
43
|
+
export const SEARCH_PARAMS_PROP_TYPE = 'string | Record<string, unknown>';
|
|
44
|
+
|
|
34
45
|
/** Shared Link base-props type literal used in every emitted call signature. */
|
|
35
46
|
export const LINK_BASE_PROPS_TYPE =
|
|
36
47
|
"Omit<import('react').AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & { prefetch?: boolean; scroll?: boolean; preserveSearchParams?: true | string[]; onNavigate?: import('./client/link.js').OnNavigateHandler; children?: import('react').ReactNode }";
|
|
@@ -71,23 +82,18 @@ export function buildResolvedPattern(route: RouteEntry): string | null {
|
|
|
71
82
|
* (convenience — values are stringified at runtime). Catch-all and
|
|
72
83
|
* optional catch-all remain `string[]` / `string[] | undefined`.
|
|
73
84
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
85
|
+
* With a global schema, the schema codec's inferred type wins. Without
|
|
86
|
+
* one, a URL segment is a raw string (widened to string | number for
|
|
87
|
+
* Link convenience) — per-segment codec chains were deleted in TIM-1342.
|
|
76
88
|
*/
|
|
77
|
-
export function formatLinkParamsType(
|
|
78
|
-
params: ParamEntry[],
|
|
79
|
-
importBase?: string,
|
|
80
|
-
hasSchema?: boolean
|
|
81
|
-
): string {
|
|
89
|
+
export function formatLinkParamsType(params: ParamEntry[], hasSchema?: boolean): string {
|
|
82
90
|
if (params.length === 0) {
|
|
83
91
|
return '{}';
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
const fields = params.map((p) => {
|
|
87
95
|
const fallback = p.type === 'string' ? 'string | number' : p.type;
|
|
88
|
-
const codecType = hasSchema
|
|
89
|
-
? buildSchemaParamType(p, fallback)
|
|
90
|
-
: buildCodecChainType(p, importBase, fallback);
|
|
96
|
+
const codecType = hasSchema ? buildSchemaParamType(p, fallback) : fallback;
|
|
91
97
|
return `${p.name}: ${codecType}`;
|
|
92
98
|
});
|
|
93
99
|
return `{ ${fields.join('; ')} }`;
|
|
@@ -109,15 +115,7 @@ export function formatLinkCatchAllOverloads(): string[] {
|
|
|
109
115
|
const lines: string[] = [];
|
|
110
116
|
const baseProps = LINK_BASE_PROPS_TYPE;
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
// `{ definition, values }` wrapper OR a flat `Record<string, unknown>`
|
|
114
|
-
// values object. External/computed hrefs can't be looked up in the
|
|
115
|
-
// runtime registry, so the wrapped form is still the reliable path;
|
|
116
|
-
// the flat form is kept permissive for callers migrating from typed-
|
|
117
|
-
// route hrefs to computed strings. `resolveHref` discriminates at
|
|
118
|
-
// runtime via the presence of a `definition` key.
|
|
119
|
-
const catchAllSearchParams =
|
|
120
|
-
'{ definition: SearchParamsDefinition<Record<string, unknown>>; values: Record<string, unknown> } | Record<string, unknown>';
|
|
118
|
+
const catchAllSearchParams = SEARCH_PARAMS_PROP_TYPE;
|
|
121
119
|
|
|
122
120
|
// ExternalHref inlined here rather than referenced as an exported
|
|
123
121
|
// alias so the generated .d.ts stands alone without source imports.
|
|
@@ -207,11 +205,7 @@ export function formatLinkCatchAllOverloads(): string[] {
|
|
|
207
205
|
* so the resolved-template-vs-pattern ordering reduces to placing the
|
|
208
206
|
* pattern variant before the resolved-template variant inside the union.
|
|
209
207
|
*/
|
|
210
|
-
export function formatTypedLinkOverloads(
|
|
211
|
-
routes: RouteEntry[],
|
|
212
|
-
importBase?: string,
|
|
213
|
-
hasSchema?: boolean
|
|
214
|
-
): string[] {
|
|
208
|
+
export function formatTypedLinkOverloads(routes: RouteEntry[], hasSchema?: boolean): string[] {
|
|
215
209
|
const lines: string[] = [];
|
|
216
210
|
const baseProps = LINK_BASE_PROPS_TYPE;
|
|
217
211
|
|
|
@@ -234,52 +228,25 @@ export function formatTypedLinkOverloads(
|
|
|
234
228
|
// segmentParams. `Record<string, unknown>` keeps generic spreads
|
|
235
229
|
// compiling and gives a readable error if a non-object is passed.
|
|
236
230
|
const paramsProp = hasDynamicParams
|
|
237
|
-
? `segmentParams: ${formatLinkParamsType(route.params,
|
|
231
|
+
? `segmentParams: ${formatLinkParamsType(route.params, hasSchema)}`
|
|
238
232
|
: 'segmentParams?: Record<string, unknown>';
|
|
239
233
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
// TIM-833: when the route has NO searchParams definition, use
|
|
247
|
-
// `Record<string, unknown>` (open shape) instead of
|
|
248
|
-
// `Record<string, never>` so generic `LinkProps`-spreading wrappers
|
|
249
|
-
// compile. The trade-off vs the original TIM-835 strict shape is
|
|
250
|
-
// that excess searchParams keys on a no-def route no longer raise
|
|
251
|
-
// a type error — but those keys are runtime no-ops anyway, and
|
|
252
|
-
// wrapper compatibility is the more common pain point. Routes WITH
|
|
253
|
-
// a searchParams definition still get strict `Partial<T>` typing.
|
|
254
|
-
const patternSearchParamsProp = searchParamsType
|
|
255
|
-
? `searchParams?: Partial<${searchParamsType}>`
|
|
256
|
-
: 'searchParams?: Record<string, unknown>';
|
|
234
|
+
// TIM-1343: searchParams is no longer typed per-route. There is no
|
|
235
|
+
// convention file to read a definition from, so codegen has nothing to
|
|
236
|
+
// narrow with — the value is either a URLSearchParams built by
|
|
237
|
+
// `definition.buildSearchParams()` (which type-checks its own
|
|
238
|
+
// `Partial<T>` at that call) or a plain String()-coerced record.
|
|
239
|
+
const searchParamsProp = `searchParams?: ${SEARCH_PARAMS_PROP_TYPE}`;
|
|
257
240
|
|
|
258
241
|
// Pattern variant FIRST (more specific).
|
|
259
242
|
variants.push(
|
|
260
|
-
`${baseProps} & { href: '${route.urlPath}'; ${paramsProp}; ${
|
|
243
|
+
`${baseProps} & { href: '${route.urlPath}'; ${paramsProp}; ${searchParamsProp} }`
|
|
261
244
|
);
|
|
262
245
|
|
|
263
246
|
// Resolved-template variant SECOND (looser, matches interpolated hrefs).
|
|
264
247
|
if (hasDynamicParams) {
|
|
265
248
|
const templatePattern = buildResolvedPattern(route);
|
|
266
249
|
if (templatePattern) {
|
|
267
|
-
// TIM-830: resolved-template href keeps the WRAPPED
|
|
268
|
-
// `{ definition, values }` shape — the registry can't be looked
|
|
269
|
-
// up by an already-interpolated href.
|
|
270
|
-
//
|
|
271
|
-
// TIM-833: searchParams falls back to `Record<string, unknown>`
|
|
272
|
-
// (was `Record<string, never>`) when the route has no
|
|
273
|
-
// definition, mirroring the pattern-variant change above. The
|
|
274
|
-
// resolved-template `segmentParams` is intentionally KEPT as
|
|
275
|
-
// `Record<string, never>` below to preserve TIM-835's
|
|
276
|
-
// pattern-variant typo detection: loosening segmentParams here
|
|
277
|
-
// would let the resolved-template variant shadow the pattern
|
|
278
|
-
// variant on literal pattern hrefs and silently swallow keyed
|
|
279
|
-
// typos like `<Link href="/[id]" segmentParams={{ idz: 1 }} />`.
|
|
280
|
-
const resolvedSearchParamsProp = searchParamsType
|
|
281
|
-
? `searchParams?: { definition: SearchParamsDefinition<${searchParamsType}>; values: Partial<${searchParamsType}> }`
|
|
282
|
-
: 'searchParams?: Record<string, unknown>';
|
|
283
250
|
// TIM-835: `Record<string, never>` for segmentParams forbids ANY
|
|
284
251
|
// provided keys on the resolved-template variant. Without this,
|
|
285
252
|
// the resolved-template would shadow the pattern variant for
|
|
@@ -288,7 +255,7 @@ export function formatTypedLinkOverloads(
|
|
|
288
255
|
// and the looser variant would silently swallow segmentParams
|
|
289
256
|
// type errors.
|
|
290
257
|
variants.push(
|
|
291
|
-
`${baseProps} & { href: \`${templatePattern}\`; segmentParams?: Record<string, never>; ${
|
|
258
|
+
`${baseProps} & { href: \`${templatePattern}\`; segmentParams?: Record<string, never>; ${searchParamsProp} }`
|
|
292
259
|
);
|
|
293
260
|
}
|
|
294
261
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route manifest codegen — generates virtual module source for the route
|
|
3
|
-
* manifest
|
|
3
|
+
* manifest and the global schema loader.
|
|
4
4
|
*
|
|
5
5
|
* Extracted from plugins/routing.ts to keep it under the 500-line budget.
|
|
6
6
|
* See design/18-build-system.md §"Virtual Modules".
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
import { collectInterceptionRewrites } from './interception.js';
|
|
14
|
-
import { fileHasExport } from './export-detect.js';
|
|
15
14
|
import type { RouteTree, SegmentNode, RouteFile } from './types.js';
|
|
16
15
|
|
|
17
16
|
// ── Single-file conventions ─────────────────────────────────────────────
|
|
@@ -26,7 +25,6 @@ const SINGLE_FILE_KEYS = [
|
|
|
26
25
|
'error',
|
|
27
26
|
'default',
|
|
28
27
|
'denied',
|
|
29
|
-
'params',
|
|
30
28
|
] as const;
|
|
31
29
|
|
|
32
30
|
// ── Record-shaped conventions ───────────────────────────────────────────
|
|
@@ -107,10 +105,6 @@ export function generateManifestModule(tree: RouteTree, viteRoot: string): strin
|
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
|
|
110
|
-
// searchParams is now a named export from page.tsx, not a separate file.
|
|
111
|
-
// The page module's searchParams export is loaded via the page's lazy import.
|
|
112
|
-
// Runtime registration happens in the route loader using the page module.
|
|
113
|
-
|
|
114
108
|
// Record-shaped conventions — table-driven
|
|
115
109
|
for (const key of RECORD_FILE_KEYS) {
|
|
116
110
|
const record = node[key];
|
|
@@ -160,11 +154,13 @@ export function generateManifestModule(tree: RouteTree, viteRoot: string): strin
|
|
|
160
154
|
globalErrorLine = ` globalError: { load: ${v}, filePath: ${JSON.stringify(tree.globalError.filePath)} },`;
|
|
161
155
|
}
|
|
162
156
|
|
|
157
|
+
// Serialize the rewrite objects wholesale — an explicit field list here
|
|
158
|
+
// silently drops any field added to InterceptionRewrite, and the guard that
|
|
159
|
+
// reads it would then fall back to matching too broadly. InterceptionRewrite
|
|
160
|
+
// is plain JSON data by construction (no file references, no functions).
|
|
163
161
|
const rewrites = collectInterceptionRewrites(tree.root);
|
|
164
162
|
const rewritesLine =
|
|
165
|
-
rewrites.length > 0
|
|
166
|
-
? ` interceptionRewrites: ${JSON.stringify(rewrites.map((r) => ({ interceptedPattern: r.interceptedPattern, interceptingPrefix: r.interceptingPrefix })))},`
|
|
167
|
-
: '';
|
|
163
|
+
rewrites.length > 0 ? ` interceptionRewrites: ${JSON.stringify(rewrites)},` : '';
|
|
168
164
|
|
|
169
165
|
const code = [
|
|
170
166
|
'// Auto-generated route manifest — do not edit.',
|
|
@@ -188,58 +184,6 @@ export function generateManifestModule(tree: RouteTree, viteRoot: string): strin
|
|
|
188
184
|
return code;
|
|
189
185
|
}
|
|
190
186
|
|
|
191
|
-
// ── Search-params registry ──────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Walk the route tree and collect every page that exports a
|
|
195
|
-
* `searchParams` definition, then emit a module that statically imports
|
|
196
|
-
* each export and registers it into the shared search-params registry.
|
|
197
|
-
*/
|
|
198
|
-
export function generateSearchParamsRegistryModule(tree: RouteTree): string {
|
|
199
|
-
const entries: Array<{ urlPath: string; filePath: string }> = [];
|
|
200
|
-
|
|
201
|
-
function walk(node: SegmentNode): void {
|
|
202
|
-
if (node.page) {
|
|
203
|
-
if (node.params && fileHasSearchParamsExport(node.params.filePath)) {
|
|
204
|
-
entries.push({ urlPath: node.urlPath, filePath: node.params.filePath });
|
|
205
|
-
} else if (fileHasSearchParamsExport(node.page.filePath)) {
|
|
206
|
-
entries.push({ urlPath: node.urlPath, filePath: node.page.filePath });
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
for (const child of node.children) walk(child);
|
|
210
|
-
for (const slot of Object.values(node.slots)) walk(slot);
|
|
211
|
-
}
|
|
212
|
-
walk(tree.root);
|
|
213
|
-
|
|
214
|
-
entries.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
|
|
215
|
-
|
|
216
|
-
const lines: string[] = [];
|
|
217
|
-
lines.push('// Auto-generated search-params registry — do not edit.');
|
|
218
|
-
lines.push('// Generated by timber-routing plugin (TIM-830).');
|
|
219
|
-
lines.push('');
|
|
220
|
-
lines.push("import { registerSearchParams } from '@timber-js/app/search-params';");
|
|
221
|
-
lines.push('');
|
|
222
|
-
|
|
223
|
-
entries.forEach((entry, i) => {
|
|
224
|
-
lines.push(`import { searchParams as r${i} } from ${JSON.stringify(entry.filePath)};`);
|
|
225
|
-
});
|
|
226
|
-
lines.push('');
|
|
227
|
-
|
|
228
|
-
entries.forEach((entry, i) => {
|
|
229
|
-
lines.push(`registerSearchParams(${JSON.stringify(entry.urlPath)}, r${i});`);
|
|
230
|
-
});
|
|
231
|
-
lines.push('');
|
|
232
|
-
|
|
233
|
-
lines.push('export {};');
|
|
234
|
-
lines.push('');
|
|
235
|
-
|
|
236
|
-
return lines.join('\n');
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function fileHasSearchParamsExport(filePath: string): boolean {
|
|
240
|
-
return fileHasExport(filePath, 'searchParams');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
187
|
// ── Schema module ───────────────────────────────────────────────────────
|
|
244
188
|
|
|
245
189
|
/**
|
package/src/routing/scanner.ts
CHANGED
|
@@ -18,7 +18,10 @@ import type {
|
|
|
18
18
|
ScannerConfig,
|
|
19
19
|
InterceptionMarker,
|
|
20
20
|
} from './types.js';
|
|
21
|
-
import { classifySegment } from './segment-classify.js';
|
|
21
|
+
import { classifySegment, effectiveUrlSegment } from './segment-classify.js';
|
|
22
|
+
import { validateInterceptionPlacement, computeInterceptedBase } from './interception.js';
|
|
23
|
+
import { validateRouteGroupCollisions } from './collision-spaces.js';
|
|
24
|
+
import { validateSlotPlacement } from './slot-placement.js';
|
|
22
25
|
import { DEFAULT_PAGE_EXTENSIONS } from './types.js';
|
|
23
26
|
import { classifyMetadataRoute, isDynamicMetadataExtension } from '../server/metadata-routes.js';
|
|
24
27
|
import { swallow } from '../server/logger.js';
|
|
@@ -32,7 +35,7 @@ const PAGE_EXT_CONVENTIONS = new Set(['page', 'layout', 'error', 'default', 'den
|
|
|
32
35
|
/**
|
|
33
36
|
* File convention names that are always .ts/.tsx (never .mdx etc.)
|
|
34
37
|
*/
|
|
35
|
-
const FIXED_CONVENTIONS = new Set(['middleware', 'access', 'route'
|
|
38
|
+
const FIXED_CONVENTIONS = new Set(['middleware', 'access', 'route']);
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Status-code file patterns:
|
|
@@ -76,6 +79,12 @@ export function scanRoutes(appDir: string, config: ScannerConfig = {}): RouteTre
|
|
|
76
79
|
// Scan children recursively
|
|
77
80
|
scanChildren(appDir, tree.root, extSet);
|
|
78
81
|
|
|
82
|
+
// Validate: every parallel slot sits where a renderer reaches it. First,
|
|
83
|
+
// because a slot nested inside another slot never renders, and a collision
|
|
84
|
+
// reported from inside one would send the author to fix an ambiguity between
|
|
85
|
+
// two pages that were both already dead.
|
|
86
|
+
validateSlotPlacement(tree.root);
|
|
87
|
+
|
|
79
88
|
// Validate: detect route group collisions (different groups producing pages at the same URL)
|
|
80
89
|
validateRouteGroupCollisions(tree.root);
|
|
81
90
|
|
|
@@ -91,6 +100,10 @@ export function scanRoutes(appDir: string, config: ScannerConfig = {}): RouteTre
|
|
|
91
100
|
// e.g., a-[id] and [slug]-b both have prefix+suffix length = 2
|
|
92
101
|
validateAffixedSiblingConflicts(tree.root);
|
|
93
102
|
|
|
103
|
+
// Validate: intercepting routes must be direct children of a parallel slot —
|
|
104
|
+
// the slot resolver is the only thing that can render one.
|
|
105
|
+
validateInterceptionPlacement(tree.root);
|
|
106
|
+
|
|
94
107
|
return tree;
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -171,12 +184,12 @@ function scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string
|
|
|
171
184
|
continue;
|
|
172
185
|
}
|
|
173
186
|
|
|
174
|
-
// Fixed conventions (middleware, access, route
|
|
187
|
+
// Fixed conventions (middleware, access, route) — only
|
|
175
188
|
// .ts/.tsx/.js/.jsx, never pageExtensions like .mdx. The regex is
|
|
176
189
|
// anchored: mts/cts/mjs/cjs (and stray tail matches like `.foots`)
|
|
177
190
|
// are NOT recognized. See design/07-routing.md §"Page Extensions".
|
|
178
191
|
if (FIXED_CONVENTIONS.has(name) && /^[jt]sx?$/.test(ext)) {
|
|
179
|
-
const key = name as 'middleware' | 'access' | 'route'
|
|
192
|
+
const key = name as 'middleware' | 'access' | 'route';
|
|
180
193
|
const existing = node[key];
|
|
181
194
|
if (existing) {
|
|
182
195
|
throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
@@ -355,69 +368,6 @@ function scanChildren(dirPath: string, parentNode: SegmentNode, extSet: Set<stri
|
|
|
355
368
|
}
|
|
356
369
|
}
|
|
357
370
|
|
|
358
|
-
/**
|
|
359
|
-
* Validate that route groups don't produce conflicting pages/routes at the same URL path.
|
|
360
|
-
*
|
|
361
|
-
* Two route groups like (auth)/login/page.tsx and (marketing)/login/page.tsx both claim
|
|
362
|
-
* /login — the scanner must detect and reject this at build time.
|
|
363
|
-
*
|
|
364
|
-
* Each parallel slot is validated as its OWN collision space: a slot's pages
|
|
365
|
-
* intentionally share URL paths with the parent tree (that's the point of
|
|
366
|
-
* parallel routes), but two route groups INSIDE one slot claiming the same
|
|
367
|
-
* URL — e.g. @side/(a)/x/page.tsx + @side/(b)/x/page.tsx — still collide.
|
|
368
|
-
*/
|
|
369
|
-
function validateRouteGroupCollisions(root: SegmentNode): void {
|
|
370
|
-
// Map from urlPath → { filePath, source } for the first page/route seen at that path
|
|
371
|
-
collectRoutableLeaves(root, new Map(), '');
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Walk the segment tree and collect all routable leaves (page or route files),
|
|
376
|
-
* throwing on collision. Each slot recurses with a FRESH collision map —
|
|
377
|
-
* slots are parallel routes that intentionally share URL paths with their
|
|
378
|
-
* parent tree, but collisions within a single slot are real errors.
|
|
379
|
-
*/
|
|
380
|
-
function collectRoutableLeaves(
|
|
381
|
-
node: SegmentNode,
|
|
382
|
-
seen: Map<string, { filePath: string; segmentPath: string }>,
|
|
383
|
-
segmentPath: string
|
|
384
|
-
): void {
|
|
385
|
-
// Intercepting routes are resolved by the slot resolver, not URL matching,
|
|
386
|
-
// and their computed urlPaths intentionally mirror other routes (e.g. two
|
|
387
|
-
// interceptors in one slot both produce /gallery/[id]). Skip them entirely.
|
|
388
|
-
if (node.segmentType === 'intercepting') return;
|
|
389
|
-
|
|
390
|
-
const currentPath = segmentPath
|
|
391
|
-
? `${segmentPath}/${node.segmentName}`
|
|
392
|
-
: node.segmentName || '(root)';
|
|
393
|
-
|
|
394
|
-
const routableFile = node.page ?? node.route;
|
|
395
|
-
if (routableFile) {
|
|
396
|
-
const existing = seen.get(node.urlPath);
|
|
397
|
-
if (existing) {
|
|
398
|
-
throw new Error(
|
|
399
|
-
`Build error: route collision — multiple route groups produce a page/route at the same URL path.\n` +
|
|
400
|
-
` URL path: ${node.urlPath}\n` +
|
|
401
|
-
` File 1: ${existing.filePath} (via ${existing.segmentPath})\n` +
|
|
402
|
-
` File 2: ${routableFile.filePath} (via ${currentPath})\n` +
|
|
403
|
-
`Each URL path must map to exactly one page or route handler. ` +
|
|
404
|
-
`Rename or move one of the conflicting files.`
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
seen.set(node.urlPath, { filePath: routableFile.filePath, segmentPath: currentPath });
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Recurse into children
|
|
411
|
-
for (const child of node.children) {
|
|
412
|
-
collectRoutableLeaves(child, seen, currentPath);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Recurse into slots — each slot gets a fresh map (its own collision space)
|
|
416
|
-
for (const slotNode of Object.values(node.slots)) {
|
|
417
|
-
collectRoutableLeaves(slotNode, new Map(), currentPath);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
371
|
/**
|
|
422
372
|
* Validate that no route chain contains duplicate dynamic param names.
|
|
423
373
|
*
|
|
@@ -438,12 +388,20 @@ function validateDuplicateParamNames(root: SegmentNode): void {
|
|
|
438
388
|
* Throws on the first duplicate found.
|
|
439
389
|
*/
|
|
440
390
|
function walkForDuplicateParams(node: SegmentNode, seen: Map<string, string>): void {
|
|
391
|
+
// Read through the effective identity so an intercepting directory is seen
|
|
392
|
+
// as the segment it intercepts: the scanner leaves `paramName` unset on
|
|
393
|
+
// `(.)[id]`, so a validator reading the node directly is blind to a param
|
|
394
|
+
// the resolver goes on to bind — `@modal/(.)[id]/[id]` would pass here and
|
|
395
|
+
// then silently overwrite the outer `id` at match time, where the ordinary
|
|
396
|
+
// `/[id]/[id]` is rejected. See TIM-1288.
|
|
397
|
+
const { paramName } = effectiveUrlSegment(node);
|
|
398
|
+
|
|
441
399
|
// If this node introduces a param name, check for duplicates
|
|
442
|
-
if (
|
|
443
|
-
const existing = seen.get(
|
|
400
|
+
if (paramName) {
|
|
401
|
+
const existing = seen.get(paramName);
|
|
444
402
|
if (existing) {
|
|
445
403
|
throw new Error(
|
|
446
|
-
`[timber] Duplicate param name '${
|
|
404
|
+
`[timber] Duplicate param name '${paramName}' in route chain.\n` +
|
|
447
405
|
` First defined at: ${existing}\n` +
|
|
448
406
|
` Duplicate at: ${node.urlPath || '/'}\n` +
|
|
449
407
|
` Rename one of the segments to avoid ambiguity.`
|
|
@@ -451,7 +409,7 @@ function walkForDuplicateParams(node: SegmentNode, seen: Map<string, string>): v
|
|
|
451
409
|
}
|
|
452
410
|
// Add to seen for descendants (use a new Map to avoid polluting siblings)
|
|
453
411
|
seen = new Map(seen);
|
|
454
|
-
seen.set(
|
|
412
|
+
seen.set(paramName, node.urlPath || '/');
|
|
455
413
|
}
|
|
456
414
|
|
|
457
415
|
// Recurse into children (they inherit the accumulated params)
|
|
@@ -459,8 +417,16 @@ function walkForDuplicateParams(node: SegmentNode, seen: Map<string, string>): v
|
|
|
459
417
|
walkForDuplicateParams(child, seen);
|
|
460
418
|
}
|
|
461
419
|
|
|
462
|
-
//
|
|
463
|
-
//
|
|
420
|
+
// A slot INHERITS the ancestor params rather than starting fresh: a slot
|
|
421
|
+
// child may not reuse a name its owner already bound
|
|
422
|
+
// (`app/gallery/[id]/@modal/[id]` is rejected). The copy is so sibling
|
|
423
|
+
// slots don't see each other's names, not so the chain is reset.
|
|
424
|
+
//
|
|
425
|
+
// Whether that rule is right is a separate question — slot params are
|
|
426
|
+
// merged over the main route's at read time, so a collision is defined
|
|
427
|
+
// rather than ambiguous — but changing it would change every slot.
|
|
428
|
+
// Tracked as TIM-1287; do not "fix" this by making the map fresh without
|
|
429
|
+
// deciding that.
|
|
464
430
|
for (const slotNode of Object.values(node.slots)) {
|
|
465
431
|
walkForDuplicateParams(slotNode, new Map(seen));
|
|
466
432
|
}
|
|
@@ -488,25 +454,29 @@ function collectBracketForms(
|
|
|
488
454
|
node: SegmentNode,
|
|
489
455
|
forms: Map<string, { segmentType: SegmentType; urlPath: string }>
|
|
490
456
|
): void {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
457
|
+
// Effective identity, so `(.)[...rest]` is compared as the catch-all it
|
|
458
|
+
// intercepts rather than skipped as an 'intercepting' node (TIM-1288).
|
|
459
|
+
const { paramName, segmentType } = effectiveUrlSegment(node);
|
|
460
|
+
|
|
461
|
+
if (paramName && segmentType !== 'static' && segmentType !== 'group') {
|
|
462
|
+
const existing = forms.get(paramName);
|
|
463
|
+
if (existing && existing.segmentType !== segmentType) {
|
|
494
464
|
const bracketExamples: Record<string, string> = {
|
|
495
|
-
'dynamic': `[${
|
|
496
|
-
'catch-all': `[...${
|
|
497
|
-
'optional-catch-all': `[[...${
|
|
465
|
+
'dynamic': `[${paramName}]`,
|
|
466
|
+
'catch-all': `[...${paramName}]`,
|
|
467
|
+
'optional-catch-all': `[[...${paramName}]]`,
|
|
498
468
|
};
|
|
499
469
|
throw new Error(
|
|
500
|
-
`[timber] Param name '${
|
|
470
|
+
`[timber] Param name '${paramName}' uses multiple bracket forms.\n` +
|
|
501
471
|
` ${bracketExamples[existing.segmentType] ?? existing.segmentType} at: ${existing.urlPath}\n` +
|
|
502
|
-
` ${bracketExamples[
|
|
472
|
+
` ${bracketExamples[segmentType] ?? segmentType} at: ${node.urlPath || '/'}\n` +
|
|
503
473
|
` Each param name must use a single bracket form across all routes.\n` +
|
|
504
|
-
` Rename one of the segments to fix this (e.g., [${
|
|
474
|
+
` Rename one of the segments to fix this (e.g., [${paramName}] → [${paramName}Id]).`
|
|
505
475
|
);
|
|
506
476
|
}
|
|
507
477
|
if (!existing) {
|
|
508
|
-
forms.set(
|
|
509
|
-
segmentType
|
|
478
|
+
forms.set(paramName, {
|
|
479
|
+
segmentType,
|
|
510
480
|
urlPath: node.urlPath || '/',
|
|
511
481
|
});
|
|
512
482
|
}
|
|
@@ -530,20 +500,39 @@ function collectBracketForms(
|
|
|
530
500
|
* Route groups are URL-transparent, so affixed segments inside different groups
|
|
531
501
|
* at the same level still conflict with each other.
|
|
532
502
|
*
|
|
533
|
-
*
|
|
503
|
+
* Ambiguity requires the two segments to be candidates for the *same* URL
|
|
504
|
+
* part, which is narrower than "same parent" once interception is involved:
|
|
505
|
+
*
|
|
506
|
+
* - Plain children and intercepting children never compete. They are resolved
|
|
507
|
+
* by different walks — `findSlotMatch` matches the slot subtree (where
|
|
508
|
+
* `tree-match.ts` ignores intercepting nodes outright) and
|
|
509
|
+
* `findInterceptingMatch` builds a synthetic parent holding only
|
|
510
|
+
* interceptors.
|
|
511
|
+
* - Two interceptors compete only at the same anchor. `(.)a-[id]` and
|
|
512
|
+
* `(..)[slug]-b` in one slot are based at different URL depths, so no
|
|
513
|
+
* pathname reaches both, and `anchorIndex` rejects the one whose base does
|
|
514
|
+
* not match the target. Grouping them together would fail the build for a
|
|
515
|
+
* pair that can never be ambiguous.
|
|
516
|
+
*
|
|
517
|
+
* So candidates are keyed by the position they contend for, then by affix
|
|
518
|
+
* length. See design/07-routing.md §"Priority" and §"Intercepting Routes".
|
|
534
519
|
*/
|
|
535
520
|
function validateAffixedSiblingConflicts(node: SegmentNode): void {
|
|
536
521
|
const affixed = collectEffectiveAffixed(node);
|
|
537
522
|
|
|
538
523
|
if (affixed.length >= 2) {
|
|
539
|
-
const byLength = new Map<
|
|
524
|
+
const byLength = new Map<string, SegmentNode[]>();
|
|
540
525
|
for (const child of affixed) {
|
|
541
|
-
|
|
542
|
-
|
|
526
|
+
// `\0` as the separator, written as the escape rather than a literal
|
|
527
|
+
// byte: a raw NUL in the source makes git and grep treat this file as
|
|
528
|
+
// binary. A contention key is a URL path, which cannot contain one, so
|
|
529
|
+
// the two halves of the key can never run together.
|
|
530
|
+
const key = `${contentionKey(node, child)}\0${affixLengthOf(child)}`;
|
|
531
|
+
const group = byLength.get(key);
|
|
543
532
|
if (group) {
|
|
544
533
|
group.push(child);
|
|
545
534
|
} else {
|
|
546
|
-
byLength.set(
|
|
535
|
+
byLength.set(key, [child]);
|
|
547
536
|
}
|
|
548
537
|
}
|
|
549
538
|
|
|
@@ -553,7 +542,7 @@ function validateAffixedSiblingConflicts(node: SegmentNode): void {
|
|
|
553
542
|
`Build error: ambiguous affixed dynamic siblings with equal specificity.\n` +
|
|
554
543
|
` Parent: ${node.urlPath || '/'}\n` +
|
|
555
544
|
` Segments: ${group.map((s) => s.segmentName).join(', ')}\n` +
|
|
556
|
-
` Combined prefix+suffix length: ${(group[0]
|
|
545
|
+
` Combined prefix+suffix length: ${affixLengthOf(group[0]!)}\n` +
|
|
557
546
|
`The router cannot deterministically choose between these — filesystem scan order is platform-dependent.\n` +
|
|
558
547
|
`Rename one segment to give it a longer or shorter combined affix.`
|
|
559
548
|
);
|
|
@@ -569,16 +558,52 @@ function validateAffixedSiblingConflicts(node: SegmentNode): void {
|
|
|
569
558
|
}
|
|
570
559
|
}
|
|
571
560
|
|
|
561
|
+
/**
|
|
562
|
+
* The URL position a child contends for, as a comparable key.
|
|
563
|
+
*
|
|
564
|
+
* Plain children contend for the parent's next part. An intercepting child
|
|
565
|
+
* contends for a part determined by its marker's intercepted base, so two
|
|
566
|
+
* markers of different depth are simply different positions — and two markers
|
|
567
|
+
* that compute the *same* base do contend, which is why the key is the base
|
|
568
|
+
* rather than the marker (`(..)` and `(...)` under `/feed` both base at `/`).
|
|
569
|
+
*
|
|
570
|
+
* `groupByInterceptedAnchor` (`routing/interception.ts`) is the canonical
|
|
571
|
+
* statement of that grouping — it is what the resolver walks and what the
|
|
572
|
+
* collision validator checks. This computes the same key from the same
|
|
573
|
+
* `computeInterceptedBase` rather than calling it, because affix ambiguity is
|
|
574
|
+
* decided per *child* and `collectEffectiveAffixed` looks through route groups,
|
|
575
|
+
* so the two do not partition the same set of nodes.
|
|
576
|
+
*/
|
|
577
|
+
function contentionKey(parent: SegmentNode, child: SegmentNode): string {
|
|
578
|
+
if (child.segmentType !== 'intercepting') return 'child';
|
|
579
|
+
return `intercept:${computeInterceptedBase(parent.urlPath, child.interceptionMarker ?? '(.)')}`;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** Combined affix length of a node, read through its effective identity. */
|
|
583
|
+
function affixLengthOf(node: SegmentNode): number {
|
|
584
|
+
const { paramPrefix, paramSuffix } = effectiveUrlSegment(node);
|
|
585
|
+
return (paramPrefix?.length ?? 0) + (paramSuffix?.length ?? 0);
|
|
586
|
+
}
|
|
587
|
+
|
|
572
588
|
/**
|
|
573
589
|
* Collect affixed dynamic segments that are effective children of a node.
|
|
574
590
|
* Route groups are URL-transparent, so we look through them recursively.
|
|
591
|
+
*
|
|
592
|
+
* Segments are read through `effectiveUrlSegment`, so an intercepting
|
|
593
|
+
* directory counts as the affixed segment it intercepts: `(.)a-[id]` is
|
|
594
|
+
* `a-[id]`. The proxy the slot resolver builds hands those affixes to
|
|
595
|
+
* `matchUrlParts`, whose equal-length sort is not stable across equal
|
|
596
|
+
* specificity — so two intercepting siblings with the same combined affix
|
|
597
|
+
* length would resolve by filesystem enumeration order, which is precisely
|
|
598
|
+
* what this validator exists to prevent for ordinary siblings (TIM-1288).
|
|
575
599
|
*/
|
|
576
600
|
function collectEffectiveAffixed(node: SegmentNode): SegmentNode[] {
|
|
577
601
|
const result: SegmentNode[] = [];
|
|
578
602
|
for (const child of node.children) {
|
|
579
|
-
|
|
603
|
+
const { segmentType, paramPrefix, paramSuffix } = effectiveUrlSegment(child);
|
|
604
|
+
if (segmentType === 'dynamic' && (paramPrefix || paramSuffix)) {
|
|
580
605
|
result.push(child);
|
|
581
|
-
} else if (
|
|
606
|
+
} else if (segmentType === 'group') {
|
|
582
607
|
result.push(...collectEffectiveAffixed(child));
|
|
583
608
|
}
|
|
584
609
|
}
|
|
@@ -192,6 +192,63 @@ export function classifySegment(dirName: string): SegmentClassification {
|
|
|
192
192
|
return { type: 'static' };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* The URL-matching identity of a segment node.
|
|
197
|
+
*
|
|
198
|
+
* For every node except an intercepting one this is the node itself. An
|
|
199
|
+
* intercepting node is different: its directory name carries a marker
|
|
200
|
+
* (`(.)photo`, `(.)[id]`), so the node's own `segmentType` is
|
|
201
|
+
* `'intercepting'` and the bracket syntax of the segment it intercepts was
|
|
202
|
+
* never classified. Everything that has to reason about *the URL part this
|
|
203
|
+
* node stands for* — matching it, and keying its param for codec coercion —
|
|
204
|
+
* needs that classification.
|
|
205
|
+
*
|
|
206
|
+
* Deriving it here, from `interceptedSegmentName`, keeps interception out
|
|
207
|
+
* of `classifySegment`'s output and out of the serialized manifest: there
|
|
208
|
+
* is no second copy of the classification to drift from this one. The
|
|
209
|
+
* returned `segmentName` is the intercepted name (`photo`, `[id]`), NOT the
|
|
210
|
+
* node's directory name — callers that need the directory name for tree
|
|
211
|
+
* paths must keep reading the node. See TIM-1281.
|
|
212
|
+
*/
|
|
213
|
+
export interface UrlSegmentIdentity {
|
|
214
|
+
segmentName: string;
|
|
215
|
+
segmentType: SegmentType;
|
|
216
|
+
paramName?: string;
|
|
217
|
+
paramPrefix?: string;
|
|
218
|
+
paramSuffix?: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function effectiveUrlSegment(node: {
|
|
222
|
+
segmentName: string;
|
|
223
|
+
segmentType: SegmentType;
|
|
224
|
+
paramName?: string;
|
|
225
|
+
paramPrefix?: string;
|
|
226
|
+
paramSuffix?: string;
|
|
227
|
+
interceptedSegmentName?: string;
|
|
228
|
+
}): UrlSegmentIdentity {
|
|
229
|
+
if (node.segmentType !== 'intercepting' || !node.interceptedSegmentName) {
|
|
230
|
+
return {
|
|
231
|
+
segmentName: node.segmentName,
|
|
232
|
+
segmentType: node.segmentType,
|
|
233
|
+
paramName: node.paramName,
|
|
234
|
+
paramPrefix: node.paramPrefix,
|
|
235
|
+
paramSuffix: node.paramSuffix,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const seg = classifyUrlSegment(node.interceptedSegmentName);
|
|
240
|
+
if (seg.kind === 'static') {
|
|
241
|
+
return { segmentName: seg.value, segmentType: 'static' };
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
segmentName: node.interceptedSegmentName,
|
|
245
|
+
segmentType: seg.kind,
|
|
246
|
+
paramName: seg.name,
|
|
247
|
+
paramPrefix: seg.kind === 'dynamic' ? seg.prefix : undefined,
|
|
248
|
+
paramSuffix: seg.kind === 'dynamic' ? seg.suffix : undefined,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
195
252
|
/**
|
|
196
253
|
* Parse an interception marker from a directory name.
|
|
197
254
|
*
|