@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/dist/client/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
2
|
+
import { n as __exportAll } from "../_chunks/chunk-DYhsFzuS.js";
|
|
3
|
+
import { t as classifyUrlSegment } from "../_chunks/segment-classify-BDNn6EzD.js";
|
|
4
|
+
import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-query-states-DAhgj8Gx.js";
|
|
5
|
+
import { n as useSegmentContext, r as mergePreservedSearchParams, t as SegmentProvider } from "../_chunks/segment-context-hzuJ048X.js";
|
|
6
|
+
import { a as _setCachedSearch, c as cachedSearch, d as globalRouter, i as setSsrData, l as cachedSearchParams, n as clearSsrData, o as _setCurrentParams, r as getSsrData, s as _setGlobalRouter, t as TimberErrorBoundary, u as currentParams } from "../_chunks/error-boundary-D9hzsveV.js";
|
|
7
|
+
import { t as _registerUseCookieModule } from "../_chunks/define-cookie-B5mewxwM.js";
|
|
8
|
+
import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useEffect, useRef, useState, useSyncExternalStore, useTransition } from "react";
|
|
7
9
|
import { jsx } from "react/jsx-runtime";
|
|
8
10
|
//#region src/client/use-link-status.ts
|
|
9
11
|
/**
|
|
@@ -40,13 +42,85 @@ function useLinkStatus() {
|
|
|
40
42
|
return useContext(LinkStatusContext);
|
|
41
43
|
}
|
|
42
44
|
//#endregion
|
|
45
|
+
//#region src/client/router-ref.ts
|
|
46
|
+
/**
|
|
47
|
+
* Set the global router instance. Called once during bootstrap.
|
|
48
|
+
*/
|
|
49
|
+
function setGlobalRouter(router) {
|
|
50
|
+
_setGlobalRouter(router);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the global router instance. Throws if called before bootstrap.
|
|
54
|
+
* Used by client-side hooks (useNavigationPending, etc.)
|
|
55
|
+
*/
|
|
56
|
+
function getRouter() {
|
|
57
|
+
if (!globalRouter) throw new Error("[timber] Router not initialized. getRouter() was called before bootstrap().");
|
|
58
|
+
return globalRouter;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the global router instance or null if not yet initialized.
|
|
62
|
+
* Used by useRouter() methods to avoid silent failures — callers
|
|
63
|
+
* can log a meaningful warning instead of silently no-oping.
|
|
64
|
+
*/
|
|
65
|
+
function getRouterOrNull() {
|
|
66
|
+
return globalRouter;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/client/link-pending-store.ts
|
|
70
|
+
var LINK_PENDING_KEY = Symbol.for("__timber_link_pending");
|
|
71
|
+
/** Status object indicating link is pending — shared reference */
|
|
72
|
+
var PENDING_LINK_STATUS = { pending: true };
|
|
73
|
+
/** Status object indicating link is idle — shared reference */
|
|
74
|
+
var IDLE_LINK_STATUS = { pending: false };
|
|
75
|
+
function getStore() {
|
|
76
|
+
const g = globalThis;
|
|
77
|
+
if (!g[LINK_PENDING_KEY]) g[LINK_PENDING_KEY] = {
|
|
78
|
+
current: null,
|
|
79
|
+
navId: 0
|
|
80
|
+
};
|
|
81
|
+
return g[LINK_PENDING_KEY];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Register the link instance that initiated the current navigation.
|
|
85
|
+
*
|
|
86
|
+
* Called from <Link>'s click handler before router.navigate().
|
|
87
|
+
* - Resets the previous pending link to IDLE (urgent update, immediate)
|
|
88
|
+
* - Does NOT set the new link to PENDING here — the Link's click handler
|
|
89
|
+
* calls setLinkStatus(PENDING) directly for the eager show
|
|
90
|
+
* - Increments the navId counter for stale-clear protection
|
|
91
|
+
*
|
|
92
|
+
* Pass `null` to clear (e.g., for programmatic navigations).
|
|
93
|
+
*/
|
|
94
|
+
function setLinkForCurrentNavigation(link) {
|
|
95
|
+
const store = getStore();
|
|
96
|
+
const prev = store.current;
|
|
97
|
+
if (prev && prev !== link) prev.setLinkStatus(IDLE_LINK_STATUS);
|
|
98
|
+
store.current = link;
|
|
99
|
+
store.navId++;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Unmount a link instance from navigation tracking. Called when a Link
|
|
103
|
+
* component unmounts while it is the current navigation link. Prevents
|
|
104
|
+
* calling setState on an unmounted component.
|
|
105
|
+
*/
|
|
106
|
+
function unmountLinkForCurrentNavigation(link) {
|
|
107
|
+
const store = getStore();
|
|
108
|
+
if (store.current === link) store.current = null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Store metadata from Link's onClick for the next navigate event.
|
|
112
|
+
* Called synchronously in the click handler — the navigate event
|
|
113
|
+
* fires synchronously after onClick returns.
|
|
114
|
+
*/
|
|
115
|
+
function setNavLinkMetadata(metadata) {}
|
|
116
|
+
//#endregion
|
|
43
117
|
//#region src/client/navigation-context.ts
|
|
44
118
|
/**
|
|
45
119
|
* NavigationContext — React context for navigation state.
|
|
46
120
|
*
|
|
47
121
|
* Holds the current route params and pathname, updated atomically
|
|
48
122
|
* with the RSC tree on each navigation. This replaces the previous
|
|
49
|
-
* useSyncExternalStore approach for
|
|
123
|
+
* useSyncExternalStore approach for useSegmentParams() and usePathname(),
|
|
50
124
|
* which suffered from a timing gap: the new tree could commit before
|
|
51
125
|
* the external store re-renders fired, causing a frame where both
|
|
52
126
|
* old and new active states were visible simultaneously.
|
|
@@ -87,8 +161,8 @@ function useLinkStatus() {
|
|
|
87
161
|
* Context instances are stored on globalThis (NOT in module-level
|
|
88
162
|
* variables) because the ESM bundler can duplicate this module across
|
|
89
163
|
* chunks. Module-level variables would create separate instances per
|
|
90
|
-
* chunk — the provider in
|
|
91
|
-
* context A while the consumer in
|
|
164
|
+
* chunk — the provider in NavigationRoot (index chunk) would use
|
|
165
|
+
* context A while the consumer in useNavigationPending (shared chunk)
|
|
92
166
|
* reads from context B. globalThis guarantees a single instance.
|
|
93
167
|
*
|
|
94
168
|
* See design/27-chunking-strategy.md §"Singleton Safety"
|
|
@@ -107,7 +181,7 @@ function getOrCreateContext() {
|
|
|
107
181
|
/**
|
|
108
182
|
* Read the navigation context. Returns null during SSR (no provider)
|
|
109
183
|
* or in the RSC environment (no context available).
|
|
110
|
-
* Internal — used by
|
|
184
|
+
* Internal — used by useSegmentParams() and usePathname().
|
|
111
185
|
*/
|
|
112
186
|
function useNavigationContext() {
|
|
113
187
|
const ctx = getOrCreateContext();
|
|
@@ -156,8 +230,9 @@ function getNavigationState() {
|
|
|
156
230
|
}
|
|
157
231
|
/**
|
|
158
232
|
* Separate context for the in-flight navigation URL. Provided by
|
|
159
|
-
*
|
|
160
|
-
* and
|
|
233
|
+
* NavigationRoot (urgent useState), consumed by useNavigationPending
|
|
234
|
+
* and TopLoader. Per-link pending state uses useOptimistic instead
|
|
235
|
+
* (see link-pending-store.ts).
|
|
161
236
|
*
|
|
162
237
|
* Uses globalThis via Symbol.for for the same reason as NavigationContext
|
|
163
238
|
* above — the bundler may duplicate this module across chunks, and module-
|
|
@@ -183,48 +258,90 @@ function usePendingNavigationUrl() {
|
|
|
183
258
|
return React.useContext(ctx);
|
|
184
259
|
}
|
|
185
260
|
//#endregion
|
|
186
|
-
//#region src/client/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
261
|
+
//#region src/client/top-loader.tsx
|
|
262
|
+
/**
|
|
263
|
+
* TopLoader — Built-in progress bar for client navigations.
|
|
264
|
+
*
|
|
265
|
+
* Shows an animated progress bar at the top of the viewport while an RSC
|
|
266
|
+
* navigation is in flight. Injected automatically by the framework into
|
|
267
|
+
* NavigationRoot — users never render this component directly.
|
|
268
|
+
*
|
|
269
|
+
* Configuration is via timber.config.ts `topLoader` key. Enabled by default.
|
|
270
|
+
* Users who want a fully custom progress indicator disable the built-in one
|
|
271
|
+
* (`topLoader: { enabled: false }`) and use `useNavigationPending()` directly.
|
|
272
|
+
*
|
|
273
|
+
* Animation approach: pure CSS @keyframes. The bar crawls from 0% to ~90%
|
|
274
|
+
* width over ~30s using ease-out timing. When navigation completes, the bar
|
|
275
|
+
* snaps to 100% and fades out over 200ms. No JS animation loops (RAF, setInterval).
|
|
276
|
+
*
|
|
277
|
+
* Phase transitions are derived synchronously during render (React's
|
|
278
|
+
* getDerivedStateFromProps pattern) — no useEffect needed for state tracking.
|
|
279
|
+
* The finishing → hidden cleanup uses onTransitionEnd from the CSS transition.
|
|
280
|
+
*
|
|
281
|
+
* When delay > 0, CSS animation-delay + a visibility keyframe ensure the bar
|
|
282
|
+
* stays invisible during the delay period. If navigation finishes before the
|
|
283
|
+
* delay, the bar was never visible so the finish transition is also invisible.
|
|
284
|
+
*
|
|
285
|
+
* See design/19-client-navigation.md §"useNavigationPending()"
|
|
286
|
+
* See LOCAL-336 for design decisions.
|
|
287
|
+
*/
|
|
201
288
|
//#endregion
|
|
202
|
-
//#region src/client/
|
|
289
|
+
//#region src/client/navigation-root.tsx
|
|
203
290
|
/**
|
|
204
|
-
*
|
|
291
|
+
* Module-level flag indicating a hard (MPA) navigation is in progress.
|
|
292
|
+
*
|
|
293
|
+
* When true:
|
|
294
|
+
* - NavigationRoot throws an unresolved thenable to suspend forever,
|
|
295
|
+
* preventing React from rendering children during page teardown
|
|
296
|
+
* (avoids "Rendered more hooks" crashes).
|
|
297
|
+
* - The Navigation API handler skips interception, letting the browser
|
|
298
|
+
* perform a full page load (prevents infinite loops where
|
|
299
|
+
* window.location.href → navigate event → router.navigate → 500 →
|
|
300
|
+
* window.location.href → ...).
|
|
301
|
+
*
|
|
302
|
+
* Uses globalThis for singleton guarantee across chunks (same pattern
|
|
303
|
+
* as NavigationContext). See design/19-client-navigation.md §"Singleton
|
|
304
|
+
* Guarantee via globalThis".
|
|
205
305
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
306
|
+
var HARD_NAV_KEY = Symbol.for("__timber_hard_navigating");
|
|
307
|
+
function getHardNavStore() {
|
|
308
|
+
const g = globalThis;
|
|
309
|
+
if (!g[HARD_NAV_KEY]) g[HARD_NAV_KEY] = { value: false };
|
|
310
|
+
return g[HARD_NAV_KEY];
|
|
208
311
|
}
|
|
209
312
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
313
|
+
* Set the hard-navigating flag. Call this BEFORE setting
|
|
314
|
+
* window.location.href or window.location.reload() to prevent:
|
|
315
|
+
* 1. React from rendering children during page teardown
|
|
316
|
+
* 2. Navigation API from intercepting the hard navigation
|
|
212
317
|
*/
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
return globalRouter;
|
|
318
|
+
function setHardNavigating(value) {
|
|
319
|
+
getHardNavStore().value = value;
|
|
216
320
|
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/client/navigation-api.ts
|
|
217
323
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* can log a meaningful warning instead of silently no-oping.
|
|
324
|
+
* Returns true if the Navigation API is available in the current environment.
|
|
325
|
+
* Feature-detected at runtime — no polyfill.
|
|
221
326
|
*/
|
|
222
|
-
function
|
|
223
|
-
return
|
|
327
|
+
function hasNavigationApi() {
|
|
328
|
+
return typeof window !== "undefined" && "navigation" in window && window.navigation != null;
|
|
224
329
|
}
|
|
225
330
|
//#endregion
|
|
226
331
|
//#region src/client/link.tsx
|
|
227
332
|
/**
|
|
333
|
+
* Read the current URL's search string without requiring a React hook.
|
|
334
|
+
* On the client, reads window.location.search. During SSR, reads from
|
|
335
|
+
* the request context (getSsrData). Returns empty string if unavailable.
|
|
336
|
+
*/
|
|
337
|
+
function getCurrentSearch() {
|
|
338
|
+
if (typeof window !== "undefined") return window.location.search;
|
|
339
|
+
const data = getSsrData();
|
|
340
|
+
if (!data) return "";
|
|
341
|
+
const str = new URLSearchParams(data.searchParams).toString();
|
|
342
|
+
return str ? `?${str}` : "";
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
228
345
|
* Reject dangerous URL schemes that could execute script.
|
|
229
346
|
* Security: design/13-security.md § Link scheme injection (test #9)
|
|
230
347
|
*/
|
|
@@ -247,25 +364,53 @@ function isInternalHref(href) {
|
|
|
247
364
|
* - [...param] → catch-all (joined with /)
|
|
248
365
|
* - [[...param]] → optional catch-all (omitted if undefined/empty)
|
|
249
366
|
*/
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
367
|
+
/**
|
|
368
|
+
* Parse a route pattern's path portion into classified segments.
|
|
369
|
+
* Exported for testing. Uses the shared character-based classifier.
|
|
370
|
+
*/
|
|
371
|
+
function parseSegments(pattern) {
|
|
372
|
+
return pattern.split("/").filter(Boolean).map(classifyUrlSegment);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Resolve a single classified segment into its string representation.
|
|
376
|
+
* Returns null for optional catch-all with no value (filtered out before join).
|
|
377
|
+
*/
|
|
378
|
+
function resolveSegment(seg, params, pattern) {
|
|
379
|
+
switch (seg.kind) {
|
|
380
|
+
case "static": return seg.value;
|
|
381
|
+
case "optional-catch-all": {
|
|
382
|
+
const value = params[seg.name];
|
|
383
|
+
if (value === void 0 || Array.isArray(value) && value.length === 0) return null;
|
|
255
384
|
return (Array.isArray(value) ? value : [value]).map(encodeURIComponent).join("/");
|
|
256
385
|
}
|
|
257
|
-
|
|
258
|
-
const value = params[
|
|
259
|
-
if (value === void 0) throw new Error(`<Link> missing required catch-all param "${
|
|
386
|
+
case "catch-all": {
|
|
387
|
+
const value = params[seg.name];
|
|
388
|
+
if (value === void 0) throw new Error(`<Link> missing required catch-all param "${seg.name}" for pattern "${pattern}".`);
|
|
260
389
|
const segments = Array.isArray(value) ? value : [value];
|
|
261
|
-
if (segments.length === 0) throw new Error(`<Link> catch-all param "${
|
|
390
|
+
if (segments.length === 0) throw new Error(`<Link> catch-all param "${seg.name}" must have at least one segment for pattern "${pattern}".`);
|
|
262
391
|
return segments.map(encodeURIComponent).join("/");
|
|
263
392
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
393
|
+
case "dynamic": {
|
|
394
|
+
const value = params[seg.name];
|
|
395
|
+
if (value === void 0) throw new Error(`<Link> missing required param "${seg.name}" for pattern "${pattern}".`);
|
|
396
|
+
if (Array.isArray(value)) throw new Error(`<Link> param "${seg.name}" expected a string but received an array for pattern "${pattern}".`);
|
|
397
|
+
return encodeURIComponent(String(value));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Split a URL pattern into the path portion and any trailing ?query/#hash suffix.
|
|
403
|
+
* Uses URL parsing for correctness rather than manual index arithmetic.
|
|
404
|
+
*/
|
|
405
|
+
function splitPatternSuffix(pattern) {
|
|
406
|
+
if (!pattern.includes("?") && !pattern.includes("#")) return [pattern, ""];
|
|
407
|
+
const url = new URL(pattern, "http://x");
|
|
408
|
+
const suffix = url.search + url.hash;
|
|
409
|
+
return [pattern.slice(0, pattern.length - suffix.length), suffix];
|
|
410
|
+
}
|
|
411
|
+
function interpolateParams(pattern, params) {
|
|
412
|
+
const [pathPart, suffix] = splitPatternSuffix(pattern);
|
|
413
|
+
return ("/" + parseSegments(pathPart).map((seg) => resolveSegment(seg, params, pattern)).filter((s) => s !== null).join("/") || "/") + suffix;
|
|
269
414
|
}
|
|
270
415
|
/**
|
|
271
416
|
* Resolve the final href string from Link props.
|
|
@@ -323,16 +468,33 @@ function shouldInterceptClick(event, resolvedHref) {
|
|
|
323
468
|
* navigation via the router. No global event delegation — each Link owns
|
|
324
469
|
* its own click handling.
|
|
325
470
|
*
|
|
326
|
-
* Supports typed routes via
|
|
327
|
-
*
|
|
471
|
+
* Supports typed routes via the Routes interface (populated by codegen).
|
|
472
|
+
* At runtime:
|
|
473
|
+
* - `segmentParams` prop interpolates dynamic segments in the href pattern
|
|
328
474
|
* - `searchParams` prop serializes query parameters via a SearchParamsDefinition
|
|
475
|
+
*
|
|
476
|
+
* Typed via the LinkFunction callable interface. The base call signature
|
|
477
|
+
* forbids segmentParams; per-route signatures are added by codegen via
|
|
478
|
+
* interface merging. See TIM-624.
|
|
329
479
|
*/
|
|
330
|
-
|
|
331
|
-
const { href:
|
|
480
|
+
var Link = function LinkImpl(props) {
|
|
481
|
+
const { href, prefetch, scroll, segmentParams, searchParams, preserveSearchParams, onNavigate, onClick: userOnClick, onMouseEnter: userOnMouseEnter, children, ...rest } = props;
|
|
482
|
+
const { href: baseHref } = buildLinkProps({
|
|
332
483
|
href,
|
|
333
|
-
params,
|
|
484
|
+
params: segmentParams,
|
|
334
485
|
searchParams
|
|
335
486
|
});
|
|
487
|
+
const [linkStatus, setLinkStatus] = useState(IDLE_LINK_STATUS);
|
|
488
|
+
const linkInstanceRef = useRef(null);
|
|
489
|
+
if (!linkInstanceRef.current) linkInstanceRef.current = { setLinkStatus };
|
|
490
|
+
else linkInstanceRef.current.setLinkStatus = setLinkStatus;
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
const instance = linkInstanceRef.current;
|
|
493
|
+
return () => {
|
|
494
|
+
if (instance) unmountLinkForCurrentNavigation(instance);
|
|
495
|
+
};
|
|
496
|
+
}, []);
|
|
497
|
+
const resolvedHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : baseHref;
|
|
336
498
|
const internal = isInternalHref(resolvedHref);
|
|
337
499
|
const handleClick = internal ? (event) => {
|
|
338
500
|
userOnClick?.(event);
|
|
@@ -349,26 +511,39 @@ function Link({ href, prefetch, scroll, params, searchParams, onNavigate, onClic
|
|
|
349
511
|
}
|
|
350
512
|
const router = getRouterOrNull();
|
|
351
513
|
if (!router) return;
|
|
352
|
-
event.preventDefault();
|
|
353
514
|
const shouldScroll = scroll !== false;
|
|
354
|
-
|
|
515
|
+
setLinkStatus(PENDING_LINK_STATUS);
|
|
516
|
+
setLinkForCurrentNavigation(linkInstanceRef.current);
|
|
517
|
+
if (hasNavigationApi()) {
|
|
518
|
+
setNavLinkMetadata({
|
|
519
|
+
scroll: shouldScroll,
|
|
520
|
+
linkInstance: linkInstanceRef.current
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
const navHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : resolvedHref;
|
|
526
|
+
router.navigate(navHref, { scroll: shouldScroll });
|
|
355
527
|
} : userOnClick;
|
|
356
528
|
const handleMouseEnter = internal && prefetch ? (event) => {
|
|
357
529
|
userOnMouseEnter?.(event);
|
|
358
530
|
const router = getRouterOrNull();
|
|
359
|
-
if (router)
|
|
531
|
+
if (router) {
|
|
532
|
+
const prefetchHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : resolvedHref;
|
|
533
|
+
router.prefetch(prefetchHref);
|
|
534
|
+
}
|
|
360
535
|
} : userOnMouseEnter;
|
|
361
536
|
return /* @__PURE__ */ jsx("a", {
|
|
362
537
|
...rest,
|
|
363
538
|
href: resolvedHref,
|
|
364
539
|
onClick: handleClick,
|
|
365
540
|
onMouseEnter: handleMouseEnter,
|
|
366
|
-
children: /* @__PURE__ */ jsx(
|
|
367
|
-
|
|
541
|
+
children: /* @__PURE__ */ jsx(LinkStatusContext.Provider, {
|
|
542
|
+
value: linkStatus,
|
|
368
543
|
children
|
|
369
544
|
})
|
|
370
545
|
});
|
|
371
|
-
}
|
|
546
|
+
};
|
|
372
547
|
//#endregion
|
|
373
548
|
//#region src/client/segment-cache.ts
|
|
374
549
|
/**
|
|
@@ -482,18 +657,38 @@ var PrefetchCache = class PrefetchCache {
|
|
|
482
657
|
* On forward navigation, the new page's payload is pushed onto the stack.
|
|
483
658
|
* On popstate, the cached payload is replayed instantly.
|
|
484
659
|
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
660
|
+
* Supports two keying modes:
|
|
661
|
+
* - **URL-keyed** (default): entries keyed by pathname + search.
|
|
662
|
+
* Used with the History API fallback.
|
|
663
|
+
* - **Entry-key + URL**: when the Navigation API is available,
|
|
664
|
+
* entries can also be stored by Navigation entry key for
|
|
665
|
+
* disambiguation of duplicate URLs in the history stack.
|
|
666
|
+
* Falls back to URL lookup when entry key is not found.
|
|
667
|
+
*
|
|
668
|
+
* Scroll positions are stored in history.state or Navigation API entry
|
|
669
|
+
* state, not in this stack — see design/19-client-navigation.md §Scroll Restoration.
|
|
487
670
|
*
|
|
488
671
|
* Entries persist for the session duration (no expiry) and are cleared
|
|
489
672
|
* when the tab is closed — matching browser back-button behavior.
|
|
490
673
|
*/
|
|
491
674
|
var HistoryStack = class {
|
|
492
675
|
entries = /* @__PURE__ */ new Map();
|
|
493
|
-
|
|
676
|
+
/** Entries keyed by Navigation API entry key for duplicate URL disambiguation. */
|
|
677
|
+
entryKeyMap = /* @__PURE__ */ new Map();
|
|
678
|
+
push(url, entry, entryKey) {
|
|
494
679
|
this.entries.set(url, entry);
|
|
680
|
+
if (entryKey) this.entryKeyMap.set(entryKey, entry);
|
|
495
681
|
}
|
|
496
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Get an entry. When an entry key is provided (Navigation API),
|
|
684
|
+
* tries the entry-key map first for accurate disambiguation of
|
|
685
|
+
* duplicate URLs, then falls back to URL lookup.
|
|
686
|
+
*/
|
|
687
|
+
get(url, entryKey) {
|
|
688
|
+
if (entryKey) {
|
|
689
|
+
const byKey = this.entryKeyMap.get(entryKey);
|
|
690
|
+
if (byKey) return byKey;
|
|
691
|
+
}
|
|
497
692
|
return this.entries.get(url);
|
|
498
693
|
}
|
|
499
694
|
has(url) {
|
|
@@ -520,7 +715,7 @@ var HistoryStack = class {
|
|
|
520
715
|
function setCurrentParams(params) {
|
|
521
716
|
_setCurrentParams(params);
|
|
522
717
|
}
|
|
523
|
-
function
|
|
718
|
+
function useSegmentParams(_route) {
|
|
524
719
|
try {
|
|
525
720
|
const navContext = useNavigationContext();
|
|
526
721
|
if (navContext !== null) return navContext.params;
|
|
@@ -759,10 +954,28 @@ function generateCacheBustId() {
|
|
|
759
954
|
function appendRscParam(url) {
|
|
760
955
|
return `${url}${url.includes("?") ? "&" : "?"}_rsc=${generateCacheBustId()}`;
|
|
761
956
|
}
|
|
957
|
+
/**
|
|
958
|
+
* The client's deployment ID, set at bootstrap from the runtime config.
|
|
959
|
+
* Sent with every RSC/action request for version skew detection.
|
|
960
|
+
* Null in dev mode. See TIM-446.
|
|
961
|
+
*/
|
|
962
|
+
var clientDeploymentId = null;
|
|
963
|
+
/** Header name used by the server to signal a version skew reload. */
|
|
964
|
+
var RELOAD_HEADER = "X-Timber-Reload";
|
|
965
|
+
/** Header name for the client's deployment ID. */
|
|
966
|
+
var DEPLOYMENT_ID_HEADER = "X-Timber-Deployment-Id";
|
|
967
|
+
/**
|
|
968
|
+
* Check if a response signals a version skew reload.
|
|
969
|
+
* Triggers a full page reload if the server indicates the client is stale.
|
|
970
|
+
*/
|
|
971
|
+
function checkReloadSignal(response) {
|
|
972
|
+
return response.headers.get(RELOAD_HEADER) === "1";
|
|
973
|
+
}
|
|
762
974
|
function buildRscHeaders(stateTree, currentUrl) {
|
|
763
975
|
const headers = { Accept: RSC_CONTENT_TYPE };
|
|
764
976
|
if (stateTree) headers["X-Timber-State-Tree"] = JSON.stringify(stateTree);
|
|
765
977
|
if (currentUrl) headers["X-Timber-URL"] = currentUrl;
|
|
978
|
+
if (clientDeploymentId) headers[DEPLOYMENT_ID_HEADER] = clientDeploymentId;
|
|
766
979
|
return headers;
|
|
767
980
|
}
|
|
768
981
|
/**
|
|
@@ -817,7 +1030,7 @@ function extractSkippedSegments(response) {
|
|
|
817
1030
|
* Extract route params from the X-Timber-Params response header.
|
|
818
1031
|
* Returns null if the header is missing or malformed.
|
|
819
1032
|
*
|
|
820
|
-
* Used to populate
|
|
1033
|
+
* Used to populate useSegmentParams() after client-side navigation.
|
|
821
1034
|
*/
|
|
822
1035
|
function extractParams(response) {
|
|
823
1036
|
const header = response.headers.get("X-Timber-Params");
|
|
@@ -840,6 +1053,34 @@ var RedirectError = class extends Error {
|
|
|
840
1053
|
}
|
|
841
1054
|
};
|
|
842
1055
|
/**
|
|
1056
|
+
* Thrown when the server signals a version skew (X-Timber-Reload header).
|
|
1057
|
+
* Caught in navigate() to trigger a full page reload via triggerStaleReload().
|
|
1058
|
+
* See TIM-446.
|
|
1059
|
+
*/
|
|
1060
|
+
var VersionSkewError = class extends Error {
|
|
1061
|
+
constructor() {
|
|
1062
|
+
super("Version skew detected — server has been redeployed");
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
/**
|
|
1066
|
+
* Thrown when the server returns an error for an RSC payload request.
|
|
1067
|
+
* The server sends X-Timber-Error header and a JSON body instead of a
|
|
1068
|
+
* broken RSC stream for any RenderError (4xx or 5xx). Caught in
|
|
1069
|
+
* navigate() to trigger a hard navigation so the server can render
|
|
1070
|
+
* the error page as HTML.
|
|
1071
|
+
*
|
|
1072
|
+
* See design/10-error-handling.md §"Error Page Rendering for Client Navigation"
|
|
1073
|
+
*/
|
|
1074
|
+
var ServerErrorResponse = class extends Error {
|
|
1075
|
+
status;
|
|
1076
|
+
url;
|
|
1077
|
+
constructor(status, url) {
|
|
1078
|
+
super(`Server error ${status} during navigation to ${url}`);
|
|
1079
|
+
this.status = status;
|
|
1080
|
+
this.url = url;
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
/**
|
|
843
1084
|
* Fetch an RSC payload from the server. If a decodeRsc function is provided,
|
|
844
1085
|
* the response is decoded into a React element tree via createFromFetch.
|
|
845
1086
|
* Otherwise, the raw response text is returned (test mode).
|
|
@@ -847,21 +1088,24 @@ var RedirectError = class extends Error {
|
|
|
847
1088
|
* Also extracts head elements from the X-Timber-Head response header
|
|
848
1089
|
* so the client can update document.title and <meta> tags after navigation.
|
|
849
1090
|
*/
|
|
850
|
-
async function fetchRscPayload(url, deps, stateTree, currentUrl) {
|
|
1091
|
+
async function fetchRscPayload(url, deps, stateTree, currentUrl, signal) {
|
|
851
1092
|
const rscUrl = appendRscParam(url);
|
|
852
1093
|
const headers = buildRscHeaders(stateTree, currentUrl);
|
|
853
1094
|
if (deps.decodeRsc) {
|
|
854
1095
|
const fetchPromise = deps.fetch(rscUrl, {
|
|
855
1096
|
headers,
|
|
856
|
-
redirect: "manual"
|
|
1097
|
+
redirect: "manual",
|
|
1098
|
+
signal
|
|
857
1099
|
});
|
|
858
1100
|
let headElements = null;
|
|
859
1101
|
let segmentInfo = null;
|
|
860
1102
|
let params = null;
|
|
861
1103
|
let skippedSegments = null;
|
|
862
1104
|
const wrappedPromise = fetchPromise.then((response) => {
|
|
1105
|
+
if (checkReloadSignal(response)) throw new VersionSkewError();
|
|
863
1106
|
const redirectLocation = response.headers.get("X-Timber-Redirect") || (response.status >= 300 && response.status < 400 ? response.headers.get("Location") : null);
|
|
864
1107
|
if (redirectLocation) throw new RedirectError(redirectLocation);
|
|
1108
|
+
if (response.headers.get("X-Timber-Error") === "1") throw new ServerErrorResponse(response.status, url);
|
|
865
1109
|
headElements = extractHeadElements(response);
|
|
866
1110
|
segmentInfo = extractSegmentInfo(response);
|
|
867
1111
|
params = extractParams(response);
|
|
@@ -879,7 +1123,8 @@ async function fetchRscPayload(url, deps, stateTree, currentUrl) {
|
|
|
879
1123
|
}
|
|
880
1124
|
const response = await deps.fetch(rscUrl, {
|
|
881
1125
|
headers,
|
|
882
|
-
redirect: "manual"
|
|
1126
|
+
redirect: "manual",
|
|
1127
|
+
signal
|
|
883
1128
|
});
|
|
884
1129
|
if (response.status >= 300 && response.status < 400) {
|
|
885
1130
|
const location = response.headers.get("Location");
|
|
@@ -904,23 +1149,34 @@ function isAbortError(error) {
|
|
|
904
1149
|
if (error instanceof Error && error.name === "AbortError") return true;
|
|
905
1150
|
return false;
|
|
906
1151
|
}
|
|
907
|
-
/**
|
|
908
|
-
* Create a router instance. In production, called once at app hydration
|
|
909
|
-
* with real browser APIs. In tests, called with mock dependencies.
|
|
910
|
-
*/
|
|
911
1152
|
function createRouter(deps) {
|
|
912
1153
|
const segmentCache = new SegmentCache();
|
|
913
1154
|
const prefetchCache = new PrefetchCache();
|
|
914
1155
|
const historyStack = new HistoryStack();
|
|
915
1156
|
const segmentElementCache = new SegmentElementCache();
|
|
916
|
-
let
|
|
917
|
-
let pendingUrl = null;
|
|
1157
|
+
let routerPhase = { phase: "idle" };
|
|
918
1158
|
const pendingListeners = /* @__PURE__ */ new Set();
|
|
1159
|
+
let currentNavAbort = null;
|
|
1160
|
+
/**
|
|
1161
|
+
* Create a new AbortController for a navigation, aborting any
|
|
1162
|
+
* previous in-flight navigation. Optionally links to an external
|
|
1163
|
+
* signal (e.g., from the Navigation API's NavigateEvent.signal).
|
|
1164
|
+
*/
|
|
1165
|
+
function createNavAbort(externalSignal) {
|
|
1166
|
+
currentNavAbort?.abort();
|
|
1167
|
+
const controller = new AbortController();
|
|
1168
|
+
currentNavAbort = controller;
|
|
1169
|
+
if (externalSignal) if (externalSignal.aborted) controller.abort();
|
|
1170
|
+
else externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
1171
|
+
return controller;
|
|
1172
|
+
}
|
|
919
1173
|
function setPending(value, url) {
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1174
|
+
const next = value && url ? {
|
|
1175
|
+
phase: "navigating",
|
|
1176
|
+
targetUrl: url
|
|
1177
|
+
} : { phase: "idle" };
|
|
1178
|
+
if (routerPhase.phase === next.phase && (routerPhase.phase === "idle" || routerPhase.phase === "navigating" && next.phase === "navigating" && routerPhase.targetUrl === next.targetUrl)) return;
|
|
1179
|
+
routerPhase = next;
|
|
924
1180
|
for (const listener of pendingListeners) listener(value);
|
|
925
1181
|
}
|
|
926
1182
|
/** Update the segment cache from server-provided segment metadata. */
|
|
@@ -930,8 +1186,8 @@ function createRouter(deps) {
|
|
|
930
1186
|
if (tree) segmentCache.set("/", tree);
|
|
931
1187
|
}
|
|
932
1188
|
/** Render a decoded RSC payload into the DOM if a renderer is available. */
|
|
933
|
-
function renderPayload(payload) {
|
|
934
|
-
if (deps.renderRoot) deps.renderRoot(payload);
|
|
1189
|
+
function renderPayload(payload, navState) {
|
|
1190
|
+
if (deps.renderRoot) deps.renderRoot(payload, navState);
|
|
935
1191
|
}
|
|
936
1192
|
/**
|
|
937
1193
|
* Merge a partial RSC payload with cached segment elements if segments
|
|
@@ -947,24 +1203,26 @@ function createRouter(deps) {
|
|
|
947
1203
|
/**
|
|
948
1204
|
* Update navigation state (params + pathname) for the next render.
|
|
949
1205
|
*
|
|
950
|
-
* Sets
|
|
951
|
-
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
1206
|
+
* Sets the module-level fallback (for tests and SSR) and the
|
|
1207
|
+
* globalThis bridge, then returns the NavigationState so callers
|
|
1208
|
+
* can pass it explicitly to renderRoot/wrapPayload — eliminating
|
|
1209
|
+
* temporal coupling with getNavigationState().
|
|
954
1210
|
*/
|
|
955
1211
|
function updateNavigationState(params, url) {
|
|
956
1212
|
const resolvedParams = params ?? {};
|
|
957
1213
|
setCurrentParams(resolvedParams);
|
|
958
|
-
|
|
1214
|
+
const navState = {
|
|
959
1215
|
params: resolvedParams,
|
|
960
1216
|
pathname: url.startsWith("http") ? new URL(url).pathname : url.split("?")[0] || "/"
|
|
961
|
-
}
|
|
1217
|
+
};
|
|
1218
|
+
setNavigationState(navState);
|
|
1219
|
+
return navState;
|
|
962
1220
|
}
|
|
963
1221
|
/**
|
|
964
1222
|
* Render a payload via navigateTransition (production) or renderRoot (tests).
|
|
965
|
-
* The perform callback should fetch data, update state, and return the
|
|
966
|
-
*
|
|
967
|
-
*
|
|
1223
|
+
* The perform callback should fetch data, update state, and return the
|
|
1224
|
+
* FetchResult plus the NavigationState (so it can be passed explicitly
|
|
1225
|
+
* to wrapPayload/renderRoot without temporal coupling).
|
|
968
1226
|
*/
|
|
969
1227
|
async function renderViaTransition(url, perform) {
|
|
970
1228
|
if (deps.navigateTransition) {
|
|
@@ -978,7 +1236,7 @@ function createRouter(deps) {
|
|
|
978
1236
|
headElements: result.headElements,
|
|
979
1237
|
params: result.params
|
|
980
1238
|
});
|
|
981
|
-
return wrapPayload(merged);
|
|
1239
|
+
return wrapPayload(merged, result.navState);
|
|
982
1240
|
});
|
|
983
1241
|
return headElements;
|
|
984
1242
|
}
|
|
@@ -989,7 +1247,7 @@ function createRouter(deps) {
|
|
|
989
1247
|
headElements: result.headElements,
|
|
990
1248
|
params: result.params
|
|
991
1249
|
});
|
|
992
|
-
renderPayload(merged);
|
|
1250
|
+
renderPayload(merged, result.navState);
|
|
993
1251
|
return result.headElements;
|
|
994
1252
|
}
|
|
995
1253
|
/** Apply head elements (title, meta tags) to the DOM if available. */
|
|
@@ -1002,6 +1260,16 @@ function createRouter(deps) {
|
|
|
1002
1260
|
else callback();
|
|
1003
1261
|
}
|
|
1004
1262
|
/**
|
|
1263
|
+
* Schedule scroll restoration after the next paint and fire the
|
|
1264
|
+
* scroll-restored event. Used by navigate, popstate, and refresh.
|
|
1265
|
+
*/
|
|
1266
|
+
function restoreScrollAfterPaint(scrollY) {
|
|
1267
|
+
afterPaint(() => {
|
|
1268
|
+
deps.scrollTo(0, scrollY);
|
|
1269
|
+
window.dispatchEvent(new Event("timber:scroll-restored"));
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1005
1273
|
* Core navigation logic shared between the transition and fallback paths.
|
|
1006
1274
|
* Fetches the RSC payload, updates all state, and returns the result.
|
|
1007
1275
|
*/
|
|
@@ -1017,87 +1285,130 @@ function createRouter(deps) {
|
|
|
1017
1285
|
if (result === void 0) {
|
|
1018
1286
|
const stateTree = segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths());
|
|
1019
1287
|
const rawCurrentUrl = deps.getCurrentUrl();
|
|
1020
|
-
result = await fetchRscPayload(url, deps, stateTree, rawCurrentUrl.startsWith("http") ? new URL(rawCurrentUrl).pathname : new URL(rawCurrentUrl, "http://localhost").pathname);
|
|
1288
|
+
result = await fetchRscPayload(url, deps, stateTree, rawCurrentUrl.startsWith("http") ? new URL(rawCurrentUrl).pathname : new URL(rawCurrentUrl, "http://localhost").pathname, options.signal);
|
|
1289
|
+
}
|
|
1290
|
+
if (!options.skipHistory) {
|
|
1291
|
+
deps.setRouterNavigating?.(true);
|
|
1292
|
+
if (options.replace) deps.replaceState({
|
|
1293
|
+
timber: true,
|
|
1294
|
+
scrollY: 0
|
|
1295
|
+
}, "", url);
|
|
1296
|
+
else deps.pushState({
|
|
1297
|
+
timber: true,
|
|
1298
|
+
scrollY: 0
|
|
1299
|
+
}, "", url);
|
|
1300
|
+
deps.setRouterNavigating?.(false);
|
|
1021
1301
|
}
|
|
1022
|
-
if (options.replace) deps.replaceState({
|
|
1023
|
-
timber: true,
|
|
1024
|
-
scrollY: 0
|
|
1025
|
-
}, "", url);
|
|
1026
|
-
else deps.pushState({
|
|
1027
|
-
timber: true,
|
|
1028
|
-
scrollY: 0
|
|
1029
|
-
}, "", url);
|
|
1030
1302
|
updateSegmentCache(result.segmentInfo);
|
|
1031
|
-
updateNavigationState(result.params, url);
|
|
1032
|
-
return
|
|
1303
|
+
const navState = updateNavigationState(result.params, url);
|
|
1304
|
+
return {
|
|
1305
|
+
...result,
|
|
1306
|
+
navState
|
|
1307
|
+
};
|
|
1033
1308
|
}
|
|
1034
1309
|
async function navigate(url, options = {}) {
|
|
1035
1310
|
const scroll = options.scroll !== false;
|
|
1036
1311
|
const replace = options.replace === true;
|
|
1312
|
+
const externalSignal = options._signal;
|
|
1313
|
+
const skipHistory = options._skipHistory === true;
|
|
1314
|
+
const navAbort = createNavAbort(externalSignal);
|
|
1037
1315
|
const currentScrollY = deps.getScrollY();
|
|
1038
|
-
deps.
|
|
1316
|
+
if (deps.saveNavigationEntryScroll) deps.saveNavigationEntryScroll(currentScrollY);
|
|
1317
|
+
else deps.replaceState({
|
|
1039
1318
|
timber: true,
|
|
1040
1319
|
scrollY: currentScrollY
|
|
1041
1320
|
}, "", deps.getCurrentUrl());
|
|
1321
|
+
let effectiveSkipHistory = skipHistory;
|
|
1322
|
+
if (!skipHistory && deps.navigationNavigate) {
|
|
1323
|
+
deps.setRouterNavigating?.(true);
|
|
1324
|
+
deps.navigationNavigate(url, replace);
|
|
1325
|
+
deps.setRouterNavigating?.(false);
|
|
1326
|
+
effectiveSkipHistory = true;
|
|
1327
|
+
}
|
|
1042
1328
|
setPending(true, url);
|
|
1043
1329
|
try {
|
|
1044
|
-
applyHead(await renderViaTransition(url, () => performNavigationFetch(url, {
|
|
1330
|
+
applyHead(await renderViaTransition(url, () => performNavigationFetch(url, {
|
|
1331
|
+
replace,
|
|
1332
|
+
signal: navAbort.signal,
|
|
1333
|
+
skipHistory: effectiveSkipHistory
|
|
1334
|
+
})));
|
|
1045
1335
|
window.dispatchEvent(new Event("timber:navigation-end"));
|
|
1046
|
-
|
|
1047
|
-
if (scroll) deps.scrollTo(0, 0);
|
|
1048
|
-
else deps.scrollTo(0, currentScrollY);
|
|
1049
|
-
window.dispatchEvent(new Event("timber:scroll-restored"));
|
|
1050
|
-
});
|
|
1336
|
+
restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
|
|
1051
1337
|
} catch (error) {
|
|
1338
|
+
if (error instanceof VersionSkewError) {
|
|
1339
|
+
setHardNavigating(true);
|
|
1340
|
+
const { triggerStaleReload } = await import("../_chunks/stale-reload-BLUC_Pl_.js");
|
|
1341
|
+
triggerStaleReload();
|
|
1342
|
+
return new Promise(() => {});
|
|
1343
|
+
}
|
|
1052
1344
|
if (error instanceof RedirectError) {
|
|
1053
1345
|
setPending(false);
|
|
1346
|
+
deps.completeRouterNavigation?.();
|
|
1054
1347
|
await navigate(error.redirectUrl, { replace: true });
|
|
1055
1348
|
return;
|
|
1056
1349
|
}
|
|
1350
|
+
if (error instanceof ServerErrorResponse) {
|
|
1351
|
+
setHardNavigating(true);
|
|
1352
|
+
window.location.href = error.url;
|
|
1353
|
+
return new Promise(() => {});
|
|
1354
|
+
}
|
|
1057
1355
|
if (isAbortError(error)) return;
|
|
1058
1356
|
throw error;
|
|
1059
1357
|
} finally {
|
|
1358
|
+
if (currentNavAbort === navAbort) currentNavAbort = null;
|
|
1060
1359
|
setPending(false);
|
|
1360
|
+
deps.completeRouterNavigation?.();
|
|
1061
1361
|
}
|
|
1062
1362
|
}
|
|
1063
1363
|
async function refresh() {
|
|
1064
1364
|
const currentUrl = deps.getCurrentUrl();
|
|
1365
|
+
const navAbort = createNavAbort();
|
|
1065
1366
|
setPending(true, currentUrl);
|
|
1066
1367
|
try {
|
|
1067
1368
|
applyHead(await renderViaTransition(currentUrl, async () => {
|
|
1068
|
-
const result = await fetchRscPayload(currentUrl, deps);
|
|
1369
|
+
const result = await fetchRscPayload(currentUrl, deps, void 0, void 0, navAbort.signal);
|
|
1069
1370
|
updateSegmentCache(result.segmentInfo);
|
|
1070
|
-
updateNavigationState(result.params, currentUrl);
|
|
1071
|
-
return
|
|
1371
|
+
const navState = updateNavigationState(result.params, currentUrl);
|
|
1372
|
+
return {
|
|
1373
|
+
...result,
|
|
1374
|
+
navState
|
|
1375
|
+
};
|
|
1072
1376
|
}));
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
if (isAbortError(error)) return;
|
|
1379
|
+
throw error;
|
|
1073
1380
|
} finally {
|
|
1381
|
+
if (currentNavAbort === navAbort) currentNavAbort = null;
|
|
1074
1382
|
setPending(false);
|
|
1383
|
+
deps.completeRouterNavigation?.();
|
|
1075
1384
|
}
|
|
1076
1385
|
}
|
|
1077
|
-
async function handlePopState(url, scrollY = 0) {
|
|
1386
|
+
async function handlePopState(url, scrollY = 0, externalSignal) {
|
|
1078
1387
|
const entry = historyStack.get(url);
|
|
1079
1388
|
if (entry && entry.payload !== null) {
|
|
1080
|
-
updateNavigationState(entry.params, url);
|
|
1081
|
-
renderPayload(entry.payload);
|
|
1389
|
+
const navState = updateNavigationState(entry.params, url);
|
|
1390
|
+
renderPayload(entry.payload, navState);
|
|
1082
1391
|
applyHead(entry.headElements);
|
|
1083
|
-
|
|
1084
|
-
deps.scrollTo(0, scrollY);
|
|
1085
|
-
window.dispatchEvent(new Event("timber:scroll-restored"));
|
|
1086
|
-
});
|
|
1392
|
+
restoreScrollAfterPaint(scrollY);
|
|
1087
1393
|
} else {
|
|
1394
|
+
const navAbort = createNavAbort(externalSignal);
|
|
1088
1395
|
setPending(true, url);
|
|
1089
1396
|
try {
|
|
1090
1397
|
applyHead(await renderViaTransition(url, async () => {
|
|
1091
|
-
const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths()));
|
|
1398
|
+
const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths()), void 0, navAbort.signal);
|
|
1092
1399
|
updateSegmentCache(result.segmentInfo);
|
|
1093
|
-
updateNavigationState(result.params, url);
|
|
1094
|
-
return
|
|
1400
|
+
const navState = updateNavigationState(result.params, url);
|
|
1401
|
+
return {
|
|
1402
|
+
...result,
|
|
1403
|
+
navState
|
|
1404
|
+
};
|
|
1095
1405
|
}));
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1406
|
+
restoreScrollAfterPaint(scrollY);
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
if (isAbortError(error)) return;
|
|
1409
|
+
throw error;
|
|
1100
1410
|
} finally {
|
|
1411
|
+
if (currentNavAbort === navAbort) currentNavAbort = null;
|
|
1101
1412
|
setPending(false);
|
|
1102
1413
|
}
|
|
1103
1414
|
}
|
|
@@ -1117,8 +1428,8 @@ function createRouter(deps) {
|
|
|
1117
1428
|
navigate,
|
|
1118
1429
|
refresh,
|
|
1119
1430
|
handlePopState,
|
|
1120
|
-
isPending: () =>
|
|
1121
|
-
getPendingUrl: () =>
|
|
1431
|
+
isPending: () => routerPhase.phase === "navigating",
|
|
1432
|
+
getPendingUrl: () => routerPhase.phase === "navigating" ? routerPhase.targetUrl : null,
|
|
1122
1433
|
onPendingChange(listener) {
|
|
1123
1434
|
pendingListeners.add(listener);
|
|
1124
1435
|
return () => pendingListeners.delete(listener);
|
|
@@ -1131,7 +1442,7 @@ function createRouter(deps) {
|
|
|
1131
1442
|
payload: merged,
|
|
1132
1443
|
headElements
|
|
1133
1444
|
});
|
|
1134
|
-
renderPayload(merged);
|
|
1445
|
+
renderPayload(merged, getNavigationState());
|
|
1135
1446
|
applyHead(headElements);
|
|
1136
1447
|
},
|
|
1137
1448
|
initSegmentCache: (segments) => updateSegmentCache(segments),
|
|
@@ -1354,36 +1665,6 @@ function useSearchParams() {
|
|
|
1354
1665
|
return typeof window !== "undefined" ? getSearchParams() : getServerSearchParams();
|
|
1355
1666
|
}
|
|
1356
1667
|
//#endregion
|
|
1357
|
-
//#region src/client/segment-context.ts
|
|
1358
|
-
/**
|
|
1359
|
-
* Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.
|
|
1360
|
-
*
|
|
1361
|
-
* Each layout in the segment tree is wrapped with a SegmentProvider that stores
|
|
1362
|
-
* the URL segments from root to the current layout level. The hooks read this
|
|
1363
|
-
* context to determine which child segments are active below the calling layout.
|
|
1364
|
-
*
|
|
1365
|
-
* The context value is intentionally minimal: just the segment path array and
|
|
1366
|
-
* parallel route keys. No internal cache details are exposed.
|
|
1367
|
-
*
|
|
1368
|
-
* Design docs: design/19-client-navigation.md, design/14-ecosystem.md
|
|
1369
|
-
*/
|
|
1370
|
-
var SegmentContext = createContext(null);
|
|
1371
|
-
/** Read the segment context. Returns null if no provider is above this component. */
|
|
1372
|
-
function useSegmentContext() {
|
|
1373
|
-
return useContext(SegmentContext);
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
* Wraps each layout to provide segment position context.
|
|
1377
|
-
* Injected by rsc-entry.ts during element tree construction.
|
|
1378
|
-
*/
|
|
1379
|
-
function SegmentProvider({ segments, segmentId: _segmentId, parallelRouteKeys, children }) {
|
|
1380
|
-
const value = useMemo(() => ({
|
|
1381
|
-
segments,
|
|
1382
|
-
parallelRouteKeys
|
|
1383
|
-
}), [segments.join("/"), parallelRouteKeys.join(",")]);
|
|
1384
|
-
return createElement(SegmentContext.Provider, { value }, children);
|
|
1385
|
-
}
|
|
1386
|
-
//#endregion
|
|
1387
1668
|
//#region src/client/use-selected-layout-segment.ts
|
|
1388
1669
|
/**
|
|
1389
1670
|
* useSelectedLayoutSegment / useSelectedLayoutSegments — client-side hooks
|
|
@@ -1589,6 +1870,96 @@ function useFormErrors(result) {
|
|
|
1589
1870
|
};
|
|
1590
1871
|
}
|
|
1591
1872
|
//#endregion
|
|
1592
|
-
|
|
1873
|
+
//#region src/client/use-cookie.ts
|
|
1874
|
+
/**
|
|
1875
|
+
* useCookie — reactive client-side cookie hook.
|
|
1876
|
+
*
|
|
1877
|
+
* Uses useSyncExternalStore for SSR-safe, reactive cookie access.
|
|
1878
|
+
* All components reading the same cookie name re-render on change.
|
|
1879
|
+
* No cross-tab sync (intentional — see design/29-cookies.md).
|
|
1880
|
+
*
|
|
1881
|
+
* See design/29-cookies.md §"useCookie(name) Hook"
|
|
1882
|
+
*/
|
|
1883
|
+
var use_cookie_exports = /* @__PURE__ */ __exportAll({ useCookie: () => useCookie });
|
|
1884
|
+
/** Per-name subscriber sets. */
|
|
1885
|
+
var listeners = /* @__PURE__ */ new Map();
|
|
1886
|
+
/** Parse a cookie name from document.cookie. */
|
|
1887
|
+
function getCookieValue(name) {
|
|
1888
|
+
if (typeof document === "undefined") return void 0;
|
|
1889
|
+
const match = document.cookie.match(new RegExp("(?:^|;\\s*)" + name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*=\\s*([^;]*)"));
|
|
1890
|
+
return match ? decodeURIComponent(match[1]) : void 0;
|
|
1891
|
+
}
|
|
1892
|
+
/** Serialize options into a cookie string suffix. */
|
|
1893
|
+
function serializeOptions(options) {
|
|
1894
|
+
if (!options) return "; Path=/; SameSite=Lax";
|
|
1895
|
+
const parts = [];
|
|
1896
|
+
parts.push(`Path=${options.path ?? "/"}`);
|
|
1897
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
1898
|
+
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
1899
|
+
if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
1900
|
+
const sameSite = options.sameSite ?? "lax";
|
|
1901
|
+
parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
|
|
1902
|
+
if (options.secure) parts.push("Secure");
|
|
1903
|
+
return "; " + parts.join("; ");
|
|
1904
|
+
}
|
|
1905
|
+
/** Notify all subscribers for a given cookie name. */
|
|
1906
|
+
function notify(name) {
|
|
1907
|
+
const subs = listeners.get(name);
|
|
1908
|
+
if (subs) for (const fn of subs) fn();
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Reactive hook for reading/writing a client-side cookie.
|
|
1912
|
+
*
|
|
1913
|
+
* Returns `[value, setCookie, deleteCookie]`:
|
|
1914
|
+
* - `value`: current cookie value (string | undefined)
|
|
1915
|
+
* - `setCookie`: sets the cookie and triggers re-renders
|
|
1916
|
+
* - `deleteCookie`: deletes the cookie and triggers re-renders
|
|
1917
|
+
*
|
|
1918
|
+
* @param name - Cookie name.
|
|
1919
|
+
* @param defaultOptions - Default options for setCookie calls.
|
|
1920
|
+
*/
|
|
1921
|
+
function useCookie(name, defaultOptions) {
|
|
1922
|
+
const subscribe = (callback) => {
|
|
1923
|
+
let subs = listeners.get(name);
|
|
1924
|
+
if (!subs) {
|
|
1925
|
+
subs = /* @__PURE__ */ new Set();
|
|
1926
|
+
listeners.set(name, subs);
|
|
1927
|
+
}
|
|
1928
|
+
subs.add(callback);
|
|
1929
|
+
return () => {
|
|
1930
|
+
subs.delete(callback);
|
|
1931
|
+
if (subs.size === 0) listeners.delete(name);
|
|
1932
|
+
};
|
|
1933
|
+
};
|
|
1934
|
+
const getSnapshot = () => getCookieValue(name);
|
|
1935
|
+
const getServerSnapshot = () => getSsrData()?.cookies.get(name);
|
|
1936
|
+
const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
1937
|
+
const setCookie = (newValue, options) => {
|
|
1938
|
+
const merged = {
|
|
1939
|
+
...defaultOptions,
|
|
1940
|
+
...options
|
|
1941
|
+
};
|
|
1942
|
+
document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;
|
|
1943
|
+
notify(name);
|
|
1944
|
+
};
|
|
1945
|
+
const deleteCookie = () => {
|
|
1946
|
+
const path = defaultOptions?.path ?? "/";
|
|
1947
|
+
const domain = defaultOptions?.domain;
|
|
1948
|
+
let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;
|
|
1949
|
+
if (domain) cookieStr += `; Domain=${domain}`;
|
|
1950
|
+
document.cookie = cookieStr;
|
|
1951
|
+
notify(name);
|
|
1952
|
+
};
|
|
1953
|
+
return [
|
|
1954
|
+
value,
|
|
1955
|
+
setCookie,
|
|
1956
|
+
deleteCookie
|
|
1957
|
+
];
|
|
1958
|
+
}
|
|
1959
|
+
//#endregion
|
|
1960
|
+
//#region src/client/index.ts
|
|
1961
|
+
_registerUseCookieModule(use_cookie_exports);
|
|
1962
|
+
//#endregion
|
|
1963
|
+
export { HistoryStack, Link, LinkStatusContext, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, buildLinkProps, clearSsrData, createRouter, getNavigationState, getRouter, getRouterOrNull, getSsrData, interpolateParams, mergePreservedSearchParams, resolveHref, setCurrentParams, setGlobalRouter, setNavigationState, setSsrData, useActionState, useCookie, useFormAction, useFormErrors, useLinkStatus, useNavigationPending, usePathname, useQueryStates, useRouter, useSearchParams, useSegmentContext, useSegmentParams, useSelectedLayoutSegment, useSelectedLayoutSegments, validateLinkHref };
|
|
1593
1964
|
|
|
1594
1965
|
//# sourceMappingURL=index.js.map
|