@timber-js/app 0.2.0-alpha.17 → 0.2.0-alpha.171
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/LICENSE +8 -0
- package/agent-skill.md +344 -0
- package/dist/_chunks/actions-O_LsyCE4.js +317 -0
- package/dist/_chunks/actions-O_LsyCE4.js.map +1 -0
- package/dist/_chunks/build-output-helper-DF5uScqS.js +158 -0
- package/dist/_chunks/build-output-helper-DF5uScqS.js.map +1 -0
- package/dist/_chunks/cache-api-B-lhk9p4.js +1202 -0
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
- package/dist/_chunks/canonicalize-Du3o_ptW.js +247 -0
- package/dist/_chunks/canonicalize-Du3o_ptW.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-B5FDplGI.js +688 -0
- package/dist/_chunks/cli-schema-sync-B5FDplGI.js.map +1 -0
- package/dist/_chunks/cloudflare-AHoWYTYr.js +1188 -0
- package/dist/_chunks/cloudflare-AHoWYTYr.js.map +1 -0
- package/dist/_chunks/codec-registry-oOUxugz3.js +21 -0
- package/dist/_chunks/codec-registry-oOUxugz3.js.map +1 -0
- package/dist/_chunks/define-COtkxMRT.js +191 -0
- package/dist/_chunks/define-COtkxMRT.js.map +1 -0
- package/dist/_chunks/define-c4au4I9R.js +82 -0
- package/dist/_chunks/define-c4au4I9R.js.map +1 -0
- package/dist/_chunks/dist-BA3u1z3W.js +423 -0
- package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
- package/dist/_chunks/fast-hash-D6hIVt1Y.js +36 -0
- package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +1 -0
- package/dist/_chunks/format-CfwjgPz9.js +14 -0
- package/dist/_chunks/format-CfwjgPz9.js.map +1 -0
- package/dist/_chunks/href-validation-CMc5JRls.js +141 -0
- package/dist/_chunks/href-validation-CMc5JRls.js.map +1 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
- package/dist/_chunks/logger-AWfuX-KJ.js +1468 -0
- package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
- package/dist/_chunks/navigation-root-bUDZLw74.js +124 -0
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +1 -0
- package/dist/_chunks/plugin-context---kTF5v8.js +315 -0
- package/dist/_chunks/plugin-context---kTF5v8.js.map +1 -0
- package/dist/_chunks/registry-DbJPKoBp.js +20 -0
- package/dist/_chunks/registry-DbJPKoBp.js.map +1 -0
- package/dist/_chunks/resolve-schema-Dz3fcFUo.js +50 -0
- package/dist/_chunks/resolve-schema-Dz3fcFUo.js.map +1 -0
- package/dist/_chunks/router-ref-CtmF-aPv.js +28 -0
- package/dist/_chunks/router-ref-CtmF-aPv.js.map +1 -0
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +119 -0
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +1 -0
- package/dist/_chunks/segment-classify-Byy425ng.js +176 -0
- package/dist/_chunks/segment-classify-Byy425ng.js.map +1 -0
- package/dist/_chunks/segment-context-ZDnXDkbz.js +34 -0
- package/dist/_chunks/segment-context-ZDnXDkbz.js.map +1 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
- package/dist/_chunks/{ssr-data-MjmprTmO.js → ssr-data-BOWsq18U.js} +1 -1
- package/dist/_chunks/ssr-data-BOWsq18U.js.map +1 -0
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DFvWd-EA.js} +71 -26
- package/dist/_chunks/use-query-states-DFvWd-EA.js.map +1 -0
- package/dist/_chunks/use-segment-params-D1CzlgBo.js +151 -0
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +1 -0
- package/dist/_chunks/walkers-DCoE-LJf.js +1002 -0
- package/dist/_chunks/walkers-DCoE-LJf.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +29 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.d.ts +109 -0
- package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +73 -0
- package/dist/adapters/cloudflare-dev.js.map +1 -0
- package/dist/adapters/cloudflare-kv-cache.d.ts +65 -0
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -0
- package/dist/adapters/cloudflare-kv-cache.js +124 -0
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +189 -21
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +2 -188
- package/dist/adapters/compress-module.d.ts +5 -4
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +41 -3
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +224 -172
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +6 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/types.d.ts +2 -6
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/cache-api.d.ts +48 -0
- package/dist/cache/cache-api.d.ts.map +1 -0
- package/dist/cache/handler-store.d.ts +33 -0
- package/dist/cache/handler-store.d.ts.map +1 -0
- package/dist/cache/index.d.ts +31 -8
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +3 -326
- package/dist/cache/invalidation-epoch.d.ts +43 -0
- package/dist/cache/invalidation-epoch.d.ts.map +1 -0
- package/dist/cache/json-lossy-check.d.ts +11 -0
- package/dist/cache/json-lossy-check.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +140 -8
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +19 -2
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/sizeof.d.ts +22 -0
- package/dist/cache/sizeof.d.ts.map +1 -0
- package/dist/cache/stable-stringify.d.ts +9 -0
- package/dist/cache/stable-stringify.d.ts.map +1 -1
- package/dist/cache/store.d.ts +53 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.js +29 -0
- package/dist/cache/stores/cloudflare-kv.js.map +1 -0
- package/dist/cache/stores/memory.d.ts +42 -0
- package/dist/cache/stores/memory.d.ts.map +1 -0
- package/dist/cache/stores/memory.js +90 -0
- package/dist/cache/stores/memory.js.map +1 -0
- package/dist/cache/stores/redis.d.ts +69 -0
- package/dist/cache/stores/redis.d.ts.map +1 -0
- package/dist/cache/stores/redis.js +35 -0
- package/dist/cache/stores/redis.js.map +1 -0
- package/dist/cache/stores/vercel.d.ts +36 -0
- package/dist/cache/stores/vercel.d.ts.map +1 -0
- package/dist/cache/stores/vercel.js +33 -0
- package/dist/cache/stores/vercel.js.map +1 -0
- package/dist/cache/tag-aware-handler.d.ts +52 -0
- package/dist/cache/tag-aware-handler.d.ts.map +1 -0
- package/dist/cache/timber-cache.d.ts +20 -10
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts +23 -0
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
- package/dist/cdn/fastly-purge.d.ts +25 -0
- package/dist/cdn/fastly-purge.d.ts.map +1 -0
- package/dist/cdn/headers.d.ts +97 -0
- package/dist/cdn/headers.d.ts.map +1 -0
- package/dist/cdn/index.d.ts +5 -0
- package/dist/cdn/index.d.ts.map +1 -0
- package/dist/cdn/purge-store.d.ts +23 -0
- package/dist/cdn/purge-store.d.ts.map +1 -0
- package/dist/cdn/workers-cache-purge.d.ts +16 -0
- package/dist/cdn/workers-cache-purge.d.ts.map +1 -0
- package/dist/cli-schema-sync.d.ts +59 -0
- package/dist/cli-schema-sync.d.ts.map +1 -0
- package/dist/cli.d.ts +34 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +109 -34
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-dev.d.ts +37 -1
- package/dist/client/browser-dev.d.ts.map +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts +17 -0
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -0
- package/dist/client/browser-entry/hmr.d.ts +21 -0
- package/dist/client/browser-entry/hmr.d.ts.map +1 -0
- package/dist/client/browser-entry/hydrate.d.ts +46 -0
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -0
- package/dist/client/browser-entry/index.d.ts +29 -0
- package/dist/client/browser-entry/index.d.ts.map +1 -0
- package/dist/client/browser-entry/post-hydration.d.ts +26 -0
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -0
- package/dist/client/browser-entry/router-init.d.ts +27 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -0
- package/dist/client/browser-entry/rsc-stream.d.ts +24 -0
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -0
- package/dist/client/browser-entry/scroll.d.ts +28 -0
- package/dist/client/browser-entry/scroll.d.ts.map +1 -0
- package/dist/client/child-segment-context.d.ts +22 -0
- package/dist/client/child-segment-context.d.ts.map +1 -0
- package/dist/client/child-segment-outlet.d.ts +18 -0
- package/dist/client/child-segment-outlet.d.ts.map +1 -0
- package/dist/client/child-segment-provider.d.ts +21 -0
- package/dist/client/child-segment-provider.d.ts.map +1 -0
- package/dist/client/error-boundary.d.ts +12 -5
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +47 -10
- package/dist/client/error-boundary.js.map +1 -1
- package/dist/client/error-reconstituter.d.ts +61 -0
- package/dist/client/error-reconstituter.d.ts.map +1 -0
- package/dist/client/form.d.ts +16 -27
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/history.d.ts +21 -6
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +10 -24
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +185 -1134
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +19 -0
- package/dist/client/internal.d.ts.map +1 -0
- package/dist/client/internal.js +849 -0
- package/dist/client/internal.js.map +1 -0
- package/dist/client/link.d.ts +69 -60
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +120 -0
- package/dist/client/navigation-api.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +3 -2
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +119 -0
- package/dist/client/navigation-root.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts +1 -1
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/router-ref.d.ts +1 -1
- package/dist/client/router.d.ts +93 -14
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +47 -14
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +4 -4
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +49 -0
- package/dist/client/segment-outlet.d.ts.map +1 -0
- package/dist/client/segment-update-context.d.ts +21 -0
- package/dist/client/segment-update-context.d.ts.map +1 -0
- package/dist/client/shallow-url.d.ts +10 -0
- package/dist/client/shallow-url.d.ts.map +1 -0
- package/dist/client/slot-context.d.ts +29 -0
- package/dist/client/slot-context.d.ts.map +1 -0
- package/dist/client/slot-outlet.d.ts +16 -0
- package/dist/client/slot-outlet.d.ts.map +1 -0
- package/dist/client/slot-provider.d.ts +20 -0
- package/dist/client/slot-provider.d.ts.map +1 -0
- package/dist/client/ssr-data.d.ts +13 -4
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/state.d.ts +1 -1
- package/dist/client/top-loader.d.ts +5 -5
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts +12 -0
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/use-link-status.d.ts +5 -5
- package/dist/client/use-link-status.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts.map +1 -1
- package/dist/client/use-pending-navigation.d.ts +23 -0
- package/dist/client/use-pending-navigation.d.ts.map +1 -0
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +1 -1
- package/dist/client/use-router.d.ts.map +1 -1
- package/dist/client/use-search-params.d.ts +12 -5
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +8 -6
- package/dist/client/use-segment-params.d.ts.map +1 -0
- package/dist/codec.d.ts +69 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +76 -0
- package/dist/codec.js.map +1 -0
- package/dist/config-types.d.ts +284 -0
- package/dist/config-types.d.ts.map +1 -0
- package/dist/config-validation.d.ts +51 -0
- package/dist/config-validation.d.ts.map +1 -0
- package/dist/content/index.d.ts +1 -10
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +0 -2
- package/dist/cookies/define-cookie.d.ts +58 -25
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -1
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +257 -36
- package/dist/cookies/index.js.map +1 -1
- package/dist/cookies/json-cookie.d.ts +64 -0
- package/dist/cookies/json-cookie.d.ts.map +1 -0
- package/dist/cookies/validation.d.ts +34 -0
- package/dist/cookies/validation.d.ts.map +1 -0
- package/dist/dev-tools/404-page.d.ts +50 -0
- package/dist/dev-tools/404-page.d.ts.map +1 -0
- package/dist/dev-tools/browser-logs.d.ts +40 -0
- package/dist/dev-tools/browser-logs.d.ts.map +1 -0
- package/dist/dev-tools/dev-page-shell.d.ts +78 -0
- package/dist/dev-tools/dev-page-shell.d.ts.map +1 -0
- package/dist/dev-tools/error-page.d.ts +32 -0
- package/dist/dev-tools/error-page.d.ts.map +1 -0
- package/dist/dev-tools/holding-server.d.ts +54 -0
- package/dist/dev-tools/holding-server.d.ts.map +1 -0
- package/dist/dev-tools/hydration-overlay.d.ts +11 -0
- package/dist/dev-tools/hydration-overlay.d.ts.map +1 -0
- package/dist/dev-tools/index.d.ts +31 -0
- package/dist/dev-tools/index.d.ts.map +1 -0
- package/dist/dev-tools/instrumentation.d.ts +59 -0
- package/dist/dev-tools/instrumentation.d.ts.map +1 -0
- package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -0
- package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +17 -3
- package/dist/dev-tools/logs.d.ts.map +1 -0
- package/dist/dev-tools/overlay.d.ts +91 -0
- package/dist/dev-tools/overlay.d.ts.map +1 -0
- package/dist/dev-tools/stack-classifier.d.ts +34 -0
- package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
- package/dist/dev-tools/terminal.d.ts +28 -0
- package/dist/dev-tools/terminal.d.ts.map +1 -0
- package/dist/fonts/ast-walk.d.ts +68 -0
- package/dist/fonts/ast-walk.d.ts.map +1 -0
- package/dist/fonts/ast.d.ts +110 -6
- package/dist/fonts/ast.d.ts.map +1 -1
- package/dist/fonts/bundle.d.ts +50 -0
- package/dist/fonts/bundle.d.ts.map +1 -0
- package/dist/fonts/dev-middleware.d.ts +22 -0
- package/dist/fonts/dev-middleware.d.ts.map +1 -0
- package/dist/fonts/fallbacks.d.ts +0 -7
- package/dist/fonts/fallbacks.d.ts.map +1 -1
- package/dist/fonts/google.d.ts +4 -2
- package/dist/fonts/google.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +4 -4
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/fonts/paths.d.ts +43 -0
- package/dist/fonts/paths.d.ts.map +1 -0
- package/dist/fonts/pipeline.d.ts +177 -0
- package/dist/fonts/pipeline.d.ts.map +1 -0
- package/dist/fonts/transform.d.ts +82 -0
- package/dist/fonts/transform.d.ts.map +1 -0
- package/dist/fonts/types.d.ts +47 -1
- package/dist/fonts/types.d.ts.map +1 -1
- package/dist/fonts/virtual-modules.d.ts +77 -0
- package/dist/fonts/virtual-modules.d.ts.map +1 -0
- package/dist/index.d.ts +46 -180
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8542 -13615
- package/dist/index.js.map +1 -1
- package/dist/params/codec-registry.d.ts +26 -0
- package/dist/params/codec-registry.d.ts.map +1 -0
- package/dist/params/define-schema.d.ts +81 -0
- package/dist/params/define-schema.d.ts.map +1 -0
- package/dist/params/index.d.ts +5 -0
- package/dist/params/index.d.ts.map +1 -0
- package/dist/params/index.js +34 -0
- package/dist/params/index.js.map +1 -0
- package/dist/params/resolve-schema.d.ts +34 -0
- package/dist/params/resolve-schema.d.ts.map +1 -0
- package/dist/plugin-context.d.ts +217 -0
- package/dist/plugin-context.d.ts.map +1 -0
- package/dist/plugins/adapter-build.d.ts +21 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +7 -3
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +43 -7
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +64 -0
- package/dist/plugins/cache.d.ts.map +1 -0
- package/dist/plugins/callsite-ast.d.ts +117 -0
- package/dist/plugins/callsite-ast.d.ts.map +1 -0
- package/dist/plugins/callsite-jsx.d.ts +35 -0
- package/dist/plugins/callsite-jsx.d.ts.map +1 -0
- package/dist/plugins/chunks.d.ts +11 -4
- package/dist/plugins/chunks.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +40 -0
- package/dist/plugins/client-chunks.d.ts.map +1 -0
- package/dist/plugins/cloudflare-wasm.d.ts +21 -0
- package/dist/plugins/cloudflare-wasm.d.ts.map +1 -0
- package/dist/plugins/content.d.ts +1 -1
- package/dist/plugins/content.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts +17 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +28 -4
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +16 -73
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +1 -1
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/prebuilt-capture.d.ts +60 -0
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
- package/dist/plugins/prebuilt-options-analysis.d.ts +40 -0
- package/dist/plugins/prebuilt-options-analysis.d.ts.map +1 -0
- package/dist/plugins/prebuilt.d.ts +82 -0
- package/dist/plugins/prebuilt.d.ts.map +1 -0
- package/dist/plugins/prerender-sugar.d.ts +42 -0
- package/dist/plugins/prerender-sugar.d.ts.map +1 -0
- package/dist/plugins/routing.d.ts +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts +29 -5
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +5 -26
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-shared.d.ts +67 -0
- package/dist/routing/codegen-shared.d.ts.map +1 -0
- package/dist/routing/codegen-types.d.ts +64 -0
- package/dist/routing/codegen-types.d.ts.map +1 -0
- package/dist/routing/codegen.d.ts +7 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts +41 -0
- package/dist/routing/convention-lint.d.ts.map +1 -0
- package/dist/routing/export-detect.d.ts +56 -0
- package/dist/routing/export-detect.d.ts.map +1 -0
- package/dist/routing/file-cache.d.ts +8 -0
- package/dist/routing/file-cache.d.ts.map +1 -0
- package/dist/routing/index.d.ts +7 -1
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +4 -2
- package/dist/routing/interception.d.ts +0 -5
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/link-codegen.d.ts +90 -0
- package/dist/routing/link-codegen.d.ts.map +1 -0
- package/dist/routing/manifest-codegen.d.ts +32 -0
- package/dist/routing/manifest-codegen.d.ts.map +1 -0
- package/dist/routing/scanner.d.ts +1 -10
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/schema-validation.d.ts +35 -0
- package/dist/routing/schema-validation.d.ts.map +1 -0
- package/dist/routing/segment-classify.d.ts +81 -0
- package/dist/routing/segment-classify.d.ts.map +1 -0
- package/dist/routing/segment-codegen.d.ts +38 -0
- package/dist/routing/segment-codegen.d.ts.map +1 -0
- package/dist/routing/types.d.ts +81 -25
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +51 -0
- package/dist/routing/walkers.d.ts.map +1 -0
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +12 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +91 -0
- package/dist/schema-bridge.d.ts.map +1 -0
- package/dist/search-params/define.d.ts +188 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -7
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +34 -441
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/registry.d.ts +2 -2
- package/dist/search-params/registry.d.ts.map +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/segment-params/define.d.ts +89 -0
- package/dist/segment-params/define.d.ts.map +1 -0
- package/dist/segment-params/index.d.ts +3 -0
- package/dist/segment-params/index.d.ts.map +1 -0
- package/dist/segment-params/index.js +2 -0
- package/dist/server/access-gate.d.ts +8 -3
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-client.d.ts +42 -7
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/action-encryption.d.ts +76 -0
- package/dist/server/action-encryption.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts +35 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +6 -30
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +93 -11
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/asset-headers.d.ts +8 -18
- package/dist/server/asset-headers.d.ts.map +1 -1
- package/dist/server/body-limits.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +6 -29
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/canonicalize.d.ts +10 -0
- package/dist/server/canonicalize.d.ts.map +1 -1
- package/dist/server/coalesce-tick.d.ts +40 -0
- package/dist/server/coalesce-tick.d.ts.map +1 -0
- package/dist/server/cookie-context.d.ts +170 -0
- package/dist/server/cookie-context.d.ts.map +1 -0
- package/dist/server/cookie-parsing.d.ts +65 -0
- package/dist/server/cookie-parsing.d.ts.map +1 -0
- package/dist/server/csrf.d.ts +3 -2
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +117 -0
- package/dist/server/deny-boundary.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts +2 -2
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/dev-source-map.d.ts +22 -0
- package/dist/server/dev-source-map.d.ts.map +1 -0
- package/dist/server/early-hints-sender.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -11
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +7 -1
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +12 -7
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +66 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +42 -0
- package/dist/server/flight-scripts.d.ts.map +1 -0
- package/dist/server/form-data.d.ts +41 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +242 -0
- package/dist/server/html-injector-core.d.ts.map +1 -0
- package/dist/server/html-injectors.d.ts +82 -41
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +11 -47
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +182 -2906
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +49 -0
- package/dist/server/internal.d.ts.map +1 -0
- package/dist/server/internal.js +3945 -0
- package/dist/server/internal.js.map +1 -0
- package/dist/server/logger.d.ts +39 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/metadata-routes.d.ts +47 -11
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +19 -4
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +119 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/param-coercion.d.ts +54 -0
- package/dist/server/param-coercion.d.ts.map +1 -0
- package/dist/server/pipeline-helpers.d.ts +95 -0
- package/dist/server/pipeline-helpers.d.ts.map +1 -0
- package/dist/server/pipeline-interception.d.ts +1 -1
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-metadata.d.ts +6 -0
- package/dist/server/pipeline-metadata.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts +49 -0
- package/dist/server/pipeline-outcome.d.ts.map +1 -0
- package/dist/server/pipeline-phases.d.ts +59 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +140 -19
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +117 -0
- package/dist/server/port-resolution.d.ts.map +1 -0
- package/dist/server/prebuilt/cache-key.d.ts +62 -0
- package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +57 -0
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
- package/dist/server/prebuilt/key-discipline.d.ts +23 -0
- package/dist/server/prebuilt/key-discipline.d.ts.map +1 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
- package/dist/server/prebuilt/overlay.d.ts +100 -0
- package/dist/server/prebuilt/overlay.d.ts.map +1 -0
- package/dist/server/prebuilt/payload-source.d.ts +78 -0
- package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
- package/dist/server/prebuilt/slots.d.ts +74 -0
- package/dist/server/prebuilt/slots.d.ts.map +1 -0
- package/dist/server/prebuilt/synthetic-store.d.ts +66 -0
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
- package/dist/server/prebuilt-builder.d.ts +120 -0
- package/dist/server/prebuilt-builder.d.ts.map +1 -0
- package/dist/server/prebuilt-runtime.d.ts +111 -0
- package/dist/server/prebuilt-runtime.d.ts.map +1 -0
- package/dist/server/primitives.d.ts +88 -43
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/render-utils.d.ts +25 -0
- package/dist/server/render-utils.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +62 -107
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +55 -25
- 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 +25 -36
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
- package/dist/server/rsc-entry/api-handler.d.ts +12 -3
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts +29 -0
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -0
- package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +51 -28
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +28 -6
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +51 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
- package/dist/server/rsc-entry/revalidate-renderer.d.ts +108 -0
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -0
- package/dist/server/rsc-entry/rsc-payload.d.ts +5 -6
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +25 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +5 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +34 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +89 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
- package/dist/server/safe-load.d.ts +46 -0
- package/dist/server/safe-load.d.ts.map +1 -0
- package/dist/server/sensitive-fields.d.ts +74 -0
- package/dist/server/sensitive-fields.d.ts.map +1 -0
- package/dist/server/sitemap-generator.d.ts +129 -0
- package/dist/server/sitemap-generator.d.ts.map +1 -0
- package/dist/server/sitemap-handler.d.ts +22 -0
- package/dist/server/sitemap-handler.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +7 -5
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +121 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -0
- package/dist/server/ssr-entry.d.ts +2 -43
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +64 -21
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +50 -0
- package/dist/server/ssr-wrappers.d.ts.map +1 -0
- package/dist/server/state-tree-diff.d.ts +36 -6
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/status-code-resolver.d.ts +22 -11
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +47 -0
- package/dist/server/stream-utils.d.ts.map +1 -0
- package/dist/server/tracing.d.ts +44 -9
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +62 -30
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/tree-match.d.ts +49 -0
- package/dist/server/tree-match.d.ts.map +1 -0
- package/dist/server/types.d.ts +71 -6
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/escape-html.d.ts +14 -0
- package/dist/server/utils/escape-html.d.ts.map +1 -0
- package/dist/server/utils/mdx-file.d.ts +15 -0
- package/dist/server/utils/mdx-file.d.ts.map +1 -0
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/workers-cache-bridge.d.ts +26 -0
- package/dist/server/workers-cache-bridge.d.ts.map +1 -0
- package/dist/shared/href-validation.d.ts +49 -0
- package/dist/shared/href-validation.d.ts.map +1 -0
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -0
- package/dist/shared/redirect-type.d.ts +17 -0
- package/dist/shared/redirect-type.d.ts.map +1 -0
- package/dist/shims/font-google.d.ts +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/font-google.js +42 -0
- package/dist/shims/font-google.js.map +1 -0
- package/dist/shims/font-local.d.ts +26 -0
- package/dist/shims/font-local.d.ts.map +1 -0
- package/dist/shims/font-local.js +20 -0
- package/dist/shims/font-local.js.map +1 -0
- package/dist/shims/headers.d.ts +53 -1
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/image.d.ts +120 -120
- package/dist/shims/image.d.ts.map +1 -1
- package/dist/shims/navigation-client.d.ts +35 -11
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +11 -5
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +233 -0
- package/docs/api/31-api-client.mdx +161 -0
- package/docs/api/32-api-cache.mdx +311 -0
- package/docs/api/33-api-search-params.mdx +154 -0
- package/docs/api/34-api-config.mdx +325 -0
- package/docs/api/35-api-typescript.mdx +170 -0
- package/docs/api/36-cli.mdx +143 -0
- package/docs/learn/00-introduction.mdx +109 -0
- package/docs/learn/01-quick-start.mdx +94 -0
- package/docs/learn/02-pages-and-layouts.mdx +147 -0
- package/docs/learn/03-fetching-data.mdx +211 -0
- package/docs/learn/03b-access-control.mdx +130 -0
- package/docs/learn/04-loading-states.mdx +67 -0
- package/docs/learn/04b-the-flush-point.mdx +115 -0
- package/docs/learn/05-typed-params.mdx +275 -0
- package/docs/learn/06-forms-and-actions.mdx +146 -0
- package/docs/learn/07-typed-routes.mdx +127 -0
- package/docs/learn/08-streaming.mdx +146 -0
- package/docs/learn/09-caching.mdx +252 -0
- package/docs/learn/10-middleware.mdx +157 -0
- package/docs/learn/11-error-handling.mdx +156 -0
- package/docs/learn/12-client-navigation.mdx +176 -0
- package/docs/learn/13-configuration.mdx +162 -0
- package/docs/learn/14-deploying.mdx +264 -0
- package/docs/more/01-advanced-routing.mdx +173 -0
- package/docs/more/02-advanced-forms.mdx +137 -0
- package/docs/more/03-coming-from-nextjs.mdx +186 -0
- package/docs/more/04-metadata-and-fonts.mdx +193 -0
- package/docs/more/04b-mdx.mdx +209 -0
- package/docs/more/05-content-collections.mdx +90 -0
- package/docs/more/06-instrumentation.mdx +214 -0
- package/docs/more/07-security.mdx +129 -0
- package/docs/more/08-developer-experience.mdx +134 -0
- package/docs/more/40-why-timber.mdx +50 -0
- package/docs/more/41-timber-vs-nextjs.mdx +81 -0
- package/docs/more/42-timber-vs-others.mdx +68 -0
- package/docs/more/50-ai-agent-instructions.mdx +169 -0
- package/llms.txt +55 -0
- package/package.json +105 -26
- package/src/adapters/build-output-helper.ts +199 -0
- package/src/adapters/cloudflare-dev.ts +177 -0
- package/src/adapters/cloudflare-kv-cache.ts +204 -0
- package/src/adapters/cloudflare.ts +620 -129
- package/src/adapters/compress-module.ts +27 -92
- package/src/adapters/nitro.ts +266 -122
- package/src/adapters/raw-imports.d.ts +5 -0
- package/src/adapters/shared.ts +68 -0
- package/src/adapters/types.ts +2 -7
- package/src/adapters/wrangler.d.ts +7 -0
- package/src/cache/cache-api.ts +107 -0
- package/src/cache/handler-store.ts +76 -0
- package/src/cache/index.ts +114 -21
- package/src/cache/invalidation-epoch.ts +93 -0
- package/src/cache/json-lossy-check.ts +75 -0
- package/src/cache/redis-handler.ts +197 -19
- package/src/cache/singleflight.ts +64 -6
- package/src/cache/sizeof.ts +33 -0
- package/src/cache/stable-stringify.ts +65 -2
- package/src/cache/store.ts +71 -0
- package/src/cache/stores/cloudflare-kv.ts +52 -0
- package/src/cache/stores/memory.ts +125 -0
- package/src/cache/stores/redis.ts +97 -0
- package/src/cache/stores/vercel.ts +59 -0
- package/src/cache/tag-aware-handler.ts +237 -0
- package/src/cache/timber-cache.ts +161 -36
- package/src/cdn/cloudflare-purge.ts +50 -0
- package/src/cdn/fastly-purge.ts +63 -0
- package/src/cdn/headers.ts +153 -0
- package/src/cdn/index.ts +17 -0
- package/src/cdn/purge-store.ts +34 -0
- package/src/cdn/workers-cache-purge.ts +30 -0
- package/src/cli-schema-sync.ts +401 -0
- package/src/cli.ts +157 -34
- package/src/client/browser-dev.ts +156 -1
- package/src/client/browser-entry/action-dispatch.ts +124 -0
- package/src/client/browser-entry/hmr.ts +70 -0
- package/src/client/browser-entry/hydrate.ts +187 -0
- package/src/client/browser-entry/index.ts +113 -0
- package/src/client/browser-entry/post-hydration.ts +110 -0
- package/src/client/browser-entry/router-init.ts +289 -0
- package/src/client/browser-entry/rsc-stream.ts +157 -0
- package/src/client/browser-entry/scroll.ts +50 -0
- package/src/client/child-segment-context.ts +40 -0
- package/src/client/child-segment-outlet.tsx +25 -0
- package/src/client/child-segment-provider.tsx +27 -0
- package/src/client/error-boundary.tsx +127 -22
- package/src/client/error-reconstituter.tsx +79 -0
- package/src/client/form.tsx +31 -32
- package/src/client/history.ts +21 -6
- package/src/client/index.ts +67 -45
- package/src/client/internal.ts +58 -0
- package/src/client/link.tsx +349 -150
- package/src/client/navigation-api.ts +341 -0
- package/src/client/navigation-context.ts +15 -7
- package/src/client/navigation-root.tsx +355 -0
- package/src/client/nuqs-adapter.tsx +16 -3
- package/src/client/router-ref.ts +1 -1
- package/src/client/router.ts +538 -206
- package/src/client/rsc-fetch.ts +214 -33
- package/src/client/segment-cache.ts +11 -12
- package/src/client/segment-outlet.tsx +77 -0
- package/src/client/segment-update-context.ts +42 -0
- package/src/client/shallow-url.ts +22 -0
- package/src/client/slot-context.ts +48 -0
- package/src/client/slot-outlet.tsx +22 -0
- package/src/client/slot-provider.tsx +25 -0
- package/src/client/ssr-data.ts +13 -5
- package/src/client/state.ts +2 -2
- package/src/client/top-loader.tsx +11 -9
- package/src/client/use-cookie.ts +60 -15
- package/src/client/use-link-status.ts +7 -7
- package/src/client/use-pathname.ts +1 -0
- package/src/client/use-pending-navigation.ts +40 -0
- package/src/client/use-query-states.ts +108 -10
- package/src/client/use-router.ts +4 -1
- package/src/client/use-search-params.ts +31 -44
- package/src/client/{use-params.ts → use-segment-params.ts} +9 -6
- package/src/codec.ts +149 -0
- package/src/config-types.ts +282 -0
- package/src/config-validation.ts +330 -0
- package/src/content/index.ts +5 -13
- package/src/cookies/define-cookie.ts +217 -53
- package/src/cookies/index.ts +12 -1
- package/src/cookies/json-cookie.ts +105 -0
- package/src/cookies/validation.ts +192 -0
- package/src/dev-tools/404-page.ts +286 -0
- package/src/dev-tools/browser-logs.ts +55 -0
- package/src/dev-tools/dev-page-shell.ts +260 -0
- package/src/dev-tools/error-page.ts +331 -0
- package/src/dev-tools/holding-server.ts +187 -0
- package/src/dev-tools/hydration-overlay.ts +158 -0
- package/src/dev-tools/index.ts +83 -0
- package/src/dev-tools/instrumentation.ts +213 -0
- package/src/{server/dev-logger.ts → dev-tools/logger.ts} +33 -51
- package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +122 -26
- package/src/dev-tools/overlay.ts +469 -0
- package/src/dev-tools/stack-classifier.ts +75 -0
- package/src/dev-tools/terminal.ts +182 -0
- package/src/fonts/ast-walk.ts +235 -0
- package/src/fonts/ast.ts +477 -126
- package/src/fonts/bundle.ts +138 -0
- package/src/fonts/dev-middleware.ts +85 -0
- package/src/fonts/fallbacks.ts +0 -23
- package/src/fonts/google.ts +100 -18
- package/src/fonts/local.ts +9 -8
- package/src/fonts/paths.ts +58 -0
- package/src/fonts/pipeline.ts +348 -0
- package/src/fonts/transform.ts +311 -0
- package/src/fonts/types.ts +52 -1
- package/src/fonts/virtual-modules.ts +179 -0
- package/src/index.ts +444 -352
- package/src/params/codec-registry.ts +34 -0
- package/src/params/define-schema.ts +115 -0
- package/src/params/index.ts +17 -0
- package/src/params/resolve-schema.ts +84 -0
- package/src/plugin-context.ts +432 -0
- package/src/plugins/adapter-build.ts +220 -7
- package/src/plugins/build-manifest.ts +168 -23
- package/src/plugins/build-report.ts +253 -35
- package/src/plugins/cache.ts +243 -0
- package/src/plugins/callsite-ast.ts +472 -0
- package/src/plugins/callsite-jsx.ts +243 -0
- package/src/plugins/chunks.ts +74 -5
- package/src/plugins/client-chunks.ts +185 -0
- package/src/plugins/cloudflare-wasm.ts +141 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-server.ts +284 -94
- package/src/plugins/entries.ts +134 -16
- package/src/plugins/fonts.ts +225 -660
- package/src/plugins/mdx.ts +47 -10
- package/src/plugins/prebuilt-capture.ts +217 -0
- package/src/plugins/prebuilt-options-analysis.ts +175 -0
- package/src/plugins/prebuilt.ts +655 -0
- package/src/plugins/prerender-sugar.ts +328 -0
- package/src/plugins/routing.ts +237 -241
- package/src/plugins/server-bundle.ts +89 -12
- package/src/plugins/shims.ts +241 -64
- package/src/plugins/static-build.ts +15 -90
- package/src/routing/codegen-shared.ts +115 -0
- package/src/routing/codegen-types.ts +65 -0
- package/src/routing/codegen.ts +372 -178
- package/src/routing/convention-lint.ts +394 -0
- package/src/routing/export-detect.ts +304 -0
- package/src/routing/file-cache.ts +23 -0
- package/src/routing/index.ts +10 -1
- package/src/routing/interception.ts +3 -22
- package/src/routing/link-codegen.ts +313 -0
- package/src/routing/manifest-codegen.ts +287 -0
- package/src/routing/scanner.ts +338 -189
- package/src/routing/schema-validation.ts +155 -0
- package/src/routing/segment-classify.ts +223 -0
- package/src/routing/segment-codegen.ts +98 -0
- package/src/routing/types.ts +82 -25
- package/src/routing/walkers.ts +90 -0
- package/src/rsc-runtime/rsc.ts +9 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/schema-bridge.ts +253 -0
- package/src/search-params/define.ts +576 -0
- package/src/search-params/index.ts +14 -20
- package/src/search-params/registry.ts +2 -2
- package/src/search-params/wrappers.ts +101 -0
- package/src/segment-params/define.ts +249 -0
- package/src/segment-params/index.ts +9 -0
- package/src/server/access-gate.tsx +84 -34
- package/src/server/action-client.ts +120 -29
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +123 -33
- package/src/server/actions.ts +48 -54
- package/src/server/als-registry.ts +137 -20
- package/src/server/asset-headers.ts +15 -37
- package/src/server/body-limits.ts +10 -2
- package/src/server/build-manifest.ts +13 -56
- package/src/server/canonicalize.ts +6 -3
- package/src/server/coalesce-tick.ts +75 -0
- package/src/server/compress.ts +34 -7
- package/src/server/cookie-context.ts +484 -0
- package/src/server/cookie-parsing.ts +163 -0
- package/src/server/csrf.ts +39 -6
- package/src/server/default-logger.ts +99 -0
- package/src/server/deny-boundary.ts +314 -0
- package/src/server/deny-renderer.ts +85 -77
- package/src/server/dev-source-map.ts +31 -0
- package/src/server/early-hints-sender.ts +3 -2
- package/src/server/early-hints.ts +37 -27
- package/src/server/error-boundary-wrapper.ts +61 -22
- package/src/server/fallback-error.ts +79 -102
- package/src/server/flight-injection-state.ts +113 -0
- package/src/server/flight-scripts.ts +62 -0
- package/src/server/form-data.ts +124 -12
- package/src/server/html-injector-core.ts +495 -0
- package/src/server/html-injectors.ts +271 -254
- package/src/server/index.ts +31 -185
- package/src/server/internal.ts +181 -0
- package/src/server/logger.ts +72 -36
- package/src/server/metadata-routes.ts +110 -24
- package/src/server/middleware-runner.ts +31 -4
- package/src/server/node-stream-transforms.ts +336 -0
- package/src/server/param-coercion.ts +207 -0
- package/src/server/pipeline-helpers.ts +204 -0
- package/src/server/pipeline-interception.ts +45 -18
- package/src/server/pipeline-metadata.ts +14 -0
- package/src/server/pipeline-outcome.ts +197 -0
- package/src/server/pipeline-phases.ts +545 -0
- package/src/server/pipeline.ts +274 -382
- package/src/server/port-resolution.ts +215 -0
- package/src/server/prebuilt/cache-key.ts +298 -0
- package/src/server/prebuilt/capture-state.ts +89 -0
- package/src/server/prebuilt/key-discipline.ts +53 -0
- package/src/server/prebuilt/manifest-tag-index.ts +46 -0
- package/src/server/prebuilt/overlay.ts +256 -0
- package/src/server/prebuilt/payload-source.ts +201 -0
- package/src/server/prebuilt/slots.ts +167 -0
- package/src/server/prebuilt/synthetic-store.ts +121 -0
- package/src/server/prebuilt-builder.ts +808 -0
- package/src/server/prebuilt-runtime.ts +815 -0
- package/src/server/primitives.ts +160 -83
- package/src/server/render-timeout.ts +108 -0
- package/src/server/render-utils.ts +31 -0
- package/src/server/request-context.ts +168 -337
- package/src/server/route-element-builder.ts +661 -278
- package/src/server/route-handler.ts +31 -4
- package/src/server/route-matcher.ts +158 -277
- package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
- package/src/server/rsc-entry/api-handler.ts +69 -32
- package/src/server/rsc-entry/deny-fallback.ts +92 -0
- package/src/server/rsc-entry/error-renderer.ts +306 -89
- package/src/server/rsc-entry/helpers.ts +165 -79
- package/src/server/rsc-entry/index.ts +332 -341
- package/src/server/rsc-entry/render-route.ts +292 -0
- package/src/server/rsc-entry/revalidate-renderer.ts +349 -0
- package/src/server/rsc-entry/rsc-payload.ts +95 -39
- package/src/server/rsc-entry/rsc-stream.ts +93 -13
- package/src/server/rsc-entry/ssr-bridge.ts +18 -5
- package/src/server/rsc-entry/ssr-renderer.ts +273 -43
- package/src/server/rsc-entry/wrap-action-dispatch.ts +186 -0
- package/src/server/safe-load.ts +60 -0
- package/src/server/sensitive-fields.ts +230 -0
- package/src/server/server-only-guard-noop.js +3 -0
- package/src/server/sitemap-generator.ts +345 -0
- package/src/server/sitemap-handler.ts +157 -0
- package/src/server/slot-resolver.ts +264 -257
- package/src/server/ssr-bridge-types.ts +124 -0
- package/src/server/ssr-entry.ts +206 -78
- package/src/server/ssr-render.ts +345 -82
- package/src/server/ssr-wrappers.tsx +141 -0
- package/src/server/state-tree-diff.ts +76 -24
- package/src/server/status-code-resolver.ts +115 -130
- package/src/server/stream-utils.ts +288 -0
- package/src/server/tracing.ts +126 -14
- package/src/server/tree-builder.ts +204 -90
- package/src/server/tree-match.ts +262 -0
- package/src/server/types.ts +78 -9
- package/src/server/utils/escape-html.ts +20 -0
- package/src/server/utils/mdx-file.ts +22 -0
- package/src/server/version-skew.ts +104 -0
- package/src/server/workers-cache-bridge.ts +33 -0
- package/src/shared/href-validation.ts +159 -0
- package/src/shared/merge-search-params.ts +55 -0
- package/src/shared/redirect-type.ts +17 -0
- package/src/shims/font-google.ts +1 -1
- package/src/shims/font-local.ts +34 -0
- package/src/shims/headers.ts +122 -1
- package/src/shims/navigation-client.ts +51 -29
- package/src/shims/navigation.ts +14 -7
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js +0 -41
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-B3Gypr3D.js +0 -108
- package/dist/_chunks/debug-B3Gypr3D.js.map +0 -1
- package/dist/_chunks/format-RyoGQL74.js +0 -164
- package/dist/_chunks/format-RyoGQL74.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js +0 -676
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/metadata-routes-Cjmvi3rQ.js +0 -153
- package/dist/_chunks/metadata-routes-Cjmvi3rQ.js.map +0 -1
- package/dist/_chunks/request-context-BQUC8PHn.js +0 -330
- package/dist/_chunks/request-context-BQUC8PHn.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
- package/dist/_chunks/tracing-CemImE6h.js +0 -187
- package/dist/_chunks/tracing-CemImE6h.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
- package/dist/adapters/cloudflare.js.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/cache/register-cached-function.d.ts +0 -17
- package/dist/cache/register-cached-function.d.ts.map +0 -1
- package/dist/client/browser-entry.d.ts +0 -21
- package/dist/client/browser-entry.d.ts.map +0 -1
- package/dist/client/head.d.ts +0 -16
- package/dist/client/head.d.ts.map +0 -1
- package/dist/client/link-status-provider.d.ts +0 -11
- package/dist/client/link-status-provider.d.ts.map +0 -1
- package/dist/client/segment-merger.d.ts +0 -119
- package/dist/client/segment-merger.d.ts.map +0 -1
- package/dist/client/stale-reload.d.ts +0 -44
- package/dist/client/stale-reload.d.ts.map +0 -1
- package/dist/client/transition-root.d.ts +0 -74
- package/dist/client/transition-root.d.ts.map +0 -1
- package/dist/client/use-navigation-pending.d.ts +0 -26
- package/dist/client/use-navigation-pending.d.ts.map +0 -1
- package/dist/client/use-params.d.ts.map +0 -1
- package/dist/plugins/cache-transform.d.ts +0 -36
- package/dist/plugins/cache-transform.d.ts.map +0 -1
- package/dist/plugins/dev-error-overlay.d.ts +0 -60
- package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
- package/dist/plugins/dev-logs.d.ts.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.d.ts.map +0 -1
- package/dist/plugins/server-action-exports.d.ts +0 -26
- package/dist/plugins/server-action-exports.d.ts.map +0 -1
- package/dist/routing/status-file-lint.d.ts +0 -33
- package/dist/routing/status-file-lint.d.ts.map +0 -1
- package/dist/search-params/analyze.d.ts +0 -54
- package/dist/search-params/analyze.d.ts.map +0 -1
- package/dist/search-params/builtin-codecs.d.ts +0 -105
- package/dist/search-params/builtin-codecs.d.ts.map +0 -1
- package/dist/search-params/codecs.d.ts +0 -53
- package/dist/search-params/codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/server/client-module-map.d.ts +0 -47
- package/dist/server/client-module-map.d.ts.map +0 -1
- package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
- package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
- package/dist/server/dev-logger.d.ts.map +0 -1
- package/dist/server/dev-span-processor.d.ts +0 -29
- package/dist/server/dev-span-processor.d.ts.map +0 -1
- package/dist/server/dev-warnings.d.ts +0 -129
- package/dist/server/dev-warnings.d.ts.map +0 -1
- package/dist/server/flush.d.ts +0 -74
- package/dist/server/flush.d.ts.map +0 -1
- package/dist/server/manifest-status-resolver.d.ts +0 -58
- package/dist/server/manifest-status-resolver.d.ts.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/utils/directive-parser.d.ts +0 -70
- package/dist/utils/directive-parser.d.ts.map +0 -1
- package/src/cache/register-cached-function.ts +0 -103
- package/src/client/browser-entry.ts +0 -678
- package/src/client/head.ts +0 -61
- package/src/client/link-status-provider.tsx +0 -30
- package/src/client/segment-merger.ts +0 -339
- package/src/client/stale-reload.ts +0 -87
- package/src/client/transition-root.tsx +0 -166
- package/src/client/use-navigation-pending.ts +0 -38
- package/src/plugins/cache-transform.ts +0 -199
- package/src/plugins/dev-error-overlay.ts +0 -230
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/plugins/server-action-exports.ts +0 -220
- package/src/routing/status-file-lint.ts +0 -112
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/codecs.ts +0 -153
- package/src/search-params/create.ts +0 -321
- package/src/server/client-module-map.ts +0 -58
- package/src/server/dev-fetch-instrumentation.ts +0 -96
- package/src/server/dev-span-processor.ts +0 -78
- package/src/server/dev-warnings.ts +0 -283
- package/src/server/flush.ts +0 -182
- package/src/server/manifest-status-resolver.ts +0 -215
- package/src/server/prerender.ts +0 -139
- package/src/utils/directive-parser.ts +0 -529
|
@@ -6,43 +6,159 @@
|
|
|
6
6
|
* rsc-entry.ts under the 500-line limit.
|
|
7
7
|
*
|
|
8
8
|
* This module handles:
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3. Resolving metadata (
|
|
9
|
+
* 1. Running access.ts checks eagerly to gate metadata resolution (TIM-1027)
|
|
10
|
+
* 2. Loading page/layout components from the segment chain
|
|
11
|
+
* 3. Resolving metadata (skipped for denied segments to prevent side effects)
|
|
12
12
|
* 4. Building the React element tree (page → error boundaries → access gates → layouts)
|
|
13
13
|
* 5. Resolving parallel slots
|
|
14
14
|
*
|
|
15
15
|
* See design/02-rendering-pipeline.md, design/04-authorization.md
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { createElement } from 'react';
|
|
18
|
+
import { createElement, Fragment } from 'react';
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
19
20
|
|
|
20
|
-
import { withSpan
|
|
21
|
+
import { withSpan } from './tracing.js';
|
|
21
22
|
import type { RouteMatch } from './pipeline.js';
|
|
22
23
|
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
23
24
|
import { resolveMetadata, renderMetadataToElements } from './metadata.js';
|
|
24
|
-
import type { HeadElement as MetadataHeadElement } from './metadata.js';
|
|
25
25
|
import type { Metadata } from './types.js';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
METADATA_ROUTE_CONVENTIONS,
|
|
28
|
+
getMetadataRouteAutoLink,
|
|
29
|
+
resolveServePathForFile,
|
|
30
|
+
} from './metadata-routes.js';
|
|
31
|
+
|
|
32
|
+
// In dev mode, use a per-startup nonce for metadata route cache busting
|
|
33
|
+
// instead of per-file content hashes (avoids rehashing on every request).
|
|
34
|
+
let _devNonce: string | undefined;
|
|
35
|
+
function getDevNonce(): string {
|
|
36
|
+
_devNonce ??= randomUUID().slice(0, 8);
|
|
37
|
+
return _devNonce;
|
|
38
|
+
}
|
|
27
39
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
28
40
|
import { AccessGate } from './access-gate.js';
|
|
41
|
+
import { requestContextAls } from './als-registry.js';
|
|
42
|
+
import {
|
|
43
|
+
PageDenyBoundary,
|
|
44
|
+
buildDenyPageChain,
|
|
45
|
+
renderMatchingDenyPage,
|
|
46
|
+
setDenyStatus,
|
|
47
|
+
} from './deny-boundary.js';
|
|
48
|
+
import type { DenyPageEntry } from './deny-boundary.js';
|
|
29
49
|
import { resolveSlotElement } from './slot-resolver.js';
|
|
30
|
-
import { SegmentProvider } from '
|
|
31
|
-
import {
|
|
32
|
-
import
|
|
50
|
+
import { SegmentProvider } from '../client/segment-context.js';
|
|
51
|
+
import { SegmentOutlet } from '../client/segment-outlet.js';
|
|
52
|
+
import { ChildSegmentOutlet } from '../client/child-segment-outlet.js';
|
|
53
|
+
import { ChildSegmentProvider } from '../client/child-segment-provider.js';
|
|
54
|
+
|
|
33
55
|
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
|
|
34
56
|
import type { InterceptionContext } from './pipeline.js';
|
|
35
|
-
import { shouldSkipSegment } from './state-tree-diff.js';
|
|
57
|
+
import { shouldSkipSegment, computeSegmentKeys } from './state-tree-diff.js';
|
|
58
|
+
import type { HeadElement } from './metadata.js';
|
|
36
59
|
|
|
37
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Convert HeadElement descriptors to React elements for Float hoisting.
|
|
62
|
+
*
|
|
63
|
+
* React 19 Float hoists <title>, <meta>, and <link> rendered anywhere in
|
|
64
|
+
* the tree into <head> during SSR, and manages them in document.head
|
|
65
|
+
* during client-side rendering. This replaces the X-Timber-Head header
|
|
66
|
+
* side-channel and the imperative client DOM applier.
|
|
67
|
+
*/
|
|
68
|
+
export function headElementsToReact(elements: HeadElement[]): React.ReactElement {
|
|
69
|
+
const children: React.ReactElement[] = [];
|
|
70
|
+
for (let i = 0; i < elements.length; i++) {
|
|
71
|
+
const el = elements[i];
|
|
72
|
+
if (el.tag === 'title' && el.content !== undefined) {
|
|
73
|
+
children.push(createElement('title', { key: `t${i}` }, el.content));
|
|
74
|
+
} else if (el.attrs) {
|
|
75
|
+
children.push(createElement(el.tag, { key: `h${i}`, ...el.attrs }));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return createElement(Fragment, null, ...children);
|
|
79
|
+
}
|
|
80
|
+
import { loadModule } from './safe-load.js';
|
|
38
81
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Replace the outermost SegmentOutlet in a partial payload with its
|
|
84
|
+
* SegmentProvider child. Walks through AccessGate wrappers (which sit
|
|
85
|
+
* outside SegmentOutlet) to find the outlet.
|
|
86
|
+
*/
|
|
87
|
+
function replaceOutermostSegmentOutlet(
|
|
88
|
+
element: React.ReactElement,
|
|
89
|
+
replacement: React.ReactElement
|
|
90
|
+
): React.ReactElement {
|
|
91
|
+
if (element.type === SegmentOutlet) {
|
|
92
|
+
return replacement;
|
|
93
|
+
}
|
|
94
|
+
// AccessGate wraps outside SegmentOutlet — recurse through it.
|
|
95
|
+
const props = element.props as Record<string, unknown>;
|
|
96
|
+
if (element.type === AccessGate && props.children) {
|
|
97
|
+
const inner = replaceOutermostSegmentOutlet(props.children as React.ReactElement, replacement);
|
|
98
|
+
/* eslint-disable react/no-children-prop -- createElement API */
|
|
99
|
+
return createElement(element.type as React.FunctionComponent<Record<string, unknown>>, {
|
|
100
|
+
...props,
|
|
101
|
+
children: inner,
|
|
102
|
+
});
|
|
103
|
+
/* eslint-enable react/no-children-prop */
|
|
104
|
+
}
|
|
105
|
+
return element;
|
|
44
106
|
}
|
|
45
107
|
|
|
108
|
+
// ─── Client Reference Detection ──────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Symbol used by React Flight to mark client references.
|
|
112
|
+
* Client references are proxy objects created by @vitejs/plugin-rsc for
|
|
113
|
+
* 'use client' modules in the RSC environment. They must be passed to
|
|
114
|
+
* createElement() — calling them as functions throws:
|
|
115
|
+
* "Unexpectedly client reference export 'default' is called on server"
|
|
116
|
+
*/
|
|
117
|
+
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Detect whether a component is a React client reference.
|
|
121
|
+
* Client references have $$typeof set to Symbol.for('react.client.reference')
|
|
122
|
+
* by registerClientReference() in the React Flight server runtime.
|
|
123
|
+
*
|
|
124
|
+
* Used to skip OTEL tracing wrappers that would call the component as a
|
|
125
|
+
* function. Client components must go through createElement only — they are
|
|
126
|
+
* serialized as references in the RSC Flight stream, not executed on the server.
|
|
127
|
+
*/
|
|
128
|
+
export function isClientReference(component: unknown): boolean {
|
|
129
|
+
return (
|
|
130
|
+
component != null &&
|
|
131
|
+
typeof component === 'function' &&
|
|
132
|
+
(component as unknown as Record<string, unknown>).$$typeof === CLIENT_REFERENCE_TAG
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Typed Errors ────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Thrown when a defineSegmentParams codec's parse() fails.
|
|
140
|
+
* The pipeline catches this and responds with 404.
|
|
141
|
+
*/
|
|
142
|
+
export class ParamCoercionError extends Error {
|
|
143
|
+
constructor(message: string) {
|
|
144
|
+
super(message);
|
|
145
|
+
this.name = 'ParamCoercionError';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Thrown when a matched route has no page component in its leaf segment.
|
|
151
|
+
* The pipeline catches this and renders the custom 404 page.
|
|
152
|
+
*/
|
|
153
|
+
export class NoPageComponentError extends Error {
|
|
154
|
+
constructor(message: string) {
|
|
155
|
+
super(message);
|
|
156
|
+
this.name = 'NoPageComponentError';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── Types ────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
46
162
|
/** Layout entry with component and segment. */
|
|
47
163
|
export interface LayoutComponentEntry {
|
|
48
164
|
component: (...args: unknown[]) => unknown;
|
|
@@ -53,8 +169,6 @@ export interface LayoutComponentEntry {
|
|
|
53
169
|
export interface RouteElementResult {
|
|
54
170
|
/** The React element tree (page wrapped in layouts, access gates, error boundaries). */
|
|
55
171
|
element: React.ReactElement;
|
|
56
|
-
/** Resolved head elements for metadata. */
|
|
57
|
-
headElements: HeadElement[];
|
|
58
172
|
/** Layout components loaded along the segment chain. */
|
|
59
173
|
layoutComponents: LayoutComponentEntry[];
|
|
60
174
|
/** Segments from the route match. */
|
|
@@ -68,46 +182,142 @@ export interface RouteElementResult {
|
|
|
68
182
|
* See design/19-client-navigation.md §"X-Timber-State-Tree Header"
|
|
69
183
|
*/
|
|
70
184
|
skippedSegments: string[];
|
|
185
|
+
/**
|
|
186
|
+
* Resolves when every deny-capable shell component (PageDenyBoundary and
|
|
187
|
+
* each TracedLayout with a deny page chain) has finished executing, OR
|
|
188
|
+
* immediately when any of them catches a DenySignal in-tree (the HTTP
|
|
189
|
+
* status is known at that point; inner components may never run).
|
|
190
|
+
* Undefined when the route has no deny-capable server components.
|
|
191
|
+
* See TIM-1045 (pages), TIM-1208 (layouts).
|
|
192
|
+
*/
|
|
193
|
+
shellSettled?: Promise<void>;
|
|
71
194
|
}
|
|
72
195
|
|
|
196
|
+
// ─── Module Processing Helpers ─────────────────────────────────────────────
|
|
197
|
+
|
|
73
198
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
199
|
+
* Reject the legacy `generateMetadata` export with a helpful migration message.
|
|
200
|
+
* Throws if the module exports `generateMetadata` instead of `metadata`.
|
|
76
201
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
202
|
+
function rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {
|
|
203
|
+
if ('generateMetadata' in mod) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`${filePath}: "generateMetadata" is not a valid export. ` +
|
|
206
|
+
`Export an async function named "metadata" instead.\n\n` +
|
|
207
|
+
` // Before\n` +
|
|
208
|
+
` export async function generateMetadata({ params }) { ... }\n\n` +
|
|
209
|
+
` // After\n` +
|
|
210
|
+
` export async function metadata() { ... }`
|
|
211
|
+
);
|
|
84
212
|
}
|
|
85
213
|
}
|
|
86
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Extract and resolve metadata from a module (layout or page).
|
|
217
|
+
* Handles both static metadata objects and async metadata functions.
|
|
218
|
+
* Returns the resolved Metadata, or null if none exported.
|
|
219
|
+
*
|
|
220
|
+
* Metadata functions no longer receive { params } — they access params
|
|
221
|
+
* via getSegmentParams() from ALS, same as page/layout components.
|
|
222
|
+
*/
|
|
223
|
+
async function extractMetadata(
|
|
224
|
+
mod: Record<string, unknown>,
|
|
225
|
+
segment: ManifestSegmentNode
|
|
226
|
+
): Promise<Metadata | null> {
|
|
227
|
+
if (typeof mod.metadata === 'function') {
|
|
228
|
+
type MetadataFn = () => Promise<Metadata>;
|
|
229
|
+
return (
|
|
230
|
+
(await withSpan(
|
|
231
|
+
'timber.metadata',
|
|
232
|
+
{ 'timber.segment': segment.segmentName ?? segment.urlPath },
|
|
233
|
+
() => (mod.metadata as MetadataFn)()
|
|
234
|
+
)) ?? null
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (mod.metadata) {
|
|
238
|
+
return mod.metadata as Metadata;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extract `deferSuspenseFor` from a module and return the maximum
|
|
245
|
+
* of the current value and the module's value.
|
|
246
|
+
*/
|
|
247
|
+
function extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {
|
|
248
|
+
if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {
|
|
249
|
+
return mod.deferSuspenseFor;
|
|
250
|
+
}
|
|
251
|
+
return current;
|
|
252
|
+
}
|
|
253
|
+
|
|
87
254
|
// ─── Builder ──────────────────────────────────────────────────────────────
|
|
88
255
|
|
|
89
256
|
/**
|
|
90
257
|
* Build a React element tree from a matched route.
|
|
91
258
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
259
|
+
* Runs access checks eagerly to gate metadata resolution, then loads
|
|
260
|
+
* modules, resolves metadata (skipping denied segments), and constructs
|
|
261
|
+
* the element tree. DenySignal and RedirectSignal from access are stored
|
|
262
|
+
* as AccessGate verdicts for synchronous replay during render.
|
|
95
263
|
*
|
|
96
264
|
* Does NOT serialize to RSC Flight — the caller decides whether to render
|
|
97
265
|
* to a stream or use the element directly (e.g., for action revalidation).
|
|
266
|
+
*
|
|
267
|
+
* For passing segments, AccessGate re-runs access during render so that
|
|
268
|
+
* React.cache is populated for layout dedup (TIM-662). For denied
|
|
269
|
+
* segments, the pre-computed verdict is replayed — no re-execution,
|
|
270
|
+
* no metadata side effects, no head leak (TIM-1027).
|
|
98
271
|
*/
|
|
99
272
|
export async function buildRouteElement(
|
|
100
273
|
req: Request,
|
|
101
274
|
match: RouteMatch,
|
|
102
275
|
interception?: InterceptionContext,
|
|
103
|
-
clientStateTree?: Set<string> | null
|
|
276
|
+
clientStateTree?: Set<string> | null,
|
|
277
|
+
metadataRouteHashes?: Record<string, string>
|
|
104
278
|
): Promise<RouteElementResult> {
|
|
105
|
-
const segments = match.segments
|
|
279
|
+
const segments = match.segments;
|
|
106
280
|
|
|
107
|
-
//
|
|
108
|
-
|
|
281
|
+
// ── Access pre-pass ──────────────────────────────────────────────────
|
|
282
|
+
// Run access checks eagerly to determine which segments are denied.
|
|
283
|
+
// This gates metadata() resolution: denied segments' metadata functions
|
|
284
|
+
// are never called, preventing unauthorized side effects (DB queries)
|
|
285
|
+
// and metadata content leaking into the <head> of denied responses.
|
|
286
|
+
//
|
|
287
|
+
// Verdicts are passed to AccessGate for synchronous replay (DenySignal
|
|
288
|
+
// renders the deny page in-tree; RedirectSignal propagates). For PASSING
|
|
289
|
+
// segments, no verdict is passed — AccessGate re-runs access during
|
|
290
|
+
// renderToReadableStream to populate React.cache for layout dedup.
|
|
291
|
+
//
|
|
292
|
+
// See TIM-1027, design/04-authorization.md, design/13-security.md.
|
|
293
|
+
const accessVerdicts = new Map<number, DenySignal | RedirectSignal>();
|
|
294
|
+
let firstDeniedIndex = Infinity;
|
|
295
|
+
|
|
296
|
+
for (let i = 0; i < segments.length; i++) {
|
|
297
|
+
const segment = segments[i];
|
|
298
|
+
if (!segment.access || i >= firstDeniedIndex) continue;
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const accessMod = await loadModule(segment.access);
|
|
302
|
+
const accessFn = accessMod.default as (() => Promise<void>) | undefined;
|
|
303
|
+
if (accessFn) {
|
|
304
|
+
await withSpan(
|
|
305
|
+
'timber.access.pre',
|
|
306
|
+
{ 'timber.segment': segment.segmentName ?? 'unknown' },
|
|
307
|
+
() => accessFn()
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
} catch (error: unknown) {
|
|
311
|
+
if (error instanceof DenySignal || error instanceof RedirectSignal) {
|
|
312
|
+
accessVerdicts.set(i, error);
|
|
313
|
+
firstDeniedIndex = i;
|
|
314
|
+
} else {
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
109
319
|
|
|
110
|
-
//
|
|
320
|
+
// ── Module loading + metadata resolution ─────────────────────────────
|
|
111
321
|
const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];
|
|
112
322
|
const layoutComponents: LayoutComponentEntry[] = [];
|
|
113
323
|
let PageComponent: ((...args: unknown[]) => unknown) | null = null;
|
|
@@ -116,196 +326,226 @@ export async function buildRouteElement(
|
|
|
116
326
|
for (let i = 0; i < segments.length; i++) {
|
|
117
327
|
const segment = segments[i];
|
|
118
328
|
const isLeaf = i === segments.length - 1;
|
|
329
|
+
const isDenied = i >= firstDeniedIndex;
|
|
119
330
|
|
|
120
331
|
// Load layout
|
|
121
332
|
if (segment.layout) {
|
|
122
|
-
const mod =
|
|
333
|
+
const mod = await loadModule(segment.layout);
|
|
123
334
|
if (mod.default) {
|
|
124
335
|
layoutComponents.push({
|
|
125
336
|
component: mod.default as (...args: unknown[]) => unknown,
|
|
126
337
|
segment,
|
|
127
338
|
});
|
|
128
339
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
` export async function metadata({ params }) { ... }`
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
// Unified metadata export: static object or async function
|
|
142
|
-
if (typeof mod.metadata === 'function') {
|
|
143
|
-
type MetadataFn = (props: Record<string, unknown>) => Promise<Metadata>;
|
|
144
|
-
const generated = await withSpan(
|
|
145
|
-
'timber.metadata',
|
|
146
|
-
{ 'timber.segment': segment.segmentName ?? segment.urlPath },
|
|
147
|
-
() => (mod.metadata as MetadataFn)({ params: paramsPromise })
|
|
148
|
-
);
|
|
149
|
-
if (generated) {
|
|
150
|
-
metadataEntries.push({ metadata: generated, isPage: false });
|
|
340
|
+
|
|
341
|
+
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
342
|
+
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
343
|
+
|
|
344
|
+
rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);
|
|
345
|
+
if (!isDenied) {
|
|
346
|
+
const layoutMetadata = await extractMetadata(mod, segment);
|
|
347
|
+
if (layoutMetadata) {
|
|
348
|
+
metadataEntries.push({ metadata: layoutMetadata, isPage: false });
|
|
151
349
|
}
|
|
152
|
-
} else if (mod.metadata) {
|
|
153
|
-
metadataEntries.push({ metadata: mod.metadata as Metadata, isPage: false });
|
|
154
|
-
}
|
|
155
|
-
// deferSuspenseFor hold window — max across all segments
|
|
156
|
-
if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > deferSuspenseFor) {
|
|
157
|
-
deferSuspenseFor = mod.deferSuspenseFor;
|
|
158
350
|
}
|
|
351
|
+
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
159
352
|
}
|
|
160
353
|
|
|
161
354
|
// Load page (leaf segment only)
|
|
162
355
|
if (isLeaf && segment.page) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
default?: SearchParamsDefinition<Record<string, unknown>>;
|
|
168
|
-
};
|
|
169
|
-
if (spMod.default) {
|
|
170
|
-
const rawSearchParams = new URL(req.url).searchParams;
|
|
171
|
-
const parsed = spMod.default.parse(rawSearchParams);
|
|
172
|
-
setParsedSearchParams(parsed);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
356
|
+
const mod = await loadModule(segment.page);
|
|
357
|
+
|
|
358
|
+
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
359
|
+
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
175
360
|
|
|
176
|
-
const mod = (await segment.page.load()) as Record<string, unknown>;
|
|
177
361
|
if (mod.default) {
|
|
178
362
|
PageComponent = mod.default as (...args: unknown[]) => unknown;
|
|
179
363
|
}
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
`Export an async function named "metadata" instead.\n\n` +
|
|
186
|
-
` // Before\n` +
|
|
187
|
-
` export async function generateMetadata({ params }) { ... }\n\n` +
|
|
188
|
-
` // After\n` +
|
|
189
|
-
` export async function metadata({ params }) { ... }`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
// Unified metadata export: static object or async function
|
|
193
|
-
if (typeof mod.metadata === 'function') {
|
|
194
|
-
type MetadataFn = (props: Record<string, unknown>) => Promise<Metadata>;
|
|
195
|
-
const generated = await withSpan(
|
|
196
|
-
'timber.metadata',
|
|
197
|
-
{ 'timber.segment': segment.segmentName ?? segment.urlPath },
|
|
198
|
-
() => (mod.metadata as MetadataFn)({ params: paramsPromise })
|
|
199
|
-
);
|
|
200
|
-
if (generated) {
|
|
201
|
-
metadataEntries.push({ metadata: generated, isPage: true });
|
|
364
|
+
rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);
|
|
365
|
+
if (!isDenied) {
|
|
366
|
+
const pageMetadata = await extractMetadata(mod, segment);
|
|
367
|
+
if (pageMetadata) {
|
|
368
|
+
metadataEntries.push({ metadata: pageMetadata, isPage: true });
|
|
202
369
|
}
|
|
203
|
-
} else if (mod.metadata) {
|
|
204
|
-
metadataEntries.push({ metadata: mod.metadata as Metadata, isPage: true });
|
|
205
|
-
}
|
|
206
|
-
// deferSuspenseFor hold window — max across all segments
|
|
207
|
-
if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > deferSuspenseFor) {
|
|
208
|
-
deferSuspenseFor = mod.deferSuspenseFor;
|
|
209
370
|
}
|
|
371
|
+
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
210
372
|
}
|
|
211
373
|
}
|
|
212
374
|
|
|
213
375
|
if (!PageComponent) {
|
|
214
|
-
|
|
376
|
+
const segmentInfo = segments
|
|
377
|
+
.map(
|
|
378
|
+
(s, i) =>
|
|
379
|
+
` [${i}] ${s.segmentName} (page: ${s.page ? 'yes' : 'no'}, layout: ${s.layout ? 'yes' : 'no'}, children: ${s.children?.length ?? 0})${i === segments.length - 1 ? ' ← leaf' : ''}`
|
|
380
|
+
)
|
|
381
|
+
.join('\n');
|
|
382
|
+
throw new NoPageComponentError(
|
|
383
|
+
`No page component found for route: ${new URL(req.url).pathname}\nMatched segments:\n${segmentInfo}`
|
|
384
|
+
);
|
|
215
385
|
}
|
|
216
386
|
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
// See
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const accessMod = (await segment.access.load()) as Record<string, unknown>;
|
|
229
|
-
const accessFn = accessMod.default as
|
|
230
|
-
| ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
|
|
231
|
-
| undefined;
|
|
232
|
-
if (accessFn) {
|
|
233
|
-
try {
|
|
234
|
-
await withSpan(
|
|
235
|
-
'timber.access',
|
|
236
|
-
{ 'timber.segment': segment.segmentName ?? 'unknown' },
|
|
237
|
-
async () => {
|
|
238
|
-
try {
|
|
239
|
-
await accessFn({ params: match.params, searchParams: {} });
|
|
240
|
-
await setSpanAttribute('timber.result', 'pass');
|
|
241
|
-
accessVerdicts.set(si, 'pass');
|
|
242
|
-
} catch (error) {
|
|
243
|
-
if (error instanceof DenySignal) {
|
|
244
|
-
await setSpanAttribute('timber.result', 'deny');
|
|
245
|
-
await setSpanAttribute('timber.deny_status', error.status);
|
|
246
|
-
if (error.sourceFile) {
|
|
247
|
-
await setSpanAttribute('timber.deny_file', error.sourceFile);
|
|
248
|
-
}
|
|
249
|
-
accessVerdicts.set(si, error);
|
|
250
|
-
} else if (error instanceof RedirectSignal) {
|
|
251
|
-
await setSpanAttribute('timber.result', 'redirect');
|
|
252
|
-
accessVerdicts.set(si, error);
|
|
253
|
-
}
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
);
|
|
258
|
-
} catch (error) {
|
|
259
|
-
if (error instanceof DenySignal || error instanceof RedirectSignal) {
|
|
260
|
-
throw new RouteSignalWithContext(error, layoutComponents, segments);
|
|
261
|
-
}
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
387
|
+
// Build deny page fallback chains for each segment position.
|
|
388
|
+
// When AccessGate or PageDenyBoundary catches a DenySignal, they render
|
|
389
|
+
// the matching deny page in-tree instead of throwing into React Flight.
|
|
390
|
+
// The chain walks from the current segment outward to root, collecting
|
|
391
|
+
// status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order.
|
|
392
|
+
// See TIM-666.
|
|
393
|
+
const denyPageChains = new Map<number, DenyPageEntry[]>();
|
|
394
|
+
for (let i = 0; i < segments.length; i++) {
|
|
395
|
+
const chain = await buildDenyPageChain(segments, i);
|
|
396
|
+
if (chain.length > 0) {
|
|
397
|
+
denyPageChains.set(i, chain);
|
|
265
398
|
}
|
|
266
399
|
}
|
|
267
400
|
|
|
268
|
-
// Resolve metadata
|
|
401
|
+
// Resolve metadata and convert to React elements that React 19 Float
|
|
402
|
+
// hoists into <head> during both SSR and client rendering. This replaces
|
|
403
|
+
// the previous HeadElement descriptor system (string splice for SSR,
|
|
404
|
+
// X-Timber-Head header for RSC nav, imperative DOM updates on client).
|
|
405
|
+
// React manages <title>/<meta>/<link> lifecycle across navigations.
|
|
406
|
+
// See design/16-metadata.md, TIM-1151.
|
|
269
407
|
const resolvedMetadata = resolveMetadata(metadataEntries);
|
|
270
408
|
const headElements = renderMetadataToElements(resolvedMetadata);
|
|
271
409
|
|
|
272
|
-
// Auto-link metadata route files (icon, apple-icon, manifest)
|
|
410
|
+
// Auto-link metadata route files (icon, apple-icon, manifest, opengraph-image).
|
|
411
|
+
// opengraph-image emits both og:image and twitter:image (no separate twitter-image convention).
|
|
412
|
+
// Skip OG auto-linking when the user already declared images in metadata.
|
|
273
413
|
// See design/16-metadata.md §"Auto-Linking"
|
|
274
|
-
|
|
414
|
+
const hasUserOgImage = Boolean(resolvedMetadata.openGraph?.images);
|
|
415
|
+
const hasUserTwitterImage = Boolean(resolvedMetadata.twitter?.images);
|
|
416
|
+
const requestUrl = new URL(req.url);
|
|
417
|
+
const requestPathname = requestUrl.pathname;
|
|
418
|
+
// In dev mode, use the request origin so OG URLs resolve to localhost.
|
|
419
|
+
// In production, use metadataBase (the canonical domain).
|
|
420
|
+
const ogBase =
|
|
421
|
+
process.env.NODE_ENV !== 'production'
|
|
422
|
+
? new URL(requestUrl.origin)
|
|
423
|
+
: resolvedMetadata.metadataBase;
|
|
424
|
+
|
|
425
|
+
for (let si = 0; si < segments.length; si++) {
|
|
426
|
+
const segment = segments[si];
|
|
275
427
|
if (!segment.metadataRoutes) continue;
|
|
428
|
+
// Skip auto-linking for denied segments — same gate as metadata().
|
|
429
|
+
if (si >= firstDeniedIndex) continue;
|
|
276
430
|
for (const baseName of Object.keys(segment.metadataRoutes)) {
|
|
277
431
|
const convention = METADATA_ROUTE_CONVENTIONS[baseName];
|
|
278
432
|
if (!convention) continue;
|
|
279
433
|
// Non-nestable routes only auto-link from root
|
|
280
434
|
if (!convention.nestable && segment.urlPath !== '/') continue;
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
435
|
+
// Skip auto-linking entirely if user declared openGraph.images (covers both og + twitter)
|
|
436
|
+
if (convention.type === 'opengraph-image' && hasUserOgImage) continue;
|
|
437
|
+
// Build the href using the actual request path (not the pattern with [param]).
|
|
438
|
+
// For nestable routes, the metadata route sits under the same resolved path
|
|
439
|
+
// as the page. For root-only routes, use '/'.
|
|
440
|
+
const resolvedPrefix = convention.nestable
|
|
441
|
+
? requestPathname === '/'
|
|
442
|
+
? ''
|
|
443
|
+
: requestPathname
|
|
444
|
+
: '';
|
|
445
|
+
const metaFile = segment.metadataRoutes[baseName];
|
|
446
|
+
const fileServePath = metaFile?.filePath
|
|
447
|
+
? resolveServePathForFile(baseName, metaFile.filePath)
|
|
448
|
+
: convention.serveExtension
|
|
449
|
+
? `${convention.servePath}.${convention.serveExtension}`
|
|
450
|
+
: convention.servePath;
|
|
451
|
+
let href = `${resolvedPrefix}/${fileServePath}`;
|
|
452
|
+
// Append cache-bust query param for image metadata routes
|
|
453
|
+
if (convention.type === 'opengraph-image') {
|
|
454
|
+
const fileHash = metaFile?.filePath ? metadataRouteHashes?.[metaFile.filePath] : undefined;
|
|
455
|
+
const cacheBust = fileHash ?? getDevNonce();
|
|
456
|
+
href = `${href}?${cacheBust}`;
|
|
457
|
+
}
|
|
458
|
+
// Resolve to absolute URL for og:image/twitter:image (social crawlers need full URLs)
|
|
459
|
+
if (ogBase && convention.type === 'opengraph-image') {
|
|
460
|
+
href = new URL(href, ogBase).toString();
|
|
461
|
+
}
|
|
462
|
+
for (const autoLink of getMetadataRouteAutoLink(convention.type, href)) {
|
|
463
|
+
// Skip auto-linked twitter:image when user declared twitter.images
|
|
464
|
+
if (
|
|
465
|
+
hasUserTwitterImage &&
|
|
466
|
+
autoLink.tag === 'meta' &&
|
|
467
|
+
'name' in autoLink &&
|
|
468
|
+
autoLink.name === 'twitter:image'
|
|
469
|
+
)
|
|
470
|
+
continue;
|
|
471
|
+
if (autoLink.tag === 'link') {
|
|
472
|
+
const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };
|
|
473
|
+
if (autoLink.type) attrs.type = autoLink.type;
|
|
474
|
+
headElements.push({ tag: 'link', attrs });
|
|
475
|
+
} else {
|
|
476
|
+
const attrs: Record<string, string> = { content: autoLink.content };
|
|
477
|
+
if (autoLink.property) attrs.property = autoLink.property;
|
|
478
|
+
if (autoLink.name) attrs.name = autoLink.name;
|
|
479
|
+
headElements.push({ tag: 'meta', attrs });
|
|
480
|
+
}
|
|
289
481
|
}
|
|
290
482
|
}
|
|
291
483
|
}
|
|
292
484
|
|
|
485
|
+
// Convert HeadElement descriptors to React elements for Float hoisting.
|
|
486
|
+
// Placed at the page segment level so they're always re-rendered on
|
|
487
|
+
// SPA navigation (sync layouts may be skipped by segment tree diffing).
|
|
488
|
+
const metadataElement = headElementsToReact(headElements);
|
|
489
|
+
|
|
293
490
|
// Build element tree: page wrapped in layouts (innermost to outermost)
|
|
294
491
|
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
295
492
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
493
|
+
// Shell settlement tracking (TIM-1045 pages, TIM-1208 layouts).
|
|
494
|
+
// ChildSegmentProvider is 'use client', so React Flight eagerly
|
|
495
|
+
// serializes both the layout and child content as independent subtrees —
|
|
496
|
+
// the page can settle before an outer layout. We need all deny-capable
|
|
497
|
+
// components to settle before committing the HTTP status.
|
|
498
|
+
//
|
|
499
|
+
// Each tracked component gets its own deferred. shellSettled resolves
|
|
500
|
+
// when ALL settle (Promise.all) OR when any in-tree deny fires
|
|
501
|
+
// (short-circuit via onDenyStatus — covers AccessGate, TracedLayout,
|
|
502
|
+
// and PageDenyBoundary without threading callbacks into each).
|
|
503
|
+
const shellPromises: Promise<void>[] = [];
|
|
504
|
+
let resolveShortCircuit: (() => void) | undefined;
|
|
505
|
+
const trackShellComponent = (): (() => void) => {
|
|
506
|
+
let resolve!: () => void;
|
|
507
|
+
shellPromises.push(new Promise<void>((r) => (resolve = r)));
|
|
508
|
+
return resolve;
|
|
303
509
|
};
|
|
304
510
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
511
|
+
// Build the page element.
|
|
512
|
+
// Server component pages are wrapped in PageDenyBoundary which calls
|
|
513
|
+
// them as async functions and catches DenySignal — rendering the deny
|
|
514
|
+
// page in-tree instead of throwing into React Flight. This eliminates
|
|
515
|
+
// the second render pass for deny pages. See TIM-666.
|
|
516
|
+
//
|
|
517
|
+
// Client reference pages ('use client') can't call deny() (server-only),
|
|
518
|
+
// so they go through createElement normally — no wrapper needed.
|
|
519
|
+
const leafIndex = segments.length - 1;
|
|
520
|
+
const leafDenyPages = denyPageChains.get(leafIndex);
|
|
521
|
+
let element: React.ReactElement;
|
|
522
|
+
if (isClientReference(PageComponent)) {
|
|
523
|
+
element = h(PageComponent, {});
|
|
524
|
+
} else if (leafDenyPages && leafDenyPages.length > 0) {
|
|
525
|
+
const onSettled = trackShellComponent();
|
|
526
|
+
element = h(PageDenyBoundary, {
|
|
527
|
+
Page: PageComponent,
|
|
528
|
+
route: match.segments[leafIndex]?.urlPath ?? '/',
|
|
529
|
+
denyPages: leafDenyPages,
|
|
530
|
+
onSettled,
|
|
531
|
+
});
|
|
532
|
+
} else {
|
|
533
|
+
// Server component page WITHOUT deny page chain — trace only
|
|
534
|
+
const TracedPage = async (props: Record<string, unknown>) => {
|
|
535
|
+
return withSpan(
|
|
536
|
+
'timber.page',
|
|
537
|
+
{ 'timber.route': match.segments[leafIndex]?.urlPath ?? '/' },
|
|
538
|
+
() => (PageComponent as (props: Record<string, unknown>) => unknown)(props)
|
|
539
|
+
);
|
|
540
|
+
};
|
|
541
|
+
element = h(TracedPage, {});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Wrap page + metadata in a Fragment so Float-hoisted elements are at
|
|
545
|
+
// the page segment level (always re-rendered, never skipped by segment
|
|
546
|
+
// tree diffing). React 19 Float hoists the <title>/<meta>/<link>
|
|
547
|
+
// children into <head> regardless of where they appear in the tree.
|
|
548
|
+
element = h(Fragment, null, metadataElement, element);
|
|
309
549
|
|
|
310
550
|
// Build a lookup of layout components by segment for O(1) access.
|
|
311
551
|
const layoutBySegment = new Map(
|
|
@@ -316,147 +556,290 @@ export async function buildRouteElement(
|
|
|
316
556
|
// The client uses this to merge the partial payload with its cached segments.
|
|
317
557
|
const skippedSegments: string[] = [];
|
|
318
558
|
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
559
|
+
// Compute state-tree keys for all segments. Route groups accumulate
|
|
560
|
+
// ancestor group names for globally unique keys (e.g., "/(a)/(shared)"
|
|
561
|
+
// instead of just "/(shared)"). This is the single key computation —
|
|
562
|
+
// the same keys are used for skip decisions, SegmentOutlet props, and
|
|
563
|
+
// the X-Timber-Segments header (via buildSegmentInfo).
|
|
564
|
+
const segmentKeys = computeSegmentKeys(segments);
|
|
565
|
+
|
|
566
|
+
// Pre-compute the contiguous skippable prefix (top-down).
|
|
324
567
|
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
568
|
+
// Skipped segments must form a contiguous prefix from root to avoid
|
|
569
|
+
// non-contiguous skips that break the client merge. Walk from root
|
|
570
|
+
// toward leaf, marking segments as skippable. Stop at the first
|
|
571
|
+
// segment that can't be skipped (async, not in client tree, leaf).
|
|
572
|
+
// Layoutless segments are transparent (no outlet, no layout to skip).
|
|
329
573
|
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
// there
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
574
|
+
// After finding the prefix, validate: the first non-skipped layout
|
|
575
|
+
// must exist in the client's state tree (the client needs a mounted
|
|
576
|
+
// outlet there to receive the partial payload).
|
|
577
|
+
const skippableSet = new Set<number>();
|
|
578
|
+
if (clientStateTree) {
|
|
579
|
+
for (let i = 0; i < segments.length; i++) {
|
|
580
|
+
const isLeaf = i === segments.length - 1;
|
|
581
|
+
const layoutComponent = layoutBySegment.get(segments[i]);
|
|
582
|
+
|
|
583
|
+
// Never skip denied segments — their AccessGate must render the
|
|
584
|
+
// deny page, not reuse the client's cached (passing) layout.
|
|
585
|
+
if (i >= firstDeniedIndex) break;
|
|
586
|
+
|
|
587
|
+
if (shouldSkipSegment(segmentKeys[i], layoutComponent, isLeaf, clientStateTree)) {
|
|
588
|
+
skippableSet.add(i);
|
|
589
|
+
} else if (layoutComponent) {
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Ensure there's a merge point: a non-skipped layout whose key is
|
|
595
|
+
// in the client's state tree (so the client has a mounted outlet).
|
|
596
|
+
// If all layouts are in the prefix, keep the innermost as merge point.
|
|
597
|
+
if (skippableSet.size > 0) {
|
|
598
|
+
let mergePointIndex = -1;
|
|
599
|
+
for (let i = 0; i < segments.length; i++) {
|
|
600
|
+
if (skippableSet.has(i)) continue;
|
|
601
|
+
if (layoutBySegment.has(segments[i])) {
|
|
602
|
+
mergePointIndex = i;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (mergePointIndex === -1) {
|
|
608
|
+
// All layouts are skippable — keep the innermost as merge point
|
|
609
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
610
|
+
if (skippableSet.has(i) && layoutBySegment.has(segments[i])) {
|
|
611
|
+
skippableSet.delete(i);
|
|
612
|
+
mergePointIndex = i;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Cross-section guard: at least one rendered layout below the
|
|
619
|
+
// prefix must exist in the client's state tree (the client needs
|
|
620
|
+
// a mounted outlet to receive the partial payload). Check ALL
|
|
621
|
+
// non-skipped layouts, not just the merge point — async layouts
|
|
622
|
+
// are excluded from the client state tree but may sit above a
|
|
623
|
+
// sync layout that IS in the tree.
|
|
624
|
+
let hasClientOutlet = false;
|
|
625
|
+
for (let j = 0; j < segments.length; j++) {
|
|
626
|
+
if (skippableSet.has(j)) continue;
|
|
627
|
+
if (layoutBySegment.has(segments[j]) && clientStateTree.has(segmentKeys[j])) {
|
|
628
|
+
hasClientOutlet = true;
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (!hasClientOutlet) {
|
|
633
|
+
skippableSet.clear();
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
let outermostSegmentProvider: React.ReactElement | null = null;
|
|
338
639
|
|
|
339
640
|
for (let i = segments.length - 1; i >= 0; i--) {
|
|
340
641
|
const segment = segments[i];
|
|
341
642
|
const isLeaf = i === segments.length - 1;
|
|
342
643
|
const layoutComponent = layoutBySegment.get(segment);
|
|
343
644
|
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
// no AccessGate in element tree (access already ran pre-render).
|
|
347
|
-
//
|
|
348
|
-
// Additional constraints beyond shouldSkipSegment:
|
|
349
|
-
// - Must have a rendered layout below (so the merger can find an
|
|
350
|
-
// inner SegmentProvider to splice the new content into)
|
|
351
|
-
// - Route groups are never skipped because sibling groups share the
|
|
352
|
-
// same urlPath (e.g., /(marketing) and /(app) both have "/"),
|
|
353
|
-
// which would cause the wrong cached layout to be reused
|
|
354
|
-
const skip =
|
|
355
|
-
shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&
|
|
356
|
-
hasRenderedLayoutBelow &&
|
|
357
|
-
segment.segmentType !== 'group';
|
|
358
|
-
|
|
359
|
-
if (skip) {
|
|
360
|
-
// Skip this segment entirely — the client uses its cached version.
|
|
361
|
-
// Access.ts already ran in the pre-render loop (security guarantee).
|
|
362
|
-
// Metadata was already resolved above (head elements are correct).
|
|
645
|
+
// Skip decision was pre-computed in the top-down prefix pass.
|
|
646
|
+
if (skippableSet.has(i)) {
|
|
363
647
|
// Record for X-Timber-Skipped-Segments header (outermost first, so prepend).
|
|
364
|
-
skippedSegments.unshift(
|
|
365
|
-
|
|
366
|
-
|
|
648
|
+
skippedSegments.unshift(segmentKeys[i]);
|
|
649
|
+
|
|
650
|
+
// SECURITY: Even though the layout is skipped, AccessGate MUST still
|
|
651
|
+
// wrap the element tree. access.ts runs on every navigation regardless
|
|
652
|
+
// of cached layouts or state tree content.
|
|
653
|
+
// See design/13-security.md §"Auth always runs" (test #11).
|
|
654
|
+
if (segment.access) {
|
|
655
|
+
const accessMod = await loadModule(segment.access);
|
|
656
|
+
const accessFn = accessMod.default as (() => unknown) | undefined;
|
|
657
|
+
if (accessFn) {
|
|
658
|
+
const verdict = accessVerdicts.get(i);
|
|
659
|
+
element = h(AccessGate, {
|
|
660
|
+
accessFn,
|
|
661
|
+
segmentName: segment.segmentName,
|
|
662
|
+
denyPages: denyPageChains.get(i),
|
|
663
|
+
...(verdict ? { verdict } : {}),
|
|
664
|
+
children: element,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
367
668
|
|
|
368
|
-
|
|
369
|
-
// a rendered layout below them and can safely be skipped.
|
|
370
|
-
if (layoutComponent) {
|
|
371
|
-
hasRenderedLayoutBelow = true;
|
|
669
|
+
continue;
|
|
372
670
|
}
|
|
373
671
|
|
|
374
672
|
// Wrap with error boundaries from this segment (inside layout).
|
|
673
|
+
// Keep ALL error boundaries (including 4xx) — they're the safety net for
|
|
674
|
+
// DenySignal from nested server components that escape AccessGate/PageDenyBoundary
|
|
675
|
+
// try/catch. Status-code files must be 'use client' TSX or MDX to serialize
|
|
676
|
+
// as error boundary fallbacks. See TIM-666.
|
|
375
677
|
element = await wrapSegmentWithErrorBoundaries(segment, element, h);
|
|
376
678
|
|
|
377
|
-
// Wrap
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const accessFn = accessMod.default as
|
|
383
|
-
| ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
|
|
384
|
-
| undefined;
|
|
385
|
-
if (accessFn) {
|
|
386
|
-
element = h(AccessGate, {
|
|
387
|
-
accessFn,
|
|
388
|
-
params: match.params,
|
|
389
|
-
searchParams: {},
|
|
390
|
-
segmentName: segment.segmentName,
|
|
391
|
-
verdict: accessVerdicts.get(i),
|
|
392
|
-
children: element,
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Wrap with layout if this segment has one — traced with OTEL span
|
|
679
|
+
// Wrap with layout BEFORE AccessGate — AccessGate is OUTSIDE the layout.
|
|
680
|
+
// When AccessGate denies, the layout never renders. The deny page appears
|
|
681
|
+
// at the AccessGate level, wrapped by PARENT layouts only.
|
|
682
|
+
// This prevents leaking layout UI (sidebars, nav) on denied pages.
|
|
683
|
+
// See design/04-authorization.md §"Access Failure".
|
|
398
684
|
if (layoutComponent) {
|
|
399
|
-
// Resolve parallel slots for this layout
|
|
685
|
+
// Resolve parallel slots for this layout.
|
|
686
|
+
// Compute the parent tree path so slots can build their full
|
|
687
|
+
// segment path for per-slot param storage in ALS.
|
|
688
|
+
const parentTreePath =
|
|
689
|
+
'/' +
|
|
690
|
+
segments
|
|
691
|
+
.slice(0, i + 1)
|
|
692
|
+
.map((s) => s.segmentName)
|
|
693
|
+
.filter(Boolean)
|
|
694
|
+
.join('/');
|
|
400
695
|
const slotProps: Record<string, unknown> = {};
|
|
401
696
|
const slotEntries = Object.entries(segment.slots ?? {});
|
|
402
697
|
for (const [slotName, slotNode] of slotEntries) {
|
|
403
698
|
slotProps[slotName] = await resolveSlotElement(
|
|
404
699
|
slotNode as ManifestSegmentNode,
|
|
405
700
|
match,
|
|
406
|
-
paramsPromise,
|
|
407
701
|
h,
|
|
408
|
-
interception
|
|
702
|
+
interception,
|
|
703
|
+
parentTreePath
|
|
409
704
|
);
|
|
410
705
|
}
|
|
411
706
|
|
|
412
|
-
|
|
707
|
+
// '/'.split('/') → ['', ''] (length 2), but root should be [''] (length 1).
|
|
708
|
+
// The extra empty string makes useSelectedLayoutSegment skip the first URL segment.
|
|
709
|
+
const rawPath = segment.urlPath.split('/');
|
|
710
|
+
const segmentPath = rawPath.length === 2 && rawPath[1] === '' ? [''] : rawPath;
|
|
413
711
|
const parallelRouteKeys = Object.keys(segment.slots ?? {});
|
|
414
712
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
713
|
+
const segmentId = segmentKeys[i];
|
|
714
|
+
|
|
715
|
+
// Build the layout element.
|
|
716
|
+
// Layouts receive <ChildSegmentOutlet /> as children — a stable client
|
|
717
|
+
// reference — instead of the varying inner content. The actual child
|
|
718
|
+
// content is provided via ChildSegmentProvider above the layout.
|
|
719
|
+
// This makes layout cache.component hits possible: the cached payload
|
|
720
|
+
// contains a deterministic children reference, not varying content.
|
|
721
|
+
// See design/45-cache-lifetimes.md §Layout Propagation.
|
|
722
|
+
const childOutlet = h(ChildSegmentOutlet, {});
|
|
723
|
+
let layoutElement: React.ReactElement;
|
|
724
|
+
if (isClientReference(layoutComponent)) {
|
|
725
|
+
layoutElement = h(layoutComponent, {
|
|
726
|
+
...slotProps,
|
|
727
|
+
children: childOutlet,
|
|
728
|
+
});
|
|
729
|
+
} else {
|
|
730
|
+
// Server component layout — wrap with OTEL tracing AND DenySignal
|
|
731
|
+
// catching. If the layout calls deny(), the signal is caught here
|
|
732
|
+
// and the matching deny page renders in-tree (same pattern as
|
|
733
|
+
// AccessGate and PageDenyBoundary). Without this, DenySignal
|
|
734
|
+
// escapes to React Flight onError and triggers the re-render
|
|
735
|
+
// fallback path. See TIM-668, design/04-authorization.md.
|
|
736
|
+
const layoutComponentRef = layoutComponent;
|
|
737
|
+
const layoutDenyPages = denyPageChains.get(i);
|
|
738
|
+
const hasLayoutDenyChain = !!layoutDenyPages && layoutDenyPages.length > 0;
|
|
739
|
+
const onLayoutSettled = hasLayoutDenyChain ? trackShellComponent() : undefined;
|
|
740
|
+
const TracedLayout = async (props: Record<string, unknown>) => {
|
|
741
|
+
try {
|
|
742
|
+
return await withSpan('timber.layout', { 'timber.segment': segmentId }, () =>
|
|
743
|
+
(layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)
|
|
744
|
+
);
|
|
745
|
+
} catch (error: unknown) {
|
|
746
|
+
if (error instanceof DenySignal && layoutDenyPages) {
|
|
747
|
+
const denyElement = renderMatchingDenyPage(layoutDenyPages, error.status, error.data);
|
|
748
|
+
if (denyElement) {
|
|
749
|
+
setDenyStatus(error.status);
|
|
750
|
+
return denyElement;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// Non-deny errors (RedirectSignal, runtime errors) propagate normally
|
|
754
|
+
// to onError — signalDetected / renderError handling covers those.
|
|
755
|
+
throw error;
|
|
756
|
+
} finally {
|
|
757
|
+
onLayoutSettled?.();
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
layoutElement = h(TracedLayout, {
|
|
761
|
+
...slotProps,
|
|
762
|
+
children: childOutlet,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Wrap the layout in ChildSegmentProvider so ChildSegmentOutlet
|
|
767
|
+
// (inside the layout's children) can read the actual child content.
|
|
768
|
+
layoutElement = h(ChildSegmentProvider, {
|
|
769
|
+
childContent: element,
|
|
770
|
+
children: layoutElement,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const segmentProviderElement = h(SegmentProvider, {
|
|
441
774
|
segments: segmentPath,
|
|
442
775
|
segmentId,
|
|
443
776
|
parallelRouteKeys,
|
|
444
|
-
children:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
777
|
+
children: layoutElement,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
element = h(SegmentOutlet, {
|
|
781
|
+
segmentPath: segmentId,
|
|
782
|
+
children: segmentProviderElement,
|
|
450
783
|
});
|
|
784
|
+
|
|
785
|
+
// Track the SegmentProvider for the outermost rendered layout.
|
|
786
|
+
// On partial navigation (segments skipped above), the client already
|
|
787
|
+
// has a SegmentOutlet mounted at this position. Sending another one
|
|
788
|
+
// in the payload causes double-wrapping and infinite context recursion.
|
|
789
|
+
// We'll strip the outermost SegmentOutlet after the loop.
|
|
790
|
+
outermostSegmentProvider = segmentProviderElement;
|
|
451
791
|
}
|
|
792
|
+
|
|
793
|
+
// Wrap in AccessGate OUTSIDE the layout.
|
|
794
|
+
// If access denies, the deny page renders here — the layout above
|
|
795
|
+
// never executes. Parent layouts (from outer iterations) form the shell.
|
|
796
|
+
// See TIM-662, TIM-666, design/04-authorization.md §"Access Failure".
|
|
797
|
+
if (segment.access) {
|
|
798
|
+
const accessMod = await loadModule(segment.access);
|
|
799
|
+
const accessFn = accessMod.default as (() => unknown) | undefined;
|
|
800
|
+
if (accessFn) {
|
|
801
|
+
// Pass verdict for denied/redirected segments so AccessGate replays
|
|
802
|
+
// without re-execution. Passing segments omit verdict so AccessGate
|
|
803
|
+
// re-runs access during render for React.cache population.
|
|
804
|
+
const verdict = accessVerdicts.get(i);
|
|
805
|
+
element = h(AccessGate, {
|
|
806
|
+
accessFn,
|
|
807
|
+
segmentName: segment.segmentName,
|
|
808
|
+
denyPages: denyPageChains.get(i),
|
|
809
|
+
...(verdict ? { verdict } : {}),
|
|
810
|
+
children: element,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// On partial navigation (some segments skipped), the client already has a
|
|
817
|
+
// SegmentOutlet mounted at the outermost non-skipped segment's position.
|
|
818
|
+
// Sending another SegmentOutlet in the payload causes double-wrapping —
|
|
819
|
+
// the inner outlet reads the same context update and recurses infinitely.
|
|
820
|
+
// Replace the outermost SegmentOutlet with just its SegmentProvider child.
|
|
821
|
+
if (skippedSegments.length > 0 && outermostSegmentProvider) {
|
|
822
|
+
element = replaceOutermostSegmentOutlet(element, outermostSegmentProvider);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Compose shellSettled: resolves when ALL deny-capable components settle
|
|
826
|
+
// (Promise.all) OR immediately when any component calls setDenyStatus()
|
|
827
|
+
// (short-circuit via onDenyStatus — covers AccessGate, TracedLayout, and
|
|
828
|
+
// PageDenyBoundary). See TIM-1045, TIM-1208.
|
|
829
|
+
let shellSettled: Promise<void> | undefined;
|
|
830
|
+
if (shellPromises.length > 0) {
|
|
831
|
+
const shortCircuit = new Promise<void>((r) => (resolveShortCircuit = r));
|
|
832
|
+
const store = requestContextAls.getStore();
|
|
833
|
+
if (store) store.onDenyStatus = () => resolveShortCircuit?.();
|
|
834
|
+
shellSettled = Promise.race([Promise.all(shellPromises).then(() => {}), shortCircuit]);
|
|
452
835
|
}
|
|
453
836
|
|
|
454
837
|
return {
|
|
455
838
|
element,
|
|
456
|
-
headElements: headElements as HeadElement[],
|
|
457
839
|
layoutComponents,
|
|
458
840
|
segments,
|
|
459
841
|
deferSuspenseFor,
|
|
460
842
|
skippedSegments,
|
|
843
|
+
shellSettled,
|
|
461
844
|
};
|
|
462
845
|
}
|