@timber-js/app 0.2.0-alpha.16 → 0.2.0-alpha.161
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-cjklt63G.js +317 -0
- package/dist/_chunks/actions-cjklt63G.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-CzYUlgXA.js +1125 -0
- package/dist/_chunks/cache-api-CzYUlgXA.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → canonicalize-P41GR6tY.js} +94 -18
- package/dist/_chunks/canonicalize-P41GR6tY.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-NfLbLnDw.js +638 -0
- package/dist/_chunks/cli-schema-sync-NfLbLnDw.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-Bssfp6ot.js +82 -0
- package/dist/_chunks/define-Bssfp6ot.js.map +1 -0
- package/dist/_chunks/define-CFmvb4Bt.js +203 -0
- package/dist/_chunks/define-CFmvb4Bt.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/logger-B_O6-mdJ.js +1485 -0
- package/dist/_chunks/logger-B_O6-mdJ.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-DeAxFRMq.js +280 -0
- package/dist/_chunks/plugin-context-DeAxFRMq.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-3iUvBV5T.js +50 -0
- package/dist/_chunks/resolve-schema-3iUvBV5T.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-BY3QLBL7.js +119 -0
- package/dist/_chunks/schema-bridge-BY3QLBL7.js.map +1 -0
- package/dist/_chunks/segment-classify-CDDRVKs7.js +137 -0
- package/dist/_chunks/segment-classify-CDDRVKs7.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/tree-match-D2l830j2.js +102 -0
- package/dist/_chunks/tree-match-D2l830j2.js.map +1 -0
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-CbeQmext.js} +7 -20
- package/dist/_chunks/use-query-states-CbeQmext.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-9mz9T7mb.js +988 -0
- package/dist/_chunks/walkers-9mz9T7mb.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 +64 -0
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -0
- package/dist/adapters/cloudflare-kv-cache.js +117 -0
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +177 -20
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +337 -64
- package/dist/adapters/cloudflare.js.map +1 -1
- 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 +1 -3
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +144 -152
- 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 +0 -6
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/cache-api.d.ts +26 -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 +27 -7
- 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/redis-handler.d.ts +97 -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 +51 -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 +107 -33
- 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 +26 -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 +11 -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 +182 -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 +825 -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 +87 -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/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 +75 -0
- package/dist/codec.js.map +1 -0
- package/dist/config-types.d.ts +304 -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} +2 -2
- 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 +158 -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 +8471 -14108
- 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 +190 -0
- package/dist/plugin-context.d.ts.map +1 -0
- package/dist/plugins/adapter-build.d.ts +20 -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 +1 -1
- 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.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 +75 -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 +77 -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 +157 -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 +37 -440
- 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 +41 -6
- 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 +86 -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 +115 -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 +221 -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 +170 -2911
- 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 +3652 -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 +23 -7
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +36 -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 +56 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +122 -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 +58 -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/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/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 +101 -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 +46 -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/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 +50 -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 +17 -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 +119 -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 +12 -5
- 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 +16 -16
- 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 +199 -0
- package/docs/api/33-api-search-params.mdx +141 -0
- package/docs/api/34-api-config.mdx +189 -0
- package/docs/api/35-api-typescript.mdx +170 -0
- package/docs/api/36-cli.mdx +143 -0
- package/docs/learn/00-introduction.mdx +76 -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 +136 -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 +168 -0
- package/docs/learn/13-configuration.mdx +145 -0
- package/docs/learn/14-deploying.mdx +264 -0
- package/docs/more/01-advanced-routing.mdx +139 -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 +103 -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 +179 -0
- package/src/adapters/cloudflare.ts +571 -118
- package/src/adapters/compress-module.ts +27 -92
- package/src/adapters/nitro.ts +131 -107
- package/src/adapters/raw-imports.d.ts +5 -0
- package/src/adapters/shared.ts +68 -0
- package/src/adapters/types.ts +0 -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 +88 -18
- package/src/cache/invalidation-epoch.ts +93 -0
- package/src/cache/redis-handler.ts +122 -15
- 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 +233 -0
- package/src/cache/timber-cache.ts +142 -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 +388 -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 +111 -0
- package/src/client/browser-entry/post-hydration.ts +103 -0
- package/src/client/browser-entry/router-init.ts +287 -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 +11 -7
- package/src/client/index.ts +67 -45
- package/src/client/internal.ts +58 -0
- package/src/client/link.tsx +346 -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 +482 -166
- package/src/client/rsc-fetch.ts +214 -33
- package/src/client/segment-cache.ts +7 -7
- 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/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 +9 -3
- 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 +147 -0
- package/src/config-types.ts +302 -0
- package/src/config-validation.ts +328 -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} +9 -5
- 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 +63 -8
- package/src/fonts/local.ts +9 -8
- package/src/fonts/paths.ts +58 -0
- package/src/fonts/pipeline.ts +316 -0
- package/src/fonts/transform.ts +305 -0
- package/src/fonts/types.ts +52 -1
- package/src/fonts/virtual-modules.ts +179 -0
- package/src/index.ts +431 -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 +378 -0
- package/src/plugins/adapter-build.ts +98 -4
- package/src/plugins/build-manifest.ts +168 -23
- package/src/plugins/build-report.ts +253 -35
- package/src/plugins/cache.ts +229 -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 +144 -0
- package/src/plugins/cloudflare-wasm.ts +141 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-server.ts +293 -46
- package/src/plugins/entries.ts +129 -8
- package/src/plugins/fonts.ts +199 -664
- package/src/plugins/mdx.ts +47 -10
- package/src/plugins/prebuilt-capture.ts +217 -0
- package/src/plugins/prebuilt.ts +700 -0
- package/src/plugins/prerender-sugar.ts +329 -0
- package/src/plugins/routing.ts +237 -241
- package/src/plugins/server-bundle.ts +97 -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 +360 -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 +310 -0
- package/src/routing/manifest-codegen.ts +281 -0
- package/src/routing/scanner.ts +248 -188
- package/src/routing/schema-validation.ts +149 -0
- package/src/routing/segment-classify.ts +188 -0
- package/src/routing/segment-codegen.ts +98 -0
- package/src/routing/types.ts +78 -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 +252 -0
- package/src/search-params/define.ts +570 -0
- package/src/search-params/index.ts +14 -20
- package/src/search-params/registry.ts +2 -2
- package/src/search-params/wrappers.ts +110 -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 +119 -28
- 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 +130 -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 +311 -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 +437 -0
- package/src/server/html-injectors.ts +267 -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 +44 -19
- package/src/server/middleware-runner.ts +75 -4
- package/src/server/node-stream-transforms.ts +331 -0
- package/src/server/param-coercion.ts +206 -0
- package/src/server/pipeline-helpers.ts +204 -0
- package/src/server/pipeline-interception.ts +32 -18
- package/src/server/pipeline-metadata.ts +14 -0
- package/src/server/pipeline-outcome.ts +197 -0
- package/src/server/pipeline-phases.ts +538 -0
- package/src/server/pipeline.ts +226 -382
- package/src/server/port-resolution.ts +215 -0
- package/src/server/prebuilt/cache-key.ts +224 -0
- package/src/server/prebuilt/capture-state.ts +89 -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/synthetic-store.ts +121 -0
- package/src/server/prebuilt-builder.ts +774 -0
- package/src/server/prebuilt-runtime.ts +731 -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 +548 -233
- package/src/server/route-handler.ts +31 -4
- package/src/server/route-matcher.ts +144 -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/error-renderer.ts +306 -89
- package/src/server/rsc-entry/helpers.ts +160 -76
- package/src/server/rsc-entry/index.ts +394 -330
- package/src/server/rsc-entry/render-route.ts +288 -0
- package/src/server/rsc-entry/revalidate-renderer.ts +349 -0
- package/src/server/rsc-entry/rsc-payload.ts +74 -35
- package/src/server/rsc-entry/rsc-stream.ts +85 -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 +449 -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 +344 -0
- package/src/server/sitemap-handler.ts +157 -0
- package/src/server/slot-resolver.ts +258 -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 +30 -23
- package/src/server/status-code-resolver.ts +115 -130
- package/src/server/stream-utils.ts +286 -0
- package/src/server/tracing.ts +126 -14
- package/src/server/tree-builder.ts +204 -90
- package/src/server/tree-match.ts +220 -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.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/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,154 @@
|
|
|
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
26
|
import { METADATA_ROUTE_CONVENTIONS, getMetadataRouteAutoLink } from './metadata-routes.js';
|
|
27
|
+
|
|
28
|
+
// In dev mode, use a per-startup nonce for metadata route cache busting
|
|
29
|
+
// instead of per-file content hashes (avoids rehashing on every request).
|
|
30
|
+
let _devNonce: string | undefined;
|
|
31
|
+
function getDevNonce(): string {
|
|
32
|
+
_devNonce ??= randomUUID().slice(0, 8);
|
|
33
|
+
return _devNonce;
|
|
34
|
+
}
|
|
27
35
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
28
36
|
import { AccessGate } from './access-gate.js';
|
|
37
|
+
import {
|
|
38
|
+
PageDenyBoundary,
|
|
39
|
+
buildDenyPageChain,
|
|
40
|
+
renderMatchingDenyPage,
|
|
41
|
+
setDenyStatus,
|
|
42
|
+
} from './deny-boundary.js';
|
|
43
|
+
import type { DenyPageEntry } from './deny-boundary.js';
|
|
29
44
|
import { resolveSlotElement } from './slot-resolver.js';
|
|
30
|
-
import { SegmentProvider } from '
|
|
31
|
-
import {
|
|
32
|
-
import
|
|
45
|
+
import { SegmentProvider } from '../client/segment-context.js';
|
|
46
|
+
import { SegmentOutlet } from '../client/segment-outlet.js';
|
|
47
|
+
import { ChildSegmentOutlet } from '../client/child-segment-outlet.js';
|
|
48
|
+
import { ChildSegmentProvider } from '../client/child-segment-provider.js';
|
|
49
|
+
|
|
33
50
|
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
|
|
34
51
|
import type { InterceptionContext } from './pipeline.js';
|
|
35
52
|
import { shouldSkipSegment } from './state-tree-diff.js';
|
|
53
|
+
import type { HeadElement } from './metadata.js';
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Convert HeadElement descriptors to React elements for Float hoisting.
|
|
57
|
+
*
|
|
58
|
+
* React 19 Float hoists <title>, <meta>, and <link> rendered anywhere in
|
|
59
|
+
* the tree into <head> during SSR, and manages them in document.head
|
|
60
|
+
* during client-side rendering. This replaces the X-Timber-Head header
|
|
61
|
+
* side-channel and the imperative client DOM applier.
|
|
62
|
+
*/
|
|
63
|
+
export function headElementsToReact(elements: HeadElement[]): React.ReactElement {
|
|
64
|
+
const children: React.ReactElement[] = [];
|
|
65
|
+
for (let i = 0; i < elements.length; i++) {
|
|
66
|
+
const el = elements[i];
|
|
67
|
+
if (el.tag === 'title' && el.content !== undefined) {
|
|
68
|
+
children.push(createElement('title', { key: `t${i}` }, el.content));
|
|
69
|
+
} else if (el.attrs) {
|
|
70
|
+
children.push(createElement(el.tag, { key: `h${i}`, ...el.attrs }));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return createElement(Fragment, null, ...children);
|
|
74
|
+
}
|
|
75
|
+
import { loadModule } from './safe-load.js';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Replace the outermost SegmentOutlet in a partial payload with its
|
|
79
|
+
* SegmentProvider child. Walks through AccessGate wrappers (which sit
|
|
80
|
+
* outside SegmentOutlet) to find the outlet.
|
|
81
|
+
*/
|
|
82
|
+
function replaceOutermostSegmentOutlet(
|
|
83
|
+
element: React.ReactElement,
|
|
84
|
+
replacement: React.ReactElement
|
|
85
|
+
): React.ReactElement {
|
|
86
|
+
if (element.type === SegmentOutlet) {
|
|
87
|
+
return replacement;
|
|
88
|
+
}
|
|
89
|
+
// AccessGate wraps outside SegmentOutlet — recurse through it.
|
|
90
|
+
const props = element.props as Record<string, unknown>;
|
|
91
|
+
if (element.type === AccessGate && props.children) {
|
|
92
|
+
const inner = replaceOutermostSegmentOutlet(props.children as React.ReactElement, replacement);
|
|
93
|
+
/* eslint-disable react/no-children-prop -- createElement API */
|
|
94
|
+
return createElement(element.type as React.FunctionComponent<Record<string, unknown>>, {
|
|
95
|
+
...props,
|
|
96
|
+
children: inner,
|
|
97
|
+
});
|
|
98
|
+
/* eslint-enable react/no-children-prop */
|
|
99
|
+
}
|
|
100
|
+
return element;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Client Reference Detection ──────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Symbol used by React Flight to mark client references.
|
|
107
|
+
* Client references are proxy objects created by @vitejs/plugin-rsc for
|
|
108
|
+
* 'use client' modules in the RSC environment. They must be passed to
|
|
109
|
+
* createElement() — calling them as functions throws:
|
|
110
|
+
* "Unexpectedly client reference export 'default' is called on server"
|
|
111
|
+
*/
|
|
112
|
+
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
|
|
38
113
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Detect whether a component is a React client reference.
|
|
116
|
+
* Client references have $$typeof set to Symbol.for('react.client.reference')
|
|
117
|
+
* by registerClientReference() in the React Flight server runtime.
|
|
118
|
+
*
|
|
119
|
+
* Used to skip OTEL tracing wrappers that would call the component as a
|
|
120
|
+
* function. Client components must go through createElement only — they are
|
|
121
|
+
* serialized as references in the RSC Flight stream, not executed on the server.
|
|
122
|
+
*/
|
|
123
|
+
export function isClientReference(component: unknown): boolean {
|
|
124
|
+
return (
|
|
125
|
+
component != null &&
|
|
126
|
+
typeof component === 'function' &&
|
|
127
|
+
(component as unknown as Record<string, unknown>).$$typeof === CLIENT_REFERENCE_TAG
|
|
128
|
+
);
|
|
44
129
|
}
|
|
45
130
|
|
|
131
|
+
// ─── Typed Errors ────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Thrown when a defineSegmentParams codec's parse() fails.
|
|
135
|
+
* The pipeline catches this and responds with 404.
|
|
136
|
+
*/
|
|
137
|
+
export class ParamCoercionError extends Error {
|
|
138
|
+
constructor(message: string) {
|
|
139
|
+
super(message);
|
|
140
|
+
this.name = 'ParamCoercionError';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Thrown when a matched route has no page component in its leaf segment.
|
|
146
|
+
* The pipeline catches this and renders the custom 404 page.
|
|
147
|
+
*/
|
|
148
|
+
export class NoPageComponentError extends Error {
|
|
149
|
+
constructor(message: string) {
|
|
150
|
+
super(message);
|
|
151
|
+
this.name = 'NoPageComponentError';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─── Types ────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
46
157
|
/** Layout entry with component and segment. */
|
|
47
158
|
export interface LayoutComponentEntry {
|
|
48
159
|
component: (...args: unknown[]) => unknown;
|
|
@@ -53,8 +164,6 @@ export interface LayoutComponentEntry {
|
|
|
53
164
|
export interface RouteElementResult {
|
|
54
165
|
/** The React element tree (page wrapped in layouts, access gates, error boundaries). */
|
|
55
166
|
element: React.ReactElement;
|
|
56
|
-
/** Resolved head elements for metadata. */
|
|
57
|
-
headElements: HeadElement[];
|
|
58
167
|
/** Layout components loaded along the segment chain. */
|
|
59
168
|
layoutComponents: LayoutComponentEntry[];
|
|
60
169
|
/** Segments from the route match. */
|
|
@@ -70,18 +179,62 @@ export interface RouteElementResult {
|
|
|
70
179
|
skippedSegments: string[];
|
|
71
180
|
}
|
|
72
181
|
|
|
182
|
+
// ─── Module Processing Helpers ─────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Reject the legacy `generateMetadata` export with a helpful migration message.
|
|
186
|
+
* Throws if the module exports `generateMetadata` instead of `metadata`.
|
|
187
|
+
*/
|
|
188
|
+
function rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {
|
|
189
|
+
if ('generateMetadata' in mod) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`${filePath}: "generateMetadata" is not a valid export. ` +
|
|
192
|
+
`Export an async function named "metadata" instead.\n\n` +
|
|
193
|
+
` // Before\n` +
|
|
194
|
+
` export async function generateMetadata({ params }) { ... }\n\n` +
|
|
195
|
+
` // After\n` +
|
|
196
|
+
` export async function metadata() { ... }`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
73
201
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
202
|
+
* Extract and resolve metadata from a module (layout or page).
|
|
203
|
+
* Handles both static metadata objects and async metadata functions.
|
|
204
|
+
* Returns the resolved Metadata, or null if none exported.
|
|
205
|
+
*
|
|
206
|
+
* Metadata functions no longer receive { params } — they access params
|
|
207
|
+
* via getSegmentParams() from ALS, same as page/layout components.
|
|
76
208
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
209
|
+
async function extractMetadata(
|
|
210
|
+
mod: Record<string, unknown>,
|
|
211
|
+
segment: ManifestSegmentNode
|
|
212
|
+
): Promise<Metadata | null> {
|
|
213
|
+
if (typeof mod.metadata === 'function') {
|
|
214
|
+
type MetadataFn = () => Promise<Metadata>;
|
|
215
|
+
return (
|
|
216
|
+
(await withSpan(
|
|
217
|
+
'timber.metadata',
|
|
218
|
+
{ 'timber.segment': segment.segmentName ?? segment.urlPath },
|
|
219
|
+
() => (mod.metadata as MetadataFn)()
|
|
220
|
+
)) ?? null
|
|
221
|
+
);
|
|
84
222
|
}
|
|
223
|
+
if (mod.metadata) {
|
|
224
|
+
return mod.metadata as Metadata;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Extract `deferSuspenseFor` from a module and return the maximum
|
|
231
|
+
* of the current value and the module's value.
|
|
232
|
+
*/
|
|
233
|
+
function extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {
|
|
234
|
+
if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {
|
|
235
|
+
return mod.deferSuspenseFor;
|
|
236
|
+
}
|
|
237
|
+
return current;
|
|
85
238
|
}
|
|
86
239
|
|
|
87
240
|
// ─── Builder ──────────────────────────────────────────────────────────────
|
|
@@ -89,25 +242,68 @@ export class RouteSignalWithContext extends Error {
|
|
|
89
242
|
/**
|
|
90
243
|
* Build a React element tree from a matched route.
|
|
91
244
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
245
|
+
* Runs access checks eagerly to gate metadata resolution, then loads
|
|
246
|
+
* modules, resolves metadata (skipping denied segments), and constructs
|
|
247
|
+
* the element tree. DenySignal and RedirectSignal from access are stored
|
|
248
|
+
* as AccessGate verdicts for synchronous replay during render.
|
|
95
249
|
*
|
|
96
250
|
* Does NOT serialize to RSC Flight — the caller decides whether to render
|
|
97
251
|
* to a stream or use the element directly (e.g., for action revalidation).
|
|
252
|
+
*
|
|
253
|
+
* For passing segments, AccessGate re-runs access during render so that
|
|
254
|
+
* React.cache is populated for layout dedup (TIM-662). For denied
|
|
255
|
+
* segments, the pre-computed verdict is replayed — no re-execution,
|
|
256
|
+
* no metadata side effects, no head leak (TIM-1027).
|
|
98
257
|
*/
|
|
99
258
|
export async function buildRouteElement(
|
|
100
259
|
req: Request,
|
|
101
260
|
match: RouteMatch,
|
|
102
261
|
interception?: InterceptionContext,
|
|
103
|
-
clientStateTree?: Set<string> | null
|
|
262
|
+
clientStateTree?: Set<string> | null,
|
|
263
|
+
metadataRouteHashes?: Record<string, string>
|
|
104
264
|
): Promise<RouteElementResult> {
|
|
105
|
-
const segments = match.segments
|
|
265
|
+
const segments = match.segments;
|
|
266
|
+
|
|
267
|
+
// ── Access pre-pass ──────────────────────────────────────────────────
|
|
268
|
+
// Run access checks eagerly to determine which segments are denied.
|
|
269
|
+
// This gates metadata() resolution: denied segments' metadata functions
|
|
270
|
+
// are never called, preventing unauthorized side effects (DB queries)
|
|
271
|
+
// and metadata content leaking into the <head> of denied responses.
|
|
272
|
+
//
|
|
273
|
+
// Verdicts are passed to AccessGate for synchronous replay (DenySignal
|
|
274
|
+
// renders the deny page in-tree; RedirectSignal propagates). For PASSING
|
|
275
|
+
// segments, no verdict is passed — AccessGate re-runs access during
|
|
276
|
+
// renderToReadableStream to populate React.cache for layout dedup.
|
|
277
|
+
//
|
|
278
|
+
// See TIM-1027, design/04-authorization.md, design/13-security.md.
|
|
279
|
+
const accessVerdicts = new Map<number, DenySignal | RedirectSignal>();
|
|
280
|
+
let firstDeniedIndex = Infinity;
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < segments.length; i++) {
|
|
283
|
+
const segment = segments[i];
|
|
284
|
+
if (!segment.access || i >= firstDeniedIndex) continue;
|
|
106
285
|
|
|
107
|
-
|
|
108
|
-
|
|
286
|
+
try {
|
|
287
|
+
const accessMod = await loadModule(segment.access);
|
|
288
|
+
const accessFn = accessMod.default as (() => Promise<void>) | undefined;
|
|
289
|
+
if (accessFn) {
|
|
290
|
+
await withSpan(
|
|
291
|
+
'timber.access.pre',
|
|
292
|
+
{ 'timber.segment': segment.segmentName ?? 'unknown' },
|
|
293
|
+
() => accessFn()
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
} catch (error: unknown) {
|
|
297
|
+
if (error instanceof DenySignal || error instanceof RedirectSignal) {
|
|
298
|
+
accessVerdicts.set(i, error);
|
|
299
|
+
firstDeniedIndex = i;
|
|
300
|
+
} else {
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
109
305
|
|
|
110
|
-
//
|
|
306
|
+
// ── Module loading + metadata resolution ─────────────────────────────
|
|
111
307
|
const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];
|
|
112
308
|
const layoutComponents: LayoutComponentEntry[] = [];
|
|
113
309
|
let PageComponent: ((...args: unknown[]) => unknown) | null = null;
|
|
@@ -116,196 +312,199 @@ export async function buildRouteElement(
|
|
|
116
312
|
for (let i = 0; i < segments.length; i++) {
|
|
117
313
|
const segment = segments[i];
|
|
118
314
|
const isLeaf = i === segments.length - 1;
|
|
315
|
+
const isDenied = i >= firstDeniedIndex;
|
|
119
316
|
|
|
120
317
|
// Load layout
|
|
121
318
|
if (segment.layout) {
|
|
122
|
-
const mod =
|
|
319
|
+
const mod = await loadModule(segment.layout);
|
|
123
320
|
if (mod.default) {
|
|
124
321
|
layoutComponents.push({
|
|
125
322
|
component: mod.default as (...args: unknown[]) => unknown,
|
|
126
323
|
segment,
|
|
127
324
|
});
|
|
128
325
|
}
|
|
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 });
|
|
326
|
+
|
|
327
|
+
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
328
|
+
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
329
|
+
|
|
330
|
+
rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);
|
|
331
|
+
if (!isDenied) {
|
|
332
|
+
const layoutMetadata = await extractMetadata(mod, segment);
|
|
333
|
+
if (layoutMetadata) {
|
|
334
|
+
metadataEntries.push({ metadata: layoutMetadata, isPage: false });
|
|
151
335
|
}
|
|
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
336
|
}
|
|
337
|
+
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
159
338
|
}
|
|
160
339
|
|
|
161
340
|
// Load page (leaf segment only)
|
|
162
341
|
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
|
-
}
|
|
342
|
+
const mod = await loadModule(segment.page);
|
|
343
|
+
|
|
344
|
+
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
345
|
+
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
175
346
|
|
|
176
|
-
const mod = (await segment.page.load()) as Record<string, unknown>;
|
|
177
347
|
if (mod.default) {
|
|
178
348
|
PageComponent = mod.default as (...args: unknown[]) => unknown;
|
|
179
349
|
}
|
|
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 });
|
|
350
|
+
rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);
|
|
351
|
+
if (!isDenied) {
|
|
352
|
+
const pageMetadata = await extractMetadata(mod, segment);
|
|
353
|
+
if (pageMetadata) {
|
|
354
|
+
metadataEntries.push({ metadata: pageMetadata, isPage: true });
|
|
202
355
|
}
|
|
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
356
|
}
|
|
357
|
+
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
210
358
|
}
|
|
211
359
|
}
|
|
212
360
|
|
|
213
361
|
if (!PageComponent) {
|
|
214
|
-
|
|
362
|
+
const segmentInfo = segments
|
|
363
|
+
.map(
|
|
364
|
+
(s, i) =>
|
|
365
|
+
` [${i}] ${s.segmentName} (page: ${s.page ? 'yes' : 'no'}, layout: ${s.layout ? 'yes' : 'no'}, children: ${s.children?.length ?? 0})${i === segments.length - 1 ? ' ← leaf' : ''}`
|
|
366
|
+
)
|
|
367
|
+
.join('\n');
|
|
368
|
+
throw new NoPageComponentError(
|
|
369
|
+
`No page component found for route: ${new URL(req.url).pathname}\nMatched segments:\n${segmentInfo}`
|
|
370
|
+
);
|
|
215
371
|
}
|
|
216
372
|
|
|
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
|
-
}
|
|
373
|
+
// Build deny page fallback chains for each segment position.
|
|
374
|
+
// When AccessGate or PageDenyBoundary catches a DenySignal, they render
|
|
375
|
+
// the matching deny page in-tree instead of throwing into React Flight.
|
|
376
|
+
// The chain walks from the current segment outward to root, collecting
|
|
377
|
+
// status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order.
|
|
378
|
+
// See TIM-666.
|
|
379
|
+
const denyPageChains = new Map<number, DenyPageEntry[]>();
|
|
380
|
+
for (let i = 0; i < segments.length; i++) {
|
|
381
|
+
const chain = await buildDenyPageChain(segments, i);
|
|
382
|
+
if (chain.length > 0) {
|
|
383
|
+
denyPageChains.set(i, chain);
|
|
265
384
|
}
|
|
266
385
|
}
|
|
267
386
|
|
|
268
|
-
// Resolve metadata
|
|
387
|
+
// Resolve metadata and convert to React elements that React 19 Float
|
|
388
|
+
// hoists into <head> during both SSR and client rendering. This replaces
|
|
389
|
+
// the previous HeadElement descriptor system (string splice for SSR,
|
|
390
|
+
// X-Timber-Head header for RSC nav, imperative DOM updates on client).
|
|
391
|
+
// React manages <title>/<meta>/<link> lifecycle across navigations.
|
|
392
|
+
// See design/16-metadata.md, TIM-1151.
|
|
269
393
|
const resolvedMetadata = resolveMetadata(metadataEntries);
|
|
270
394
|
const headElements = renderMetadataToElements(resolvedMetadata);
|
|
271
395
|
|
|
272
|
-
// Auto-link metadata route files (icon, apple-icon, manifest)
|
|
396
|
+
// Auto-link metadata route files (icon, apple-icon, manifest, opengraph-image).
|
|
397
|
+
// opengraph-image emits both og:image and twitter:image (no separate twitter-image convention).
|
|
398
|
+
// Skip OG auto-linking when the user already declared images in metadata.
|
|
273
399
|
// See design/16-metadata.md §"Auto-Linking"
|
|
274
|
-
|
|
400
|
+
const hasUserOgImage = Boolean(resolvedMetadata.openGraph?.images);
|
|
401
|
+
const requestUrl = new URL(req.url);
|
|
402
|
+
const requestPathname = requestUrl.pathname;
|
|
403
|
+
// In dev mode, use the request origin so OG URLs resolve to localhost.
|
|
404
|
+
// In production, use metadataBase (the canonical domain).
|
|
405
|
+
const ogBase =
|
|
406
|
+
process.env.NODE_ENV !== 'production'
|
|
407
|
+
? new URL(requestUrl.origin)
|
|
408
|
+
: resolvedMetadata.metadataBase;
|
|
409
|
+
|
|
410
|
+
for (let si = 0; si < segments.length; si++) {
|
|
411
|
+
const segment = segments[si];
|
|
275
412
|
if (!segment.metadataRoutes) continue;
|
|
413
|
+
// Skip auto-linking for denied segments — same gate as metadata().
|
|
414
|
+
if (si >= firstDeniedIndex) continue;
|
|
276
415
|
for (const baseName of Object.keys(segment.metadataRoutes)) {
|
|
277
416
|
const convention = METADATA_ROUTE_CONVENTIONS[baseName];
|
|
278
417
|
if (!convention) continue;
|
|
279
418
|
// Non-nestable routes only auto-link from root
|
|
280
419
|
if (!convention.nestable && segment.urlPath !== '/') continue;
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
420
|
+
// Skip auto-linking if user already declared the image in metadata
|
|
421
|
+
if (convention.type === 'opengraph-image' && hasUserOgImage) continue;
|
|
422
|
+
// Build the href using the actual request path (not the pattern with [param]).
|
|
423
|
+
// For nestable routes, the metadata route sits under the same resolved path
|
|
424
|
+
// as the page. For root-only routes, use '/'.
|
|
425
|
+
const resolvedPrefix = convention.nestable
|
|
426
|
+
? requestPathname === '/'
|
|
427
|
+
? ''
|
|
428
|
+
: requestPathname
|
|
429
|
+
: '';
|
|
430
|
+
let href = `${resolvedPrefix}/${convention.servePath}`;
|
|
431
|
+
// Append cache-bust query param for image metadata routes
|
|
432
|
+
if (convention.type === 'opengraph-image') {
|
|
433
|
+
const metaFile = segment.metadataRoutes[baseName];
|
|
434
|
+
const fileHash = metaFile?.filePath ? metadataRouteHashes?.[metaFile.filePath] : undefined;
|
|
435
|
+
const cacheBust = fileHash ?? getDevNonce();
|
|
436
|
+
href = `${href}?${cacheBust}`;
|
|
437
|
+
}
|
|
438
|
+
// Resolve to absolute URL for og:image/twitter:image (social crawlers need full URLs)
|
|
439
|
+
if (ogBase && convention.type === 'opengraph-image') {
|
|
440
|
+
href = new URL(href, ogBase).toString();
|
|
441
|
+
}
|
|
442
|
+
for (const autoLink of getMetadataRouteAutoLink(convention.type, href)) {
|
|
443
|
+
if (autoLink.tag === 'link') {
|
|
444
|
+
const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };
|
|
445
|
+
if (autoLink.type) attrs.type = autoLink.type;
|
|
446
|
+
headElements.push({ tag: 'link', attrs });
|
|
447
|
+
} else {
|
|
448
|
+
const attrs: Record<string, string> = { content: autoLink.content };
|
|
449
|
+
if (autoLink.property) attrs.property = autoLink.property;
|
|
450
|
+
if (autoLink.name) attrs.name = autoLink.name;
|
|
451
|
+
headElements.push({ tag: 'meta', attrs });
|
|
452
|
+
}
|
|
289
453
|
}
|
|
290
454
|
}
|
|
291
455
|
}
|
|
292
456
|
|
|
457
|
+
// Convert HeadElement descriptors to React elements for Float hoisting.
|
|
458
|
+
// Placed at the page segment level so they're always re-rendered on
|
|
459
|
+
// SPA navigation (sync layouts may be skipped by segment tree diffing).
|
|
460
|
+
const metadataElement = headElementsToReact(headElements);
|
|
461
|
+
|
|
293
462
|
// Build element tree: page wrapped in layouts (innermost to outermost)
|
|
294
463
|
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
295
464
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
465
|
+
// Build the page element.
|
|
466
|
+
// Client references ('use client' pages) must NOT be called as functions —
|
|
467
|
+
// they are proxy objects that throw when invoked. They must go through
|
|
468
|
+
// createElement only, which serializes them as client references in the
|
|
469
|
+
// RSC Flight stream. OTEL tracing is skipped for client components.
|
|
470
|
+
// See TIM-627 for the original bug.
|
|
471
|
+
// Build the page element.
|
|
472
|
+
// Server component pages are wrapped in PageDenyBoundary which calls
|
|
473
|
+
// them as async functions and catches DenySignal — rendering the deny
|
|
474
|
+
// page in-tree instead of throwing into React Flight. This eliminates
|
|
475
|
+
// the second render pass for deny pages. See TIM-666.
|
|
476
|
+
//
|
|
477
|
+
// Client reference pages ('use client') can't call deny() (server-only),
|
|
478
|
+
// so they go through createElement normally — no wrapper needed.
|
|
479
|
+
const leafIndex = segments.length - 1;
|
|
480
|
+
const leafDenyPages = denyPageChains.get(leafIndex);
|
|
481
|
+
let element: React.ReactElement;
|
|
482
|
+
if (isClientReference(PageComponent)) {
|
|
483
|
+
element = h(PageComponent, {});
|
|
484
|
+
} else if (leafDenyPages && leafDenyPages.length > 0) {
|
|
485
|
+
// Server component page WITH deny page chain — wrap in PageDenyBoundary
|
|
486
|
+
element = h(PageDenyBoundary, {
|
|
487
|
+
Page: PageComponent,
|
|
488
|
+
route: match.segments[leafIndex]?.urlPath ?? '/',
|
|
489
|
+
denyPages: leafDenyPages,
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
// Server component page WITHOUT deny page chain — trace only
|
|
493
|
+
const TracedPage = async (props: Record<string, unknown>) => {
|
|
494
|
+
return withSpan(
|
|
495
|
+
'timber.page',
|
|
496
|
+
{ 'timber.route': match.segments[leafIndex]?.urlPath ?? '/' },
|
|
497
|
+
() => (PageComponent as (props: Record<string, unknown>) => unknown)(props)
|
|
498
|
+
);
|
|
499
|
+
};
|
|
500
|
+
element = h(TracedPage, {});
|
|
501
|
+
}
|
|
304
502
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
503
|
+
// Wrap page + metadata in a Fragment so Float-hoisted elements are at
|
|
504
|
+
// the page segment level (always re-rendered, never skipped by segment
|
|
505
|
+
// tree diffing). React 19 Float hoists the <title>/<meta>/<link>
|
|
506
|
+
// children into <head> regardless of where they appear in the tree.
|
|
507
|
+
element = h(Fragment, null, metadataElement, element);
|
|
309
508
|
|
|
310
509
|
// Build a lookup of layout components by segment for O(1) access.
|
|
311
510
|
const layoutBySegment = new Map(
|
|
@@ -324,7 +523,7 @@ export async function buildRouteElement(
|
|
|
324
523
|
//
|
|
325
524
|
// When clientStateTree is provided (from X-Timber-State-Tree header on
|
|
326
525
|
// client navigation), sync layouts the client already has are skipped.
|
|
327
|
-
// Access.ts
|
|
526
|
+
// Access.ts was pre-checked eagerly above for metadata gating (TIM-1027).
|
|
328
527
|
// See design/19-client-navigation.md §"X-Timber-State-Tree Header"
|
|
329
528
|
//
|
|
330
529
|
// hasRenderedLayoutBelow tracks whether a non-skipped layout has been
|
|
@@ -335,6 +534,12 @@ export async function buildRouteElement(
|
|
|
335
534
|
// Without this guard, skipping the innermost layout causes the merger
|
|
336
535
|
// to drop the layout entirely and replace it with just the page.
|
|
337
536
|
let hasRenderedLayoutBelow = false;
|
|
537
|
+
let outermostSegmentProvider: React.ReactElement | null = null;
|
|
538
|
+
// Track whether any rendered inner layout also exists in the client's
|
|
539
|
+
// state tree. This prevents cross-section skipping: e.g., navigating
|
|
540
|
+
// from /(group-a) to /dashboard shouldn't skip root "/" because the
|
|
541
|
+
// client has no mounted outlet at "/dashboard".
|
|
542
|
+
let innerRenderedInClientTree = false;
|
|
338
543
|
|
|
339
544
|
for (let i = segments.length - 1; i >= 0; i--) {
|
|
340
545
|
const segment = segments[i];
|
|
@@ -351,17 +556,43 @@ export async function buildRouteElement(
|
|
|
351
556
|
// - Route groups are never skipped because sibling groups share the
|
|
352
557
|
// same urlPath (e.g., /(marketing) and /(app) both have "/"),
|
|
353
558
|
// which would cause the wrong cached layout to be reused
|
|
559
|
+
// - At least one inner rendered layout must exist in the client's
|
|
560
|
+
// state tree, ensuring the client has a mounted outlet to receive
|
|
561
|
+
// the partial payload
|
|
354
562
|
const skip =
|
|
355
563
|
shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&
|
|
356
564
|
hasRenderedLayoutBelow &&
|
|
357
|
-
segment.segmentType !== 'group'
|
|
565
|
+
segment.segmentType !== 'group' &&
|
|
566
|
+
innerRenderedInClientTree;
|
|
358
567
|
|
|
359
568
|
if (skip) {
|
|
360
|
-
// Skip this segment
|
|
361
|
-
// Access.ts already ran in the pre-render loop (security guarantee).
|
|
569
|
+
// Skip this segment's layout/error boundaries — the client uses its cached version.
|
|
362
570
|
// Metadata was already resolved above (head elements are correct).
|
|
363
571
|
// Record for X-Timber-Skipped-Segments header (outermost first, so prepend).
|
|
364
572
|
skippedSegments.unshift(segment.urlPath);
|
|
573
|
+
|
|
574
|
+
// SECURITY: Even though the layout is skipped, AccessGate MUST still
|
|
575
|
+
// wrap the element tree. access.ts runs on every navigation regardless
|
|
576
|
+
// of cached layouts or state tree content.
|
|
577
|
+
// See design/13-security.md §"Auth always runs" (test #11).
|
|
578
|
+
if (segment.access) {
|
|
579
|
+
const accessMod = await loadModule(segment.access);
|
|
580
|
+
const accessFn = accessMod.default as (() => unknown) | undefined;
|
|
581
|
+
if (accessFn) {
|
|
582
|
+
// Pass verdict for denied/redirected segments so AccessGate replays
|
|
583
|
+
// without re-execution. Passing segments omit verdict so AccessGate
|
|
584
|
+
// re-runs access during render for React.cache population.
|
|
585
|
+
const verdict = accessVerdicts.get(i);
|
|
586
|
+
element = h(AccessGate, {
|
|
587
|
+
accessFn,
|
|
588
|
+
segmentName: segment.segmentName,
|
|
589
|
+
denyPages: denyPageChains.get(i),
|
|
590
|
+
...(verdict ? { verdict } : {}),
|
|
591
|
+
children: element,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
365
596
|
continue;
|
|
366
597
|
}
|
|
367
598
|
|
|
@@ -369,91 +600,175 @@ export async function buildRouteElement(
|
|
|
369
600
|
// a rendered layout below them and can safely be skipped.
|
|
370
601
|
if (layoutComponent) {
|
|
371
602
|
hasRenderedLayoutBelow = true;
|
|
603
|
+
// Use segmentId for the client state tree check. Route groups share
|
|
604
|
+
// their parent's urlPath (both "/"), but their segmentId includes
|
|
605
|
+
// the group name (e.g., "/(group-a)"), so the check correctly
|
|
606
|
+
// fails when the client has never visited that group.
|
|
607
|
+
const outletKey =
|
|
608
|
+
segment.segmentType === 'group'
|
|
609
|
+
? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`
|
|
610
|
+
: segment.urlPath;
|
|
611
|
+
if (clientStateTree?.has(outletKey)) {
|
|
612
|
+
innerRenderedInClientTree = true;
|
|
613
|
+
}
|
|
372
614
|
}
|
|
373
615
|
|
|
374
616
|
// Wrap with error boundaries from this segment (inside layout).
|
|
617
|
+
// Keep ALL error boundaries (including 4xx) — they're the safety net for
|
|
618
|
+
// DenySignal from nested server components that escape AccessGate/PageDenyBoundary
|
|
619
|
+
// try/catch. Status-code files must be 'use client' TSX or MDX to serialize
|
|
620
|
+
// as error boundary fallbacks. See TIM-666.
|
|
375
621
|
element = await wrapSegmentWithErrorBoundaries(segment, element, h);
|
|
376
622
|
|
|
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
|
|
623
|
+
// Wrap with layout BEFORE AccessGate — AccessGate is OUTSIDE the layout.
|
|
624
|
+
// When AccessGate denies, the layout never renders. The deny page appears
|
|
625
|
+
// at the AccessGate level, wrapped by PARENT layouts only.
|
|
626
|
+
// This prevents leaking layout UI (sidebars, nav) on denied pages.
|
|
627
|
+
// See design/04-authorization.md §"Access Failure".
|
|
398
628
|
if (layoutComponent) {
|
|
399
|
-
// Resolve parallel slots for this layout
|
|
629
|
+
// Resolve parallel slots for this layout.
|
|
630
|
+
// Compute the parent tree path so slots can build their full
|
|
631
|
+
// segment path for per-slot param storage in ALS.
|
|
632
|
+
const parentTreePath =
|
|
633
|
+
'/' +
|
|
634
|
+
segments
|
|
635
|
+
.slice(0, i + 1)
|
|
636
|
+
.map((s) => s.segmentName)
|
|
637
|
+
.filter(Boolean)
|
|
638
|
+
.join('/');
|
|
400
639
|
const slotProps: Record<string, unknown> = {};
|
|
401
640
|
const slotEntries = Object.entries(segment.slots ?? {});
|
|
402
641
|
for (const [slotName, slotNode] of slotEntries) {
|
|
403
642
|
slotProps[slotName] = await resolveSlotElement(
|
|
404
643
|
slotNode as ManifestSegmentNode,
|
|
405
644
|
match,
|
|
406
|
-
paramsPromise,
|
|
407
645
|
h,
|
|
408
|
-
interception
|
|
646
|
+
interception,
|
|
647
|
+
parentTreePath
|
|
409
648
|
);
|
|
410
649
|
}
|
|
411
650
|
|
|
412
|
-
|
|
651
|
+
// '/'.split('/') → ['', ''] (length 2), but root should be [''] (length 1).
|
|
652
|
+
// The extra empty string makes useSelectedLayoutSegment skip the first URL segment.
|
|
653
|
+
const rawPath = segment.urlPath.split('/');
|
|
654
|
+
const segmentPath = rawPath.length === 2 && rawPath[1] === '' ? [''] : rawPath;
|
|
413
655
|
const parallelRouteKeys = Object.keys(segment.slots ?? {});
|
|
414
656
|
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
// from the root "layout /".
|
|
419
|
-
const segmentForSpan = segment;
|
|
420
|
-
const layoutComponentForSpan = layoutComponent;
|
|
421
|
-
const segmentLabel =
|
|
422
|
-
segmentForSpan.segmentType === 'group'
|
|
423
|
-
? `${segmentForSpan.urlPath === '/' ? '' : segmentForSpan.urlPath}/${segmentForSpan.segmentName}`
|
|
424
|
-
: segmentForSpan.urlPath;
|
|
425
|
-
const TracedLayout = async (props: Record<string, unknown>) => {
|
|
426
|
-
return withSpan('timber.layout', { 'timber.segment': segmentLabel }, () =>
|
|
427
|
-
(layoutComponentForSpan as (props: Record<string, unknown>) => unknown)(props)
|
|
428
|
-
);
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// segmentId uniquely identifies this segment for client-side element
|
|
432
|
-
// caching. For route groups, urlPath is shared with the parent (both "/"),
|
|
433
|
-
// so we include the group name to distinguish them. Without this, the
|
|
434
|
-
// segment merger's element cache would conflate root and group elements.
|
|
657
|
+
// For route groups, urlPath is shared with the parent (both "/"),
|
|
658
|
+
// so include the group name to distinguish them. Used for both OTEL
|
|
659
|
+
// span labels and client-side element caching (segmentId).
|
|
435
660
|
const segmentId =
|
|
436
661
|
segment.segmentType === 'group'
|
|
437
662
|
? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`
|
|
438
663
|
: segment.urlPath;
|
|
439
664
|
|
|
440
|
-
|
|
665
|
+
// Build the layout element.
|
|
666
|
+
// Layouts receive <ChildSegmentOutlet /> as children — a stable client
|
|
667
|
+
// reference — instead of the varying inner content. The actual child
|
|
668
|
+
// content is provided via ChildSegmentProvider above the layout.
|
|
669
|
+
// This makes layout cache.component hits possible: the cached payload
|
|
670
|
+
// contains a deterministic children reference, not varying content.
|
|
671
|
+
// See design/45-cache-lifetimes.md §Layout Propagation.
|
|
672
|
+
const childOutlet = h(ChildSegmentOutlet, {});
|
|
673
|
+
let layoutElement: React.ReactElement;
|
|
674
|
+
if (isClientReference(layoutComponent)) {
|
|
675
|
+
layoutElement = h(layoutComponent, {
|
|
676
|
+
...slotProps,
|
|
677
|
+
children: childOutlet,
|
|
678
|
+
});
|
|
679
|
+
} else {
|
|
680
|
+
// Server component layout — wrap with OTEL tracing AND DenySignal
|
|
681
|
+
// catching. If the layout calls deny(), the signal is caught here
|
|
682
|
+
// and the matching deny page renders in-tree (same pattern as
|
|
683
|
+
// AccessGate and PageDenyBoundary). Without this, DenySignal
|
|
684
|
+
// escapes to React Flight onError and triggers the re-render
|
|
685
|
+
// fallback path. See TIM-668, design/04-authorization.md.
|
|
686
|
+
const layoutComponentRef = layoutComponent;
|
|
687
|
+
const layoutDenyPages = denyPageChains.get(i);
|
|
688
|
+
const TracedLayout = async (props: Record<string, unknown>) => {
|
|
689
|
+
try {
|
|
690
|
+
return await withSpan('timber.layout', { 'timber.segment': segmentId }, () =>
|
|
691
|
+
(layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)
|
|
692
|
+
);
|
|
693
|
+
} catch (error: unknown) {
|
|
694
|
+
if (error instanceof DenySignal && layoutDenyPages) {
|
|
695
|
+
const denyElement = renderMatchingDenyPage(layoutDenyPages, error.status, error.data);
|
|
696
|
+
if (denyElement) {
|
|
697
|
+
setDenyStatus(error.status);
|
|
698
|
+
return denyElement;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// Non-deny errors (RedirectSignal, runtime errors) propagate normally.
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
layoutElement = h(TracedLayout, {
|
|
706
|
+
...slotProps,
|
|
707
|
+
children: childOutlet,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Wrap the layout in ChildSegmentProvider so ChildSegmentOutlet
|
|
712
|
+
// (inside the layout's children) can read the actual child content.
|
|
713
|
+
layoutElement = h(ChildSegmentProvider, {
|
|
714
|
+
childContent: element,
|
|
715
|
+
children: layoutElement,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
const segmentProviderElement = h(SegmentProvider, {
|
|
441
719
|
segments: segmentPath,
|
|
442
720
|
segmentId,
|
|
443
721
|
parallelRouteKeys,
|
|
444
|
-
children:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
722
|
+
children: layoutElement,
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
element = h(SegmentOutlet, {
|
|
726
|
+
segmentPath: segmentId,
|
|
727
|
+
children: segmentProviderElement,
|
|
450
728
|
});
|
|
729
|
+
|
|
730
|
+
// Track the SegmentProvider for the outermost rendered layout.
|
|
731
|
+
// On partial navigation (segments skipped above), the client already
|
|
732
|
+
// has a SegmentOutlet mounted at this position. Sending another one
|
|
733
|
+
// in the payload causes double-wrapping and infinite context recursion.
|
|
734
|
+
// We'll strip the outermost SegmentOutlet after the loop.
|
|
735
|
+
outermostSegmentProvider = segmentProviderElement;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Wrap in AccessGate OUTSIDE the layout.
|
|
739
|
+
// If access denies, the deny page renders here — the layout above
|
|
740
|
+
// never executes. Parent layouts (from outer iterations) form the shell.
|
|
741
|
+
// See TIM-662, TIM-666, design/04-authorization.md §"Access Failure".
|
|
742
|
+
if (segment.access) {
|
|
743
|
+
const accessMod = await loadModule(segment.access);
|
|
744
|
+
const accessFn = accessMod.default as (() => unknown) | undefined;
|
|
745
|
+
if (accessFn) {
|
|
746
|
+
// Pass verdict for denied/redirected segments so AccessGate replays
|
|
747
|
+
// without re-execution. Passing segments omit verdict so AccessGate
|
|
748
|
+
// re-runs access during render for React.cache population.
|
|
749
|
+
const verdict = accessVerdicts.get(i);
|
|
750
|
+
element = h(AccessGate, {
|
|
751
|
+
accessFn,
|
|
752
|
+
segmentName: segment.segmentName,
|
|
753
|
+
denyPages: denyPageChains.get(i),
|
|
754
|
+
...(verdict ? { verdict } : {}),
|
|
755
|
+
children: element,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
451
758
|
}
|
|
452
759
|
}
|
|
453
760
|
|
|
761
|
+
// On partial navigation (some segments skipped), the client already has a
|
|
762
|
+
// SegmentOutlet mounted at the outermost non-skipped segment's position.
|
|
763
|
+
// Sending another SegmentOutlet in the payload causes double-wrapping —
|
|
764
|
+
// the inner outlet reads the same context update and recurses infinitely.
|
|
765
|
+
// Replace the outermost SegmentOutlet with just its SegmentProvider child.
|
|
766
|
+
if (skippedSegments.length > 0 && outermostSegmentProvider) {
|
|
767
|
+
element = replaceOutermostSegmentOutlet(element, outermostSegmentProvider);
|
|
768
|
+
}
|
|
769
|
+
|
|
454
770
|
return {
|
|
455
771
|
element,
|
|
456
|
-
headElements: headElements as HeadElement[],
|
|
457
772
|
layoutComponents,
|
|
458
773
|
segments,
|
|
459
774
|
deferSuspenseFor,
|