@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,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route collision spaces.
|
|
3
|
+
*
|
|
4
|
+
* A collision space is a set of routes that compete for URLs with each other,
|
|
5
|
+
* and there are three: the main tree, each parallel slot, and each intercepting
|
|
6
|
+
* subtree. This module owns the first two; `interception.ts` owns the third.
|
|
7
|
+
* All of them decide order-dependence with the one oracle in
|
|
8
|
+
* `collision-probe.ts`, so they cannot drift on what counts as two routes
|
|
9
|
+
* answering a single URL.
|
|
10
|
+
*
|
|
11
|
+
* See design/07-routing.md §"Route Collisions".
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SegmentNode } from './types.js';
|
|
15
|
+
import { computeInterceptedUrlBase } from './interception.js';
|
|
16
|
+
import { findOrderDependentCollision, type ProbeLeaf } from './collision-probe.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate that no two pages/routes in one collision space answer the same URL.
|
|
20
|
+
*
|
|
21
|
+
* Two route groups like (auth)/login/page.tsx and (marketing)/login/page.tsx both claim
|
|
22
|
+
* /login — the scanner must detect and reject this at build time.
|
|
23
|
+
*
|
|
24
|
+
* Each parallel slot is validated as its OWN collision space: a slot's pages
|
|
25
|
+
* intentionally share URL paths with the parent tree (that's the point of
|
|
26
|
+
* parallel routes), but two route groups INSIDE one slot claiming the same
|
|
27
|
+
* URL — e.g. @side/(a)/x/page.tsx + @side/(b)/x/page.tsx — still collide.
|
|
28
|
+
*/
|
|
29
|
+
export function validateRouteGroupCollisions(root: SegmentNode): void {
|
|
30
|
+
validateCollisionSpace(root, '', root.urlPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate one collision space — the main tree, or one parallel slot.
|
|
35
|
+
*
|
|
36
|
+
* **Two checks, and the difference between them matters.** Two leaves at the
|
|
37
|
+
* same `urlPath` are decided by string equality, which is exact and needs no
|
|
38
|
+
* probing: `(auth)/login` and `(marketing)/login` claim one set of URLs
|
|
39
|
+
* whatever the precedence rules are, and naming the shared path is the clearest
|
|
40
|
+
* thing to tell the author.
|
|
41
|
+
*
|
|
42
|
+
* Everything else is decided by asking the matcher, through the shared oracle
|
|
43
|
+
* in `collision-probe.ts` (TIM-1308). Comparing `urlPath` spellings misses
|
|
44
|
+
* every pair that matches the same URLs while being *spelled* differently:
|
|
45
|
+
* `x/(a)/[id]` beside `x/(b)/[slug]` are two bare dynamics in one priority
|
|
46
|
+
* tier, and `x/(a)/page.tsx` beside `x/(b)/[[...rest]]` both answer `/x`
|
|
47
|
+
* because an optional catch-all also matches zero parts. Both built before
|
|
48
|
+
* TIM-1308 with filesystem enumeration order deciding which page renders,
|
|
49
|
+
* which is precisely what this validator and `validateAffixedSiblingConflicts`
|
|
50
|
+
* exist to reject.
|
|
51
|
+
*
|
|
52
|
+
* Segment *kinds* stay distinct without any special-casing here, because the
|
|
53
|
+
* oracle inherits `matchNode`'s priority ordering: `x/(a)/[id]` beside
|
|
54
|
+
* `x/(b)/[...rest]` resolves the same way in either `children` order, so it is
|
|
55
|
+
* not reported. So does an affixed sibling against a bare one; the
|
|
56
|
+
* equal-specificity affix tie is `validateAffixedSiblingConflicts`' job, and
|
|
57
|
+
* duplicating it here would double-report.
|
|
58
|
+
*/
|
|
59
|
+
function validateCollisionSpace(spaceRoot: SegmentNode, segmentPath: string, base: string): void {
|
|
60
|
+
const leaves: ProbeLeaf[] = [];
|
|
61
|
+
collectRoutableLeaves(spaceRoot, new Map(), segmentPath, [], leaves, base);
|
|
62
|
+
|
|
63
|
+
const collision = findOrderDependentCollision(spaceRoot, leaves, base);
|
|
64
|
+
if (!collision) return;
|
|
65
|
+
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Build error: route collision — two pages/routes answer the same URL.\n` +
|
|
68
|
+
` URL: ${collision.url}\n` +
|
|
69
|
+
` File 1: ${collision.first}\n` +
|
|
70
|
+
` File 2: ${collision.second}\n` +
|
|
71
|
+
`Both match it, so which one renders would be decided by filesystem enumeration ` +
|
|
72
|
+
`order — renaming a route group can flip it. Rename or move one of them.\n` +
|
|
73
|
+
`Routes are compared by the URLs they match, not by how they are spelled: [id] and ` +
|
|
74
|
+
`[slug] match the same URLs, and an optional catch-all also matches with zero ` +
|
|
75
|
+
`segments.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Walk one collision space and collect its routable leaves (page or route
|
|
81
|
+
* files), throwing on an exact `urlPath` collision. Each slot recurses as its
|
|
82
|
+
* OWN space — slots are parallel routes that intentionally share URL paths with
|
|
83
|
+
* their parent tree, but collisions within a single slot are real errors.
|
|
84
|
+
*
|
|
85
|
+
* The early return at an intercepting node drops that subtree's own pages from
|
|
86
|
+
* this space, but not its slots: a slot is its own collision space wherever it
|
|
87
|
+
* sits, so `validateSlotsInsideInterceptingSubtree` picks those up before
|
|
88
|
+
* returning.
|
|
89
|
+
*
|
|
90
|
+
* `chain` accumulates the URL-contributing nodes from the space root down, for
|
|
91
|
+
* the probe oracle. Route groups are excluded because they are transparent for
|
|
92
|
+
* matching, and the space root itself is excluded because `matchUrlParts`
|
|
93
|
+
* matches parts against its *children* — the root consumes nothing.
|
|
94
|
+
*/
|
|
95
|
+
function collectRoutableLeaves(
|
|
96
|
+
node: SegmentNode,
|
|
97
|
+
seen: Map<string, { filePath: string; segmentPath: string }>,
|
|
98
|
+
segmentPath: string,
|
|
99
|
+
chain: SegmentNode[],
|
|
100
|
+
leaves: ProbeLeaf[],
|
|
101
|
+
base: string
|
|
102
|
+
): void {
|
|
103
|
+
// Intercepting routes are resolved by the interception resolvers, not URL
|
|
104
|
+
// matching, and their computed urlPaths are not addresses — an interception
|
|
105
|
+
// mirrors the canonical route it replaces, so comparing those paths here
|
|
106
|
+
// would reject every correct interception. Skipping is not "unvalidated":
|
|
107
|
+
// `assertNoInterceptedPatternCollisions` gives each intercepting subtree its
|
|
108
|
+
// own collision space, keyed by the intercepted pattern rather than by these
|
|
109
|
+
// paths (TIM-1305). See design/07-routing.md §"Collisions Inside an
|
|
110
|
+
// Intercepting Subtree" and §"Why urlPath Stays Parent-Relative Inside an
|
|
111
|
+
// Intercepting Subtree".
|
|
112
|
+
const currentPath = segmentPath
|
|
113
|
+
? `${segmentPath}/${node.segmentName}`
|
|
114
|
+
: node.segmentName || '(root)';
|
|
115
|
+
|
|
116
|
+
// The intercepting subtree's own pages leave this space — but its SLOTS do
|
|
117
|
+
// not leave every space, so they are picked up before returning. See
|
|
118
|
+
// `validateSlotsInsideInterceptingSubtree`.
|
|
119
|
+
if (node.segmentType === 'intercepting') {
|
|
120
|
+
// `node.urlPath` on an intercepting node is its OWNER's path — the input
|
|
121
|
+
// `computeInterceptedUrlBase` wants — because an intercepting segment adds
|
|
122
|
+
// no URL depth.
|
|
123
|
+
validateSlotsInsideInterceptingSubtree(
|
|
124
|
+
node,
|
|
125
|
+
currentPath,
|
|
126
|
+
interceptedBaseOf(node, node.urlPath)
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const routableFile = node.page ?? node.route;
|
|
132
|
+
if (routableFile) {
|
|
133
|
+
const existing = seen.get(node.urlPath);
|
|
134
|
+
if (existing) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Build error: route collision — multiple route groups produce a page/route at the same URL path.\n` +
|
|
137
|
+
` URL path: ${urlFromChain(base, chain)}\n` +
|
|
138
|
+
` File 1: ${existing.filePath} (via ${existing.segmentPath})\n` +
|
|
139
|
+
` File 2: ${routableFile.filePath} (via ${currentPath})\n` +
|
|
140
|
+
`Each URL path must map to exactly one page or route handler. ` +
|
|
141
|
+
`Rename or move one of the conflicting files.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
seen.set(node.urlPath, { filePath: routableFile.filePath, segmentPath: currentPath });
|
|
145
|
+
leaves.push({ filePath: routableFile.filePath, chain });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Recurse into children. A route group contributes no URL part, so it is
|
|
149
|
+
// absent from the chain — exactly as `collectEffectiveChildren` flattens it
|
|
150
|
+
// away in the matcher.
|
|
151
|
+
for (const child of node.children) {
|
|
152
|
+
const isGroup = child.segmentType === 'group';
|
|
153
|
+
collectRoutableLeaves(
|
|
154
|
+
child,
|
|
155
|
+
seen,
|
|
156
|
+
currentPath,
|
|
157
|
+
isGroup ? chain : [...chain, child],
|
|
158
|
+
leaves,
|
|
159
|
+
base
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Recurse into slots — each slot is its own collision space, with its own
|
|
164
|
+
// seen map, its own leaf set and its own probe run.
|
|
165
|
+
//
|
|
166
|
+
// The base is accumulated from this space's own base rather than read off
|
|
167
|
+
// `slotNode.urlPath`, because this walk also runs on slot spaces nested inside
|
|
168
|
+
// an intercepting subtree, where `urlPath` is parent-relative and not an
|
|
169
|
+
// address. Outside one the two are identical by construction.
|
|
170
|
+
for (const slotNode of Object.values(node.slots)) {
|
|
171
|
+
validateCollisionSpace(slotNode, currentPath, urlFromChain(base, chain));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Validate every parallel slot nested anywhere inside an intercepting subtree.
|
|
177
|
+
*
|
|
178
|
+
* Two walks each skip these slots for their own good reason, and together they
|
|
179
|
+
* left them checked by nothing: `collectRoutableLeaves` returns at the
|
|
180
|
+
* intercepting node before it reaches them, and `walkInterceptedLeaves`
|
|
181
|
+
* deliberately does not descend slots — correct for rewrite generation, since a
|
|
182
|
+
* slot inside an intercepting subtree produces no rewrite. But "produces no
|
|
183
|
+
* rewrite" answers the rewrite question and not the resolution one, so
|
|
184
|
+
* `@side/(a)/x/page.tsx` beside `@side/(b)/x/page.tsx` under an interceptor
|
|
185
|
+
* built with filesystem order picking the page — the ordinary per-slot
|
|
186
|
+
* collision that fails the build anywhere else in the tree (codex, on PR #1002).
|
|
187
|
+
*
|
|
188
|
+
* A slot is its own collision space wherever it sits, so each one is validated
|
|
189
|
+
* as one.
|
|
190
|
+
*
|
|
191
|
+
* **Which interception shape this still applies to.** TIM-1311 answered the
|
|
192
|
+
* question this was written under — whether such a slot renders at all — and
|
|
193
|
+
* the answer is per-shape:
|
|
194
|
+
*
|
|
195
|
+
* - **Children path** (`(browse)/(...)photo/@side`): the intercepting node
|
|
196
|
+
* joins the main route chain, so `route-element-builder.ts` resolves slots on
|
|
197
|
+
* it and below it like any other chain segment. They render, and this is the
|
|
198
|
+
* space where the check is load-bearing.
|
|
199
|
+
* - **Modal** (`feed/@modal/(.)photo/@side`): the slot sits inside another
|
|
200
|
+
* slot's subtree, which no renderer descends for slots — so it never renders,
|
|
201
|
+
* and `validateSlotPlacement` now rejects the placement before this runs.
|
|
202
|
+
* That rule subsumes this one for the modal shape rather than conflicting
|
|
203
|
+
* with it, exactly as anticipated.
|
|
204
|
+
*
|
|
205
|
+
* So this walk is reached only for children-path interceptors in a valid tree.
|
|
206
|
+
* It is not narrowed to them: the rule it states — a slot is its own collision
|
|
207
|
+
* space wherever it sits — is shape-independent, and making it conditional on a
|
|
208
|
+
* property the placement validator already guarantees would just be a second
|
|
209
|
+
* place for the two to disagree.
|
|
210
|
+
*
|
|
211
|
+
* The walk covers the whole subtree, not just the interceptor's direct slot
|
|
212
|
+
* children: `(...)photo/[id]/@side` is reached by neither walk for exactly the
|
|
213
|
+
* same reason as `(...)photo/@side`.
|
|
214
|
+
*/
|
|
215
|
+
function validateSlotsInsideInterceptingSubtree(
|
|
216
|
+
node: SegmentNode,
|
|
217
|
+
segmentPath: string,
|
|
218
|
+
base: string
|
|
219
|
+
): void {
|
|
220
|
+
for (const slotNode of Object.values(node.slots)) {
|
|
221
|
+
// A slot adds no URL depth, so it answers its owner's base.
|
|
222
|
+
validateCollisionSpace(slotNode, segmentPath, base);
|
|
223
|
+
}
|
|
224
|
+
for (const child of node.children) {
|
|
225
|
+
validateSlotsInsideInterceptingSubtree(
|
|
226
|
+
child,
|
|
227
|
+
`${segmentPath}/${child.segmentName}`,
|
|
228
|
+
childInterceptedBase(child, base)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The URL a leaf answers, built from its space's base and its own chain.
|
|
235
|
+
*
|
|
236
|
+
* Read instead of `node.urlPath` because `urlPath` inside an intercepting
|
|
237
|
+
* subtree is parent-relative and is not an address — the collision URL has to
|
|
238
|
+
* be one the author can actually find. Outside an intercepting subtree the two
|
|
239
|
+
* are identical by construction: the base is the space root's `urlPath` and the
|
|
240
|
+
* chain is every URL-contributing segment below it.
|
|
241
|
+
*/
|
|
242
|
+
function urlFromChain(base: string, chain: SegmentNode[]): string {
|
|
243
|
+
if (chain.length === 0) return base || '/';
|
|
244
|
+
const tail = chain.map((n) => n.segmentName).join('/');
|
|
245
|
+
return `${base === '/' ? '' : base}/${tail}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The intercepted URL an intercepting directory's own page sits at.
|
|
250
|
+
*
|
|
251
|
+
* `urlPath` inside an intercepting subtree is deliberately parent-relative and
|
|
252
|
+
* is **not** an address (see design/07-routing.md §"Why urlPath Stays
|
|
253
|
+
* Parent-Relative Inside an Intercepting Subtree"), so a collision reported
|
|
254
|
+
* from one names a URL the author cannot find. The intercepted base is the real
|
|
255
|
+
* address, and it is what rewrite generation and the interception collision
|
|
256
|
+
* validator both already use.
|
|
257
|
+
*/
|
|
258
|
+
function interceptedBaseOf(node: SegmentNode, ownerUrlPath: string): string {
|
|
259
|
+
return computeInterceptedUrlBase(
|
|
260
|
+
ownerUrlPath,
|
|
261
|
+
node.interceptionMarker ?? '(.)',
|
|
262
|
+
node.interceptedSegmentName ?? node.segmentName
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Extend an intercepted base by one child segment.
|
|
268
|
+
*
|
|
269
|
+
* Route groups are transparent and contribute nothing. A nested intercepting
|
|
270
|
+
* node re-bases from its own marker rather than appending — that tree is
|
|
271
|
+
* rejected by `validateInterceptionPlacement`, but collision validation runs
|
|
272
|
+
* first, so it still has to produce a sensible URL rather than one containing a
|
|
273
|
+
* literal "(.)b".
|
|
274
|
+
*/
|
|
275
|
+
function childInterceptedBase(child: SegmentNode, base: string): string {
|
|
276
|
+
// Slots are not in `children` — `scanChildren` files them under `slots` — so
|
|
277
|
+
// only groups are transparent here.
|
|
278
|
+
if (child.segmentType === 'group') return base;
|
|
279
|
+
if (child.segmentType === 'intercepting') return interceptedBaseOf(child, base);
|
|
280
|
+
return base === '/' ? `/${child.segmentName}` : `${base}/${child.segmentName}`;
|
|
281
|
+
}
|
|
@@ -4,17 +4,25 @@
|
|
|
4
4
|
* Runs at scan time (build and dev startup). Each check produces a warning
|
|
5
5
|
* with the file path, what's wrong, and what to do about it.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Most checks are warnings — they don't block the build. The goal is to catch
|
|
8
|
+
* issues that would otherwise produce cryptic runtime behavior (silent 404s,
|
|
9
|
+
* empty pages, confusing React errors).
|
|
10
|
+
*
|
|
11
|
+
* A check emits `level: 'error'` only when the misconfiguration is *proven*
|
|
12
|
+
* (never inferred from an absence the analysis cannot see through) and fatal
|
|
13
|
+
* to rendering. `plugins/routing.ts` turns those into a thrown build error,
|
|
14
|
+
* so a false positive there refuses a working app — see
|
|
15
|
+
* `classifyClientBoundaryFile` for how that bar is met.
|
|
10
16
|
*
|
|
11
17
|
* Design doc: 07-routing.md, 10-error-handling.md
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
20
|
import { existsSync } from 'node:fs';
|
|
15
|
-
import type { RouteTree, SegmentNode } from './types.js';
|
|
21
|
+
import type { RouteFile, RouteTree, SegmentNode } from './types.js';
|
|
16
22
|
import { swallow } from '../server/logger.js';
|
|
23
|
+
import { isMdxFilePath } from '../server/utils/mdx-file.js';
|
|
17
24
|
import {
|
|
25
|
+
fileDefaultExportIsLocalValue,
|
|
18
26
|
fileHasAnyExport,
|
|
19
27
|
fileHasDefaultExport,
|
|
20
28
|
fileHasDirective,
|
|
@@ -61,10 +69,10 @@ export function lintConventions(tree: RouteTree, appDir: string): ConventionWarn
|
|
|
61
69
|
// Check 5: page.tsx / layout.tsx without default export
|
|
62
70
|
checkDefaultExports(tree.root, warnings);
|
|
63
71
|
|
|
64
|
-
// Check 6: error.tsx /
|
|
65
|
-
|
|
66
|
-
if (tree.globalError
|
|
67
|
-
checkFileUseClientDirective(tree.globalError
|
|
72
|
+
// Check 6: error.tsx / 404.tsx / 4xx.tsx / … without 'use client' directive
|
|
73
|
+
checkClientBoundaryFiles(tree.root, warnings);
|
|
74
|
+
if (tree.globalError) {
|
|
75
|
+
checkFileUseClientDirective(tree.globalError, 'global-error', warnings);
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
return warnings;
|
|
@@ -272,54 +280,132 @@ function checkFileDefaultExport(
|
|
|
272
280
|
}
|
|
273
281
|
}
|
|
274
282
|
|
|
275
|
-
// ─── Check:
|
|
283
|
+
// ─── Check: Client Boundary Directive ───────────────────────────────────────
|
|
276
284
|
|
|
277
285
|
/**
|
|
278
|
-
*
|
|
286
|
+
* Check every file the renderer hands to `TimberErrorBoundary` as a component
|
|
287
|
+
* prop: `error.tsx`, `global-error.tsx`, and the status-code family
|
|
288
|
+
* (`404.tsx`, `4xx.tsx`, `503.tsx`, …).
|
|
289
|
+
*
|
|
290
|
+
* `TimberErrorBoundary` is a client component, so a server component passed as
|
|
291
|
+
* `fallbackComponent` is a function crossing the RSC → client boundary. React
|
|
292
|
+
* Flight refuses to serialize it, and because the boundary wraps the segment's
|
|
293
|
+
* *children*, the failure hits every page under that segment — not just the
|
|
294
|
+
* error path. The raw message ("Functions cannot be passed directly to Client
|
|
295
|
+
* Components") names neither the file nor the fix (TIM-1329).
|
|
279
296
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
297
|
+
* MDX status files are exempt: they are server components by design and the
|
|
298
|
+
* renderer pre-renders them into `fallbackElement` instead. See TIM-503 and
|
|
299
|
+
* `server/error-boundary-wrapper.ts`.
|
|
300
|
+
*
|
|
301
|
+
* `.json` status files never reach React at all, and are not in `statusFiles`.
|
|
283
302
|
*/
|
|
284
|
-
function
|
|
285
|
-
if (node.error
|
|
286
|
-
checkFileUseClientDirective(node.error
|
|
303
|
+
function checkClientBoundaryFiles(node: SegmentNode, warnings: ConventionWarning[]): void {
|
|
304
|
+
if (node.error) {
|
|
305
|
+
checkFileUseClientDirective(node.error, 'error', warnings);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for (const [key, file] of Object.entries(node.statusFiles ?? {})) {
|
|
309
|
+
checkFileUseClientDirective(file, key, warnings);
|
|
287
310
|
}
|
|
288
311
|
|
|
289
312
|
for (const child of node.children) {
|
|
290
|
-
|
|
313
|
+
checkClientBoundaryFiles(child, warnings);
|
|
291
314
|
}
|
|
292
315
|
for (const slot of Object.values(node.slots)) {
|
|
293
|
-
|
|
316
|
+
checkClientBoundaryFiles(slot, warnings);
|
|
294
317
|
}
|
|
295
318
|
}
|
|
296
319
|
|
|
320
|
+
/**
|
|
321
|
+
* `'client'` — carries the directive.
|
|
322
|
+
* `'server'` — proven server component: no directive, and the default export is
|
|
323
|
+
* a function or class built by this very module, so no other file's directive
|
|
324
|
+
* can be governing it.
|
|
325
|
+
* `'unknown'` — no directive, but the exported value may come from elsewhere
|
|
326
|
+
* (`export { default } from './client-thing'`), or the file could not be
|
|
327
|
+
* read or parsed. Never escalated to a build error.
|
|
328
|
+
*/
|
|
329
|
+
type ClientBoundaryVerdict = 'client' | 'server' | 'unknown';
|
|
330
|
+
|
|
331
|
+
function classifyClientBoundaryFile(filePath: string): ClientBoundaryVerdict {
|
|
332
|
+
if (fileHasDirective(filePath, 'use client')) return 'client';
|
|
333
|
+
if (fileDefaultExportIsLocalValue(filePath)) return 'server';
|
|
334
|
+
return 'unknown';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Example component name for the fix snippet, keyed by convention name. */
|
|
338
|
+
function exampleComponentName(convention: string): string {
|
|
339
|
+
if (convention === 'global-error') return 'GlobalError';
|
|
340
|
+
if (convention === 'error') return 'ErrorBoundary';
|
|
341
|
+
if (convention === '404') return 'NotFound';
|
|
342
|
+
return 'StatusPage';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @param file the route file (its extension decides whether the check applies)
|
|
347
|
+
* @param convention the convention name — `error`, `global-error`, `404`, `4xx`, …
|
|
348
|
+
*/
|
|
297
349
|
function checkFileUseClientDirective(
|
|
298
|
-
|
|
299
|
-
|
|
350
|
+
file: RouteFile,
|
|
351
|
+
convention: string,
|
|
300
352
|
warnings: ConventionWarning[]
|
|
301
353
|
): void {
|
|
354
|
+
// MDX/markdown status and error files are server components by design — the
|
|
355
|
+
// renderer pre-renders them into `fallbackElement`, so they never cross the
|
|
356
|
+
// boundary as a function.
|
|
357
|
+
//
|
|
358
|
+
// The exemption is the *complement* of the renderer's own test, using the
|
|
359
|
+
// renderer's own helper: everything `error-boundary-wrapper.ts` and
|
|
360
|
+
// `error-renderer.ts` do not treat as MDX, they pass as a component. An
|
|
361
|
+
// independent "is this a script extension?" list here would be a second
|
|
362
|
+
// expression of that fact, free to drift into a gap where a file is a
|
|
363
|
+
// component to the renderer and exempt to the linter.
|
|
364
|
+
if (isMdxFilePath(file.filePath)) return;
|
|
365
|
+
|
|
366
|
+
const filePath = file.filePath;
|
|
367
|
+
const label = `${convention}.${file.extension}`;
|
|
368
|
+
let verdict: ClientBoundaryVerdict;
|
|
302
369
|
try {
|
|
303
|
-
|
|
304
|
-
warnings.push({
|
|
305
|
-
id: `ERROR_NO_USE_CLIENT:${filePath}`,
|
|
306
|
-
summary: `${fileType}.tsx is missing 'use client': ${filePath}`,
|
|
307
|
-
details:
|
|
308
|
-
` File: ${filePath}\n\n` +
|
|
309
|
-
` ${fileType}.tsx must start with 'use client' because React error\n` +
|
|
310
|
-
' boundaries require client components. Without it, the error boundary\n' +
|
|
311
|
-
' silently fails and errors are uncaught.\n\n' +
|
|
312
|
-
" To fix: Add 'use client' as the first line of the file:\n\n" +
|
|
313
|
-
" 'use client';\n\n" +
|
|
314
|
-
` export default function ${fileType === 'global-error' ? 'GlobalError' : 'ErrorBoundary'}({ error, reset }) {\n` +
|
|
315
|
-
' return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;\n' +
|
|
316
|
-
' }\n',
|
|
317
|
-
level: 'warn',
|
|
318
|
-
});
|
|
319
|
-
}
|
|
370
|
+
verdict = classifyClientBoundaryFile(filePath);
|
|
320
371
|
} catch (err) {
|
|
321
|
-
swallow(err, `convention-lint: unreadable ${
|
|
372
|
+
swallow(err, `convention-lint: unreadable ${label} file ${filePath}`);
|
|
373
|
+
return;
|
|
322
374
|
}
|
|
375
|
+
if (verdict === 'client') return;
|
|
376
|
+
|
|
377
|
+
const isError = verdict === 'server';
|
|
378
|
+
const name = exampleComponentName(convention);
|
|
379
|
+
const props =
|
|
380
|
+
convention === 'error' || convention === 'global-error' ? '{ error, reset }' : '{ status }';
|
|
381
|
+
|
|
382
|
+
warnings.push({
|
|
383
|
+
id: `NEEDS_USE_CLIENT:${filePath}`,
|
|
384
|
+
summary: isError
|
|
385
|
+
? `${label} is a server component and must be a client component: ${filePath}`
|
|
386
|
+
: `${label} may be missing 'use client': ${filePath}`,
|
|
387
|
+
details:
|
|
388
|
+
` File: ${filePath}\n\n` +
|
|
389
|
+
` ${label} is handed to timber's error boundary — a client\n` +
|
|
390
|
+
' component — as a prop. A server component cannot cross that boundary:\n' +
|
|
391
|
+
' React fails with "Functions cannot be passed directly to Client\n' +
|
|
392
|
+
' Components", and every page under this segment fails to render.\n\n' +
|
|
393
|
+
(isError
|
|
394
|
+
? " To fix: Add 'use client' as the first line of the file:\n\n" +
|
|
395
|
+
" 'use client';\n\n" +
|
|
396
|
+
` export default function ${name}(${props}) {\n` +
|
|
397
|
+
' return <h1>…</h1>;\n' +
|
|
398
|
+
' }\n'
|
|
399
|
+
: // The default export comes from somewhere this analysis cannot follow
|
|
400
|
+
// — a re-export, an imported binding, a wrapper call. It may well
|
|
401
|
+
// already be a client component, so say what was checked rather than
|
|
402
|
+
// assert a break.
|
|
403
|
+
" This file has no 'use client' directive, and its default export\n" +
|
|
404
|
+
' comes from elsewhere, so timber cannot tell which it is. If the\n' +
|
|
405
|
+
" module it re-exports starts with 'use client', nothing is wrong\n" +
|
|
406
|
+
' and you can ignore this. Otherwise add the directive there.\n'),
|
|
407
|
+
level: isError ? 'error' : 'warn',
|
|
408
|
+
});
|
|
323
409
|
}
|
|
324
410
|
|
|
325
411
|
// ─── Check: App Directory Exists ────────────────────────────────────────────
|
|
@@ -352,14 +438,27 @@ import { styleText } from 'node:util';
|
|
|
352
438
|
|
|
353
439
|
type Format = InspectColor | readonly InspectColor[];
|
|
354
440
|
const noValidate = { validateStream: false } as const;
|
|
355
|
-
|
|
441
|
+
|
|
442
|
+
export interface FormatOptions {
|
|
443
|
+
/**
|
|
444
|
+
* Emit ANSI escapes. Off when the text becomes an `Error` message —
|
|
445
|
+
* whoever prints it decides its formatting, and colored escapes inside
|
|
446
|
+
* an exception leak into logs and stack traces.
|
|
447
|
+
*/
|
|
448
|
+
color?: boolean;
|
|
449
|
+
}
|
|
356
450
|
|
|
357
451
|
/**
|
|
358
452
|
* Format warnings for terminal output.
|
|
359
453
|
*
|
|
360
454
|
* Groups by severity, uses colors, and includes fix suggestions.
|
|
361
455
|
*/
|
|
362
|
-
export function formatConventionWarnings(
|
|
456
|
+
export function formatConventionWarnings(
|
|
457
|
+
warnings: ConventionWarning[],
|
|
458
|
+
options: FormatOptions = {}
|
|
459
|
+
): string {
|
|
460
|
+
const { color = true } = options;
|
|
461
|
+
const style = (fmt: Format, text: string) => (color ? styleText(fmt, text, noValidate) : text);
|
|
363
462
|
if (warnings.length === 0) return '';
|
|
364
463
|
|
|
365
464
|
const errors = warnings.filter((w) => w.level === 'error');
|