@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
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { n as __exportAll } from "./chunk-DYhsFzuS.js";
|
|
2
|
+
import { t as isDebug } from "./debug-ECi_61pb.js";
|
|
3
|
+
import { r as requestContextAls } from "./als-registry-Ba7URUIn.js";
|
|
4
|
+
import { t as _setRawSearchParamsFn } from "./define-TK8C1M3x.js";
|
|
5
|
+
import { t as _setRawSegmentParamsFn } from "./define-CT98cU9c.js";
|
|
4
6
|
//#region src/server/request-context.ts
|
|
5
7
|
/**
|
|
6
8
|
* Request Context — per-request ALS store for headers() and cookies().
|
|
@@ -13,26 +15,20 @@ import { createHmac, timingSafeEqual } from "node:crypto";
|
|
|
13
15
|
* and design/11-platform.md §"AsyncLocalStorage".
|
|
14
16
|
* See design/29-cookies.md for cookie mutation semantics.
|
|
15
17
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* The first secret (index 0) is used for signing new cookies.
|
|
31
|
-
* All secrets are tried for verification (supports key rotation).
|
|
32
|
-
*/
|
|
33
|
-
function setCookieSecrets(secrets) {
|
|
34
|
-
_cookieSecrets = secrets.filter(Boolean);
|
|
35
|
-
}
|
|
18
|
+
var request_context_exports = /* @__PURE__ */ __exportAll({
|
|
19
|
+
applyRequestHeaderOverlay: () => applyRequestHeaderOverlay,
|
|
20
|
+
cookies: () => cookies,
|
|
21
|
+
getRequestSearchString: () => getRequestSearchString,
|
|
22
|
+
getSetCookieHeaders: () => getSetCookieHeaders,
|
|
23
|
+
headers: () => headers,
|
|
24
|
+
markResponseFlushed: () => markResponseFlushed,
|
|
25
|
+
rawSearchParams: () => rawSearchParams,
|
|
26
|
+
rawSegmentParams: () => rawSegmentParams,
|
|
27
|
+
requestContextAls: () => requestContextAls,
|
|
28
|
+
runWithRequestContext: () => runWithRequestContext,
|
|
29
|
+
setMutableCookieContext: () => setMutableCookieContext,
|
|
30
|
+
setSegmentParams: () => setSegmentParams
|
|
31
|
+
});
|
|
36
32
|
/**
|
|
37
33
|
* Returns a read-only view of the current request's headers.
|
|
38
34
|
*
|
|
@@ -80,32 +76,22 @@ function cookies() {
|
|
|
80
76
|
get size() {
|
|
81
77
|
return map.size;
|
|
82
78
|
},
|
|
83
|
-
getSigned(name) {
|
|
84
|
-
const raw = map.get(name);
|
|
85
|
-
if (!raw || _cookieSecrets.length === 0) return void 0;
|
|
86
|
-
return verifySignedCookie(raw, _cookieSecrets);
|
|
87
|
-
},
|
|
88
79
|
set(name, value, options) {
|
|
89
80
|
assertMutable(store, "set");
|
|
90
81
|
if (store.flushed) {
|
|
91
82
|
if (isDebug()) console.warn(`[timber] warn: cookies().set('${name}') called after response headers were committed.\n The cookie will NOT be sent. Move cookie mutations to middleware.ts, a server action,\n or a route.ts handler.`);
|
|
92
83
|
return;
|
|
93
84
|
}
|
|
94
|
-
let storedValue = value;
|
|
95
|
-
if (options?.signed) {
|
|
96
|
-
if (_cookieSecrets.length === 0) throw new Error(`[timber] cookies().set('${name}', ..., { signed: true }) requires cookies.secret or cookies.secrets in timber.config.ts.`);
|
|
97
|
-
storedValue = signCookieValue(value, _cookieSecrets[0]);
|
|
98
|
-
}
|
|
99
85
|
const opts = {
|
|
100
86
|
...DEFAULT_COOKIE_OPTIONS,
|
|
101
87
|
...options
|
|
102
88
|
};
|
|
103
89
|
store.cookieJar.set(name, {
|
|
104
90
|
name,
|
|
105
|
-
value
|
|
91
|
+
value,
|
|
106
92
|
options: opts
|
|
107
93
|
});
|
|
108
|
-
map.set(name,
|
|
94
|
+
map.set(name, value);
|
|
109
95
|
},
|
|
110
96
|
delete(name, options) {
|
|
111
97
|
assertMutable(store, "delete");
|
|
@@ -145,19 +131,83 @@ function cookies() {
|
|
|
145
131
|
}
|
|
146
132
|
};
|
|
147
133
|
}
|
|
148
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Returns a Promise resolving to the current request's raw URLSearchParams.
|
|
136
|
+
*
|
|
137
|
+
* For typed, parsed search params, import the definition from params.ts
|
|
138
|
+
* and call `.load()` or `.parse()`:
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* import { searchParams } from './params'
|
|
142
|
+
* const parsed = await searchParams.load()
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* Or explicitly:
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { rawSearchParams } from '@timber-js/app/server'
|
|
149
|
+
* import { searchParams } from './params'
|
|
150
|
+
* const parsed = searchParams.parse(await rawSearchParams())
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* Throws if called outside a request context.
|
|
154
|
+
*/
|
|
155
|
+
function rawSearchParams() {
|
|
149
156
|
const store = requestContextAls.getStore();
|
|
150
|
-
if (!store) throw new Error("[timber]
|
|
157
|
+
if (!store) throw new Error("[timber] rawSearchParams() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
151
158
|
return store.searchParamsPromise;
|
|
152
159
|
}
|
|
160
|
+
_setRawSearchParamsFn(rawSearchParams);
|
|
161
|
+
_setRawSegmentParamsFn(rawSegmentParams);
|
|
153
162
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
163
|
+
* Returns a Promise resolving to the current request's coerced segment params.
|
|
164
|
+
*
|
|
165
|
+
* Segment params are set by the pipeline after route matching and param
|
|
166
|
+
* coercion (via params.ts codecs). When no params.ts exists, values are
|
|
167
|
+
* raw strings. When codecs are defined, values are already coerced
|
|
168
|
+
* (e.g., `id` is a `number` if `defineSegmentParams({ id: z.coerce.number() })`).
|
|
169
|
+
*
|
|
170
|
+
* This is the primary way page and layout components access route params:
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { rawSegmentParams } from '@timber-js/app/server'
|
|
174
|
+
*
|
|
175
|
+
* export default async function Page() {
|
|
176
|
+
* const { slug } = await rawSegmentParams()
|
|
177
|
+
* // ...
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* Throws if called outside a request context.
|
|
182
|
+
*/
|
|
183
|
+
function rawSegmentParams() {
|
|
184
|
+
const store = requestContextAls.getStore();
|
|
185
|
+
if (!store) throw new Error("[timber] rawSegmentParams() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
|
|
186
|
+
if (!store.segmentParamsPromise) throw new Error("[timber] rawSegmentParams() called before route matching completed. Segment params are not available until after the route is matched.");
|
|
187
|
+
return store.segmentParamsPromise;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Set the segment params promise on the current request context.
|
|
191
|
+
* Called by the pipeline after route matching and param coercion.
|
|
192
|
+
*
|
|
193
|
+
* @internal — framework use only
|
|
157
194
|
*/
|
|
158
|
-
function
|
|
195
|
+
function setSegmentParams(params) {
|
|
159
196
|
const store = requestContextAls.getStore();
|
|
160
|
-
if (store)
|
|
197
|
+
if (!store) throw new Error("[timber] setSegmentParams() called outside of a request context.");
|
|
198
|
+
store.segmentParamsPromise = Promise.resolve(params);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Returns the raw search string from the current request URL (e.g. "?foo=bar").
|
|
202
|
+
* Synchronous — safe for use in `redirect()` which throws synchronously.
|
|
203
|
+
*
|
|
204
|
+
* Returns empty string if called outside a request context (non-throwing for
|
|
205
|
+
* use in redirect's optional preserveSearchParams path).
|
|
206
|
+
*
|
|
207
|
+
* @internal — used by redirect() for preserveSearchParams support.
|
|
208
|
+
*/
|
|
209
|
+
function getRequestSearchString() {
|
|
210
|
+
return requestContextAls.getStore()?.searchString ?? "";
|
|
161
211
|
}
|
|
162
212
|
var DEFAULT_COOKIE_OPTIONS = {
|
|
163
213
|
path: "/",
|
|
@@ -174,11 +224,13 @@ var DEFAULT_COOKIE_OPTIONS = {
|
|
|
174
224
|
*/
|
|
175
225
|
function runWithRequestContext(req, fn) {
|
|
176
226
|
const originalCopy = new Headers(req.headers);
|
|
227
|
+
const parsedUrl = new URL(req.url);
|
|
177
228
|
const store = {
|
|
178
229
|
headers: freezeHeaders(req.headers),
|
|
179
230
|
originalHeaders: originalCopy,
|
|
180
231
|
cookieHeader: req.headers.get("cookie") ?? "",
|
|
181
|
-
searchParamsPromise: Promise.resolve(
|
|
232
|
+
searchParamsPromise: Promise.resolve(parsedUrl.searchParams),
|
|
233
|
+
searchString: parsedUrl.search,
|
|
182
234
|
cookieJar: /* @__PURE__ */ new Map(),
|
|
183
235
|
flushed: false,
|
|
184
236
|
mutableContext: false
|
|
@@ -286,30 +338,6 @@ function parseCookieHeader(header) {
|
|
|
286
338
|
}
|
|
287
339
|
return map;
|
|
288
340
|
}
|
|
289
|
-
/**
|
|
290
|
-
* Sign a cookie value with HMAC-SHA256.
|
|
291
|
-
* Returns `value.hex_signature`.
|
|
292
|
-
*/
|
|
293
|
-
function signCookieValue(value, secret) {
|
|
294
|
-
return `${value}.${createHmac("sha256", secret).update(value).digest("hex")}`;
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Verify a signed cookie value against an array of secrets.
|
|
298
|
-
* Returns the original value if any secret produces a matching signature,
|
|
299
|
-
* or undefined if none match. Uses timing-safe comparison.
|
|
300
|
-
*
|
|
301
|
-
* The signed format is `value.hex_signature` — split at the last `.`.
|
|
302
|
-
*/
|
|
303
|
-
function verifySignedCookie(raw, secrets) {
|
|
304
|
-
const lastDot = raw.lastIndexOf(".");
|
|
305
|
-
if (lastDot <= 0 || lastDot === raw.length - 1) return void 0;
|
|
306
|
-
const value = raw.slice(0, lastDot);
|
|
307
|
-
const signature = raw.slice(lastDot + 1);
|
|
308
|
-
if (signature.length !== 64) return void 0;
|
|
309
|
-
const signatureBuffer = Buffer.from(signature, "hex");
|
|
310
|
-
if (signatureBuffer.length !== 32) return void 0;
|
|
311
|
-
for (const secret of secrets) if (timingSafeEqual(createHmac("sha256", secret).update(value).digest(), signatureBuffer)) return value;
|
|
312
|
-
}
|
|
313
341
|
/** Serialize a CookieEntry into a Set-Cookie header value. */
|
|
314
342
|
function serializeCookieEntry(entry) {
|
|
315
343
|
const parts = [`${entry.name}=${entry.value}`];
|
|
@@ -325,6 +353,6 @@ function serializeCookieEntry(entry) {
|
|
|
325
353
|
return parts.join("; ");
|
|
326
354
|
}
|
|
327
355
|
//#endregion
|
|
328
|
-
export {
|
|
356
|
+
export { headers as a, rawSegmentParams as c, setMutableCookieContext as d, setSegmentParams as f, getSetCookieHeaders as i, request_context_exports as l, cookies as n, markResponseFlushed as o, getRequestSearchString as r, rawSearchParams as s, applyRequestHeaderOverlay as t, runWithRequestContext as u };
|
|
329
357
|
|
|
330
|
-
//# sourceMappingURL=request-context-
|
|
358
|
+
//# sourceMappingURL=request-context-rju2rbga.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context-rju2rbga.js","names":[],"sources":["../../src/server/request-context.ts"],"sourcesContent":["/**\n * Request Context — per-request ALS store for headers() and cookies().\n *\n * Follows the same pattern as tracing.ts: a module-level AsyncLocalStorage\n * instance, public accessor functions that throw outside request scope,\n * and a framework-internal `runWithRequestContext()` to establish scope.\n *\n * See design/04-authorization.md §\"AccessContext does not include cookies or headers\"\n * and design/11-platform.md §\"AsyncLocalStorage\".\n * See design/29-cookies.md for cookie mutation semantics.\n */\n\nimport { requestContextAls, type RequestContextStore, type CookieEntry } from './als-registry.js';\nimport { isDebug } from './debug.js';\nimport { _setRawSearchParamsFn } from '../search-params/define.js';\nimport { _setRawSegmentParamsFn } from '../params/define.js';\n\n// Re-export the ALS for framework-internal consumers that need direct access.\nexport { requestContextAls };\n\n// No fallback needed — we use enterWith() instead of run() to ensure\n// the ALS context persists for the entire request lifecycle including\n// async stream consumption by React's renderToReadableStream.\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a read-only view of the current request's headers.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n */\nexport function headers(): ReadonlyHeaders {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] headers() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.headers;\n}\n\n/**\n * Returns a cookie accessor for the current request.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n *\n * Read methods (.get, .has, .getAll) are always available and reflect\n * read-your-own-writes from .set() calls in the same request.\n *\n * Mutation methods (.set, .delete, .clear) are only available in mutable\n * contexts (middleware.ts, server actions, route.ts handlers). Calling them\n * in read-only contexts (access.ts, server components) throws.\n *\n * See design/29-cookies.md\n */\nexport function cookies(): RequestCookies {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] cookies() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n // Parse cookies lazily on first access\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n const map = store.parsedCookies;\n return {\n get(name: string): string | undefined {\n return map.get(name);\n },\n has(name: string): boolean {\n return map.has(name);\n },\n getAll(): Array<{ name: string; value: string }> {\n return Array.from(map.entries()).map(([name, value]) => ({ name, value }));\n },\n get size(): number {\n return map.size;\n },\n\n set(name: string, value: string, options?: CookieOptions): void {\n assertMutable(store, 'set');\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: cookies().set('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts = { ...DEFAULT_COOKIE_OPTIONS, ...options };\n store.cookieJar.set(name, { name, value, options: opts });\n // Read-your-own-writes: update the parsed cookies map\n map.set(name, value);\n },\n\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {\n assertMutable(store, 'delete');\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: cookies().delete('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be deleted. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts: CookieOptions = {\n ...DEFAULT_COOKIE_OPTIONS,\n ...options,\n maxAge: 0,\n expires: new Date(0),\n };\n store.cookieJar.set(name, { name, value: '', options: opts });\n // Remove from read view\n map.delete(name);\n },\n\n clear(): void {\n assertMutable(store, 'clear');\n if (store.flushed) return;\n // Delete every incoming cookie\n for (const name of Array.from(map.keys())) {\n store.cookieJar.set(name, {\n name,\n value: '',\n options: { ...DEFAULT_COOKIE_OPTIONS, maxAge: 0, expires: new Date(0) },\n });\n }\n map.clear();\n },\n\n toString(): string {\n return Array.from(map.entries())\n .map(([name, value]) => `${name}=${value}`)\n .join('; ');\n },\n };\n}\n\n/**\n * Returns a Promise resolving to the current request's raw URLSearchParams.\n *\n * For typed, parsed search params, import the definition from params.ts\n * and call `.load()` or `.parse()`:\n *\n * ```ts\n * import { searchParams } from './params'\n * const parsed = await searchParams.load()\n * ```\n *\n * Or explicitly:\n *\n * ```ts\n * import { rawSearchParams } from '@timber-js/app/server'\n * import { searchParams } from './params'\n * const parsed = searchParams.parse(await rawSearchParams())\n * ```\n *\n * Throws if called outside a request context.\n */\nexport function rawSearchParams(): Promise<URLSearchParams> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] rawSearchParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.searchParamsPromise;\n}\n\n// Eagerly register rawSearchParams with the search-params module so\n// searchParams.load() can call it synchronously without a dynamic import.\n// Dynamic imports lose ALS context in React's RSC Flight renderer,\n// breaking rawSearchParams() in parallel slot pages. See TIM-523.\n_setRawSearchParamsFn(rawSearchParams);\n\n// Eagerly register rawSegmentParams with the params module so\n// segmentParams.load() can call it synchronously without a dynamic import.\n// Same pattern as search params — dynamic imports lose ALS context. See TIM-523.\n_setRawSegmentParamsFn(rawSegmentParams);\n\n/**\n * Returns a Promise resolving to the current request's coerced segment params.\n *\n * Segment params are set by the pipeline after route matching and param\n * coercion (via params.ts codecs). When no params.ts exists, values are\n * raw strings. When codecs are defined, values are already coerced\n * (e.g., `id` is a `number` if `defineSegmentParams({ id: z.coerce.number() })`).\n *\n * This is the primary way page and layout components access route params:\n *\n * ```ts\n * import { rawSegmentParams } from '@timber-js/app/server'\n *\n * export default async function Page() {\n * const { slug } = await rawSegmentParams()\n * // ...\n * }\n * ```\n *\n * Throws if called outside a request context.\n */\nexport function rawSegmentParams(): Promise<Record<string, string | string[]>> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] rawSegmentParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n if (!store.segmentParamsPromise) {\n throw new Error(\n '[timber] rawSegmentParams() called before route matching completed. ' +\n 'Segment params are not available until after the route is matched.'\n );\n }\n return store.segmentParamsPromise;\n}\n\n/**\n * Set the segment params promise on the current request context.\n * Called by the pipeline after route matching and param coercion.\n *\n * @internal — framework use only\n */\nexport function setSegmentParams(params: Record<string, string | string[]>): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] setSegmentParams() called outside of a request context.');\n }\n store.segmentParamsPromise = Promise.resolve(params);\n}\n\n/**\n * Returns the raw search string from the current request URL (e.g. \"?foo=bar\").\n * Synchronous — safe for use in `redirect()` which throws synchronously.\n *\n * Returns empty string if called outside a request context (non-throwing for\n * use in redirect's optional preserveSearchParams path).\n *\n * @internal — used by redirect() for preserveSearchParams support.\n */\nexport function getRequestSearchString(): string {\n const store = requestContextAls.getStore();\n return store?.searchString ?? '';\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Read-only Headers interface. The standard Headers class is mutable;\n * this type narrows it to read-only methods. The underlying object is\n * still a Headers instance, but user code should not mutate it.\n */\nexport type ReadonlyHeaders = Pick<\n Headers,\n 'get' | 'has' | 'entries' | 'keys' | 'values' | 'forEach' | typeof Symbol.iterator\n>;\n\n/** Options for setting a cookie. See design/29-cookies.md. */\nexport interface CookieOptions {\n /** Domain scope. Default: omitted (current domain only). */\n domain?: string;\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Expiration date. Mutually exclusive with maxAge. */\n expires?: Date;\n /** Max age in seconds. Mutually exclusive with expires. */\n maxAge?: number;\n /** Prevent client-side JS access. Default: true. */\n httpOnly?: boolean;\n /** Only send over HTTPS. Default: true. */\n secure?: boolean;\n /** Cross-site request policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Partitioned (CHIPS) — isolate cookie per top-level site. Default: false. */\n partitioned?: boolean;\n}\n\nconst DEFAULT_COOKIE_OPTIONS: CookieOptions = {\n path: '/',\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n};\n\n/**\n * Cookie accessor returned by `cookies()`.\n *\n * Read methods are always available. Mutation methods throw in read-only\n * contexts (access.ts, server components).\n */\nexport interface RequestCookies {\n /** Get a cookie value by name. Returns undefined if not present. */\n get(name: string): string | undefined;\n /** Check if a cookie exists. */\n has(name: string): boolean;\n /** Get all cookies as an array of { name, value } pairs. */\n getAll(): Array<{ name: string; value: string }>;\n /** Number of cookies. */\n readonly size: number;\n /** Set a cookie. Only available in mutable contexts (middleware, actions, route handlers). */\n set(name: string, value: string, options?: CookieOptions): void;\n /** Delete a cookie. Only available in mutable contexts. */\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void;\n /** Delete all cookies. Only available in mutable contexts. */\n clear(): void;\n /** Serialize cookies as a Cookie header string. */\n toString(): string;\n}\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Run a callback within a request context. Used by the pipeline to establish\n * per-request ALS scope so that `headers()` and `cookies()` work.\n *\n * @param req - The incoming Request object.\n * @param fn - The function to run within the request context.\n */\nexport function runWithRequestContext<T>(req: Request, fn: () => T): T {\n const originalCopy = new Headers(req.headers);\n const parsedUrl = new URL(req.url);\n const store: RequestContextStore = {\n headers: freezeHeaders(req.headers),\n originalHeaders: originalCopy,\n cookieHeader: req.headers.get('cookie') ?? '',\n searchParamsPromise: Promise.resolve(parsedUrl.searchParams),\n searchString: parsedUrl.search,\n cookieJar: new Map(),\n flushed: false,\n mutableContext: false,\n };\n return requestContextAls.run(store, fn);\n}\n\n/**\n * Enable cookie mutation for the current context. Called by the framework\n * when entering middleware.ts, server actions, or route.ts handlers.\n *\n * See design/29-cookies.md §\"Context Tracking\"\n */\nexport function setMutableCookieContext(mutable: boolean): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.mutableContext = mutable;\n }\n}\n\n/**\n * Mark the response as flushed (headers committed). After this point,\n * cookie mutations log a warning instead of throwing.\n *\n * See design/29-cookies.md §\"Streaming Constraint: Post-Flush Cookie Warning\"\n */\nexport function markResponseFlushed(): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.flushed = true;\n }\n}\n\n/**\n * Build a Map of cookie name → value reflecting the current request's\n * read-your-own-writes state. Includes incoming cookies plus any\n * mutations from cookies().set() / cookies().delete() in the same request.\n *\n * Used by SSR renderers to populate NavContext.cookies so that\n * useCookie()'s server snapshot matches the actual response state.\n *\n * See design/29-cookies.md §\"Read-Your-Own-Writes\"\n * See design/triage/TIM-441-cookie-api-triage.md §4\n */\nexport function getCookiesForSsr(): Map<string, string> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] getCookiesForSsr() called outside of a request context.');\n }\n\n // Trigger lazy parsing if not yet done\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n // The parsedCookies map already reflects read-your-own-writes:\n // - cookies().set() updates the map via map.set(name, value)\n // - cookies().delete() removes from the map via map.delete(name)\n // Return a copy so callers can't mutate the internal map.\n return new Map(store.parsedCookies);\n}\n\n/**\n * Collect all Set-Cookie headers from the cookie jar.\n * Called by the framework at flush time to apply cookies to the response.\n *\n * Returns an array of serialized Set-Cookie header values.\n */\nexport function getSetCookieHeaders(): string[] {\n const store = requestContextAls.getStore();\n if (!store) return [];\n return Array.from(store.cookieJar.values()).map(serializeCookieEntry);\n}\n\n/**\n * Apply middleware-injected request headers to the current request context.\n *\n * Called by the pipeline after middleware.ts runs. Merges overlay headers\n * on top of the original request headers so downstream code (access.ts,\n * server components, server actions) sees them via `headers()`.\n *\n * The original request headers are never mutated — a new frozen Headers\n * object is created with the overlay applied on top.\n *\n * See design/07-routing.md §\"Request Header Injection\"\n */\nexport function applyRequestHeaderOverlay(overlay: Headers): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] applyRequestHeaderOverlay() called outside of a request context.');\n }\n\n // Check if the overlay has any headers — skip if empty\n let hasOverlay = false;\n overlay.forEach(() => {\n hasOverlay = true;\n });\n if (!hasOverlay) return;\n\n // Merge: start with original headers, overlay on top\n const merged = new Headers(store.originalHeaders);\n overlay.forEach((value, key) => {\n merged.set(key, value);\n });\n store.headers = freezeHeaders(merged);\n}\n\n// ─── Read-Only Headers ────────────────────────────────────────────────────\n\nconst MUTATING_METHODS = new Set(['set', 'append', 'delete']);\n\n/**\n * Wrap a Headers object in a Proxy that throws on mutating methods.\n * Object.freeze doesn't work on Headers (native internal slots), so we\n * intercept property access and reject set/append/delete at runtime.\n *\n * Read methods (get, has, entries, etc.) must be bound to the underlying\n * Headers instance because they access private #headersList slots.\n */\nfunction freezeHeaders(source: Headers): Headers {\n const copy = new Headers(source);\n return new Proxy(copy, {\n get(target, prop) {\n if (typeof prop === 'string' && MUTATING_METHODS.has(prop)) {\n return () => {\n throw new Error(\n `[timber] headers() returns a read-only Headers object. ` +\n `Calling .${prop}() is not allowed. ` +\n `Use ctx.requestHeaders in middleware to inject headers for downstream components.`\n );\n };\n }\n const value = Reflect.get(target, prop);\n // Bind methods to the real Headers instance so private slot access works\n if (typeof value === 'function') {\n return value.bind(target);\n }\n return value;\n },\n });\n}\n\n// ─── Cookie Helpers ───────────────────────────────────────────────────────\n\n/** Throw if cookie mutation is attempted in a read-only context. */\nfunction assertMutable(store: RequestContextStore, method: string): void {\n if (!store.mutableContext) {\n throw new Error(\n `[timber] cookies().${method}() cannot be called in this context.\\n` +\n ` Set cookies in middleware.ts, server actions, or route.ts handlers.`\n );\n }\n}\n\n/**\n * Parse a Cookie header string into a Map of name → value pairs.\n * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.\n */\nfunction parseCookieHeader(header: string): Map<string, string> {\n const map = new Map<string, string>();\n if (!header) return map;\n\n for (const pair of header.split(';')) {\n const eqIndex = pair.indexOf('=');\n if (eqIndex === -1) continue;\n const name = pair.slice(0, eqIndex).trim();\n const value = pair.slice(eqIndex + 1).trim();\n if (name) {\n map.set(name, value);\n }\n }\n\n return map;\n}\n\n/** Serialize a CookieEntry into a Set-Cookie header value. */\nfunction serializeCookieEntry(entry: CookieEntry): string {\n const parts = [`${entry.name}=${entry.value}`];\n const opts = entry.options;\n\n if (opts.domain) parts.push(`Domain=${opts.domain}`);\n if (opts.path) parts.push(`Path=${opts.path}`);\n if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`);\n if (opts.httpOnly) parts.push('HttpOnly');\n if (opts.secure) parts.push('Secure');\n if (opts.sameSite) {\n parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase()}${opts.sameSite.slice(1)}`);\n }\n if (opts.partitioned) parts.push('Partitioned');\n\n return parts.join('; ');\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createContext, createElement, useContext, useMemo } from "react";
|
|
2
|
+
//#region src/shared/merge-search-params.ts
|
|
3
|
+
/**
|
|
4
|
+
* Shared utility for merging preserved search params into a target URL.
|
|
5
|
+
*
|
|
6
|
+
* Used by both <Link> (client) and redirect() (server). Extracted to a shared
|
|
7
|
+
* module to avoid importing client code ('use client') from server modules.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Merge preserved search params from the current URL into a target href.
|
|
11
|
+
*
|
|
12
|
+
* When `preserve` is `true`, all current search params are merged.
|
|
13
|
+
* When `preserve` is a `string[]`, only the named params are merged.
|
|
14
|
+
*
|
|
15
|
+
* The target href's own search params take precedence — preserved params
|
|
16
|
+
* are only added if the target doesn't already define them.
|
|
17
|
+
*
|
|
18
|
+
* @param targetHref - The resolved target href (may already contain query string)
|
|
19
|
+
* @param currentSearch - The current URL's search string (e.g. "?private=access&page=2")
|
|
20
|
+
* @param preserve - `true` to preserve all, or `string[]` to preserve specific params
|
|
21
|
+
* @returns The target href with preserved search params merged in
|
|
22
|
+
*/
|
|
23
|
+
function mergePreservedSearchParams(targetHref, currentSearch, preserve) {
|
|
24
|
+
const currentParams = new URLSearchParams(currentSearch);
|
|
25
|
+
if (currentParams.size === 0) return targetHref;
|
|
26
|
+
const hashIdx = targetHref.indexOf("#");
|
|
27
|
+
const hrefWithoutHash = hashIdx >= 0 ? targetHref.slice(0, hashIdx) : targetHref;
|
|
28
|
+
const hash = hashIdx >= 0 ? targetHref.slice(hashIdx) : "";
|
|
29
|
+
const qIdx = hrefWithoutHash.indexOf("?");
|
|
30
|
+
const targetPath = qIdx >= 0 ? hrefWithoutHash.slice(0, qIdx) : hrefWithoutHash;
|
|
31
|
+
const targetParams = new URLSearchParams(qIdx >= 0 ? hrefWithoutHash.slice(qIdx + 1) : "");
|
|
32
|
+
const merged = new URLSearchParams(targetParams);
|
|
33
|
+
for (const [key, value] of currentParams) if (!targetParams.has(key)) {
|
|
34
|
+
if (preserve === true || preserve.includes(key)) merged.append(key, value);
|
|
35
|
+
}
|
|
36
|
+
const qs = merged.toString();
|
|
37
|
+
return (qs ? `${targetPath}?${qs}` : targetPath) + hash;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/client/segment-context.ts
|
|
41
|
+
/**
|
|
42
|
+
* Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.
|
|
43
|
+
*
|
|
44
|
+
* Each layout in the segment tree is wrapped with a SegmentProvider that stores
|
|
45
|
+
* the URL segments from root to the current layout level. The hooks read this
|
|
46
|
+
* context to determine which child segments are active below the calling layout.
|
|
47
|
+
*
|
|
48
|
+
* The context value is intentionally minimal: just the segment path array and
|
|
49
|
+
* parallel route keys. No internal cache details are exposed.
|
|
50
|
+
*
|
|
51
|
+
* Design docs: design/19-client-navigation.md, design/14-ecosystem.md
|
|
52
|
+
*/
|
|
53
|
+
var SegmentContext = createContext(null);
|
|
54
|
+
/** Read the segment context. Returns null if no provider is above this component. */
|
|
55
|
+
function useSegmentContext() {
|
|
56
|
+
return useContext(SegmentContext);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wraps each layout to provide segment position context.
|
|
60
|
+
* Injected by rsc-entry.ts during element tree construction.
|
|
61
|
+
*/
|
|
62
|
+
function SegmentProvider({ segments, segmentId: _segmentId, parallelRouteKeys, children }) {
|
|
63
|
+
const value = useMemo(() => ({
|
|
64
|
+
segments,
|
|
65
|
+
parallelRouteKeys
|
|
66
|
+
}), [segments.join("/"), parallelRouteKeys.join(",")]);
|
|
67
|
+
return createElement(SegmentContext.Provider, { value }, children);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { useSegmentContext as n, mergePreservedSearchParams as r, SegmentProvider as t };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=segment-context-CyaM1mrD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment-context-CyaM1mrD.js","names":[],"sources":["../../src/shared/merge-search-params.ts","../../src/client/segment-context.ts"],"sourcesContent":["/**\n * Shared utility for merging preserved search params into a target URL.\n *\n * Used by both <Link> (client) and redirect() (server). Extracted to a shared\n * module to avoid importing client code ('use client') from server modules.\n */\n\n/**\n * Merge preserved search params from the current URL into a target href.\n *\n * When `preserve` is `true`, all current search params are merged.\n * When `preserve` is a `string[]`, only the named params are merged.\n *\n * The target href's own search params take precedence — preserved params\n * are only added if the target doesn't already define them.\n *\n * @param targetHref - The resolved target href (may already contain query string)\n * @param currentSearch - The current URL's search string (e.g. \"?private=access&page=2\")\n * @param preserve - `true` to preserve all, or `string[]` to preserve specific params\n * @returns The target href with preserved search params merged in\n */\nexport function mergePreservedSearchParams(\n targetHref: string,\n currentSearch: string,\n preserve: true | string[]\n): string {\n const currentParams = new URLSearchParams(currentSearch);\n if (currentParams.size === 0) return targetHref;\n\n // Split hash fragment from target before processing query params.\n // Hash must come after query string: /path?query=value#hash\n const hashIdx = targetHref.indexOf('#');\n const hrefWithoutHash = hashIdx >= 0 ? targetHref.slice(0, hashIdx) : targetHref;\n const hash = hashIdx >= 0 ? targetHref.slice(hashIdx) : '';\n\n // Split target into path and existing query\n const qIdx = hrefWithoutHash.indexOf('?');\n const targetPath = qIdx >= 0 ? hrefWithoutHash.slice(0, qIdx) : hrefWithoutHash;\n const targetParams = new URLSearchParams(qIdx >= 0 ? hrefWithoutHash.slice(qIdx + 1) : '');\n\n // Collect params to preserve (that aren't already in the target)\n const merged = new URLSearchParams(targetParams);\n for (const [key, value] of currentParams) {\n // Only preserve if: (a) not already in target, and (b) included in preserve list\n if (!targetParams.has(key)) {\n if (preserve === true || preserve.includes(key)) {\n merged.append(key, value);\n }\n }\n }\n\n const qs = merged.toString();\n const pathWithQuery = qs ? `${targetPath}?${qs}` : targetPath;\n return pathWithQuery + hash;\n}\n","/**\n * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.\n *\n * Each layout in the segment tree is wrapped with a SegmentProvider that stores\n * the URL segments from root to the current layout level. The hooks read this\n * context to determine which child segments are active below the calling layout.\n *\n * The context value is intentionally minimal: just the segment path array and\n * parallel route keys. No internal cache details are exposed.\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { createContext, useContext, createElement, useMemo } from 'react';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface SegmentContextValue {\n /** URL segments from root to this layout (e.g. ['', 'dashboard', 'settings']) */\n segments: string[];\n /** Parallel route slot keys available at this layout level (e.g. ['sidebar', 'modal']) */\n parallelRouteKeys: string[];\n}\n\n// ─── Context ─────────────────────────────────────────────────────\n\nconst SegmentContext = createContext<SegmentContextValue | null>(null);\n\n/** Read the segment context. Returns null if no provider is above this component. */\nexport function useSegmentContext(): SegmentContextValue | null {\n return useContext(SegmentContext);\n}\n\n// ─── Provider ────────────────────────────────────────────────────\n\ninterface SegmentProviderProps {\n segments: string[];\n /**\n * Unique identifier for this segment, used by the client-side segment\n * merger for element caching. For route groups this includes the group\n * name (e.g., \"/(marketing)\") since groups share their parent's urlPath.\n * Falls back to the reconstructed path from `segments` if not provided.\n */\n segmentId?: string;\n parallelRouteKeys: string[];\n children: React.ReactNode;\n}\n\n/**\n * Wraps each layout to provide segment position context.\n * Injected by rsc-entry.ts during element tree construction.\n */\nexport function SegmentProvider({\n segments,\n segmentId: _segmentId,\n parallelRouteKeys,\n children,\n}: SegmentProviderProps) {\n const value = useMemo(\n () => ({ segments, parallelRouteKeys }),\n // segments and parallelRouteKeys are static per layout — they don't change\n // across navigations. The layout's position in the tree is fixed.\n // Intentionally using derived keys — segments/parallelRouteKeys are static per layout\n [segments.join('/'), parallelRouteKeys.join(',')]\n );\n return createElement(SegmentContext.Provider, { value }, children);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,2BACd,YACA,eACA,UACQ;CACR,MAAM,gBAAgB,IAAI,gBAAgB,cAAc;AACxD,KAAI,cAAc,SAAS,EAAG,QAAO;CAIrC,MAAM,UAAU,WAAW,QAAQ,IAAI;CACvC,MAAM,kBAAkB,WAAW,IAAI,WAAW,MAAM,GAAG,QAAQ,GAAG;CACtE,MAAM,OAAO,WAAW,IAAI,WAAW,MAAM,QAAQ,GAAG;CAGxD,MAAM,OAAO,gBAAgB,QAAQ,IAAI;CACzC,MAAM,aAAa,QAAQ,IAAI,gBAAgB,MAAM,GAAG,KAAK,GAAG;CAChE,MAAM,eAAe,IAAI,gBAAgB,QAAQ,IAAI,gBAAgB,MAAM,OAAO,EAAE,GAAG,GAAG;CAG1F,MAAM,SAAS,IAAI,gBAAgB,aAAa;AAChD,MAAK,MAAM,CAAC,KAAK,UAAU,cAEzB,KAAI,CAAC,aAAa,IAAI,IAAI;MACpB,aAAa,QAAQ,SAAS,SAAS,IAAI,CAC7C,QAAO,OAAO,KAAK,MAAM;;CAK/B,MAAM,KAAK,OAAO,UAAU;AAE5B,SADsB,KAAK,GAAG,WAAW,GAAG,OAAO,cAC5B;;;;;;;;;;;;;;;;ACzBzB,IAAM,iBAAiB,cAA0C,KAAK;;AAGtE,SAAgB,oBAAgD;AAC9D,QAAO,WAAW,eAAe;;;;;;AAsBnC,SAAgB,gBAAgB,EAC9B,UACA,WAAW,YACX,mBACA,YACuB;CACvB,MAAM,QAAQ,eACL;EAAE;EAAU;EAAmB,GAItC,CAAC,SAAS,KAAK,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC,CAClD;AACD,QAAO,cAAc,eAAe,UAAU,EAAE,OAAO,EAAE,SAAS"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/client/stale-reload.ts
|
|
2
|
+
/**
|
|
3
|
+
* Stale Client Reference Reload
|
|
4
|
+
*
|
|
5
|
+
* When a new deployment ships updated bundles, clients running stale
|
|
6
|
+
* JavaScript may encounter "Could not find the module" errors during
|
|
7
|
+
* RSC Flight stream decoding. This happens because the RSC payload
|
|
8
|
+
* references module IDs from the new bundle that don't exist in the
|
|
9
|
+
* old client bundle.
|
|
10
|
+
*
|
|
11
|
+
* This module detects these specific errors and triggers a full page
|
|
12
|
+
* reload so the browser fetches the new bundle. A sessionStorage flag
|
|
13
|
+
* guards against infinite reload loops.
|
|
14
|
+
*
|
|
15
|
+
* See: LOCAL-332
|
|
16
|
+
*/
|
|
17
|
+
var RELOAD_FLAG_KEY = "__timber_stale_reload";
|
|
18
|
+
/**
|
|
19
|
+
* In-memory fallback counter for environments where sessionStorage is
|
|
20
|
+
* unavailable (private browsing, storage full, extension interference).
|
|
21
|
+
* Incremented each time triggerStaleReload() falls into the catch path.
|
|
22
|
+
* If the counter exceeds 0 on a subsequent call, the reload is suppressed
|
|
23
|
+
* to prevent an infinite loop. Resets naturally on page load (module
|
|
24
|
+
* re-evaluates) and can be manually reset via clearStaleReloadFlag().
|
|
25
|
+
*/
|
|
26
|
+
var memoryReloadCount = 0;
|
|
27
|
+
/**
|
|
28
|
+
* Trigger a full page reload to pick up new bundles.
|
|
29
|
+
*
|
|
30
|
+
* Sets a sessionStorage flag before reloading. If the flag is already
|
|
31
|
+
* set (meaning we already reloaded once for this reason), we don't
|
|
32
|
+
* reload again — this prevents infinite reload loops if the error
|
|
33
|
+
* persists after reload (e.g., a genuine bug rather than a stale bundle).
|
|
34
|
+
*
|
|
35
|
+
* @returns true if a reload was triggered, false if suppressed (loop guard)
|
|
36
|
+
*/
|
|
37
|
+
function triggerStaleReload() {
|
|
38
|
+
try {
|
|
39
|
+
if (sessionStorage.getItem(RELOAD_FLAG_KEY)) {
|
|
40
|
+
console.warn("[timber] Stale client reference detected again after reload. Not reloading to prevent infinite loop. This may indicate a deployment issue — try a hard refresh.");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
sessionStorage.setItem(RELOAD_FLAG_KEY, "1");
|
|
44
|
+
console.warn("[timber] Stale client reference detected — the server has been redeployed with new bundles. Reloading to pick up the new version.");
|
|
45
|
+
window.location.reload();
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
if (document.cookie.includes(RELOAD_FLAG_KEY + "=1") || memoryReloadCount > 0) {
|
|
49
|
+
console.warn("[timber] Stale client reference detected again after reload. Not reloading to prevent infinite loop. This may indicate a deployment issue — try a hard refresh.");
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
memoryReloadCount++;
|
|
53
|
+
try {
|
|
54
|
+
document.cookie = `${RELOAD_FLAG_KEY}=1; max-age=60; path=/; SameSite=Lax`;
|
|
55
|
+
} catch {}
|
|
56
|
+
console.warn("[timber] Stale client reference detected — the server has been redeployed with new bundles. Reloading to pick up the new version.");
|
|
57
|
+
window.location.reload();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { triggerStaleReload };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=stale-reload-BSSym1MJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale-reload-BSSym1MJ.js","names":[],"sources":["../../src/client/stale-reload.ts"],"sourcesContent":["/**\n * Stale Client Reference Reload\n *\n * When a new deployment ships updated bundles, clients running stale\n * JavaScript may encounter \"Could not find the module\" errors during\n * RSC Flight stream decoding. This happens because the RSC payload\n * references module IDs from the new bundle that don't exist in the\n * old client bundle.\n *\n * This module detects these specific errors and triggers a full page\n * reload so the browser fetches the new bundle. A sessionStorage flag\n * guards against infinite reload loops.\n *\n * See: LOCAL-332\n */\n\nconst RELOAD_FLAG_KEY = '__timber_stale_reload';\n\n/**\n * In-memory fallback counter for environments where sessionStorage is\n * unavailable (private browsing, storage full, extension interference).\n * Incremented each time triggerStaleReload() falls into the catch path.\n * If the counter exceeds 0 on a subsequent call, the reload is suppressed\n * to prevent an infinite loop. Resets naturally on page load (module\n * re-evaluates) and can be manually reset via clearStaleReloadFlag().\n */\nlet memoryReloadCount = 0;\n\n/**\n * Check if an error is a stale client reference error from React's\n * Flight client. These errors have the message pattern:\n * \"Could not find the module \\\"<id>\\\"\"\n *\n * This is thrown by react-server-dom-webpack's client when the RSC\n * payload references a module ID that doesn't exist in the client's\n * module map — typically because the server has been redeployed with\n * new bundle hashes while the client is still running old JavaScript.\n */\nexport function isStaleClientReference(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message;\n return msg.includes('Could not find the module') || msg.includes('client reference not found');\n}\n\n/**\n * Check if an error is a chunk load failure from a dynamic import.\n *\n * After a deployment, old chunk filenames no longer exist. When the client\n * tries to dynamically import a chunk that's been replaced, the browser\n * throws one of these errors:\n *\n * - Chromium: \"Failed to fetch dynamically imported module: <url>\"\n * - Firefox: \"error loading dynamically imported module: <url>\"\n * - Safari: \"Importing a module script failed.\"\n * - Vite/Rollup: \"Unable to preload CSS for <url>\"\n *\n * See TIM-446\n */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message.toLowerCase();\n return (\n msg.includes('failed to fetch dynamically imported module') ||\n msg.includes('error loading dynamically imported module') ||\n msg.includes('importing a module script failed') ||\n msg.includes('unable to preload css') ||\n // Webpack-style chunk load errors (unlikely in Vite but defensive)\n msg.includes('loading chunk') ||\n msg.includes('loading css chunk')\n );\n}\n\n/**\n * Trigger a full page reload to pick up new bundles.\n *\n * Sets a sessionStorage flag before reloading. If the flag is already\n * set (meaning we already reloaded once for this reason), we don't\n * reload again — this prevents infinite reload loops if the error\n * persists after reload (e.g., a genuine bug rather than a stale bundle).\n *\n * @returns true if a reload was triggered, false if suppressed (loop guard)\n */\nexport function triggerStaleReload(): boolean {\n try {\n // Check if we already reloaded — prevent infinite loop\n if (sessionStorage.getItem(RELOAD_FLAG_KEY)) {\n console.warn(\n '[timber] Stale client reference detected again after reload. ' +\n 'Not reloading to prevent infinite loop. ' +\n 'This may indicate a deployment issue — try a hard refresh.'\n );\n return false;\n }\n\n // Set the flag before reloading\n sessionStorage.setItem(RELOAD_FLAG_KEY, '1');\n\n console.warn(\n '[timber] Stale client reference detected — the server has been ' +\n 'redeployed with new bundles. Reloading to pick up the new version.'\n );\n\n window.location.reload();\n return true;\n } catch {\n // sessionStorage unavailable (private browsing, storage full, etc.)\n // Use document.cookie as a reload-persistent fallback loop guard.\n // Module-level memoryReloadCount resets on every reload, so it can't\n // detect cross-reload loops. Cookies persist across reloads and are\n // available even when sessionStorage is blocked (TIM-576).\n const cookieFlag = document.cookie.includes(RELOAD_FLAG_KEY + '=1');\n if (cookieFlag || memoryReloadCount > 0) {\n console.warn(\n '[timber] Stale client reference detected again after reload. ' +\n 'Not reloading to prevent infinite loop. ' +\n 'This may indicate a deployment issue — try a hard refresh.'\n );\n return false;\n }\n\n memoryReloadCount++;\n // Set a short-lived cookie (60s) as the persistent loop guard.\n // It auto-expires so it won't block future legitimate reloads.\n try {\n document.cookie = `${RELOAD_FLAG_KEY}=1; max-age=60; path=/; SameSite=Lax`;\n } catch {\n // Cookie API unavailable — proceed anyway, memoryReloadCount guards same-page loops\n }\n console.warn(\n '[timber] Stale client reference detected — the server has been ' +\n 'redeployed with new bundles. Reloading to pick up the new version.'\n );\n window.location.reload();\n return true;\n }\n}\n\n/**\n * Clear the stale reload flag. Called on successful bootstrap to reset\n * the loop guard — if the page loaded successfully, the next stale\n * reference error should trigger a fresh reload attempt.\n */\nexport function clearStaleReloadFlag(): void {\n memoryReloadCount = 0;\n try {\n sessionStorage.removeItem(RELOAD_FLAG_KEY);\n } catch {\n // sessionStorage unavailable — nothing to clear\n }\n // Also clear the cookie fallback\n try {\n document.cookie = `${RELOAD_FLAG_KEY}=; max-age=0; path=/; SameSite=Lax`;\n } catch {\n // Cookie API unavailable\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAM,kBAAkB;;;;;;;;;AAUxB,IAAI,oBAAoB;;;;;;;;;;;AAwDxB,SAAgB,qBAA8B;AAC5C,KAAI;AAEF,MAAI,eAAe,QAAQ,gBAAgB,EAAE;AAC3C,WAAQ,KACN,kKAGD;AACD,UAAO;;AAIT,iBAAe,QAAQ,iBAAiB,IAAI;AAE5C,UAAQ,KACN,oIAED;AAED,SAAO,SAAS,QAAQ;AACxB,SAAO;SACD;AAON,MADmB,SAAS,OAAO,SAAS,kBAAkB,KAAK,IACjD,oBAAoB,GAAG;AACvC,WAAQ,KACN,kKAGD;AACD,UAAO;;AAGT;AAGA,MAAI;AACF,YAAS,SAAS,GAAG,gBAAgB;UAC/B;AAGR,UAAQ,KACN,oIAED;AACD,SAAO,SAAS,QAAQ;AACxB,SAAO"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as traceAls } from "./als-registry-
|
|
1
|
+
import { o as traceAls } from "./als-registry-Ba7URUIn.js";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
//#region src/server/tracing.ts
|
|
4
4
|
/**
|
|
@@ -152,6 +152,20 @@ async function addSpanEvent(name, attributes) {
|
|
|
152
152
|
if (activeSpan) activeSpan.addEvent(name, attributes);
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
|
+
* Fire-and-forget span event — no await, no microtask overhead.
|
|
156
|
+
*
|
|
157
|
+
* Used on the cache hot path where awaiting addSpanEvent creates an
|
|
158
|
+
* unnecessary microtask per cache operation. If OTEL is not loaded yet,
|
|
159
|
+
* the event is silently dropped (acceptable for diagnostics).
|
|
160
|
+
*
|
|
161
|
+
* See TIM-370 for perf motivation.
|
|
162
|
+
*/
|
|
163
|
+
function addSpanEventSync(name, attributes) {
|
|
164
|
+
if (!_otelApi) return;
|
|
165
|
+
const activeSpan = _otelApi.trace.getActiveSpan();
|
|
166
|
+
if (activeSpan) activeSpan.addEvent(name, attributes);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
155
169
|
* Try to extract the OTEL trace ID from the current active span context.
|
|
156
170
|
* Returns undefined if OTEL is not active or no span exists.
|
|
157
171
|
*/
|
|
@@ -168,6 +182,6 @@ async function getOtelTraceId() {
|
|
|
168
182
|
};
|
|
169
183
|
}
|
|
170
184
|
//#endregion
|
|
171
|
-
export {
|
|
185
|
+
export { getTraceStore as a, setSpanAttribute as c, withSpan as d, getOtelTraceId as i, spanId as l, addSpanEventSync as n, replaceTraceId as o, generateTraceId as r, runWithTraceId as s, addSpanEvent as t, traceId as u };
|
|
172
186
|
|
|
173
|
-
//# sourceMappingURL=tracing-
|
|
187
|
+
//# sourceMappingURL=tracing-VYETCQsg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing-Cwn7697K.js","names":[],"sources":["../../src/server/tracing.ts"],"sourcesContent":["/**\n * Tracing — per-request trace ID via AsyncLocalStorage, OTEL span helpers.\n *\n * traceId() is always available in server code (middleware, access, components, actions).\n * Returns a 32-char lowercase hex string — the OTEL trace ID when an SDK is active,\n * or a crypto.randomUUID()-derived fallback otherwise.\n *\n * See design/17-logging.md §\"trace_id is Always Set\"\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { traceAls, type TraceStore } from './als-registry.js';\n\n// Re-export the TraceStore type for public API consumers.\nexport type { TraceStore } from './als-registry.js';\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns the current request's trace ID — always a 32-char lowercase hex string.\n *\n * With OTEL: the real OTEL trace ID (matches Jaeger/Honeycomb/Datadog).\n * Without OTEL: crypto.randomUUID() with hyphens stripped.\n *\n * Throws if called outside a request context (no ALS store).\n */\nexport function traceId(): string {\n const store = traceAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] traceId() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.traceId;\n}\n\n/**\n * Returns the current OTEL span ID if available, undefined otherwise.\n */\nexport function spanId(): string | undefined {\n return traceAls.getStore()?.spanId;\n}\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Generate a 32-char lowercase hex ID from crypto.randomUUID().\n * Same format as OTEL trace IDs — zero-friction upgrade path.\n */\nexport function generateTraceId(): string {\n return randomUUID().replace(/-/g, '');\n}\n\n/**\n * Run a callback within a trace context. Used by the pipeline to establish\n * per-request ALS scope.\n */\nexport function runWithTraceId<T>(id: string, fn: () => T): T {\n return traceAls.run({ traceId: id }, fn);\n}\n\n/**\n * Replace the trace ID in the current ALS store. Used when OTEL creates\n * a root span and we want to switch from the UUID fallback to the real\n * OTEL trace ID.\n */\nexport function replaceTraceId(newTraceId: string, newSpanId?: string): void {\n const store = traceAls.getStore();\n if (store) {\n store.traceId = newTraceId;\n store.spanId = newSpanId;\n }\n}\n\n/**\n * Update the span ID in the current ALS store. Used when entering a new\n * OTEL span to keep log–trace correlation accurate.\n */\nexport function updateSpanId(newSpanId: string | undefined): void {\n const store = traceAls.getStore();\n if (store) {\n store.spanId = newSpanId;\n }\n}\n\n/**\n * Get the current trace store, or undefined if outside a request context.\n * Framework-internal — use traceId()/spanId() in user code.\n */\nexport function getTraceStore(): TraceStore | undefined {\n return traceAls.getStore();\n}\n\n// ─── Dev-Mode OTEL Auto-Init ─────────────────────────────────────────────\n\n/**\n * Initialize a minimal OTEL SDK in dev mode so spans are recorded and\n * fed to the DevSpanProcessor for dev log output.\n *\n * If the user already configured an OTEL SDK in register(), we add\n * our DevSpanProcessor alongside theirs. If no SDK is configured,\n * we create a BasicTracerProvider with our processor.\n *\n * Only called in dev mode — zero overhead in production.\n */\nexport async function initDevTracing(\n config: import('./dev-logger.js').DevLoggerConfig\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const { DevSpanProcessor } = await import('./dev-span-processor.js');\n const { BasicTracerProvider } = await import('@opentelemetry/sdk-trace-base');\n const { AsyncLocalStorageContextManager } = await import('@opentelemetry/context-async-hooks');\n const processor = new DevSpanProcessor(config);\n\n // Register a context manager so OTEL can propagate the active span\n // across async boundaries. Without this, startActiveSpan can't make\n // spans \"active\" — child spans get random trace IDs and getActiveSpan()\n // returns undefined.\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n api.context.setGlobalContextManager(contextManager);\n\n // Create a minimal TracerProvider with our DevSpanProcessor.\n // If the user also configures an SDK in register(), their provider\n // will coexist — the global provider set last wins for new tracers,\n // but our processor captures all spans from the timber.js tracer.\n const provider = new BasicTracerProvider({\n spanProcessors: [processor],\n });\n api.trace.setGlobalTracerProvider(provider);\n\n // Reset cached tracer so next getTracer() picks up the new provider\n _tracer = undefined;\n}\n\n// ─── OTEL Span Helpers ───────────────────────────────────────────────────\n\n/**\n * Attempt to get the @opentelemetry/api tracer. Returns undefined if the\n * package is not installed or no SDK is registered.\n *\n * timber.js depends on @opentelemetry/api as the vendor-neutral interface.\n * The API is a no-op by default — spans are only emitted when the developer\n * initializes an SDK in register().\n */\nlet _otelApi: typeof import('@opentelemetry/api') | null | undefined;\n\nasync function getOtelApi(): Promise<typeof import('@opentelemetry/api') | null> {\n if (_otelApi === undefined) {\n try {\n _otelApi = await import('@opentelemetry/api');\n } catch {\n _otelApi = null;\n }\n }\n return _otelApi;\n}\n\n/** OTEL tracer instance, lazily created. */\nlet _tracer: import('@opentelemetry/api').Tracer | null | undefined;\n\n/**\n * Get the timber.js OTEL tracer. Returns null if @opentelemetry/api is not available.\n */\nexport async function getTracer(): Promise<import('@opentelemetry/api').Tracer | null> {\n if (_tracer === undefined) {\n const api = await getOtelApi();\n if (api) {\n _tracer = api.trace.getTracer('timber.js');\n } else {\n _tracer = null;\n }\n }\n return _tracer;\n}\n\n/**\n * Run a function within an OTEL span. If OTEL is not available, runs the function\n * directly without any span overhead.\n *\n * Automatically:\n * - Creates the span as a child of the current context\n * - Updates the ALS span ID for log–trace correlation\n * - Ends the span when the function completes\n * - Records exceptions on error\n */\nexport async function withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: () => T | Promise<T>\n): Promise<T> {\n const tracer = await getTracer();\n if (!tracer) {\n return fn();\n }\n\n const api = (await getOtelApi())!;\n return tracer.startActiveSpan(name, { attributes }, async (span) => {\n const prevSpanId = spanId();\n updateSpanId(span.spanContext().spanId);\n try {\n const result = await fn();\n span.setStatus({ code: api.SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({ code: api.SpanStatusCode.ERROR });\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n updateSpanId(prevSpanId);\n }\n });\n}\n\n/**\n * Set an attribute on the current active span (if any).\n * Used for setting span attributes after span creation (e.g. timber.result on access spans).\n */\nexport async function setSpanAttribute(\n key: string,\n value: string | number | boolean\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const activeSpan = api.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.setAttribute(key, value);\n }\n}\n\n/**\n * Add a span event to the current active span (if any).\n * Used for timber.cache HIT/MISS events — recorded as span events, not child spans.\n */\nexport async function addSpanEvent(\n name: string,\n attributes?: Record<string, string | number | boolean>\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const activeSpan = api.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n}\n\n/**\n * Try to extract the OTEL trace ID from the current active span context.\n * Returns undefined if OTEL is not active or no span exists.\n */\nexport async function getOtelTraceId(): Promise<{ traceId: string; spanId: string } | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const activeSpan = api.trace.getActiveSpan();\n if (!activeSpan) return undefined;\n\n const ctx = activeSpan.spanContext();\n // OTEL uses \"0000000000000000\" as invalid trace IDs\n if (!ctx.traceId || ctx.traceId === '00000000000000000000000000000000') {\n return undefined;\n }\n\n return { traceId: ctx.traceId, spanId: ctx.spanId };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,UAAkB;CAChC,MAAM,QAAQ,SAAS,UAAU;AACjC,KAAI,CAAC,MACH,OAAM,IAAI,MACR,mJAED;AAEH,QAAO,MAAM;;;;;AAMf,SAAgB,SAA6B;AAC3C,QAAO,SAAS,UAAU,EAAE;;;;;;AAS9B,SAAgB,kBAA0B;AACxC,QAAO,YAAY,CAAC,QAAQ,MAAM,GAAG;;;;;;AAOvC,SAAgB,eAAkB,IAAY,IAAgB;AAC5D,QAAO,SAAS,IAAI,EAAE,SAAS,IAAI,EAAE,GAAG;;;;;;;AAQ1C,SAAgB,eAAe,YAAoB,WAA0B;CAC3E,MAAM,QAAQ,SAAS,UAAU;AACjC,KAAI,OAAO;AACT,QAAM,UAAU;AAChB,QAAM,SAAS;;;;;;;AAQnB,SAAgB,aAAa,WAAqC;CAChE,MAAM,QAAQ,SAAS,UAAU;AACjC,KAAI,MACF,OAAM,SAAS;;;;;;AAQnB,SAAgB,gBAAwC;AACtD,QAAO,SAAS,UAAU;;;;;;;;;;AAyD5B,IAAI;AAEJ,eAAe,aAAkE;AAC/E,KAAI,aAAa,KAAA,EACf,KAAI;AACF,aAAW,MAAM,OAAO;SAClB;AACN,aAAW;;AAGf,QAAO;;;AAIT,IAAI;;;;AAKJ,eAAsB,YAAiE;AACrF,KAAI,YAAY,KAAA,GAAW;EACzB,MAAM,MAAM,MAAM,YAAY;AAC9B,MAAI,IACF,WAAU,IAAI,MAAM,UAAU,YAAY;MAE1C,WAAU;;AAGd,QAAO;;;;;;;;;;;;AAaT,eAAsB,SACpB,MACA,YACA,IACY;CACZ,MAAM,SAAS,MAAM,WAAW;AAChC,KAAI,CAAC,OACH,QAAO,IAAI;CAGb,MAAM,MAAO,MAAM,YAAY;AAC/B,QAAO,OAAO,gBAAgB,MAAM,EAAE,YAAY,EAAE,OAAO,SAAS;EAClE,MAAM,aAAa,QAAQ;AAC3B,eAAa,KAAK,aAAa,CAAC,OAAO;AACvC,MAAI;GACF,MAAM,SAAS,MAAM,IAAI;AACzB,QAAK,UAAU,EAAE,MAAM,IAAI,eAAe,IAAI,CAAC;AAC/C,UAAO;WACA,OAAO;AACd,QAAK,UAAU,EAAE,MAAM,IAAI,eAAe,OAAO,CAAC;AAClD,OAAI,iBAAiB,MACnB,MAAK,gBAAgB,MAAM;AAE7B,SAAM;YACE;AACR,QAAK,KAAK;AACV,gBAAa,WAAW;;GAE1B;;;;;;AAOJ,eAAsB,iBACpB,KACA,OACe;CACf,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK;CAEV,MAAM,aAAa,IAAI,MAAM,eAAe;AAC5C,KAAI,WACF,YAAW,aAAa,KAAK,MAAM;;;;;;AAQvC,eAAsB,aACpB,MACA,YACe;CACf,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK;CAEV,MAAM,aAAa,IAAI,MAAM,eAAe;AAC5C,KAAI,WACF,YAAW,SAAS,MAAM,WAAW;;;;;;AAQzC,eAAsB,iBAA2E;CAC/F,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK,QAAO,KAAA;CAEjB,MAAM,aAAa,IAAI,MAAM,eAAe;AAC5C,KAAI,CAAC,WAAY,QAAO,KAAA;CAExB,MAAM,MAAM,WAAW,aAAa;AAEpC,KAAI,CAAC,IAAI,WAAW,IAAI,YAAY,mCAClC;AAGF,QAAO;EAAE,SAAS,IAAI;EAAS,QAAQ,IAAI;EAAQ"}
|
|
1
|
+
{"version":3,"file":"tracing-VYETCQsg.js","names":[],"sources":["../../src/server/tracing.ts"],"sourcesContent":["/**\n * Tracing — per-request trace ID via AsyncLocalStorage, OTEL span helpers.\n *\n * traceId() is always available in server code (middleware, access, components, actions).\n * Returns a 32-char lowercase hex string — the OTEL trace ID when an SDK is active,\n * or a crypto.randomUUID()-derived fallback otherwise.\n *\n * See design/17-logging.md §\"trace_id is Always Set\"\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { traceAls, type TraceStore } from './als-registry.js';\n\n// Re-export the TraceStore type for public API consumers.\nexport type { TraceStore } from './als-registry.js';\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns the current request's trace ID — always a 32-char lowercase hex string.\n *\n * With OTEL: the real OTEL trace ID (matches Jaeger/Honeycomb/Datadog).\n * Without OTEL: crypto.randomUUID() with hyphens stripped.\n *\n * Throws if called outside a request context (no ALS store).\n */\nexport function traceId(): string {\n const store = traceAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] traceId() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.traceId;\n}\n\n/**\n * Returns the current OTEL span ID if available, undefined otherwise.\n */\nexport function spanId(): string | undefined {\n return traceAls.getStore()?.spanId;\n}\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Generate a 32-char lowercase hex ID from crypto.randomUUID().\n * Same format as OTEL trace IDs — zero-friction upgrade path.\n */\nexport function generateTraceId(): string {\n return randomUUID().replace(/-/g, '');\n}\n\n/**\n * Run a callback within a trace context. Used by the pipeline to establish\n * per-request ALS scope.\n */\nexport function runWithTraceId<T>(id: string, fn: () => T): T {\n return traceAls.run({ traceId: id }, fn);\n}\n\n/**\n * Replace the trace ID in the current ALS store. Used when OTEL creates\n * a root span and we want to switch from the UUID fallback to the real\n * OTEL trace ID.\n */\nexport function replaceTraceId(newTraceId: string, newSpanId?: string): void {\n const store = traceAls.getStore();\n if (store) {\n store.traceId = newTraceId;\n store.spanId = newSpanId;\n }\n}\n\n/**\n * Update the span ID in the current ALS store. Used when entering a new\n * OTEL span to keep log–trace correlation accurate.\n */\nexport function updateSpanId(newSpanId: string | undefined): void {\n const store = traceAls.getStore();\n if (store) {\n store.spanId = newSpanId;\n }\n}\n\n/**\n * Get the current trace store, or undefined if outside a request context.\n * Framework-internal — use traceId()/spanId() in user code.\n */\nexport function getTraceStore(): TraceStore | undefined {\n return traceAls.getStore();\n}\n\n// ─── Dev-Mode OTEL Auto-Init ─────────────────────────────────────────────\n\n/**\n * Initialize a minimal OTEL SDK in dev mode so spans are recorded and\n * fed to the DevSpanProcessor for dev log output.\n *\n * If the user already configured an OTEL SDK in register(), we add\n * our DevSpanProcessor alongside theirs. If no SDK is configured,\n * we create a BasicTracerProvider with our processor.\n *\n * Only called in dev mode — zero overhead in production.\n */\nexport async function initDevTracing(\n config: import('./dev-logger.js').DevLoggerConfig\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const { DevSpanProcessor } = await import('./dev-span-processor.js');\n const { BasicTracerProvider } = await import('@opentelemetry/sdk-trace-base');\n const { AsyncLocalStorageContextManager } = await import('@opentelemetry/context-async-hooks');\n const processor = new DevSpanProcessor(config);\n\n // Register a context manager so OTEL can propagate the active span\n // across async boundaries. Without this, startActiveSpan can't make\n // spans \"active\" — child spans get random trace IDs and getActiveSpan()\n // returns undefined.\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n api.context.setGlobalContextManager(contextManager);\n\n // Create a minimal TracerProvider with our DevSpanProcessor.\n // If the user also configures an SDK in register(), their provider\n // will coexist — the global provider set last wins for new tracers,\n // but our processor captures all spans from the timber.js tracer.\n const provider = new BasicTracerProvider({\n spanProcessors: [processor],\n });\n api.trace.setGlobalTracerProvider(provider);\n\n // Reset cached tracer so next getTracer() picks up the new provider\n _tracer = undefined;\n}\n\n// ─── OTEL Span Helpers ───────────────────────────────────────────────────\n\n/**\n * Attempt to get the @opentelemetry/api tracer. Returns undefined if the\n * package is not installed or no SDK is registered.\n *\n * timber.js depends on @opentelemetry/api as the vendor-neutral interface.\n * The API is a no-op by default — spans are only emitted when the developer\n * initializes an SDK in register().\n */\nlet _otelApi: typeof import('@opentelemetry/api') | null | undefined;\n\nasync function getOtelApi(): Promise<typeof import('@opentelemetry/api') | null> {\n if (_otelApi === undefined) {\n try {\n _otelApi = await import('@opentelemetry/api');\n } catch {\n _otelApi = null;\n }\n }\n return _otelApi;\n}\n\n/** OTEL tracer instance, lazily created. */\nlet _tracer: import('@opentelemetry/api').Tracer | null | undefined;\n\n/**\n * Get the timber.js OTEL tracer. Returns null if @opentelemetry/api is not available.\n */\nexport async function getTracer(): Promise<import('@opentelemetry/api').Tracer | null> {\n if (_tracer === undefined) {\n const api = await getOtelApi();\n if (api) {\n _tracer = api.trace.getTracer('timber.js');\n } else {\n _tracer = null;\n }\n }\n return _tracer;\n}\n\n/**\n * Run a function within an OTEL span. If OTEL is not available, runs the function\n * directly without any span overhead.\n *\n * Automatically:\n * - Creates the span as a child of the current context\n * - Updates the ALS span ID for log–trace correlation\n * - Ends the span when the function completes\n * - Records exceptions on error\n */\nexport async function withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: () => T | Promise<T>\n): Promise<T> {\n const tracer = await getTracer();\n if (!tracer) {\n return fn();\n }\n\n const api = (await getOtelApi())!;\n return tracer.startActiveSpan(name, { attributes }, async (span) => {\n const prevSpanId = spanId();\n updateSpanId(span.spanContext().spanId);\n try {\n const result = await fn();\n span.setStatus({ code: api.SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({ code: api.SpanStatusCode.ERROR });\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n updateSpanId(prevSpanId);\n }\n });\n}\n\n/**\n * Set an attribute on the current active span (if any).\n * Used for setting span attributes after span creation (e.g. timber.result on access spans).\n */\nexport async function setSpanAttribute(\n key: string,\n value: string | number | boolean\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const activeSpan = api.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.setAttribute(key, value);\n }\n}\n\n/**\n * Add a span event to the current active span (if any).\n * Used for timber.cache HIT/MISS events — recorded as span events, not child spans.\n */\nexport async function addSpanEvent(\n name: string,\n attributes?: Record<string, string | number | boolean>\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const activeSpan = api.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n}\n\n/**\n * Fire-and-forget span event — no await, no microtask overhead.\n *\n * Used on the cache hot path where awaiting addSpanEvent creates an\n * unnecessary microtask per cache operation. If OTEL is not loaded yet,\n * the event is silently dropped (acceptable for diagnostics).\n *\n * See TIM-370 for perf motivation.\n */\nexport function addSpanEventSync(\n name: string,\n attributes?: Record<string, string | number | boolean>\n): void {\n // Fast path: if OTEL API hasn't been loaded yet, skip entirely.\n // _otelApi is undefined (not yet loaded), null (failed to load), or the module.\n if (!_otelApi) return;\n\n const activeSpan = _otelApi.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n}\n\n/**\n * Try to extract the OTEL trace ID from the current active span context.\n * Returns undefined if OTEL is not active or no span exists.\n */\nexport async function getOtelTraceId(): Promise<{ traceId: string; spanId: string } | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const activeSpan = api.trace.getActiveSpan();\n if (!activeSpan) return undefined;\n\n const ctx = activeSpan.spanContext();\n // OTEL uses \"0000000000000000\" as invalid trace IDs\n if (!ctx.traceId || ctx.traceId === '00000000000000000000000000000000') {\n return undefined;\n }\n\n return { traceId: ctx.traceId, spanId: ctx.spanId };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,UAAkB;CAChC,MAAM,QAAQ,SAAS,UAAU;AACjC,KAAI,CAAC,MACH,OAAM,IAAI,MACR,mJAED;AAEH,QAAO,MAAM;;;;;AAMf,SAAgB,SAA6B;AAC3C,QAAO,SAAS,UAAU,EAAE;;;;;;AAS9B,SAAgB,kBAA0B;AACxC,QAAO,YAAY,CAAC,QAAQ,MAAM,GAAG;;;;;;AAOvC,SAAgB,eAAkB,IAAY,IAAgB;AAC5D,QAAO,SAAS,IAAI,EAAE,SAAS,IAAI,EAAE,GAAG;;;;;;;AAQ1C,SAAgB,eAAe,YAAoB,WAA0B;CAC3E,MAAM,QAAQ,SAAS,UAAU;AACjC,KAAI,OAAO;AACT,QAAM,UAAU;AAChB,QAAM,SAAS;;;;;;;AAQnB,SAAgB,aAAa,WAAqC;CAChE,MAAM,QAAQ,SAAS,UAAU;AACjC,KAAI,MACF,OAAM,SAAS;;;;;;AAQnB,SAAgB,gBAAwC;AACtD,QAAO,SAAS,UAAU;;;;;;;;;;AAyD5B,IAAI;AAEJ,eAAe,aAAkE;AAC/E,KAAI,aAAa,KAAA,EACf,KAAI;AACF,aAAW,MAAM,OAAO;SAClB;AACN,aAAW;;AAGf,QAAO;;;AAIT,IAAI;;;;AAKJ,eAAsB,YAAiE;AACrF,KAAI,YAAY,KAAA,GAAW;EACzB,MAAM,MAAM,MAAM,YAAY;AAC9B,MAAI,IACF,WAAU,IAAI,MAAM,UAAU,YAAY;MAE1C,WAAU;;AAGd,QAAO;;;;;;;;;;;;AAaT,eAAsB,SACpB,MACA,YACA,IACY;CACZ,MAAM,SAAS,MAAM,WAAW;AAChC,KAAI,CAAC,OACH,QAAO,IAAI;CAGb,MAAM,MAAO,MAAM,YAAY;AAC/B,QAAO,OAAO,gBAAgB,MAAM,EAAE,YAAY,EAAE,OAAO,SAAS;EAClE,MAAM,aAAa,QAAQ;AAC3B,eAAa,KAAK,aAAa,CAAC,OAAO;AACvC,MAAI;GACF,MAAM,SAAS,MAAM,IAAI;AACzB,QAAK,UAAU,EAAE,MAAM,IAAI,eAAe,IAAI,CAAC;AAC/C,UAAO;WACA,OAAO;AACd,QAAK,UAAU,EAAE,MAAM,IAAI,eAAe,OAAO,CAAC;AAClD,OAAI,iBAAiB,MACnB,MAAK,gBAAgB,MAAM;AAE7B,SAAM;YACE;AACR,QAAK,KAAK;AACV,gBAAa,WAAW;;GAE1B;;;;;;AAOJ,eAAsB,iBACpB,KACA,OACe;CACf,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK;CAEV,MAAM,aAAa,IAAI,MAAM,eAAe;AAC5C,KAAI,WACF,YAAW,aAAa,KAAK,MAAM;;;;;;AAQvC,eAAsB,aACpB,MACA,YACe;CACf,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK;CAEV,MAAM,aAAa,IAAI,MAAM,eAAe;AAC5C,KAAI,WACF,YAAW,SAAS,MAAM,WAAW;;;;;;;;;;;AAazC,SAAgB,iBACd,MACA,YACM;AAGN,KAAI,CAAC,SAAU;CAEf,MAAM,aAAa,SAAS,MAAM,eAAe;AACjD,KAAI,WACF,YAAW,SAAS,MAAM,WAAW;;;;;;AAQzC,eAAsB,iBAA2E;CAC/F,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK,QAAO,KAAA;CAEjB,MAAM,aAAa,IAAI,MAAM,eAAe;AAC5C,KAAI,CAAC,WAAY,QAAO,KAAA;CAExB,MAAM,MAAM,WAAW,aAAa;AAEpC,KAAI,CAAC,IAAI,WAAW,IAAI,YAAY,mCAClC;AAGF,QAAO;EAAE,SAAS,IAAI;EAAS,QAAQ,IAAI;EAAQ"}
|
|
@@ -106,4 +106,4 @@ function bindUseQueryStates(definition) {
|
|
|
106
106
|
//#endregion
|
|
107
107
|
export { registerSearchParams as i, useQueryStates$1 as n, getSearchParams as r, bindUseQueryStates as t };
|
|
108
108
|
|
|
109
|
-
//# sourceMappingURL=use-query-states-
|
|
109
|
+
//# sourceMappingURL=use-query-states-wEXY2JQB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-query-states-wEXY2JQB.js","names":[],"sources":["../../src/search-params/registry.ts","../../src/client/use-query-states.ts"],"sourcesContent":["/**\n * Runtime registry for route-scoped search params definitions.\n *\n * When a route's modules load, the framework registers its search-params\n * definition here. useQueryStates('/route') resolves codecs from this map.\n *\n * Design doc: design/23-search-params.md §\"Runtime: Registration at Route Load\"\n */\n\nimport type { SearchParamsDefinition } from './define.js';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst registry = new Map<string, SearchParamsDefinition<any>>();\n\n/**\n * Register a route's search params definition.\n * Called by the generated route manifest loader when a route's modules load.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerSearchParams(route: string, definition: SearchParamsDefinition<any>): void {\n registry.set(route, definition);\n}\n\n/**\n * Look up a route's search params definition.\n * Returns undefined if the route hasn't been loaded yet.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getSearchParams(route: string): SearchParamsDefinition<any> | undefined {\n return registry.get(route);\n}\n","/**\n * useQueryStates — client-side hook for URL-synced search params.\n *\n * Delegates to nuqs for URL synchronization, batching, React 19 transitions,\n * and throttled URL writes. Bridges timber's SearchParamCodec protocol to\n * nuqs-compatible parsers.\n *\n * Design doc: design/23-search-params.md §\"Codec Bridge\"\n */\n\n'use client';\n\nimport { useQueryStates as nuqsUseQueryStates } from 'nuqs';\nimport type { SingleParser } from 'nuqs';\nimport type {\n SearchParamCodec,\n SearchParamsDefinition,\n SetParams,\n QueryStatesOptions,\n} from '../search-params/define.js';\nimport { getSearchParams } from '../search-params/registry.js';\n\n// ─── Codec Bridge ─────────────────────────────────────────────────\n\n/**\n * Bridge a timber SearchParamCodec to a nuqs-compatible SingleParser.\n *\n * nuqs parsers: { parse(string) → T|null, serialize?(T) → string, eq?, defaultValue? }\n * timber codecs: { parse(string|string[]|undefined) → T, serialize(T) → string|null }\n */\nfunction bridgeCodec<T>(codec: SearchParamCodec<T>): SingleParser<T> & { defaultValue: T } {\n return {\n parse: (v: string) => codec.parse(v),\n serialize: (v: T) => codec.serialize(v) ?? '',\n defaultValue: codec.parse(undefined) as T,\n eq: (a: T, b: T) => codec.serialize(a) === codec.serialize(b),\n };\n}\n\n/**\n * Bridge an entire codec map to nuqs-compatible parsers.\n */\nfunction bridgeCodecs<T extends Record<string, unknown>>(codecs: {\n [K in keyof T]: SearchParamCodec<T[K]>;\n}) {\n const result: Record<string, SingleParser<unknown> & { defaultValue: unknown }> = {};\n for (const key of Object.keys(codecs)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result[key] = bridgeCodec(codecs[key as keyof T]) as any;\n }\n return result as { [K in keyof T]: SingleParser<T[K]> & { defaultValue: T[K] } };\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────\n\n/**\n * Read and write typed search params from/to the URL.\n *\n * Delegates to nuqs internally. The timber nuqs adapter (auto-injected in\n * browser-entry.ts) handles RSC navigation on non-shallow updates.\n *\n * Usage:\n * ```ts\n * // Via a SearchParamsDefinition\n * const [params, setParams] = definition.useQueryStates()\n *\n * // Standalone with inline codecs\n * const [params, setParams] = useQueryStates({\n * page: fromSchema(z.coerce.number().int().min(1).default(1)),\n * })\n * ```\n */\nexport function useQueryStates<T extends Record<string, unknown>>(\n codecsOrRoute: { [K in keyof T]: SearchParamCodec<T[K]> } | string,\n _options?: QueryStatesOptions,\n urlKeys?: Readonly<Record<string, string>>\n): [T, SetParams<T>] {\n // Route-string overload: resolve codecs from the registry\n let codecs: { [K in keyof T]: SearchParamCodec<T[K]> };\n let resolvedUrlKeys = urlKeys;\n if (typeof codecsOrRoute === 'string') {\n const definition = getSearchParams(codecsOrRoute);\n if (!definition) {\n throw new Error(\n `useQueryStates('${codecsOrRoute}'): no search params registered for this route. ` +\n `Either the route has no search-params.ts file, or it hasn't been loaded yet. ` +\n `For cross-route usage, import the definition explicitly.`\n );\n }\n codecs = definition.codecs as { [K in keyof T]: SearchParamCodec<T[K]> };\n resolvedUrlKeys = definition.urlKeys;\n } else {\n codecs = codecsOrRoute;\n }\n\n const bridged = bridgeCodecs(codecs);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nuqsOptions: any = {};\n if (resolvedUrlKeys && Object.keys(resolvedUrlKeys).length > 0) {\n nuqsOptions.urlKeys = resolvedUrlKeys;\n }\n\n let values: Record<string, unknown>;\n let setValues: Function;\n try {\n [values, setValues] = nuqsUseQueryStates(bridged, nuqsOptions);\n } catch (err) {\n if (\n err instanceof Error &&\n /Invalid hook call|cannot be called|Cannot read properties of null/i.test(err.message)\n ) {\n throw new Error(\n 'useQueryStates is a client component hook and cannot be called outside a React component. ' +\n 'Use definition.parse(searchParams) in server components instead.'\n );\n }\n throw err;\n }\n\n // Wrap the nuqs setter to match timber's SetParams<T> signature.\n // nuqs's setter accepts Partial<Nullable<Values>> | UpdaterFn | null.\n // timber's setter accepts Partial<T> with optional SetParamsOptions.\n const setParams: SetParams<T> = (partial, setOptions?) => {\n const nuqsSetOptions: Record<string, unknown> = {};\n if (setOptions?.shallow !== undefined) nuqsSetOptions.shallow = setOptions.shallow;\n if (setOptions?.scroll !== undefined) nuqsSetOptions.scroll = setOptions.scroll;\n if (setOptions?.history !== undefined) nuqsSetOptions.history = setOptions.history;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n void setValues(partial as any, nuqsSetOptions);\n };\n\n return [values as T, setParams];\n}\n\n// ─── Definition binding ───────────────────────────────────────────\n\n/**\n * Create a useQueryStates binding for a SearchParamsDefinition.\n * This is used internally by SearchParamsDefinition.useQueryStates().\n */\nexport function bindUseQueryStates<T extends Record<string, unknown>>(\n definition: SearchParamsDefinition<T>\n): (options?: QueryStatesOptions) => [T, SetParams<T>] {\n return (options?: QueryStatesOptions) => {\n return useQueryStates<T>(definition.codecs, options, definition.urlKeys);\n };\n}\n"],"mappings":";;AAYA,IAAM,2BAAW,IAAI,KAA0C;;;;;AAO/D,SAAgB,qBAAqB,OAAe,YAA+C;AACjG,UAAS,IAAI,OAAO,WAAW;;;;;;AAQjC,SAAgB,gBAAgB,OAAwD;AACtF,QAAO,SAAS,IAAI,MAAM;;;;;;;;;;;;;;;;;;;ACC5B,SAAS,YAAe,OAAmE;AACzF,QAAO;EACL,QAAQ,MAAc,MAAM,MAAM,EAAE;EACpC,YAAY,MAAS,MAAM,UAAU,EAAE,IAAI;EAC3C,cAAc,MAAM,MAAM,KAAA,EAAU;EACpC,KAAK,GAAM,MAAS,MAAM,UAAU,EAAE,KAAK,MAAM,UAAU,EAAE;EAC9D;;;;;AAMH,SAAS,aAAgD,QAEtD;CACD,MAAM,SAA4E,EAAE;AACpF,MAAK,MAAM,OAAO,OAAO,KAAK,OAAO,CAEnC,QAAO,OAAO,YAAY,OAAO,KAAgB;AAEnD,QAAO;;;;;;;;;;;;;;;;;;;AAsBT,SAAgB,iBACd,eACA,UACA,SACmB;CAEnB,IAAI;CACJ,IAAI,kBAAkB;AACtB,KAAI,OAAO,kBAAkB,UAAU;EACrC,MAAM,aAAa,gBAAgB,cAAc;AACjD,MAAI,CAAC,WACH,OAAM,IAAI,MACR,mBAAmB,cAAc,uLAGlC;AAEH,WAAS,WAAW;AACpB,oBAAkB,WAAW;OAE7B,UAAS;CAGX,MAAM,UAAU,aAAa,OAAO;CAGpC,MAAM,cAAmB,EAAE;AAC3B,KAAI,mBAAmB,OAAO,KAAK,gBAAgB,CAAC,SAAS,EAC3D,aAAY,UAAU;CAGxB,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,GAAC,QAAQ,aAAa,eAAmB,SAAS,YAAY;UACvD,KAAK;AACZ,MACE,eAAe,SACf,qEAAqE,KAAK,IAAI,QAAQ,CAEtF,OAAM,IAAI,MACR,6JAED;AAEH,QAAM;;CAMR,MAAM,aAA2B,SAAS,eAAgB;EACxD,MAAM,iBAA0C,EAAE;AAClD,MAAI,YAAY,YAAY,KAAA,EAAW,gBAAe,UAAU,WAAW;AAC3E,MAAI,YAAY,WAAW,KAAA,EAAW,gBAAe,SAAS,WAAW;AACzE,MAAI,YAAY,YAAY,KAAA,EAAW,gBAAe,UAAU,WAAW;AAEtE,YAAU,SAAgB,eAAe;;AAGhD,QAAO,CAAC,QAAa,UAAU;;;;;;AASjC,SAAgB,mBACd,YACqD;AACrD,SAAQ,YAAiC;AACvC,SAAO,iBAAkB,WAAW,QAAQ,SAAS,WAAW,QAAQ"}
|