@timber-js/app 0.2.0-alpha.6 → 0.2.0-alpha.61
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/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
- package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-CT98cU9c.js +121 -0
- package/dist/_chunks/define-CT98cU9c.js.map +1 -0
- package/dist/_chunks/define-TK8C1M3x.js +279 -0
- package/dist/_chunks/define-TK8C1M3x.js.map +1 -0
- package/dist/_chunks/define-cookie-BWr_52kY.js +93 -0
- package/dist/_chunks/define-cookie-BWr_52kY.js.map +1 -0
- package/dist/_chunks/error-boundary-DpZJBCqh.js +211 -0
- package/dist/_chunks/error-boundary-DpZJBCqh.js.map +1 -0
- package/dist/_chunks/{format-DviM89f0.js → format-cX7wzEp2.js} +2 -2
- package/dist/_chunks/{format-DviM89f0.js.map → format-cX7wzEp2.js.map} +1 -1
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-Cey5DCGr.js} +129 -77
- package/dist/_chunks/interception-Cey5DCGr.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-rju2rbga.js} +97 -69
- package/dist/_chunks/request-context-rju2rbga.js.map +1 -0
- package/dist/_chunks/segment-context-CyaM1mrD.js +72 -0
- package/dist/_chunks/segment-context-CyaM1mrD.js.map +1 -0
- package/dist/_chunks/stale-reload-BSSym1MJ.js +64 -0
- package/dist/_chunks/stale-reload-BSSym1MJ.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → tracing-VYETCQsg.js} +17 -3
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-VYETCQsg.js.map} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-wEXY2JQB.js} +1 -1
- package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
- package/dist/_chunks/wrappers-BaG1bnM3.js +63 -0
- package/dist/_chunks/wrappers-BaG1bnM3.js.map +1 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +17 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -13
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +90 -20
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/register-cached-function.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +10 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/error-reconstituter.d.ts +54 -0
- package/dist/client/error-reconstituter.d.ts.map +1 -0
- package/dist/client/form.d.ts +2 -2
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +433 -252
- package/dist/client/index.js.map +1 -1
- package/dist/client/link-pending-store.d.ts +78 -0
- package/dist/client/link-pending-store.d.ts.map +1 -0
- package/dist/client/link.d.ts +23 -9
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +2 -2
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/router.d.ts +25 -3
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +36 -2
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-merger.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +63 -0
- package/dist/client/segment-outlet.d.ts.map +1 -0
- package/dist/client/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +1 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +1 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +3 -3
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.d.ts +21 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/cookies/define-cookie.d.ts +34 -13
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -83
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/index.d.ts +127 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +665 -242
- package/dist/index.js.map +1 -1
- package/dist/params/define.d.ts +100 -0
- package/dist/params/define.d.ts.map +1 -0
- package/dist/params/index.d.ts +8 -0
- package/dist/params/index.d.ts.map +1 -0
- package/dist/params/index.js +4 -0
- package/dist/plugins/adapter-build.d.ts +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +2 -2
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +3 -3
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +32 -0
- package/dist/plugins/client-chunks.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-browser-logs.d.ts +84 -0
- package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/dev-logs.d.ts +1 -1
- package/dist/plugins/dev-logs.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts +1 -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 +9 -2
- 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/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 +6 -5
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts +2 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts +2 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +16 -4
- package/dist/routing/types.d.ts.map +1 -1
- 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/search-params/codecs.d.ts +1 -1
- package/dist/search-params/define.d.ts +159 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -5
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +4 -474
- package/dist/search-params/registry.d.ts +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/server/actions.d.ts +1 -1
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +25 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/debug.d.ts +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-renderer.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -5
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +4 -0
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +4 -3
- 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/flush.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +29 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +51 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1977 -1648
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +25 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +113 -0
- package/dist/server/node-stream-transforms.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.d.ts +20 -6
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +30 -3
- 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/request-context.d.ts +65 -38
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +7 -0
- 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 +9 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +2 -2
- 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 +48 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +8 -3
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +22 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +39 -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/status-code-resolver.d.ts +1 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +36 -0
- package/dist/server/stream-utils.d.ts.map +1 -0
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +20 -13
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -3
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.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/navigation-client.d.ts +1 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- 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/package.json +17 -17
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/index.ts +5 -2
- package/src/cache/register-cached-function.ts +7 -3
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +40 -29
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +151 -99
- package/src/client/error-boundary.tsx +18 -1
- package/src/client/error-reconstituter.tsx +65 -0
- package/src/client/form.tsx +2 -2
- package/src/client/index.ts +10 -1
- package/src/client/link-pending-store.ts +136 -0
- package/src/client/link.tsx +137 -22
- package/src/client/navigation-context.ts +6 -5
- package/src/client/router.ts +117 -60
- package/src/client/rsc-fetch.ts +90 -2
- package/src/client/segment-cache.ts +1 -1
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/segment-outlet.tsx +86 -0
- package/src/client/stale-reload.ts +73 -6
- package/src/client/top-loader.tsx +10 -9
- package/src/client/transition-root.tsx +20 -2
- package/src/client/use-params.ts +4 -4
- package/src/client/use-query-states.ts +2 -2
- package/src/codec.ts +21 -0
- package/src/cookies/define-cookie.ts +71 -20
- package/src/fonts/css.ts +2 -1
- package/src/index.ts +297 -85
- package/src/params/define.ts +327 -0
- package/src/params/index.ts +28 -0
- package/src/plugins/adapter-build.ts +8 -2
- package/src/plugins/build-manifest.ts +13 -2
- package/src/plugins/build-report.ts +3 -3
- package/src/plugins/cache-transform.ts +1 -1
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-browser-logs.ts +284 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-logs.ts +1 -1
- package/src/plugins/dev-server.ts +41 -7
- package/src/plugins/entries.ts +6 -8
- package/src/plugins/fonts.ts +102 -55
- package/src/plugins/mdx.ts +1 -1
- package/src/plugins/routing.ts +57 -17
- package/src/plugins/server-action-exports.ts +1 -1
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +69 -31
- package/src/plugins/static-build.ts +10 -6
- package/src/routing/codegen.ts +109 -88
- package/src/routing/scanner.ts +86 -7
- package/src/routing/status-file-lint.ts +3 -2
- package/src/routing/types.ts +17 -4
- package/src/rsc-runtime/rsc.ts +2 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/search-params/codecs.ts +1 -1
- package/src/search-params/define.ts +518 -0
- package/src/search-params/index.ts +12 -18
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/server/access-gate.tsx +40 -9
- package/src/server/action-client.ts +8 -2
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +20 -3
- package/src/server/actions.ts +1 -1
- package/src/server/als-registry.ts +25 -4
- package/src/server/build-manifest.ts +10 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +1 -1
- package/src/server/default-logger.ts +99 -0
- package/src/server/deny-renderer.ts +5 -3
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +58 -15
- package/src/server/fallback-error.ts +29 -14
- package/src/server/flight-injection-state.ts +113 -0
- package/src/server/flight-scripts.ts +62 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +277 -117
- package/src/server/index.ts +9 -4
- package/src/server/logger.ts +44 -36
- package/src/server/node-stream-transforms.ts +509 -0
- package/src/server/pipeline-interception.ts +1 -1
- package/src/server/pipeline.ts +148 -41
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +125 -119
- package/src/server/route-element-builder.ts +107 -115
- package/src/server/route-handler.ts +2 -1
- package/src/server/route-matcher.ts +9 -2
- package/src/server/rsc-entry/api-handler.ts +8 -8
- package/src/server/rsc-entry/error-renderer.ts +286 -81
- package/src/server/rsc-entry/helpers.ts +134 -5
- package/src/server/rsc-entry/index.ts +177 -76
- package/src/server/rsc-entry/rsc-payload.ts +91 -18
- package/src/server/rsc-entry/rsc-stream.ts +74 -18
- package/src/server/rsc-entry/ssr-bridge.ts +2 -2
- package/src/server/rsc-entry/ssr-renderer.ts +152 -34
- package/src/server/slot-resolver.ts +231 -220
- package/src/server/ssr-entry.ts +211 -32
- package/src/server/ssr-render.ts +289 -67
- package/src/server/ssr-wrappers.tsx +139 -0
- package/src/server/status-code-resolver.ts +1 -1
- package/src/server/stream-utils.ts +213 -0
- package/src/server/tracing.ts +23 -0
- package/src/server/tree-builder.ts +92 -58
- package/src/server/types.ts +1 -3
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +55 -0
- package/src/shims/font-google.ts +1 -1
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +2 -1
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
- package/dist/_chunks/ssr-data-MjmprTmO.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/client/error-boundary.js.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/cookies/index.js.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.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/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/search-params/index.js.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/server/response-cache.d.ts +0 -53
- package/dist/server/response-cache.d.ts.map +0 -1
- package/src/client/link-status-provider.tsx +0 -30
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/create.ts +0 -321
- package/src/server/prerender.ts +0 -139
- package/src/server/response-cache.ts +0 -277
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* See design/29-cookies.md for cookie mutation semantics.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
14
|
-
import type { Routes } from '#/index.js';
|
|
15
13
|
import { requestContextAls, type RequestContextStore, type CookieEntry } from './als-registry.js';
|
|
16
14
|
import { isDebug } from './debug.js';
|
|
15
|
+
import { _setRawSearchParamsFn } from '../search-params/define.js';
|
|
16
|
+
import { _setRawSegmentParamsFn } from '../params/define.js';
|
|
17
17
|
|
|
18
18
|
// Re-export the ALS for framework-internal consumers that need direct access.
|
|
19
19
|
export { requestContextAls };
|
|
@@ -22,30 +22,6 @@ export { requestContextAls };
|
|
|
22
22
|
// the ALS context persists for the entire request lifecycle including
|
|
23
23
|
// async stream consumption by React's renderToReadableStream.
|
|
24
24
|
|
|
25
|
-
// ─── Cookie Signing Secrets ──────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Module-level cookie signing secrets. Index 0 is the newest (used for signing).
|
|
29
|
-
* All entries are tried for verification (key rotation support).
|
|
30
|
-
*
|
|
31
|
-
* Set by the framework at startup via `setCookieSecrets()`.
|
|
32
|
-
* See design/29-cookies.md §"Signed Cookies"
|
|
33
|
-
*/
|
|
34
|
-
let _cookieSecrets: string[] = [];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Configure the cookie signing secrets.
|
|
38
|
-
*
|
|
39
|
-
* Called by the framework during server initialization with values from
|
|
40
|
-
* `cookies.secret` or `cookies.secrets` in timber.config.ts.
|
|
41
|
-
*
|
|
42
|
-
* The first secret (index 0) is used for signing new cookies.
|
|
43
|
-
* All secrets are tried for verification (supports key rotation).
|
|
44
|
-
*/
|
|
45
|
-
export function setCookieSecrets(secrets: string[]): void {
|
|
46
|
-
_cookieSecrets = secrets.filter(Boolean);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
25
|
// ─── Public API ───────────────────────────────────────────────────────────
|
|
50
26
|
|
|
51
27
|
/**
|
|
@@ -109,12 +85,6 @@ export function cookies(): RequestCookies {
|
|
|
109
85
|
return map.size;
|
|
110
86
|
},
|
|
111
87
|
|
|
112
|
-
getSigned(name: string): string | undefined {
|
|
113
|
-
const raw = map.get(name);
|
|
114
|
-
if (!raw || _cookieSecrets.length === 0) return undefined;
|
|
115
|
-
return verifySignedCookie(raw, _cookieSecrets);
|
|
116
|
-
},
|
|
117
|
-
|
|
118
88
|
set(name: string, value: string, options?: CookieOptions): void {
|
|
119
89
|
assertMutable(store, 'set');
|
|
120
90
|
if (store.flushed) {
|
|
@@ -127,21 +97,10 @@ export function cookies(): RequestCookies {
|
|
|
127
97
|
}
|
|
128
98
|
return;
|
|
129
99
|
}
|
|
130
|
-
let storedValue = value;
|
|
131
|
-
if (options?.signed) {
|
|
132
|
-
if (_cookieSecrets.length === 0) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`[timber] cookies().set('${name}', ..., { signed: true }) requires ` +
|
|
135
|
-
`cookies.secret or cookies.secrets in timber.config.ts.`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
storedValue = signCookieValue(value, _cookieSecrets[0]);
|
|
139
|
-
}
|
|
140
100
|
const opts = { ...DEFAULT_COOKIE_OPTIONS, ...options };
|
|
141
|
-
store.cookieJar.set(name, { name, value
|
|
142
|
-
// Read-your-own-writes: update the parsed cookies map
|
|
143
|
-
|
|
144
|
-
map.set(name, storedValue);
|
|
101
|
+
store.cookieJar.set(name, { name, value, options: opts });
|
|
102
|
+
// Read-your-own-writes: update the parsed cookies map
|
|
103
|
+
map.set(name, value);
|
|
145
104
|
},
|
|
146
105
|
|
|
147
106
|
delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {
|
|
@@ -190,41 +149,112 @@ export function cookies(): RequestCookies {
|
|
|
190
149
|
}
|
|
191
150
|
|
|
192
151
|
/**
|
|
193
|
-
* Returns a Promise resolving to the current request's
|
|
152
|
+
* Returns a Promise resolving to the current request's raw URLSearchParams.
|
|
153
|
+
*
|
|
154
|
+
* For typed, parsed search params, import the definition from params.ts
|
|
155
|
+
* and call `.load()` or `.parse()`:
|
|
194
156
|
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* import { searchParams } from './params'
|
|
159
|
+
* const parsed = await searchParams.load()
|
|
160
|
+
* ```
|
|
199
161
|
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
162
|
+
* Or explicitly:
|
|
163
|
+
*
|
|
164
|
+
* ```ts
|
|
165
|
+
* import { rawSearchParams } from '@timber-js/app/server'
|
|
166
|
+
* import { searchParams } from './params'
|
|
167
|
+
* const parsed = searchParams.parse(await rawSearchParams())
|
|
168
|
+
* ```
|
|
202
169
|
*
|
|
203
170
|
* Throws if called outside a request context.
|
|
204
171
|
*/
|
|
205
|
-
export function
|
|
206
|
-
export function searchParams(): Promise<URLSearchParams | Record<string, unknown>>;
|
|
207
|
-
export function searchParams(): Promise<URLSearchParams | Record<string, unknown>> {
|
|
172
|
+
export function rawSearchParams(): Promise<URLSearchParams> {
|
|
208
173
|
const store = requestContextAls.getStore();
|
|
209
174
|
if (!store) {
|
|
210
175
|
throw new Error(
|
|
211
|
-
'[timber]
|
|
176
|
+
'[timber] rawSearchParams() called outside of a request context. ' +
|
|
212
177
|
'It can only be used in middleware, access checks, server components, and server actions.'
|
|
213
178
|
);
|
|
214
179
|
}
|
|
215
180
|
return store.searchParamsPromise;
|
|
216
181
|
}
|
|
217
182
|
|
|
183
|
+
// Eagerly register rawSearchParams with the search-params module so
|
|
184
|
+
// searchParams.load() can call it synchronously without a dynamic import.
|
|
185
|
+
// Dynamic imports lose ALS context in React's RSC Flight renderer,
|
|
186
|
+
// breaking rawSearchParams() in parallel slot pages. See TIM-523.
|
|
187
|
+
_setRawSearchParamsFn(rawSearchParams);
|
|
188
|
+
|
|
189
|
+
// Eagerly register rawSegmentParams with the params module so
|
|
190
|
+
// segmentParams.load() can call it synchronously without a dynamic import.
|
|
191
|
+
// Same pattern as search params — dynamic imports lose ALS context. See TIM-523.
|
|
192
|
+
_setRawSegmentParamsFn(rawSegmentParams);
|
|
193
|
+
|
|
218
194
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
195
|
+
* Returns a Promise resolving to the current request's coerced segment params.
|
|
196
|
+
*
|
|
197
|
+
* Segment params are set by the pipeline after route matching and param
|
|
198
|
+
* coercion (via params.ts codecs). When no params.ts exists, values are
|
|
199
|
+
* raw strings. When codecs are defined, values are already coerced
|
|
200
|
+
* (e.g., `id` is a `number` if `defineSegmentParams({ id: z.coerce.number() })`).
|
|
201
|
+
*
|
|
202
|
+
* This is the primary way page and layout components access route params:
|
|
203
|
+
*
|
|
204
|
+
* ```ts
|
|
205
|
+
* import { rawSegmentParams } from '@timber-js/app/server'
|
|
206
|
+
*
|
|
207
|
+
* export default async function Page() {
|
|
208
|
+
* const { slug } = await rawSegmentParams()
|
|
209
|
+
* // ...
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* Throws if called outside a request context.
|
|
222
214
|
*/
|
|
223
|
-
export function
|
|
215
|
+
export function rawSegmentParams(): Promise<Record<string, string | string[]>> {
|
|
224
216
|
const store = requestContextAls.getStore();
|
|
225
|
-
if (store) {
|
|
226
|
-
|
|
217
|
+
if (!store) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
'[timber] rawSegmentParams() called outside of a request context. ' +
|
|
220
|
+
'It can only be used in middleware, access checks, server components, and server actions.'
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (!store.segmentParamsPromise) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
'[timber] rawSegmentParams() called before route matching completed. ' +
|
|
226
|
+
'Segment params are not available until after the route is matched.'
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return store.segmentParamsPromise;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Set the segment params promise on the current request context.
|
|
234
|
+
* Called by the pipeline after route matching and param coercion.
|
|
235
|
+
*
|
|
236
|
+
* @internal — framework use only
|
|
237
|
+
*/
|
|
238
|
+
export function setSegmentParams(params: Record<string, string | string[]>): void {
|
|
239
|
+
const store = requestContextAls.getStore();
|
|
240
|
+
if (!store) {
|
|
241
|
+
throw new Error('[timber] setSegmentParams() called outside of a request context.');
|
|
227
242
|
}
|
|
243
|
+
store.segmentParamsPromise = Promise.resolve(params);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Returns the raw search string from the current request URL (e.g. "?foo=bar").
|
|
248
|
+
* Synchronous — safe for use in `redirect()` which throws synchronously.
|
|
249
|
+
*
|
|
250
|
+
* Returns empty string if called outside a request context (non-throwing for
|
|
251
|
+
* use in redirect's optional preserveSearchParams path).
|
|
252
|
+
*
|
|
253
|
+
* @internal — used by redirect() for preserveSearchParams support.
|
|
254
|
+
*/
|
|
255
|
+
export function getRequestSearchString(): string {
|
|
256
|
+
const store = requestContextAls.getStore();
|
|
257
|
+
return store?.searchString ?? '';
|
|
228
258
|
}
|
|
229
259
|
|
|
230
260
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
@@ -257,12 +287,6 @@ export interface CookieOptions {
|
|
|
257
287
|
sameSite?: 'strict' | 'lax' | 'none';
|
|
258
288
|
/** Partitioned (CHIPS) — isolate cookie per top-level site. Default: false. */
|
|
259
289
|
partitioned?: boolean;
|
|
260
|
-
/**
|
|
261
|
-
* Sign the cookie value with HMAC-SHA256 for integrity verification.
|
|
262
|
-
* Requires `cookies.secret` or `cookies.secrets` in timber.config.ts.
|
|
263
|
-
* See design/29-cookies.md §"Signed Cookies".
|
|
264
|
-
*/
|
|
265
|
-
signed?: boolean;
|
|
266
290
|
}
|
|
267
291
|
|
|
268
292
|
const DEFAULT_COOKIE_OPTIONS: CookieOptions = {
|
|
@@ -287,14 +311,6 @@ export interface RequestCookies {
|
|
|
287
311
|
getAll(): Array<{ name: string; value: string }>;
|
|
288
312
|
/** Number of cookies. */
|
|
289
313
|
readonly size: number;
|
|
290
|
-
/**
|
|
291
|
-
* Get a signed cookie value, verifying its HMAC-SHA256 signature.
|
|
292
|
-
* Returns undefined if the cookie is missing, the signature is invalid,
|
|
293
|
-
* or no secrets are configured. Never throws.
|
|
294
|
-
*
|
|
295
|
-
* See design/29-cookies.md §"Signed Cookies"
|
|
296
|
-
*/
|
|
297
|
-
getSigned(name: string): string | undefined;
|
|
298
314
|
/** Set a cookie. Only available in mutable contexts (middleware, actions, route handlers). */
|
|
299
315
|
set(name: string, value: string, options?: CookieOptions): void;
|
|
300
316
|
/** Delete a cookie. Only available in mutable contexts. */
|
|
@@ -316,11 +332,13 @@ export interface RequestCookies {
|
|
|
316
332
|
*/
|
|
317
333
|
export function runWithRequestContext<T>(req: Request, fn: () => T): T {
|
|
318
334
|
const originalCopy = new Headers(req.headers);
|
|
335
|
+
const parsedUrl = new URL(req.url);
|
|
319
336
|
const store: RequestContextStore = {
|
|
320
337
|
headers: freezeHeaders(req.headers),
|
|
321
338
|
originalHeaders: originalCopy,
|
|
322
339
|
cookieHeader: req.headers.get('cookie') ?? '',
|
|
323
|
-
searchParamsPromise: Promise.resolve(
|
|
340
|
+
searchParamsPromise: Promise.resolve(parsedUrl.searchParams),
|
|
341
|
+
searchString: parsedUrl.search,
|
|
324
342
|
cookieJar: new Map(),
|
|
325
343
|
flushed: false,
|
|
326
344
|
mutableContext: false,
|
|
@@ -354,6 +372,35 @@ export function markResponseFlushed(): void {
|
|
|
354
372
|
}
|
|
355
373
|
}
|
|
356
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Build a Map of cookie name → value reflecting the current request's
|
|
377
|
+
* read-your-own-writes state. Includes incoming cookies plus any
|
|
378
|
+
* mutations from cookies().set() / cookies().delete() in the same request.
|
|
379
|
+
*
|
|
380
|
+
* Used by SSR renderers to populate NavContext.cookies so that
|
|
381
|
+
* useCookie()'s server snapshot matches the actual response state.
|
|
382
|
+
*
|
|
383
|
+
* See design/29-cookies.md §"Read-Your-Own-Writes"
|
|
384
|
+
* See design/triage/TIM-441-cookie-api-triage.md §4
|
|
385
|
+
*/
|
|
386
|
+
export function getCookiesForSsr(): Map<string, string> {
|
|
387
|
+
const store = requestContextAls.getStore();
|
|
388
|
+
if (!store) {
|
|
389
|
+
throw new Error('[timber] getCookiesForSsr() called outside of a request context.');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Trigger lazy parsing if not yet done
|
|
393
|
+
if (!store.parsedCookies) {
|
|
394
|
+
store.parsedCookies = parseCookieHeader(store.cookieHeader);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// The parsedCookies map already reflects read-your-own-writes:
|
|
398
|
+
// - cookies().set() updates the map via map.set(name, value)
|
|
399
|
+
// - cookies().delete() removes from the map via map.delete(name)
|
|
400
|
+
// Return a copy so callers can't mutate the internal map.
|
|
401
|
+
return new Map(store.parsedCookies);
|
|
402
|
+
}
|
|
403
|
+
|
|
357
404
|
/**
|
|
358
405
|
* Collect all Set-Cookie headers from the cookie jar.
|
|
359
406
|
* Called by the framework at flush time to apply cookies to the response.
|
|
@@ -467,47 +514,6 @@ function parseCookieHeader(header: string): Map<string, string> {
|
|
|
467
514
|
return map;
|
|
468
515
|
}
|
|
469
516
|
|
|
470
|
-
// ─── Cookie Signing ──────────────────────────────────────────────────────
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Sign a cookie value with HMAC-SHA256.
|
|
474
|
-
* Returns `value.hex_signature`.
|
|
475
|
-
*/
|
|
476
|
-
function signCookieValue(value: string, secret: string): string {
|
|
477
|
-
const signature = createHmac('sha256', secret).update(value).digest('hex');
|
|
478
|
-
return `${value}.${signature}`;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Verify a signed cookie value against an array of secrets.
|
|
483
|
-
* Returns the original value if any secret produces a matching signature,
|
|
484
|
-
* or undefined if none match. Uses timing-safe comparison.
|
|
485
|
-
*
|
|
486
|
-
* The signed format is `value.hex_signature` — split at the last `.`.
|
|
487
|
-
*/
|
|
488
|
-
function verifySignedCookie(raw: string, secrets: string[]): string | undefined {
|
|
489
|
-
const lastDot = raw.lastIndexOf('.');
|
|
490
|
-
if (lastDot <= 0 || lastDot === raw.length - 1) return undefined;
|
|
491
|
-
|
|
492
|
-
const value = raw.slice(0, lastDot);
|
|
493
|
-
const signature = raw.slice(lastDot + 1);
|
|
494
|
-
|
|
495
|
-
// Hex-encoded SHA-256 is always 64 chars
|
|
496
|
-
if (signature.length !== 64) return undefined;
|
|
497
|
-
|
|
498
|
-
const signatureBuffer = Buffer.from(signature, 'hex');
|
|
499
|
-
// If the hex decode produced fewer bytes, the signature was not valid hex
|
|
500
|
-
if (signatureBuffer.length !== 32) return undefined;
|
|
501
|
-
|
|
502
|
-
for (const secret of secrets) {
|
|
503
|
-
const expected = createHmac('sha256', secret).update(value).digest();
|
|
504
|
-
if (timingSafeEqual(expected, signatureBuffer)) {
|
|
505
|
-
return value;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return undefined;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
517
|
/** Serialize a CookieEntry into a Set-Cookie header value. */
|
|
512
518
|
function serializeCookieEntry(entry: CookieEntry): string {
|
|
513
519
|
const parts = [`${entry.name}=${entry.value}`];
|
|
@@ -27,13 +27,25 @@ import { METADATA_ROUTE_CONVENTIONS, getMetadataRouteAutoLink } from './metadata
|
|
|
27
27
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
28
28
|
import { AccessGate } from './access-gate.js';
|
|
29
29
|
import { resolveSlotElement } from './slot-resolver.js';
|
|
30
|
-
import { SegmentProvider } from '
|
|
31
|
-
|
|
32
|
-
import type { SearchParamsDefinition } from '#/search-params/create.js';
|
|
30
|
+
import { SegmentProvider } from '../client/segment-context.js';
|
|
31
|
+
|
|
33
32
|
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
|
|
34
33
|
import type { InterceptionContext } from './pipeline.js';
|
|
35
34
|
import { shouldSkipSegment } from './state-tree-diff.js';
|
|
36
35
|
|
|
36
|
+
// ─── Param Coercion Error ─────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Thrown when a defineSegmentParams codec's parse() fails.
|
|
40
|
+
* The pipeline catches this and responds with 404.
|
|
41
|
+
*/
|
|
42
|
+
export class ParamCoercionError extends Error {
|
|
43
|
+
constructor(message: string) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'ParamCoercionError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
38
50
|
|
|
39
51
|
/** Head element for client-side metadata updates. */
|
|
@@ -84,6 +96,64 @@ export class RouteSignalWithContext extends Error {
|
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
|
|
99
|
+
// ─── Module Processing Helpers ─────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Reject the legacy `generateMetadata` export with a helpful migration message.
|
|
103
|
+
* Throws if the module exports `generateMetadata` instead of `metadata`.
|
|
104
|
+
*/
|
|
105
|
+
function rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {
|
|
106
|
+
if ('generateMetadata' in mod) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`${filePath}: "generateMetadata" is not a valid export. ` +
|
|
109
|
+
`Export an async function named "metadata" instead.\n\n` +
|
|
110
|
+
` // Before\n` +
|
|
111
|
+
` export async function generateMetadata({ params }) { ... }\n\n` +
|
|
112
|
+
` // After\n` +
|
|
113
|
+
` export async function metadata() { ... }`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract and resolve metadata from a module (layout or page).
|
|
120
|
+
* Handles both static metadata objects and async metadata functions.
|
|
121
|
+
* Returns the resolved Metadata, or null if none exported.
|
|
122
|
+
*
|
|
123
|
+
* Metadata functions no longer receive { params } — they access params
|
|
124
|
+
* via rawSegmentParams() from ALS, same as page/layout components.
|
|
125
|
+
*/
|
|
126
|
+
async function extractMetadata(
|
|
127
|
+
mod: Record<string, unknown>,
|
|
128
|
+
segment: ManifestSegmentNode
|
|
129
|
+
): Promise<Metadata | null> {
|
|
130
|
+
if (typeof mod.metadata === 'function') {
|
|
131
|
+
type MetadataFn = () => Promise<Metadata>;
|
|
132
|
+
return (
|
|
133
|
+
(await withSpan(
|
|
134
|
+
'timber.metadata',
|
|
135
|
+
{ 'timber.segment': segment.segmentName ?? segment.urlPath },
|
|
136
|
+
() => (mod.metadata as MetadataFn)()
|
|
137
|
+
)) ?? null
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (mod.metadata) {
|
|
141
|
+
return mod.metadata as Metadata;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Extract `deferSuspenseFor` from a module and return the maximum
|
|
148
|
+
* of the current value and the module's value.
|
|
149
|
+
*/
|
|
150
|
+
function extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {
|
|
151
|
+
if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {
|
|
152
|
+
return mod.deferSuspenseFor;
|
|
153
|
+
}
|
|
154
|
+
return current;
|
|
155
|
+
}
|
|
156
|
+
|
|
87
157
|
// ─── Builder ──────────────────────────────────────────────────────────────
|
|
88
158
|
|
|
89
159
|
/**
|
|
@@ -104,9 +174,6 @@ export async function buildRouteElement(
|
|
|
104
174
|
): Promise<RouteElementResult> {
|
|
105
175
|
const segments = match.segments as unknown as ManifestSegmentNode[];
|
|
106
176
|
|
|
107
|
-
// Params are passed as a Promise to match Next.js 15+ convention.
|
|
108
|
-
const paramsPromise = Promise.resolve(match.params);
|
|
109
|
-
|
|
110
177
|
// Load all modules along the segment chain
|
|
111
178
|
const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];
|
|
112
179
|
const layoutComponents: LayoutComponentEntry[] = [];
|
|
@@ -126,87 +193,34 @@ export async function buildRouteElement(
|
|
|
126
193
|
segment,
|
|
127
194
|
});
|
|
128
195
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
` // After\n` +
|
|
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 });
|
|
151
|
-
}
|
|
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;
|
|
196
|
+
|
|
197
|
+
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
198
|
+
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
199
|
+
|
|
200
|
+
rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);
|
|
201
|
+
const layoutMetadata = await extractMetadata(mod, segment);
|
|
202
|
+
if (layoutMetadata) {
|
|
203
|
+
metadataEntries.push({ metadata: layoutMetadata, isPage: false });
|
|
158
204
|
}
|
|
205
|
+
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
159
206
|
}
|
|
160
207
|
|
|
161
208
|
// Load page (leaf segment only)
|
|
162
209
|
if (isLeaf && segment.page) {
|
|
163
|
-
// Load and apply search-params.ts definition before rendering so
|
|
164
|
-
// searchParams() from @timber-js/app/server returns parsed typed values.
|
|
165
|
-
if (segment.searchParams) {
|
|
166
|
-
const spMod = (await segment.searchParams.load()) as {
|
|
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
|
-
}
|
|
175
|
-
|
|
176
210
|
const mod = (await segment.page.load()) as Record<string, unknown>;
|
|
211
|
+
|
|
212
|
+
// Param coercion is handled in the pipeline (Stage 2c) before
|
|
213
|
+
// middleware and rendering. See coerceSegmentParams() in pipeline.ts.
|
|
214
|
+
|
|
177
215
|
if (mod.default) {
|
|
178
216
|
PageComponent = mod.default as (...args: unknown[]) => unknown;
|
|
179
217
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
`${filePath}: "generateMetadata" is not a valid export. ` +
|
|
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 });
|
|
202
|
-
}
|
|
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;
|
|
218
|
+
rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);
|
|
219
|
+
const pageMetadata = await extractMetadata(mod, segment);
|
|
220
|
+
if (pageMetadata) {
|
|
221
|
+
metadataEntries.push({ metadata: pageMetadata, isPage: true });
|
|
209
222
|
}
|
|
223
|
+
deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
|
|
210
224
|
}
|
|
211
225
|
}
|
|
212
226
|
|
|
@@ -227,7 +241,7 @@ export async function buildRouteElement(
|
|
|
227
241
|
if (segment.access) {
|
|
228
242
|
const accessMod = (await segment.access.load()) as Record<string, unknown>;
|
|
229
243
|
const accessFn = accessMod.default as
|
|
230
|
-
| ((ctx: { params: Record<string, string | string[]
|
|
244
|
+
| ((ctx: { params: Record<string, string | string[]> }) => unknown)
|
|
231
245
|
| undefined;
|
|
232
246
|
if (accessFn) {
|
|
233
247
|
try {
|
|
@@ -236,7 +250,7 @@ export async function buildRouteElement(
|
|
|
236
250
|
{ 'timber.segment': segment.segmentName ?? 'unknown' },
|
|
237
251
|
async () => {
|
|
238
252
|
try {
|
|
239
|
-
await accessFn({ params: match.params
|
|
253
|
+
await accessFn({ params: match.params });
|
|
240
254
|
await setSpanAttribute('timber.result', 'pass');
|
|
241
255
|
accessVerdicts.set(si, 'pass');
|
|
242
256
|
} catch (error) {
|
|
@@ -302,10 +316,7 @@ export async function buildRouteElement(
|
|
|
302
316
|
);
|
|
303
317
|
};
|
|
304
318
|
|
|
305
|
-
let element = h(TracedPage, {
|
|
306
|
-
params: paramsPromise,
|
|
307
|
-
searchParams: {},
|
|
308
|
-
});
|
|
319
|
+
let element = h(TracedPage, {});
|
|
309
320
|
|
|
310
321
|
// Build a lookup of layout components by segment for O(1) access.
|
|
311
322
|
const layoutBySegment = new Map(
|
|
@@ -352,12 +363,7 @@ export async function buildRouteElement(
|
|
|
352
363
|
// same urlPath (e.g., /(marketing) and /(app) both have "/"),
|
|
353
364
|
// which would cause the wrong cached layout to be reused
|
|
354
365
|
const skip =
|
|
355
|
-
shouldSkipSegment(
|
|
356
|
-
segment.urlPath,
|
|
357
|
-
layoutComponent,
|
|
358
|
-
isLeaf,
|
|
359
|
-
clientStateTree ?? null
|
|
360
|
-
) &&
|
|
366
|
+
shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&
|
|
361
367
|
hasRenderedLayoutBelow &&
|
|
362
368
|
segment.segmentType !== 'group';
|
|
363
369
|
|
|
@@ -385,13 +391,11 @@ export async function buildRouteElement(
|
|
|
385
391
|
if (segment.access) {
|
|
386
392
|
const accessMod = (await segment.access.load()) as Record<string, unknown>;
|
|
387
393
|
const accessFn = accessMod.default as
|
|
388
|
-
| ((ctx: { params: Record<string, string | string[]
|
|
394
|
+
| ((ctx: { params: Record<string, string | string[]> }) => unknown)
|
|
389
395
|
| undefined;
|
|
390
396
|
if (accessFn) {
|
|
391
397
|
element = h(AccessGate, {
|
|
392
398
|
accessFn,
|
|
393
|
-
params: match.params,
|
|
394
|
-
searchParams: {},
|
|
395
399
|
segmentName: segment.segmentName,
|
|
396
400
|
verdict: accessVerdicts.get(i),
|
|
397
401
|
children: element,
|
|
@@ -408,7 +412,6 @@ export async function buildRouteElement(
|
|
|
408
412
|
slotProps[slotName] = await resolveSlotElement(
|
|
409
413
|
slotNode as ManifestSegmentNode,
|
|
410
414
|
match,
|
|
411
|
-
paramsPromise,
|
|
412
415
|
h,
|
|
413
416
|
interception
|
|
414
417
|
);
|
|
@@ -417,39 +420,28 @@ export async function buildRouteElement(
|
|
|
417
420
|
const segmentPath = segment.urlPath.split('/');
|
|
418
421
|
const parallelRouteKeys = Object.keys(segment.slots ?? {});
|
|
419
422
|
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
// from the root "layout /".
|
|
424
|
-
const segmentForSpan = segment;
|
|
425
|
-
const layoutComponentForSpan = layoutComponent;
|
|
426
|
-
const segmentLabel =
|
|
427
|
-
segmentForSpan.segmentType === 'group'
|
|
428
|
-
? `${segmentForSpan.urlPath === '/' ? '' : segmentForSpan.urlPath}/${segmentForSpan.segmentName}`
|
|
429
|
-
: segmentForSpan.urlPath;
|
|
430
|
-
const TracedLayout = async (props: Record<string, unknown>) => {
|
|
431
|
-
return withSpan('timber.layout', { 'timber.segment': segmentLabel }, () =>
|
|
432
|
-
(layoutComponentForSpan as (props: Record<string, unknown>) => unknown)(props)
|
|
433
|
-
);
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
// segmentId uniquely identifies this segment for client-side element
|
|
437
|
-
// caching. For route groups, urlPath is shared with the parent (both "/"),
|
|
438
|
-
// so we include the group name to distinguish them. Without this, the
|
|
439
|
-
// segment merger's element cache would conflate root and group elements.
|
|
423
|
+
// For route groups, urlPath is shared with the parent (both "/"),
|
|
424
|
+
// so include the group name to distinguish them. Used for both OTEL
|
|
425
|
+
// span labels and client-side element caching (segmentId).
|
|
440
426
|
const segmentId =
|
|
441
427
|
segment.segmentType === 'group'
|
|
442
428
|
? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`
|
|
443
429
|
: segment.urlPath;
|
|
444
430
|
|
|
431
|
+
// Wrap the layout component in an OTEL span
|
|
432
|
+
const layoutComponentRef = layoutComponent;
|
|
433
|
+
const TracedLayout = async (props: Record<string, unknown>) => {
|
|
434
|
+
return withSpan('timber.layout', { 'timber.segment': segmentId }, () =>
|
|
435
|
+
(layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)
|
|
436
|
+
);
|
|
437
|
+
};
|
|
438
|
+
|
|
445
439
|
element = h(SegmentProvider, {
|
|
446
440
|
segments: segmentPath,
|
|
447
441
|
segmentId,
|
|
448
442
|
parallelRouteKeys,
|
|
449
443
|
children: h(TracedLayout, {
|
|
450
444
|
...slotProps,
|
|
451
|
-
params: paramsPromise,
|
|
452
|
-
searchParams: {},
|
|
453
445
|
children: element,
|
|
454
446
|
}),
|
|
455
447
|
});
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { RouteContext } from './types.js';
|
|
12
|
+
import { logRouteError } from './logger.js';
|
|
12
13
|
|
|
13
14
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
14
15
|
|
|
@@ -122,7 +123,7 @@ async function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Res
|
|
|
122
123
|
const res = await handler(ctx);
|
|
123
124
|
return mergeResponseHeaders(res, ctx.headers);
|
|
124
125
|
} catch (error) {
|
|
125
|
-
|
|
126
|
+
logRouteError({ method: ctx.req.method, path: new URL(ctx.req.url).pathname, error });
|
|
126
127
|
return new Response(null, { status: 500 });
|
|
127
128
|
}
|
|
128
129
|
}
|