@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
package/src/client/rsc-fetch.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface FetchResult {
|
|
|
23
23
|
headElements: HeadElement[] | null;
|
|
24
24
|
/** Segment metadata from X-Timber-Segments header for populating the segment cache. */
|
|
25
25
|
segmentInfo: SegmentInfo[] | null;
|
|
26
|
-
/** Route params from X-Timber-Params header for populating
|
|
26
|
+
/** Route params from X-Timber-Params header for populating useSegmentParams(). */
|
|
27
27
|
params: Record<string, string | string[]> | null;
|
|
28
28
|
/** Segment paths that were skipped by the server (for client-side merging). */
|
|
29
29
|
skippedSegments: string[] | null;
|
|
@@ -58,6 +58,43 @@ function appendRscParam(url: string): string {
|
|
|
58
58
|
return `${url}${separator}_rsc=${generateCacheBustId()}`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// ─── Deployment ID ───────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The client's deployment ID, set at bootstrap from the runtime config.
|
|
65
|
+
* Sent with every RSC/action request for version skew detection.
|
|
66
|
+
* Null in dev mode. See TIM-446.
|
|
67
|
+
*/
|
|
68
|
+
let clientDeploymentId: string | null = null;
|
|
69
|
+
|
|
70
|
+
/** Set the client deployment ID. Called once at bootstrap. */
|
|
71
|
+
export function setClientDeploymentId(id: string | null): void {
|
|
72
|
+
clientDeploymentId = id;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Get the client deployment ID. */
|
|
76
|
+
export function getClientDeploymentId(): string | null {
|
|
77
|
+
return clientDeploymentId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Reload Signal ───────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/** Header name used by the server to signal a version skew reload. */
|
|
83
|
+
export const RELOAD_HEADER = 'X-Timber-Reload';
|
|
84
|
+
|
|
85
|
+
/** Header name for the client's deployment ID. */
|
|
86
|
+
export const DEPLOYMENT_ID_HEADER = 'X-Timber-Deployment-Id';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if a response signals a version skew reload.
|
|
90
|
+
* Triggers a full page reload if the server indicates the client is stale.
|
|
91
|
+
*/
|
|
92
|
+
export function checkReloadSignal(response: Response): boolean {
|
|
93
|
+
return response.headers.get(RELOAD_HEADER) === '1';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Header Builder ──────────────────────────────────────────────
|
|
97
|
+
|
|
61
98
|
export function buildRscHeaders(
|
|
62
99
|
stateTree: { segments: string[] } | undefined,
|
|
63
100
|
currentUrl?: string
|
|
@@ -75,6 +112,13 @@ export function buildRscHeaders(
|
|
|
75
112
|
if (currentUrl) {
|
|
76
113
|
headers['X-Timber-URL'] = currentUrl;
|
|
77
114
|
}
|
|
115
|
+
// Send deployment ID for version skew detection (TIM-446).
|
|
116
|
+
// The server compares this against the current build's ID.
|
|
117
|
+
// On mismatch, the server signals a reload instead of returning
|
|
118
|
+
// an RSC payload with mismatched module references.
|
|
119
|
+
if (clientDeploymentId) {
|
|
120
|
+
headers[DEPLOYMENT_ID_HEADER] = clientDeploymentId;
|
|
121
|
+
}
|
|
78
122
|
return headers;
|
|
79
123
|
}
|
|
80
124
|
|
|
@@ -135,7 +179,7 @@ export function extractSkippedSegments(response: Response): string[] | null {
|
|
|
135
179
|
* Extract route params from the X-Timber-Params response header.
|
|
136
180
|
* Returns null if the header is missing or malformed.
|
|
137
181
|
*
|
|
138
|
-
* Used to populate
|
|
182
|
+
* Used to populate useSegmentParams() after client-side navigation.
|
|
139
183
|
*/
|
|
140
184
|
export function extractParams(response: Response): Record<string, string | string[]> | null {
|
|
141
185
|
const header = response.headers.get('X-Timber-Params');
|
|
@@ -161,6 +205,35 @@ export class RedirectError extends Error {
|
|
|
161
205
|
}
|
|
162
206
|
}
|
|
163
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Thrown when the server signals a version skew (X-Timber-Reload header).
|
|
210
|
+
* Caught in navigate() to trigger a full page reload via triggerStaleReload().
|
|
211
|
+
* See TIM-446.
|
|
212
|
+
*/
|
|
213
|
+
export class VersionSkewError extends Error {
|
|
214
|
+
constructor() {
|
|
215
|
+
super('Version skew detected — server has been redeployed');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Thrown when the server returns a 5xx error for an RSC payload request.
|
|
221
|
+
* The server sends X-Timber-Error header and a JSON body instead of a
|
|
222
|
+
* broken RSC stream. Caught in navigate() to trigger a hard navigation
|
|
223
|
+
* so the server can render the error page as HTML.
|
|
224
|
+
*
|
|
225
|
+
* See design/10-error-handling.md §"Error Page Rendering for Client Navigation"
|
|
226
|
+
*/
|
|
227
|
+
export class ServerErrorResponse extends Error {
|
|
228
|
+
readonly status: number;
|
|
229
|
+
readonly url: string;
|
|
230
|
+
constructor(status: number, url: string) {
|
|
231
|
+
super(`Server error ${status} during navigation to ${url}`);
|
|
232
|
+
this.status = status;
|
|
233
|
+
this.url = url;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
164
237
|
// ─── Fetch ───────────────────────────────────────────────────────
|
|
165
238
|
|
|
166
239
|
/**
|
|
@@ -192,6 +265,12 @@ export async function fetchRscPayload(
|
|
|
192
265
|
let params: Record<string, string | string[]> | null = null;
|
|
193
266
|
let skippedSegments: string[] | null = null;
|
|
194
267
|
const wrappedPromise = fetchPromise.then((response) => {
|
|
268
|
+
// Version skew detection (TIM-446): if the server signals a reload,
|
|
269
|
+
// throw VersionSkewError so the caller (router navigate) can trigger
|
|
270
|
+
// a full page reload via triggerStaleReload().
|
|
271
|
+
if (checkReloadSignal(response)) {
|
|
272
|
+
throw new VersionSkewError();
|
|
273
|
+
}
|
|
195
274
|
// Detect server-side redirects. The server returns 204 + X-Timber-Redirect
|
|
196
275
|
// for RSC payload requests instead of a raw 302, because fetch with
|
|
197
276
|
// redirect: "manual" turns 302s into opaque redirects (status 0, null body)
|
|
@@ -202,6 +281,15 @@ export async function fetchRscPayload(
|
|
|
202
281
|
if (redirectLocation) {
|
|
203
282
|
throw new RedirectError(redirectLocation);
|
|
204
283
|
}
|
|
284
|
+
// Detect server 5xx errors. The server returns X-Timber-Error header
|
|
285
|
+
// with a JSON body instead of a broken RSC stream. Hard-navigate so
|
|
286
|
+
// the server renders the error page as HTML via the SSR-only path.
|
|
287
|
+
// Only trigger for 5xx — intentional 4xx RenderErrors (e.g., 403)
|
|
288
|
+
// should stay within SPA navigation, not force a full page reload.
|
|
289
|
+
// See design/10-error-handling.md §"Error Page Rendering for Client Navigation"
|
|
290
|
+
if (response.headers.get('X-Timber-Error') === '1' && response.status >= 500) {
|
|
291
|
+
throw new ServerErrorResponse(response.status, url);
|
|
292
|
+
}
|
|
205
293
|
headElements = extractHeadElements(response);
|
|
206
294
|
segmentInfo = extractSegmentInfo(response);
|
|
207
295
|
params = extractParams(response);
|
|
@@ -11,7 +11,7 @@ export interface PrefetchResult {
|
|
|
11
11
|
headElements: HeadElement[] | null;
|
|
12
12
|
/** Segment metadata from X-Timber-Segments header for populating the segment cache. */
|
|
13
13
|
segmentInfo?: SegmentInfo[] | null;
|
|
14
|
-
/** Route params from X-Timber-Params header for populating
|
|
14
|
+
/** Route params from X-Timber-Params header for populating useSegmentParams(). */
|
|
15
15
|
params?: Record<string, string | string[]> | null;
|
|
16
16
|
/** Segment paths skipped by the server (for client-side merging). */
|
|
17
17
|
skippedSegments?: string[] | null;
|
|
@@ -52,7 +52,12 @@ interface SegmentProviderProps {
|
|
|
52
52
|
* Wraps each layout to provide segment position context.
|
|
53
53
|
* Injected by rsc-entry.ts during element tree construction.
|
|
54
54
|
*/
|
|
55
|
-
export function SegmentProvider({
|
|
55
|
+
export function SegmentProvider({
|
|
56
|
+
segments,
|
|
57
|
+
segmentId: _segmentId,
|
|
58
|
+
parallelRouteKeys,
|
|
59
|
+
children,
|
|
60
|
+
}: SegmentProviderProps) {
|
|
56
61
|
const value = useMemo(
|
|
57
62
|
() => ({ segments, parallelRouteKeys }),
|
|
58
63
|
// segments and parallelRouteKeys are static per layout — they don't change
|
|
@@ -186,10 +186,7 @@ function walkChildren(children: ReactNode, out: CachedSegmentEntry[]): void {
|
|
|
186
186
|
* Cache all segment subtrees from a fully-rendered RSC element tree.
|
|
187
187
|
* Call this after every full RSC payload render (navigate, refresh, hydration).
|
|
188
188
|
*/
|
|
189
|
-
export function cacheSegmentElements(
|
|
190
|
-
element: unknown,
|
|
191
|
-
cache: SegmentElementCache
|
|
192
|
-
): void {
|
|
189
|
+
export function cacheSegmentElements(element: unknown, cache: SegmentElementCache): void {
|
|
193
190
|
const segments = extractSegments(element);
|
|
194
191
|
for (const entry of segments) {
|
|
195
192
|
cache.set(entry.segmentPath, entry);
|
|
@@ -208,10 +205,7 @@ export function cacheSegmentElements(
|
|
|
208
205
|
*/
|
|
209
206
|
type TreePath = Array<{ element: ReactElement; childIndex: number }>;
|
|
210
207
|
|
|
211
|
-
function findSegmentProviderPath(
|
|
212
|
-
node: ReactElement,
|
|
213
|
-
targetPath?: string
|
|
214
|
-
): TreePath | null {
|
|
208
|
+
function findSegmentProviderPath(node: ReactElement, targetPath?: string): TreePath | null {
|
|
215
209
|
const children = (node.props as { children?: ReactNode }).children;
|
|
216
210
|
if (children == null) return null;
|
|
217
211
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SegmentOutlet — client component boundary at each layout segment.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the post-hoc tree walking in segment-merger.ts with an explicit
|
|
5
|
+
* client component at each segment boundary. Each outlet:
|
|
6
|
+
*
|
|
7
|
+
* 1. Knows its own segment path (prop from the server)
|
|
8
|
+
* 2. Caches its children in a ref across navigations
|
|
9
|
+
* 3. When `keepCurrent` is true (partial navigation, this segment skipped),
|
|
10
|
+
* returns the previously cached children — layout state is preserved
|
|
11
|
+
* 4. When `keepCurrent` is false (full navigation or this segment changed),
|
|
12
|
+
* stores and renders the new children
|
|
13
|
+
*
|
|
14
|
+
* This eliminates the need for client-side element tree walking, which
|
|
15
|
+
* breaks on real RSC trees due to opaque client component lazy refs,
|
|
16
|
+
* Suspense thenables, and AccessGate wrappers.
|
|
17
|
+
*
|
|
18
|
+
* Architecture is similar to Next.js's `<LayoutRouter>` client component —
|
|
19
|
+
* each layout boundary is an explicit client component that manages its
|
|
20
|
+
* own subtree. See design/19-client-navigation.md.
|
|
21
|
+
*
|
|
22
|
+
* Security: This is a performance optimization only. The server always
|
|
23
|
+
* runs all access.ts files regardless of segment skipping. A fabricated
|
|
24
|
+
* keepCurrent prop can only cause stale layouts — never auth bypass.
|
|
25
|
+
* See design/13-security.md §"State tree manipulation".
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
'use client';
|
|
29
|
+
|
|
30
|
+
import { useRef, type ReactNode } from 'react';
|
|
31
|
+
|
|
32
|
+
export interface SegmentOutletProps {
|
|
33
|
+
/**
|
|
34
|
+
* Unique identifier for this segment. For normal segments this is the
|
|
35
|
+
* urlPath (e.g., "/", "/dashboard"). For route groups this includes the
|
|
36
|
+
* group name (e.g., "/(marketing)") to distinguish siblings that share
|
|
37
|
+
* the same urlPath. Must match the segmentId used in state-tree-diff.ts.
|
|
38
|
+
*/
|
|
39
|
+
segmentPath: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* When true, the outlet returns its previously cached children instead
|
|
43
|
+
* of rendering the new children prop. Set by the server when this
|
|
44
|
+
* segment was skipped (the client already has the layout mounted).
|
|
45
|
+
*
|
|
46
|
+
* On the first render (SSR/hydration), this is always false — there's
|
|
47
|
+
* no cached content yet. On subsequent partial navigations, the server
|
|
48
|
+
* sets this to true for segments it skipped rendering.
|
|
49
|
+
*/
|
|
50
|
+
keepCurrent?: boolean;
|
|
51
|
+
|
|
52
|
+
/** The segment's React subtree (layout + inner content). */
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Client component boundary at each layout segment in the element tree.
|
|
58
|
+
*
|
|
59
|
+
* On full navigation: receives new children, stores them, renders them.
|
|
60
|
+
* On partial navigation (keepCurrent=true): ignores children prop,
|
|
61
|
+
* returns previously stored content — React reconciles the same elements,
|
|
62
|
+
* preserving all client component state in the layout subtree.
|
|
63
|
+
*
|
|
64
|
+
* React preserves the ref across `reactRoot.render()` calls because:
|
|
65
|
+
* - SegmentOutlet has a stable type (client component module reference)
|
|
66
|
+
* - It appears at the same tree position on every navigation
|
|
67
|
+
* - React reconciles same-type, same-position → instance preserved
|
|
68
|
+
*/
|
|
69
|
+
export function SegmentOutlet({
|
|
70
|
+
segmentPath: _segmentPath,
|
|
71
|
+
keepCurrent = false,
|
|
72
|
+
children,
|
|
73
|
+
}: SegmentOutletProps) {
|
|
74
|
+
// Store content in a ref to avoid triggering re-renders on cache updates.
|
|
75
|
+
// The ref persists across reactRoot.render() calls because React reconciles
|
|
76
|
+
// the same component type at the same tree position.
|
|
77
|
+
const contentRef = useRef<ReactNode>(null);
|
|
78
|
+
|
|
79
|
+
if (!keepCurrent) {
|
|
80
|
+
// Full render or this segment was re-rendered — store and render new content
|
|
81
|
+
contentRef.current = children;
|
|
82
|
+
}
|
|
83
|
+
// else: keepCurrent=true — return previously cached content
|
|
84
|
+
|
|
85
|
+
return contentRef.current;
|
|
86
|
+
}
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
|
|
17
17
|
const RELOAD_FLAG_KEY = '__timber_stale_reload';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* In-memory fallback counter for environments where sessionStorage is
|
|
21
|
+
* unavailable (private browsing, storage full, extension interference).
|
|
22
|
+
* Incremented each time triggerStaleReload() falls into the catch path.
|
|
23
|
+
* If the counter exceeds 0 on a subsequent call, the reload is suppressed
|
|
24
|
+
* to prevent an infinite loop. Resets naturally on page load (module
|
|
25
|
+
* re-evaluates) and can be manually reset via clearStaleReloadFlag().
|
|
26
|
+
*/
|
|
27
|
+
let memoryReloadCount = 0;
|
|
28
|
+
|
|
19
29
|
/**
|
|
20
30
|
* Check if an error is a stale client reference error from React's
|
|
21
31
|
* Flight client. These errors have the message pattern:
|
|
@@ -32,6 +42,34 @@ export function isStaleClientReference(error: unknown): boolean {
|
|
|
32
42
|
return msg.includes('Could not find the module') || msg.includes('client reference not found');
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Check if an error is a chunk load failure from a dynamic import.
|
|
47
|
+
*
|
|
48
|
+
* After a deployment, old chunk filenames no longer exist. When the client
|
|
49
|
+
* tries to dynamically import a chunk that's been replaced, the browser
|
|
50
|
+
* throws one of these errors:
|
|
51
|
+
*
|
|
52
|
+
* - Chromium: "Failed to fetch dynamically imported module: <url>"
|
|
53
|
+
* - Firefox: "error loading dynamically imported module: <url>"
|
|
54
|
+
* - Safari: "Importing a module script failed."
|
|
55
|
+
* - Vite/Rollup: "Unable to preload CSS for <url>"
|
|
56
|
+
*
|
|
57
|
+
* See TIM-446
|
|
58
|
+
*/
|
|
59
|
+
export function isChunkLoadError(error: unknown): boolean {
|
|
60
|
+
if (!(error instanceof Error)) return false;
|
|
61
|
+
const msg = error.message.toLowerCase();
|
|
62
|
+
return (
|
|
63
|
+
msg.includes('failed to fetch dynamically imported module') ||
|
|
64
|
+
msg.includes('error loading dynamically imported module') ||
|
|
65
|
+
msg.includes('importing a module script failed') ||
|
|
66
|
+
msg.includes('unable to preload css') ||
|
|
67
|
+
// Webpack-style chunk load errors (unlikely in Vite but defensive)
|
|
68
|
+
msg.includes('loading chunk') ||
|
|
69
|
+
msg.includes('loading css chunk')
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
35
73
|
/**
|
|
36
74
|
* Trigger a full page reload to pick up new bundles.
|
|
37
75
|
*
|
|
@@ -48,8 +86,8 @@ export function triggerStaleReload(): boolean {
|
|
|
48
86
|
if (sessionStorage.getItem(RELOAD_FLAG_KEY)) {
|
|
49
87
|
console.warn(
|
|
50
88
|
'[timber] Stale client reference detected again after reload. ' +
|
|
51
|
-
|
|
52
|
-
|
|
89
|
+
'Not reloading to prevent infinite loop. ' +
|
|
90
|
+
'This may indicate a deployment issue — try a hard refresh.'
|
|
53
91
|
);
|
|
54
92
|
return false;
|
|
55
93
|
}
|
|
@@ -59,16 +97,38 @@ export function triggerStaleReload(): boolean {
|
|
|
59
97
|
|
|
60
98
|
console.warn(
|
|
61
99
|
'[timber] Stale client reference detected — the server has been ' +
|
|
62
|
-
|
|
100
|
+
'redeployed with new bundles. Reloading to pick up the new version.'
|
|
63
101
|
);
|
|
64
102
|
|
|
65
103
|
window.location.reload();
|
|
66
104
|
return true;
|
|
67
105
|
} catch {
|
|
68
|
-
// sessionStorage
|
|
69
|
-
//
|
|
106
|
+
// sessionStorage unavailable (private browsing, storage full, etc.)
|
|
107
|
+
// Use document.cookie as a reload-persistent fallback loop guard.
|
|
108
|
+
// Module-level memoryReloadCount resets on every reload, so it can't
|
|
109
|
+
// detect cross-reload loops. Cookies persist across reloads and are
|
|
110
|
+
// available even when sessionStorage is blocked (TIM-576).
|
|
111
|
+
const cookieFlag = document.cookie.includes(RELOAD_FLAG_KEY + '=1');
|
|
112
|
+
if (cookieFlag || memoryReloadCount > 0) {
|
|
113
|
+
console.warn(
|
|
114
|
+
'[timber] Stale client reference detected again after reload. ' +
|
|
115
|
+
'Not reloading to prevent infinite loop. ' +
|
|
116
|
+
'This may indicate a deployment issue — try a hard refresh.'
|
|
117
|
+
);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
memoryReloadCount++;
|
|
122
|
+
// Set a short-lived cookie (60s) as the persistent loop guard.
|
|
123
|
+
// It auto-expires so it won't block future legitimate reloads.
|
|
124
|
+
try {
|
|
125
|
+
document.cookie = `${RELOAD_FLAG_KEY}=1; max-age=60; path=/; SameSite=Lax`;
|
|
126
|
+
} catch {
|
|
127
|
+
// Cookie API unavailable — proceed anyway, memoryReloadCount guards same-page loops
|
|
128
|
+
}
|
|
70
129
|
console.warn(
|
|
71
|
-
'[timber] Stale client reference detected
|
|
130
|
+
'[timber] Stale client reference detected — the server has been ' +
|
|
131
|
+
'redeployed with new bundles. Reloading to pick up the new version.'
|
|
72
132
|
);
|
|
73
133
|
window.location.reload();
|
|
74
134
|
return true;
|
|
@@ -81,9 +141,16 @@ export function triggerStaleReload(): boolean {
|
|
|
81
141
|
* reference error should trigger a fresh reload attempt.
|
|
82
142
|
*/
|
|
83
143
|
export function clearStaleReloadFlag(): void {
|
|
144
|
+
memoryReloadCount = 0;
|
|
84
145
|
try {
|
|
85
146
|
sessionStorage.removeItem(RELOAD_FLAG_KEY);
|
|
86
147
|
} catch {
|
|
87
148
|
// sessionStorage unavailable — nothing to clear
|
|
88
149
|
}
|
|
150
|
+
// Also clear the cookie fallback
|
|
151
|
+
try {
|
|
152
|
+
document.cookie = `${RELOAD_FLAG_KEY}=; max-age=0; path=/; SameSite=Lax`;
|
|
153
|
+
} catch {
|
|
154
|
+
// Cookie API unavailable
|
|
155
|
+
}
|
|
89
156
|
}
|
|
@@ -39,7 +39,7 @@ export interface TopLoaderConfig {
|
|
|
39
39
|
color?: string;
|
|
40
40
|
/** Bar height in pixels. Default: 3. */
|
|
41
41
|
height?: number;
|
|
42
|
-
/** Show subtle glow/shadow effect. Default:
|
|
42
|
+
/** Show subtle glow/shadow effect. Default: false. */
|
|
43
43
|
shadow?: boolean;
|
|
44
44
|
/** Delay in ms before showing the bar. Default: 0. */
|
|
45
45
|
delay?: number;
|
|
@@ -51,7 +51,7 @@ export interface TopLoaderConfig {
|
|
|
51
51
|
|
|
52
52
|
const DEFAULT_COLOR = '#2299DD';
|
|
53
53
|
const DEFAULT_HEIGHT = 3;
|
|
54
|
-
const DEFAULT_SHADOW =
|
|
54
|
+
const DEFAULT_SHADOW = false;
|
|
55
55
|
const DEFAULT_DELAY = 0;
|
|
56
56
|
const DEFAULT_Z_INDEX = 1600;
|
|
57
57
|
|
|
@@ -183,18 +183,19 @@ export function TopLoader({ config }: { config?: TopLoaderConfig }): React.React
|
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
// Clean up the finishing phase when the finish animation completes.
|
|
186
|
-
const handleAnimationEnd =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
const handleAnimationEnd =
|
|
187
|
+
phase === 'finishing'
|
|
188
|
+
? (e: React.AnimationEvent) => {
|
|
189
|
+
if (e.animationName === FINISH_KEYFRAMES) {
|
|
190
|
+
setPhase('hidden');
|
|
191
|
+
}
|
|
190
192
|
}
|
|
191
|
-
|
|
192
|
-
: undefined;
|
|
193
|
+
: undefined;
|
|
193
194
|
|
|
194
195
|
return createElement(
|
|
195
196
|
'div',
|
|
196
197
|
{
|
|
197
|
-
style: containerStyle,
|
|
198
|
+
'style': containerStyle,
|
|
198
199
|
'aria-hidden': 'true',
|
|
199
200
|
'data-timber-top-loader': '',
|
|
200
201
|
},
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { useState, useTransition, createElement, Fragment, type ReactNode } from 'react';
|
|
26
26
|
import { PendingNavigationProvider } from './navigation-context.js';
|
|
27
27
|
import { TopLoader, type TopLoaderConfig } from './top-loader.js';
|
|
28
|
+
import { getCurrentNavId, resetLinkPending } from './link-pending-store.js';
|
|
28
29
|
|
|
29
30
|
// ─── Module-level functions ──────────────────────────────────────
|
|
30
31
|
|
|
@@ -62,7 +63,13 @@ let _navigateTransition:
|
|
|
62
63
|
* Non-navigation renders:
|
|
63
64
|
* transitionRender(newWrappedElement);
|
|
64
65
|
*/
|
|
65
|
-
export function TransitionRoot({
|
|
66
|
+
export function TransitionRoot({
|
|
67
|
+
initial,
|
|
68
|
+
topLoaderConfig,
|
|
69
|
+
}: {
|
|
70
|
+
initial: ReactNode;
|
|
71
|
+
topLoaderConfig?: TopLoaderConfig;
|
|
72
|
+
}): ReactNode {
|
|
66
73
|
const [element, setElement] = useState<ReactNode>(initial);
|
|
67
74
|
const [pendingUrl, setPendingUrl] = useState<string | null>(null);
|
|
68
75
|
const [, startTransition] = useTransition();
|
|
@@ -82,20 +89,31 @@ export function TransitionRoot({ initial, topLoaderConfig }: { initial: ReactNod
|
|
|
82
89
|
// both apply in the same React commit — making the pending→active transition
|
|
83
90
|
// atomic (no frame where pending is false but the old tree is still visible).
|
|
84
91
|
_navigateTransition = (url: string, perform: () => Promise<ReactNode>) => {
|
|
85
|
-
// Urgent: show pending state immediately
|
|
92
|
+
// Urgent: show pending state immediately (for TopLoader / useNavigationPending)
|
|
86
93
|
setPendingUrl(url);
|
|
87
94
|
|
|
88
95
|
return new Promise<void>((resolve, reject) => {
|
|
89
96
|
startTransition(async () => {
|
|
97
|
+
// Capture the current nav ID before async work begins.
|
|
98
|
+
// Used to guard against stale clears when a newer navigation
|
|
99
|
+
// supersedes this one.
|
|
100
|
+
const navId = getCurrentNavId();
|
|
90
101
|
try {
|
|
91
102
|
const newElement = await perform();
|
|
92
103
|
setElement(newElement);
|
|
93
104
|
// Clear pending inside the transition — commits atomically with new tree
|
|
94
105
|
setPendingUrl(null);
|
|
106
|
+
// Reset per-link pending state. The navId guard ensures a stale
|
|
107
|
+
// transition (T1) doesn't clear a newer navigation's (T2) link.
|
|
108
|
+
// The setter call is a transition update — batched with setElement
|
|
109
|
+
// and setPendingUrl, so pending clears atomically with new tree.
|
|
110
|
+
// See design/19-client-navigation.md §"Per-Link Pending State"
|
|
111
|
+
resetLinkPending(navId);
|
|
95
112
|
resolve();
|
|
96
113
|
} catch (err) {
|
|
97
114
|
// Clear pending on error too
|
|
98
115
|
setPendingUrl(null);
|
|
116
|
+
resetLinkPending(navId);
|
|
99
117
|
reject(err);
|
|
100
118
|
}
|
|
101
119
|
});
|
package/src/client/use-params.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* Design doc: design/09-typescript.md §"Typed Routes"
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import type { Routes } from '
|
|
33
|
+
import type { Routes } from '../index.js';
|
|
34
34
|
import { getSsrData } from './ssr-data.js';
|
|
35
35
|
import { currentParams, _setCurrentParams, paramsListeners } from './state.js';
|
|
36
36
|
import { useNavigationContext } from './navigation-context.js';
|
|
@@ -119,9 +119,9 @@ export function notifyParamsListeners(): void {
|
|
|
119
119
|
* exact params shape from the generated Routes interface.
|
|
120
120
|
* @overload Fallback — returns the generic params record.
|
|
121
121
|
*/
|
|
122
|
-
export function
|
|
123
|
-
export function
|
|
124
|
-
export function
|
|
122
|
+
export function useSegmentParams<R extends keyof Routes>(route: R): Routes[R]['params'];
|
|
123
|
+
export function useSegmentParams(route?: string): Record<string, string | string[]>;
|
|
124
|
+
export function useSegmentParams(_route?: string): Record<string, string | string[]> {
|
|
125
125
|
// Try reading from NavigationContext (client-side, inside React tree).
|
|
126
126
|
// During SSR, no NavigationProvider is mounted, so this returns null.
|
|
127
127
|
// When called outside a React component, useContext throws — caught below.
|
|
@@ -17,8 +17,8 @@ import type {
|
|
|
17
17
|
SearchParamsDefinition,
|
|
18
18
|
SetParams,
|
|
19
19
|
QueryStatesOptions,
|
|
20
|
-
} from '
|
|
21
|
-
import { getSearchParams } from '
|
|
20
|
+
} from '../search-params/define.js';
|
|
21
|
+
import { getSearchParams } from '../search-params/registry.js';
|
|
22
22
|
|
|
23
23
|
// ─── Codec Bridge ─────────────────────────────────────────────────
|
|
24
24
|
|
package/src/codec.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared codec protocol for parsing and serializing string values.
|
|
3
|
+
*
|
|
4
|
+
* Used by both search params and cookies. Any object with parse + serialize
|
|
5
|
+
* methods satisfies this interface. nuqs parsers are valid codecs natively.
|
|
6
|
+
*
|
|
7
|
+
* Design doc: design/23a-search-params-triage.md §"Unify Codec<T> type"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A codec that converts between string values and typed values.
|
|
12
|
+
*
|
|
13
|
+
* The canonical protocol shared across search params, cookies, and
|
|
14
|
+
* any future timber feature that needs string ↔ typed conversion.
|
|
15
|
+
*/
|
|
16
|
+
export interface Codec<T> {
|
|
17
|
+
/** String → typed value. Receives undefined when the value is absent. */
|
|
18
|
+
parse(value: string | string[] | undefined): T;
|
|
19
|
+
/** Typed value → string. Return null to omit/clear. */
|
|
20
|
+
serialize(value: T): string | null;
|
|
21
|
+
}
|