@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71
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-BJARkOcu.js} +1 -1
- package/dist/_chunks/als-registry-BJARkOcu.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-CGuYoRHU.js +199 -0
- package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
- package/dist/_chunks/define-Dz1bqwaS.js +106 -0
- package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
- package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
- package/dist/_chunks/format-Rn922VH2.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
- package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
- package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
- package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
- package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
- package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
- package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
- package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
- package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
- package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
- package/dist/adapters/cloudflare-dev.d.ts +109 -0
- package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +73 -0
- package/dist/adapters/cloudflare-dev.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +148 -12
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +135 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- 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/cache-api.d.ts +24 -0
- package/dist/cache/cache-api.d.ts.map +1 -0
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/handler-store.d.ts +31 -0
- package/dist/cache/handler-store.d.ts.map +1 -0
- package/dist/cache/index.d.ts +7 -5
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +111 -73
- package/dist/cache/index.js.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 +12 -5
- 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/history.d.ts +19 -4
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +537 -166
- 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 +90 -32
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/nav-link-store.d.ts +36 -0
- package/dist/client/nav-link-store.d.ts.map +1 -0
- package/dist/client/navigation-api-types.d.ts +90 -0
- package/dist/client/navigation-api-types.d.ts.map +1 -0
- package/dist/client/navigation-api.d.ts +115 -0
- package/dist/client/navigation-api.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +13 -2
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
- package/dist/client/navigation-root.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/router.d.ts +70 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +38 -3
- 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/ssr-data.d.ts +13 -4
- package/dist/client/ssr-data.d.ts.map +1 -1
- 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 +3 -3
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +6 -4
- 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 +23 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +2 -0
- package/dist/cookies/define-cookie.d.ts +35 -14
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -0
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +3 -84
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/index.d.ts +154 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12092 -11916
- package/dist/index.js.map +1 -1
- 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 +19 -5
- 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.d.ts +2 -0
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -2
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +46 -0
- package/dist/routing/segment-classify.d.ts.map +1 -0
- 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/schema-bridge.d.ts +76 -0
- package/dist/schema-bridge.d.ts.map +1 -0
- package/dist/search-params/define.d.ts +139 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -6
- 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/segment-params/define.d.ts +78 -0
- package/dist/segment-params/define.d.ts.map +1 -0
- package/dist/segment-params/index.d.ts +7 -0
- package/dist/segment-params/index.d.ts.map +1 -0
- package/dist/segment-params/index.js +4 -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 +12 -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 +3 -6
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +32 -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-page-resolver.d.ts +52 -0
- package/dist/server/deny-page-resolver.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/dev-warnings.d.ts +0 -14
- package/dist/server/dev-warnings.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 +7 -1
- 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 +5 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2176 -1663
- 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/middleware-runner.d.ts +19 -4
- package/dist/server/middleware-runner.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/page-deny-boundary.d.ts +31 -0
- package/dist/server/page-deny-boundary.d.ts.map +1 -0
- package/dist/server/pipeline-interception.d.ts +1 -1
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-metadata.d.ts +6 -0
- package/dist/server/pipeline-metadata.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +32 -10
- 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 +76 -37
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +27 -1
- 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/safe-load.d.ts +46 -0
- package/dist/server/safe-load.d.ts.map +1 -0
- package/dist/server/sitemap-generator.d.ts +129 -0
- package/dist/server/sitemap-generator.d.ts.map +1 -0
- package/dist/server/sitemap-handler.d.ts +22 -0
- package/dist/server/sitemap-handler.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +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 +22 -19
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -4
- 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/font-google.js +42 -0
- package/dist/shims/font-google.js.map +1 -0
- package/dist/shims/font-local.d.ts +26 -0
- package/dist/shims/font-local.d.ts.map +1 -0
- package/dist/shims/font-local.js +20 -0
- package/dist/shims/font-local.js.map +1 -0
- package/dist/shims/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/directive-parser.d.ts +5 -2
- package/dist/utils/directive-parser.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 +37 -17
- package/src/adapters/cloudflare-dev.ts +177 -0
- package/src/adapters/cloudflare.ts +342 -28
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/adapters/wrangler.d.ts +7 -0
- package/src/cache/cache-api.ts +38 -0
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/handler-store.ts +68 -0
- package/src/cache/index.ts +9 -5
- 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 +314 -142
- package/src/client/error-boundary.tsx +48 -16
- package/src/client/error-reconstituter.tsx +65 -0
- package/src/client/form.tsx +2 -2
- package/src/client/history.ts +26 -4
- package/src/client/index.ts +13 -4
- package/src/client/link-pending-store.ts +136 -0
- package/src/client/link.tsx +346 -105
- package/src/client/nav-link-store.ts +47 -0
- package/src/client/navigation-api-types.ts +112 -0
- package/src/client/navigation-api.ts +332 -0
- package/src/client/navigation-context.ts +27 -6
- package/src/client/navigation-root.tsx +346 -0
- package/src/client/nuqs-adapter.tsx +16 -3
- package/src/client/router.ts +302 -77
- package/src/client/rsc-fetch.ts +93 -5
- 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/ssr-data.ts +13 -5
- package/src/client/stale-reload.ts +73 -6
- package/src/client/top-loader.tsx +22 -13
- package/src/client/use-navigation-pending.ts +1 -1
- package/src/client/use-params.ts +7 -5
- package/src/client/use-query-states.ts +2 -2
- package/src/codec.ts +34 -0
- package/src/cookies/define-cookie.ts +72 -21
- package/src/cookies/index.ts +7 -0
- package/src/fonts/css.ts +2 -1
- package/src/index.ts +328 -92
- 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/client-chunks.ts +65 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-browser-logs.ts +288 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-logs.ts +1 -1
- package/src/plugins/dev-server.ts +55 -9
- package/src/plugins/entries.ts +70 -9
- package/src/plugins/fonts.ts +167 -61
- 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 +76 -33
- package/src/plugins/static-build.ts +10 -6
- package/src/routing/codegen.ts +165 -105
- package/src/routing/index.ts +2 -0
- package/src/routing/scanner.ts +93 -23
- package/src/routing/segment-classify.ts +89 -0
- 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 → schema-bridge.ts} +57 -20
- package/src/search-params/define.ts +482 -0
- package/src/search-params/index.ts +13 -19
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/segment-params/define.ts +279 -0
- package/src/segment-params/index.ts +28 -0
- package/src/server/access-gate.tsx +70 -29
- package/src/server/action-client.ts +28 -3
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +20 -3
- package/src/server/actions.ts +10 -9
- package/src/server/als-registry.ts +32 -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-page-resolver.ts +154 -0
- package/src/server/deny-renderer.ts +24 -38
- package/src/server/dev-warnings.ts +2 -28
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +74 -22
- package/src/server/fallback-error.ts +31 -15
- 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 -5
- package/src/server/logger.ts +44 -36
- package/src/server/middleware-runner.ts +31 -4
- package/src/server/node-stream-transforms.ts +509 -0
- package/src/server/page-deny-boundary.tsx +56 -0
- package/src/server/pipeline-interception.ts +17 -16
- package/src/server/pipeline-metadata.ts +13 -0
- package/src/server/pipeline.ts +195 -51
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +240 -117
- package/src/server/route-element-builder.ts +284 -197
- package/src/server/route-handler.ts +24 -4
- package/src/server/route-matcher.ts +24 -20
- package/src/server/rsc-entry/api-handler.ts +15 -16
- package/src/server/rsc-entry/error-renderer.ts +300 -89
- package/src/server/rsc-entry/helpers.ts +134 -5
- package/src/server/rsc-entry/index.ts +202 -113
- package/src/server/rsc-entry/rsc-payload.ts +100 -21
- package/src/server/rsc-entry/rsc-stream.ts +74 -18
- package/src/server/rsc-entry/ssr-bridge.ts +14 -5
- package/src/server/rsc-entry/ssr-renderer.ts +173 -40
- package/src/server/safe-load.ts +60 -0
- package/src/server/sitemap-generator.ts +338 -0
- package/src/server/sitemap-handler.ts +126 -0
- package/src/server/slot-resolver.ts +243 -228
- 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 +37 -3
- package/src/server/tree-builder.ts +92 -58
- package/src/server/types.ts +3 -6
- 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/font-local.ts +34 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +2 -1
- package/src/utils/directive-parser.ts +5 -2
- 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/format-DviM89f0.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/tracing-Cwn7697K.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
- package/dist/cache/register-cached-function.d.ts +0 -17
- package/dist/cache/register-cached-function.d.ts.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/client/transition-root.d.ts.map +0 -1
- package/dist/cookies/index.js.map +0 -1
- package/dist/plugins/cache-transform.d.ts +0 -36
- package/dist/plugins/cache-transform.d.ts.map +0 -1
- package/dist/plugins/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/codecs.d.ts +0 -53
- package/dist/search-params/codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/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/cache/register-cached-function.ts +0 -99
- package/src/client/link-status-provider.tsx +0 -30
- package/src/client/transition-root.tsx +0 -160
- package/src/plugins/cache-transform.ts +0 -199
- 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/router.ts
CHANGED
|
@@ -6,13 +6,19 @@ import type { SegmentInfo } from './segment-cache';
|
|
|
6
6
|
import { HistoryStack } from './history';
|
|
7
7
|
import type { HeadElement } from './head';
|
|
8
8
|
import { setCurrentParams } from './use-params.js';
|
|
9
|
-
import { setNavigationState } from './navigation-context.js';
|
|
10
9
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from './
|
|
15
|
-
import {
|
|
10
|
+
setNavigationState,
|
|
11
|
+
getNavigationState,
|
|
12
|
+
type NavigationState,
|
|
13
|
+
} from './navigation-context.js';
|
|
14
|
+
import { SegmentElementCache, cacheSegmentElements, mergeSegmentTree } from './segment-merger.js';
|
|
15
|
+
import {
|
|
16
|
+
fetchRscPayload,
|
|
17
|
+
RedirectError,
|
|
18
|
+
ServerErrorResponse,
|
|
19
|
+
VersionSkewError,
|
|
20
|
+
} from './rsc-fetch.js';
|
|
21
|
+
import { setHardNavigating } from './navigation-root.js';
|
|
16
22
|
import type { FetchResult } from './rsc-fetch.js';
|
|
17
23
|
|
|
18
24
|
// ─── Types ───────────────────────────────────────────────────────
|
|
@@ -22,6 +28,19 @@ export interface NavigationOptions {
|
|
|
22
28
|
scroll?: boolean;
|
|
23
29
|
/** Use replaceState instead of pushState (replaces current history entry) */
|
|
24
30
|
replace?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* @internal AbortSignal from the Navigation API's NavigateEvent.
|
|
33
|
+
* When provided, the signal is linked to the router's per-navigation
|
|
34
|
+
* AbortController so in-flight RSC fetches are cancelled when a new
|
|
35
|
+
* navigation starts.
|
|
36
|
+
*/
|
|
37
|
+
_signal?: AbortSignal;
|
|
38
|
+
/**
|
|
39
|
+
* @internal Skip pushState/replaceState — the Navigation API has already
|
|
40
|
+
* updated the URL via event.intercept(). Used for external navigations
|
|
41
|
+
* intercepted by the navigate event handler.
|
|
42
|
+
*/
|
|
43
|
+
_skipHistory?: boolean;
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
/**
|
|
@@ -35,8 +54,12 @@ export type RscDecoder = (fetchPromise: Promise<Response>) => unknown;
|
|
|
35
54
|
* Function that renders a decoded RSC element tree into the DOM.
|
|
36
55
|
* In production: reactRoot.render(element).
|
|
37
56
|
* In tests: a no-op or mock.
|
|
57
|
+
*
|
|
58
|
+
* Receives the current NavigationState explicitly — no temporal
|
|
59
|
+
* coupling with setNavigationState/getNavigationState. The renderer
|
|
60
|
+
* wraps the element in NavigationProvider with this state.
|
|
38
61
|
*/
|
|
39
|
-
export type RootRenderer = (element: unknown) => void;
|
|
62
|
+
export type RootRenderer = (element: unknown, navState: NavigationState) => void;
|
|
40
63
|
|
|
41
64
|
/**
|
|
42
65
|
* Platform dependencies injected for testability. In production these
|
|
@@ -68,14 +91,54 @@ export interface RouterDeps {
|
|
|
68
91
|
*
|
|
69
92
|
* The `perform` callback receives a `wrapPayload` function to wrap the
|
|
70
93
|
* decoded RSC payload with NavigationProvider + NuqsAdapter before
|
|
71
|
-
*
|
|
94
|
+
* NavigationRoot sets it as the new element. The `wrapPayload` function
|
|
95
|
+
* receives the NavigationState explicitly — no temporal coupling with
|
|
96
|
+
* getNavigationState().
|
|
72
97
|
*
|
|
73
98
|
* If not provided (tests), the router falls back to renderRoot.
|
|
74
99
|
*/
|
|
75
100
|
navigateTransition?: (
|
|
76
101
|
pendingUrl: string,
|
|
77
|
-
perform: (
|
|
102
|
+
perform: (
|
|
103
|
+
wrapPayload: (payload: unknown, navState: NavigationState) => unknown
|
|
104
|
+
) => Promise<unknown>
|
|
78
105
|
) => Promise<void>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether the Navigation API is active and handling traversals.
|
|
109
|
+
* When true, the popstate handler is a no-op — the Navigation API's
|
|
110
|
+
* navigate event covers back/forward button presses.
|
|
111
|
+
*/
|
|
112
|
+
navigationApiActive?: boolean;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Called around pushState/replaceState to set a flag that prevents
|
|
116
|
+
* the Navigation API's navigate listener from double-handling
|
|
117
|
+
* router-initiated navigations.
|
|
118
|
+
*/
|
|
119
|
+
setRouterNavigating?: (value: boolean) => void;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Save scroll position via the Navigation API's per-entry state.
|
|
123
|
+
* When provided, used instead of history.replaceState for scroll storage.
|
|
124
|
+
*/
|
|
125
|
+
saveNavigationEntryScroll?: (scrollY: number) => void;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Signal that a router-initiated navigation has completed. Resolves the
|
|
129
|
+
* deferred promise that ties the browser's native loading state to the
|
|
130
|
+
* navigation lifecycle. Called in the finally block of navigate/refresh,
|
|
131
|
+
* aligned with when the TopLoader's pendingUrl clears.
|
|
132
|
+
*/
|
|
133
|
+
completeRouterNavigation?: () => void;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Initiate a navigation via the Navigation API (`navigation.navigate()`).
|
|
137
|
+
* Fires the navigate event BEFORE committing the URL, allowing Chrome
|
|
138
|
+
* to show its native loading indicator. Falls back to pushState when
|
|
139
|
+
* unavailable.
|
|
140
|
+
*/
|
|
141
|
+
navigationNavigate?: (url: string, replace: boolean) => void;
|
|
79
142
|
}
|
|
80
143
|
|
|
81
144
|
export interface RouterInstance {
|
|
@@ -84,7 +147,7 @@ export interface RouterInstance {
|
|
|
84
147
|
/** Full re-render of the current URL — no state tree sent */
|
|
85
148
|
refresh(): Promise<void>;
|
|
86
149
|
/** Handle a popstate event (back/forward button). scrollY is read from history.state. */
|
|
87
|
-
handlePopState(url: string, scrollY?: number): Promise<void>;
|
|
150
|
+
handlePopState(url: string, scrollY?: number, externalSignal?: AbortSignal): Promise<void>;
|
|
88
151
|
/** Whether a navigation is currently in flight */
|
|
89
152
|
isPending(): boolean;
|
|
90
153
|
/** The URL currently being navigated to, or null if idle */
|
|
@@ -134,24 +197,73 @@ function isAbortError(error: unknown): boolean {
|
|
|
134
197
|
* Create a router instance. In production, called once at app hydration
|
|
135
198
|
* with real browser APIs. In tests, called with mock dependencies.
|
|
136
199
|
*/
|
|
200
|
+
/**
|
|
201
|
+
* Router navigation phase — discriminated union replacing scattered
|
|
202
|
+
* `pending` + `pendingUrl` boolean flags.
|
|
203
|
+
*
|
|
204
|
+
* - `idle`: No navigation in flight. The committed params/pathname
|
|
205
|
+
* are current.
|
|
206
|
+
* - `navigating`: A fetch or render is in progress. `targetUrl` is
|
|
207
|
+
* the destination being navigated to.
|
|
208
|
+
*/
|
|
209
|
+
export type RouterPhase = { phase: 'idle' } | { phase: 'navigating'; targetUrl: string };
|
|
210
|
+
|
|
137
211
|
export function createRouter(deps: RouterDeps): RouterInstance {
|
|
138
212
|
const segmentCache = new SegmentCache();
|
|
139
213
|
const prefetchCache = new PrefetchCache();
|
|
140
214
|
const historyStack = new HistoryStack();
|
|
141
215
|
const segmentElementCache = new SegmentElementCache();
|
|
142
216
|
|
|
143
|
-
let
|
|
144
|
-
let pendingUrl: string | null = null;
|
|
217
|
+
let routerPhase: RouterPhase = { phase: 'idle' };
|
|
145
218
|
const pendingListeners = new Set<(pending: boolean) => void>();
|
|
146
219
|
|
|
220
|
+
// AbortController for the current in-flight navigation.
|
|
221
|
+
// When a new navigation starts, the previous controller is aborted,
|
|
222
|
+
// cancelling any in-progress RSC fetch. This provides automatic
|
|
223
|
+
// cancellation of stale fetches regardless of Navigation API support.
|
|
224
|
+
let currentNavAbort: AbortController | null = null;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Create a new AbortController for a navigation, aborting any
|
|
228
|
+
* previous in-flight navigation. Optionally links to an external
|
|
229
|
+
* signal (e.g., from the Navigation API's NavigateEvent.signal).
|
|
230
|
+
*/
|
|
231
|
+
function createNavAbort(externalSignal?: AbortSignal): AbortController {
|
|
232
|
+
// Abort previous navigation's fetch
|
|
233
|
+
currentNavAbort?.abort();
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
currentNavAbort = controller;
|
|
236
|
+
|
|
237
|
+
// If an external signal is provided (e.g., Navigation API),
|
|
238
|
+
// forward its abort to our controller.
|
|
239
|
+
if (externalSignal) {
|
|
240
|
+
if (externalSignal.aborted) {
|
|
241
|
+
controller.abort();
|
|
242
|
+
} else {
|
|
243
|
+
externalSignal.addEventListener('abort', () => controller.abort(), { once: true });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return controller;
|
|
248
|
+
}
|
|
249
|
+
|
|
147
250
|
function setPending(value: boolean, url?: string): void {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
251
|
+
const next: RouterPhase =
|
|
252
|
+
value && url ? { phase: 'navigating', targetUrl: url } : { phase: 'idle' };
|
|
253
|
+
// Skip no-op updates
|
|
254
|
+
if (
|
|
255
|
+
routerPhase.phase === next.phase &&
|
|
256
|
+
(routerPhase.phase === 'idle' ||
|
|
257
|
+
(routerPhase.phase === 'navigating' &&
|
|
258
|
+
next.phase === 'navigating' &&
|
|
259
|
+
routerPhase.targetUrl === next.targetUrl))
|
|
260
|
+
) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
routerPhase = next;
|
|
152
264
|
// Notify external store listeners (non-React consumers).
|
|
153
265
|
// React-facing pending state is handled by useOptimistic in
|
|
154
|
-
//
|
|
266
|
+
// NavigationRoot via navigateTransition — not this function.
|
|
155
267
|
for (const listener of pendingListeners) {
|
|
156
268
|
listener(value);
|
|
157
269
|
}
|
|
@@ -167,9 +279,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
167
279
|
}
|
|
168
280
|
|
|
169
281
|
/** Render a decoded RSC payload into the DOM if a renderer is available. */
|
|
170
|
-
function renderPayload(payload: unknown): void {
|
|
282
|
+
function renderPayload(payload: unknown, navState: NavigationState): void {
|
|
171
283
|
if (deps.renderRoot) {
|
|
172
|
-
deps.renderRoot(payload);
|
|
284
|
+
deps.renderRoot(payload, navState);
|
|
173
285
|
}
|
|
174
286
|
}
|
|
175
287
|
|
|
@@ -198,32 +310,34 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
198
310
|
/**
|
|
199
311
|
* Update navigation state (params + pathname) for the next render.
|
|
200
312
|
*
|
|
201
|
-
* Sets
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
313
|
+
* Sets the module-level fallback (for tests and SSR) and the
|
|
314
|
+
* globalThis bridge, then returns the NavigationState so callers
|
|
315
|
+
* can pass it explicitly to renderRoot/wrapPayload — eliminating
|
|
316
|
+
* temporal coupling with getNavigationState().
|
|
205
317
|
*/
|
|
206
318
|
function updateNavigationState(
|
|
207
319
|
params: Record<string, string | string[]> | null | undefined,
|
|
208
320
|
url: string
|
|
209
|
-
):
|
|
321
|
+
): NavigationState {
|
|
210
322
|
const resolvedParams = params ?? {};
|
|
211
323
|
// Module-level fallback for tests (no NavigationProvider) and SSR
|
|
212
324
|
setCurrentParams(resolvedParams);
|
|
213
|
-
//
|
|
325
|
+
// globalThis bridge — kept for backward compat
|
|
214
326
|
const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0] || '/';
|
|
215
|
-
|
|
327
|
+
const navState: NavigationState = { params: resolvedParams, pathname };
|
|
328
|
+
setNavigationState(navState);
|
|
329
|
+
return navState;
|
|
216
330
|
}
|
|
217
331
|
|
|
218
332
|
/**
|
|
219
333
|
* Render a payload via navigateTransition (production) or renderRoot (tests).
|
|
220
|
-
* The perform callback should fetch data, update state, and return the
|
|
221
|
-
*
|
|
222
|
-
*
|
|
334
|
+
* The perform callback should fetch data, update state, and return the
|
|
335
|
+
* FetchResult plus the NavigationState (so it can be passed explicitly
|
|
336
|
+
* to wrapPayload/renderRoot without temporal coupling).
|
|
223
337
|
*/
|
|
224
338
|
async function renderViaTransition(
|
|
225
339
|
url: string,
|
|
226
|
-
perform: () => Promise<FetchResult>
|
|
340
|
+
perform: () => Promise<FetchResult & { navState: NavigationState }>
|
|
227
341
|
): Promise<HeadElement[] | null> {
|
|
228
342
|
if (deps.navigateTransition) {
|
|
229
343
|
let headElements: HeadElement[] | null = null;
|
|
@@ -239,7 +353,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
239
353
|
headElements: result.headElements,
|
|
240
354
|
params: result.params,
|
|
241
355
|
});
|
|
242
|
-
|
|
356
|
+
// Pass navState explicitly — wrapPayload wraps element in
|
|
357
|
+
// NavigationProvider with this state, no getNavigationState() needed.
|
|
358
|
+
return wrapPayload(merged, result.navState);
|
|
243
359
|
});
|
|
244
360
|
return headElements;
|
|
245
361
|
}
|
|
@@ -253,7 +369,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
253
369
|
headElements: result.headElements,
|
|
254
370
|
params: result.params,
|
|
255
371
|
});
|
|
256
|
-
renderPayload(merged);
|
|
372
|
+
renderPayload(merged, result.navState);
|
|
257
373
|
return result.headElements;
|
|
258
374
|
}
|
|
259
375
|
|
|
@@ -273,14 +389,25 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
273
389
|
}
|
|
274
390
|
}
|
|
275
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Schedule scroll restoration after the next paint and fire the
|
|
394
|
+
* scroll-restored event. Used by navigate, popstate, and refresh.
|
|
395
|
+
*/
|
|
396
|
+
function restoreScrollAfterPaint(scrollY: number): void {
|
|
397
|
+
afterPaint(() => {
|
|
398
|
+
deps.scrollTo(0, scrollY);
|
|
399
|
+
window.dispatchEvent(new Event('timber:scroll-restored'));
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
276
403
|
/**
|
|
277
404
|
* Core navigation logic shared between the transition and fallback paths.
|
|
278
405
|
* Fetches the RSC payload, updates all state, and returns the result.
|
|
279
406
|
*/
|
|
280
407
|
async function performNavigationFetch(
|
|
281
408
|
url: string,
|
|
282
|
-
options: { replace: boolean }
|
|
283
|
-
): Promise<FetchResult> {
|
|
409
|
+
options: { replace: boolean; signal?: AbortSignal; skipHistory?: boolean }
|
|
410
|
+
): Promise<FetchResult & { navState: NavigationState }> {
|
|
284
411
|
// Check prefetch cache first. PrefetchResult has optional segmentInfo/params
|
|
285
412
|
// fields — normalize to null for FetchResult compatibility.
|
|
286
413
|
const prefetched = prefetchCache.consume(url);
|
|
@@ -302,14 +429,21 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
302
429
|
const currentUrl = rawCurrentUrl.startsWith('http')
|
|
303
430
|
? new URL(rawCurrentUrl).pathname
|
|
304
431
|
: new URL(rawCurrentUrl, 'http://localhost').pathname;
|
|
305
|
-
result = await fetchRscPayload(url, deps, stateTree, currentUrl);
|
|
432
|
+
result = await fetchRscPayload(url, deps, stateTree, currentUrl, options.signal);
|
|
306
433
|
}
|
|
307
434
|
|
|
308
|
-
// Update the browser history —
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
435
|
+
// Update the browser history — skip when the Navigation API has already
|
|
436
|
+
// updated the URL via event.intercept() (external navigations).
|
|
437
|
+
if (!options.skipHistory) {
|
|
438
|
+
// Set the router-navigating flag so the Navigation API's navigate
|
|
439
|
+
// listener doesn't double-intercept this pushState/replaceState.
|
|
440
|
+
deps.setRouterNavigating?.(true);
|
|
441
|
+
if (options.replace) {
|
|
442
|
+
deps.replaceState({ timber: true, scrollY: 0 }, '', url);
|
|
443
|
+
} else {
|
|
444
|
+
deps.pushState({ timber: true, scrollY: 0 }, '', url);
|
|
445
|
+
}
|
|
446
|
+
deps.setRouterNavigating?.(false);
|
|
313
447
|
}
|
|
314
448
|
|
|
315
449
|
// NOTE: History push is deferred — the merged payload (after segment
|
|
@@ -320,29 +454,57 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
320
454
|
// Update the segment cache with the new route's segment tree.
|
|
321
455
|
updateSegmentCache(result.segmentInfo);
|
|
322
456
|
|
|
323
|
-
// Update navigation state
|
|
324
|
-
updateNavigationState(result.params, url);
|
|
457
|
+
// Update navigation state and capture it for explicit passing.
|
|
458
|
+
const navState = updateNavigationState(result.params, url);
|
|
325
459
|
|
|
326
|
-
return result;
|
|
460
|
+
return { ...result, navState };
|
|
327
461
|
}
|
|
328
462
|
|
|
329
463
|
async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {
|
|
330
464
|
const scroll = options.scroll !== false;
|
|
331
465
|
const replace = options.replace === true;
|
|
466
|
+
const externalSignal = options._signal as AbortSignal | undefined;
|
|
467
|
+
const skipHistory = options._skipHistory === true;
|
|
468
|
+
|
|
469
|
+
// Create an abort controller for this navigation. Links to the external
|
|
470
|
+
// signal (Navigation API's event.signal) when provided.
|
|
471
|
+
const navAbort = createNavAbort(externalSignal);
|
|
332
472
|
|
|
333
473
|
// Capture the departing page's scroll position for scroll={false} preservation.
|
|
334
474
|
const currentScrollY = deps.getScrollY();
|
|
335
475
|
|
|
336
|
-
// Save the departing page's scroll position
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
476
|
+
// Save the departing page's scroll position — use Navigation API entry
|
|
477
|
+
// state when available, otherwise fall back to history.state.
|
|
478
|
+
if (deps.saveNavigationEntryScroll) {
|
|
479
|
+
deps.saveNavigationEntryScroll(currentScrollY);
|
|
480
|
+
} else {
|
|
481
|
+
deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// When Navigation API is active, initiate the navigation via
|
|
485
|
+
// navigation.navigate() BEFORE the fetch. Unlike history.pushState()
|
|
486
|
+
// which commits the URL synchronously (so Chrome sees it as "done"),
|
|
487
|
+
// navigation.navigate() fires the navigate event before committing.
|
|
488
|
+
// Our handler intercepts with a deferred promise, and Chrome shows
|
|
489
|
+
// its native loading indicator until completeRouterNavigation()
|
|
490
|
+
// resolves it in the finally block (same time as TopLoader clears).
|
|
491
|
+
let effectiveSkipHistory = skipHistory;
|
|
492
|
+
if (!skipHistory && deps.navigationNavigate) {
|
|
493
|
+
deps.setRouterNavigating?.(true);
|
|
494
|
+
deps.navigationNavigate(url, replace);
|
|
495
|
+
deps.setRouterNavigating?.(false);
|
|
496
|
+
effectiveSkipHistory = true;
|
|
497
|
+
}
|
|
340
498
|
|
|
341
499
|
setPending(true, url);
|
|
342
500
|
|
|
343
501
|
try {
|
|
344
502
|
const headElements = await renderViaTransition(url, () =>
|
|
345
|
-
performNavigationFetch(url, {
|
|
503
|
+
performNavigationFetch(url, {
|
|
504
|
+
replace,
|
|
505
|
+
signal: navAbort.signal,
|
|
506
|
+
skipHistory: effectiveSkipHistory,
|
|
507
|
+
})
|
|
346
508
|
);
|
|
347
509
|
|
|
348
510
|
// Update document.title and <meta> tags with the new page's metadata
|
|
@@ -354,51 +516,105 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
354
516
|
// Scroll-to-top on forward navigation, or restore captured position
|
|
355
517
|
// for scroll={false}. React's render() on the document root can reset
|
|
356
518
|
// scroll during DOM reconciliation, so all scroll must be actively managed.
|
|
357
|
-
|
|
358
|
-
if (scroll) {
|
|
359
|
-
deps.scrollTo(0, 0);
|
|
360
|
-
} else {
|
|
361
|
-
deps.scrollTo(0, currentScrollY);
|
|
362
|
-
}
|
|
363
|
-
window.dispatchEvent(new Event('timber:scroll-restored'));
|
|
364
|
-
});
|
|
519
|
+
restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
|
|
365
520
|
} catch (error) {
|
|
521
|
+
// Version skew — server has been redeployed. Trigger full page reload
|
|
522
|
+
// so the browser fetches the new bundle. See TIM-446.
|
|
523
|
+
// Set hard-navigating flag to prevent Navigation API interception
|
|
524
|
+
// and React from rendering during page teardown. See TIM-626.
|
|
525
|
+
if (error instanceof VersionSkewError) {
|
|
526
|
+
setHardNavigating(true);
|
|
527
|
+
// Import triggerStaleReload dynamically to avoid circular deps
|
|
528
|
+
// and keep the reload logic centralized with its loop guard.
|
|
529
|
+
const { triggerStaleReload } = await import('./stale-reload.js');
|
|
530
|
+
triggerStaleReload();
|
|
531
|
+
// Return a never-resolving promise — page is reloading.
|
|
532
|
+
return new Promise(() => {}) as never;
|
|
533
|
+
}
|
|
366
534
|
// Server-side redirect during RSC fetch → soft router navigation.
|
|
535
|
+
// The redirect navigate will push/replace its own URL.
|
|
367
536
|
if (error instanceof RedirectError) {
|
|
368
537
|
setPending(false);
|
|
538
|
+
deps.completeRouterNavigation?.();
|
|
369
539
|
await navigate(error.redirectUrl, { replace: true });
|
|
370
540
|
return;
|
|
371
541
|
}
|
|
542
|
+
// Server 5xx error — hard-navigate so the server renders the
|
|
543
|
+
// error page as HTML. See design/10-error-handling.md
|
|
544
|
+
// §"Error Page Rendering for Client Navigation".
|
|
545
|
+
//
|
|
546
|
+
// Set hard-navigating flag BEFORE setting window.location.href:
|
|
547
|
+
// 1. Prevents Navigation API from intercepting → infinite loop
|
|
548
|
+
// 2. Causes NavigationRoot to throw unresolvedThenable → prevents
|
|
549
|
+
// React from rendering children during page teardown (avoids
|
|
550
|
+
// "Rendered more hooks" crashes). See TIM-626.
|
|
551
|
+
if (error instanceof ServerErrorResponse) {
|
|
552
|
+
setHardNavigating(true);
|
|
553
|
+
window.location.href = error.url;
|
|
554
|
+
return new Promise(() => {}) as never;
|
|
555
|
+
}
|
|
372
556
|
// Abort errors are not application errors — swallow silently.
|
|
373
557
|
if (isAbortError(error)) return;
|
|
374
558
|
throw error;
|
|
375
559
|
} finally {
|
|
560
|
+
// Clear the abort controller so we don't abort a completed navigation
|
|
561
|
+
// when the next one starts. In dev mode, the RSC body stream stays
|
|
562
|
+
// open after data arrives (React's Flight client waits for debug rows).
|
|
563
|
+
// Aborting a "completed" navigation kills the open stream reader →
|
|
564
|
+
// "BodyStreamBuffer was aborted". By clearing the controller here,
|
|
565
|
+
// createNavAbort() becomes a no-op for completed navigations.
|
|
566
|
+
if (currentNavAbort === navAbort) {
|
|
567
|
+
currentNavAbort = null;
|
|
568
|
+
}
|
|
376
569
|
setPending(false);
|
|
570
|
+
// Resolve the Navigation API deferred — clears the browser's native
|
|
571
|
+
// loading state (tab spinner) at the same time as the TopLoader.
|
|
572
|
+
deps.completeRouterNavigation?.();
|
|
377
573
|
}
|
|
378
574
|
}
|
|
379
575
|
|
|
380
576
|
async function refresh(): Promise<void> {
|
|
381
577
|
const currentUrl = deps.getCurrentUrl();
|
|
578
|
+
const navAbort = createNavAbort();
|
|
382
579
|
|
|
383
580
|
setPending(true, currentUrl);
|
|
384
581
|
|
|
385
582
|
try {
|
|
386
583
|
const headElements = await renderViaTransition(currentUrl, async () => {
|
|
387
584
|
// No state tree sent — server renders the complete RSC payload
|
|
388
|
-
const result = await fetchRscPayload(
|
|
585
|
+
const result = await fetchRscPayload(
|
|
586
|
+
currentUrl,
|
|
587
|
+
deps,
|
|
588
|
+
undefined,
|
|
589
|
+
undefined,
|
|
590
|
+
navAbort.signal
|
|
591
|
+
);
|
|
389
592
|
// History push handled by renderViaTransition (stores merged payload)
|
|
390
593
|
updateSegmentCache(result.segmentInfo);
|
|
391
|
-
updateNavigationState(result.params, currentUrl);
|
|
392
|
-
return result;
|
|
594
|
+
const navState = updateNavigationState(result.params, currentUrl);
|
|
595
|
+
return { ...result, navState };
|
|
393
596
|
});
|
|
394
597
|
|
|
395
598
|
applyHead(headElements);
|
|
599
|
+
} catch (error) {
|
|
600
|
+
// Stale transition (superseded by a newer navigation) or aborted
|
|
601
|
+
// fetch — silently ignore. See TIM-629.
|
|
602
|
+
if (isAbortError(error)) return;
|
|
603
|
+
throw error;
|
|
396
604
|
} finally {
|
|
605
|
+
if (currentNavAbort === navAbort) {
|
|
606
|
+
currentNavAbort = null;
|
|
607
|
+
}
|
|
397
608
|
setPending(false);
|
|
609
|
+
deps.completeRouterNavigation?.();
|
|
398
610
|
}
|
|
399
611
|
}
|
|
400
612
|
|
|
401
|
-
async function handlePopState(
|
|
613
|
+
async function handlePopState(
|
|
614
|
+
url: string,
|
|
615
|
+
scrollY: number = 0,
|
|
616
|
+
externalSignal?: AbortSignal
|
|
617
|
+
): Promise<void> {
|
|
402
618
|
// Scroll position is read from history.state by the caller (browser-entry.ts)
|
|
403
619
|
// and passed in. This is more reliable than tracking scroll per-URL in memory
|
|
404
620
|
// because the browser maintains per-entry state even with duplicate URLs.
|
|
@@ -406,35 +622,40 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
406
622
|
|
|
407
623
|
if (entry && entry.payload !== null) {
|
|
408
624
|
// Replay cached payload — no server roundtrip
|
|
409
|
-
updateNavigationState(entry.params, url);
|
|
410
|
-
renderPayload(entry.payload);
|
|
625
|
+
const navState = updateNavigationState(entry.params, url);
|
|
626
|
+
renderPayload(entry.payload, navState);
|
|
411
627
|
applyHead(entry.headElements);
|
|
412
|
-
|
|
413
|
-
deps.scrollTo(0, scrollY);
|
|
414
|
-
window.dispatchEvent(new Event('timber:scroll-restored'));
|
|
415
|
-
});
|
|
628
|
+
restoreScrollAfterPaint(scrollY);
|
|
416
629
|
} else {
|
|
417
630
|
// No cached payload — fetch from server.
|
|
418
631
|
// This happens when navigating back to the initial SSR'd page
|
|
419
632
|
// (its payload is null since it was rendered via SSR, not RSC fetch)
|
|
420
633
|
// or when the entry doesn't exist at all.
|
|
634
|
+
const navAbort = createNavAbort(externalSignal);
|
|
421
635
|
setPending(true, url);
|
|
422
636
|
try {
|
|
423
637
|
const headElements = await renderViaTransition(url, async () => {
|
|
424
|
-
const stateTree = segmentCache.serializeStateTree(
|
|
425
|
-
|
|
638
|
+
const stateTree = segmentCache.serializeStateTree(
|
|
639
|
+
segmentElementCache.getMergeablePaths()
|
|
640
|
+
);
|
|
641
|
+
const result = await fetchRscPayload(url, deps, stateTree, undefined, navAbort.signal);
|
|
426
642
|
updateSegmentCache(result.segmentInfo);
|
|
427
|
-
updateNavigationState(result.params, url);
|
|
643
|
+
const navState = updateNavigationState(result.params, url);
|
|
428
644
|
// History push handled by renderViaTransition (stores merged payload)
|
|
429
|
-
return result;
|
|
645
|
+
return { ...result, navState };
|
|
430
646
|
});
|
|
431
647
|
|
|
432
648
|
applyHead(headElements);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
649
|
+
restoreScrollAfterPaint(scrollY);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
// Stale transition (superseded by a newer navigation) or aborted
|
|
652
|
+
// fetch — silently ignore. See TIM-629.
|
|
653
|
+
if (isAbortError(error)) return;
|
|
654
|
+
throw error;
|
|
437
655
|
} finally {
|
|
656
|
+
if (currentNavAbort === navAbort) {
|
|
657
|
+
currentNavAbort = null;
|
|
658
|
+
}
|
|
438
659
|
setPending(false);
|
|
439
660
|
}
|
|
440
661
|
}
|
|
@@ -465,8 +686,8 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
465
686
|
navigate,
|
|
466
687
|
refresh,
|
|
467
688
|
handlePopState,
|
|
468
|
-
isPending: () =>
|
|
469
|
-
getPendingUrl: () =>
|
|
689
|
+
isPending: () => routerPhase.phase === 'navigating',
|
|
690
|
+
getPendingUrl: () => (routerPhase.phase === 'navigating' ? routerPhase.targetUrl : null),
|
|
470
691
|
onPendingChange(listener) {
|
|
471
692
|
pendingListeners.add(listener);
|
|
472
693
|
return () => pendingListeners.delete(listener);
|
|
@@ -483,7 +704,11 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
483
704
|
payload: merged,
|
|
484
705
|
headElements,
|
|
485
706
|
});
|
|
486
|
-
|
|
707
|
+
// Revalidation doesn't change params/pathname — preserve current state.
|
|
708
|
+
// DO NOT call updateNavigationState(null, ...) here: that normalizes
|
|
709
|
+
// params to {}, clearing dynamic route params on every action response.
|
|
710
|
+
const navState = getNavigationState();
|
|
711
|
+
renderPayload(merged, navState);
|
|
487
712
|
applyHead(headElements);
|
|
488
713
|
},
|
|
489
714
|
initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),
|