@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17
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/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +192 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +64 -25
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +348 -128
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -28,9 +28,15 @@ const DEFAULT_ACTION_STATE: TrackedActionState = {
|
|
|
28
28
|
// Maximum number of history entries to cache (URLs visited)
|
|
29
29
|
const HISTORY_CACHE_SIZE = 20;
|
|
30
30
|
|
|
31
|
-
// Cache entry: [url-key, segments, stale, handleData?]
|
|
31
|
+
// Cache entry: [url-key, segments, stale, handleData?, routerId?]
|
|
32
32
|
// stale=true means the data may be outdated and should be revalidated on access
|
|
33
|
-
type HistoryCacheEntry = [
|
|
33
|
+
type HistoryCacheEntry = [
|
|
34
|
+
string,
|
|
35
|
+
ResolvedSegment[],
|
|
36
|
+
boolean,
|
|
37
|
+
HandleData?,
|
|
38
|
+
string?,
|
|
39
|
+
];
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
42
|
* Shallow clone handleData to avoid reference sharing between cache entries.
|
|
@@ -124,14 +130,14 @@ export interface NavigationStoreConfig {
|
|
|
124
130
|
|
|
125
131
|
/**
|
|
126
132
|
* Enable cross-tab cache invalidation via BroadcastChannel (default: true)
|
|
127
|
-
* When cache is cleared (via server actions or
|
|
133
|
+
* When cache is cleared (via server actions or invalidateClientCache()),
|
|
128
134
|
* other tabs will also clear their cache
|
|
129
135
|
*/
|
|
130
136
|
crossTabSync?: boolean;
|
|
131
137
|
|
|
132
138
|
/**
|
|
133
139
|
* Auto-refresh when another tab mutates data on the same path (default: true)
|
|
134
|
-
* Triggered when cache is cleared via server actions or
|
|
140
|
+
* Triggered when cache is cleared via server actions or invalidateClientCache()
|
|
135
141
|
* Requires crossTabSync to be enabled
|
|
136
142
|
*/
|
|
137
143
|
crossTabAutoRefresh?: boolean;
|
|
@@ -258,6 +264,11 @@ export function createNavigationStore(
|
|
|
258
264
|
// Used to maintain intercept context during action revalidation
|
|
259
265
|
let interceptSourceUrl: string | null = null;
|
|
260
266
|
|
|
267
|
+
// Router identity - tracks which router is currently active.
|
|
268
|
+
// When this changes on a partial response, the client forces a full
|
|
269
|
+
// tree replacement instead of reconciling with stale segments.
|
|
270
|
+
let currentRouterId: string | undefined;
|
|
271
|
+
|
|
261
272
|
// Action state tracking (for useAction hook)
|
|
262
273
|
// Maps action function ID to its tracked state
|
|
263
274
|
const actionStates = new Map<string, TrackedActionState>();
|
|
@@ -269,18 +280,17 @@ export function createNavigationStore(
|
|
|
269
280
|
/**
|
|
270
281
|
* Create a debounced function that batches rapid calls
|
|
271
282
|
*/
|
|
283
|
+
// A non-keyed notifier is the keyed one restricted to a single constant key;
|
|
284
|
+
// its own keyed instance means the "" key never collides with action keys.
|
|
272
285
|
function createDebouncedNotifier<T extends (...args: any[]) => void>(
|
|
273
286
|
fn: T,
|
|
274
287
|
ms: number = 20,
|
|
275
288
|
): T {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
fn(...args);
|
|
282
|
-
}, ms);
|
|
283
|
-
}) as T;
|
|
289
|
+
const keyed = createKeyedDebouncedNotifier(
|
|
290
|
+
(_key: string, ...args: any[]) => fn(...args),
|
|
291
|
+
ms,
|
|
292
|
+
);
|
|
293
|
+
return ((...args: Parameters<T>) => keyed("", ...args)) as T;
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
/**
|
|
@@ -325,12 +335,24 @@ export function createNavigationStore(
|
|
|
325
335
|
}
|
|
326
336
|
|
|
327
337
|
/**
|
|
328
|
-
* Mark
|
|
338
|
+
* Mark every history entry stale WITHOUT touching the prefetch caches or the
|
|
339
|
+
* rango state. Used by the jar-divergence observer: an external rotation has
|
|
340
|
+
* already changed the state value (so prefetch/HTTP entries strand under the
|
|
341
|
+
* retired key), and this tab must NOT re-rotate — only the history cache,
|
|
342
|
+
* which is not state-keyed, needs marking.
|
|
329
343
|
*/
|
|
330
|
-
function
|
|
344
|
+
function markHistoryStale(): void {
|
|
331
345
|
for (let i = 0; i < historyCache.length; i++) {
|
|
332
346
|
historyCache[i][2] = true;
|
|
333
347
|
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Mark all cache entries as stale (internal - does not broadcast). Also
|
|
352
|
+
* clears the prefetch caches, which rotates the rango state.
|
|
353
|
+
*/
|
|
354
|
+
function markCacheAsStaleInternal(): void {
|
|
355
|
+
markHistoryStale();
|
|
334
356
|
clearPrefetchCache();
|
|
335
357
|
}
|
|
336
358
|
|
|
@@ -571,10 +593,17 @@ export function createNavigationStore(
|
|
|
571
593
|
segments,
|
|
572
594
|
false,
|
|
573
595
|
clonedHandleData,
|
|
596
|
+
currentRouterId,
|
|
574
597
|
];
|
|
575
598
|
} else {
|
|
576
599
|
// Add new entry at the end (not stale)
|
|
577
|
-
historyCache.push([
|
|
600
|
+
historyCache.push([
|
|
601
|
+
historyKey,
|
|
602
|
+
segments,
|
|
603
|
+
false,
|
|
604
|
+
clonedHandleData,
|
|
605
|
+
currentRouterId,
|
|
606
|
+
]);
|
|
578
607
|
// Remove oldest entries if over limit
|
|
579
608
|
while (historyCache.length > cacheSize) {
|
|
580
609
|
historyCache.shift();
|
|
@@ -586,14 +615,22 @@ export function createNavigationStore(
|
|
|
586
615
|
* Get cached segments for a history entry
|
|
587
616
|
* Returns { segments, stale, handleData } or undefined if not cached
|
|
588
617
|
*/
|
|
589
|
-
getCachedSegments(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
618
|
+
getCachedSegments(historyKey: string):
|
|
619
|
+
| {
|
|
620
|
+
segments: ResolvedSegment[];
|
|
621
|
+
stale: boolean;
|
|
622
|
+
handleData?: HandleData;
|
|
623
|
+
routerId?: string;
|
|
624
|
+
}
|
|
593
625
|
| undefined {
|
|
594
626
|
const entry = historyCache.find(([key]) => key === historyKey);
|
|
595
627
|
if (!entry) return undefined;
|
|
596
|
-
return {
|
|
628
|
+
return {
|
|
629
|
+
segments: entry[1],
|
|
630
|
+
stale: entry[2],
|
|
631
|
+
handleData: entry[3],
|
|
632
|
+
routerId: entry[4],
|
|
633
|
+
};
|
|
597
634
|
},
|
|
598
635
|
|
|
599
636
|
/**
|
|
@@ -621,6 +658,7 @@ export function createNavigationStore(
|
|
|
621
658
|
entry[1],
|
|
622
659
|
entry[2],
|
|
623
660
|
clonedHandleData,
|
|
661
|
+
entry[4], // preserve routerId
|
|
624
662
|
];
|
|
625
663
|
}
|
|
626
664
|
},
|
|
@@ -633,6 +671,16 @@ export function createNavigationStore(
|
|
|
633
671
|
markCacheAsStaleInternal();
|
|
634
672
|
},
|
|
635
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Mark every history entry stale WITHOUT clearing the prefetch caches or
|
|
676
|
+
* rotating the rango state. The jar-divergence observer calls this after an
|
|
677
|
+
* external rotation has already changed the state value, so re-rotating
|
|
678
|
+
* here would ping-pong with the tab that rotated.
|
|
679
|
+
*/
|
|
680
|
+
markHistoryCacheStale(): void {
|
|
681
|
+
markHistoryStale();
|
|
682
|
+
},
|
|
683
|
+
|
|
636
684
|
/**
|
|
637
685
|
* Clear the history cache and broadcast to other tabs
|
|
638
686
|
* Use this for hard invalidation when data is definitely stale
|
|
@@ -649,14 +697,6 @@ export function createNavigationStore(
|
|
|
649
697
|
markStaleAndBroadcast();
|
|
650
698
|
},
|
|
651
699
|
|
|
652
|
-
/**
|
|
653
|
-
* Broadcast cache invalidation to other tabs without clearing local cache
|
|
654
|
-
* Used after consolidation fetch where local cache has fresh data
|
|
655
|
-
*/
|
|
656
|
-
broadcastCacheInvalidation(): void {
|
|
657
|
-
broadcastInvalidation();
|
|
658
|
-
},
|
|
659
|
-
|
|
660
700
|
/**
|
|
661
701
|
* Set the callback to invoke when cross-tab refresh is triggered
|
|
662
702
|
* Called by navigation bridge during initialization
|
|
@@ -687,6 +727,14 @@ export function createNavigationStore(
|
|
|
687
727
|
interceptSourceUrl = url;
|
|
688
728
|
},
|
|
689
729
|
|
|
730
|
+
getRouterId(): string | undefined {
|
|
731
|
+
return currentRouterId;
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
setRouterId(id: string): void {
|
|
735
|
+
currentRouterId = id;
|
|
736
|
+
},
|
|
737
|
+
|
|
690
738
|
// ========================================================================
|
|
691
739
|
// UI Update Notifications
|
|
692
740
|
// ========================================================================
|
|
@@ -765,42 +813,3 @@ export function createNavigationStore(
|
|
|
765
813
|
},
|
|
766
814
|
};
|
|
767
815
|
}
|
|
768
|
-
|
|
769
|
-
// Singleton store instance
|
|
770
|
-
let storeInstance: NavigationStore | null = null;
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* Initialize the global navigation store
|
|
774
|
-
*
|
|
775
|
-
* Should be called once during app initialization.
|
|
776
|
-
* Subsequent calls return the existing instance.
|
|
777
|
-
*/
|
|
778
|
-
export function initNavigationStore(
|
|
779
|
-
config?: NavigationStoreConfig,
|
|
780
|
-
): NavigationStore {
|
|
781
|
-
if (!storeInstance) {
|
|
782
|
-
storeInstance = createNavigationStore(config);
|
|
783
|
-
}
|
|
784
|
-
return storeInstance;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
/**
|
|
788
|
-
* Get the global navigation store
|
|
789
|
-
*
|
|
790
|
-
* Throws if store hasn't been initialized.
|
|
791
|
-
*/
|
|
792
|
-
export function getNavigationStore(): NavigationStore {
|
|
793
|
-
if (!storeInstance) {
|
|
794
|
-
throw new Error(
|
|
795
|
-
"Navigation store not initialized. Call initNavigationStore first.",
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
return storeInstance;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Reset the store instance (for testing)
|
|
803
|
-
*/
|
|
804
|
-
export function resetNavigationStore(): void {
|
|
805
|
-
storeInstance = null;
|
|
806
|
-
}
|
|
@@ -7,14 +7,12 @@ import type {
|
|
|
7
7
|
import { generateHistoryKey } from "./navigation-store.js";
|
|
8
8
|
import {
|
|
9
9
|
handleNavigationStart,
|
|
10
|
-
handleNavigationEnd,
|
|
11
10
|
ensureHistoryKey,
|
|
12
11
|
} from "./scroll-restoration.js";
|
|
13
12
|
import type { EventController, NavigationHandle } from "./event-controller.js";
|
|
14
13
|
import { debugLog } from "./logging.js";
|
|
15
|
-
import { buildHistoryState } from "./history-state.js";
|
|
14
|
+
import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
|
|
16
15
|
|
|
17
|
-
// Re-export for consumers that import from navigation-transaction
|
|
18
16
|
export { resolveNavigationState } from "./history-state.js";
|
|
19
17
|
|
|
20
18
|
/** Check if a history state object contains location state keys. */
|
|
@@ -26,7 +24,6 @@ function hasLocationState(state: unknown): boolean {
|
|
|
26
24
|
);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
// Polyfill Symbol.dispose for Safari and older browsers
|
|
30
27
|
if (typeof Symbol.dispose === "undefined") {
|
|
31
28
|
(Symbol as any).dispose = Symbol("Symbol.dispose");
|
|
32
29
|
}
|
|
@@ -81,11 +78,12 @@ export interface BoundTransaction {
|
|
|
81
78
|
readonly currentUrl: string;
|
|
82
79
|
/** Start streaming and get a token to end it when the stream completes */
|
|
83
80
|
startStreaming(): StreamingToken;
|
|
81
|
+
/** Commit the navigation. Returns the effective scroll option for the caller to handle. */
|
|
84
82
|
commit(
|
|
85
83
|
segmentIds: string[],
|
|
86
84
|
segments: ResolvedSegment[],
|
|
87
85
|
overrides?: BoundCommitOverrides,
|
|
88
|
-
):
|
|
86
|
+
): { scroll?: boolean };
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
/**
|
|
@@ -93,7 +91,7 @@ export interface BoundTransaction {
|
|
|
93
91
|
* Uses the event controller handle for lifecycle management
|
|
94
92
|
*/
|
|
95
93
|
interface NavigationTransaction extends Disposable {
|
|
96
|
-
commit(options: CommitOptions):
|
|
94
|
+
commit(options: CommitOptions): { scroll?: boolean };
|
|
97
95
|
with(
|
|
98
96
|
options: Omit<CommitOptions, "segmentIds" | "segments">,
|
|
99
97
|
): BoundTransaction;
|
|
@@ -114,13 +112,12 @@ export function createNavigationTransaction(
|
|
|
114
112
|
let committed = false;
|
|
115
113
|
const currentUrl = window.location.href;
|
|
116
114
|
|
|
117
|
-
// Start navigation in event controller (this sets loading state)
|
|
118
115
|
const handle = eventController.startNavigation(url, options);
|
|
119
116
|
|
|
120
117
|
/**
|
|
121
118
|
* Commit the navigation - updates store and URL atomically
|
|
122
119
|
*/
|
|
123
|
-
function commit(opts: CommitOptions):
|
|
120
|
+
function commit(opts: CommitOptions): { scroll?: boolean } {
|
|
124
121
|
committed = true;
|
|
125
122
|
|
|
126
123
|
const {
|
|
@@ -138,91 +135,63 @@ export function createNavigationTransaction(
|
|
|
138
135
|
|
|
139
136
|
const parsedUrl = new URL(url, window.location.origin);
|
|
140
137
|
|
|
141
|
-
// Generate history key from URL (with intercept suffix for separate caching)
|
|
142
138
|
const historyKey = generateHistoryKey(url, { intercept });
|
|
143
139
|
|
|
144
|
-
// For cache-only commits (stale revalidation), only update cache and return
|
|
145
|
-
// Don't touch store state or history - user may have navigated elsewhere
|
|
146
140
|
if (cacheOnly) {
|
|
147
141
|
const currentHandleData = eventController.getHandleState().data;
|
|
148
142
|
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
149
|
-
// Complete the navigation handle so currentNavigation is cleared.
|
|
150
|
-
// Without this, the entry lingers and weakens state-machine invariants.
|
|
151
143
|
handle.complete(parsedUrl);
|
|
152
144
|
debugLog("[Browser] Cache-only commit, historyKey:", historyKey);
|
|
153
|
-
return;
|
|
145
|
+
return { scroll: false };
|
|
154
146
|
}
|
|
155
147
|
|
|
156
|
-
// Save current scroll position before navigating
|
|
157
148
|
handleNavigationStart();
|
|
158
149
|
|
|
159
|
-
// Update segment state atomically
|
|
160
150
|
store.setSegmentIds(segmentIds);
|
|
161
151
|
store.setCurrentUrl(url);
|
|
162
152
|
store.setPath(parsedUrl.pathname);
|
|
163
153
|
|
|
164
154
|
store.setHistoryKey(historyKey);
|
|
165
155
|
|
|
166
|
-
// Cache segments with current handleData for this history entry
|
|
167
156
|
const currentHandleData = eventController.getHandleState().data;
|
|
168
157
|
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
169
158
|
|
|
170
|
-
// For server actions, skip URL/history updates but still complete navigation
|
|
171
159
|
if (storeOnly) {
|
|
172
160
|
debugLog("[Browser] Store updated (action)");
|
|
173
|
-
// Complete navigation to clear loading state
|
|
174
161
|
handle.complete(parsedUrl);
|
|
175
|
-
return;
|
|
162
|
+
return { scroll: false };
|
|
176
163
|
}
|
|
177
164
|
|
|
178
|
-
// Build history state - include user state, intercept info, and server-set state
|
|
179
165
|
const historyState = buildHistoryState(
|
|
180
166
|
opts.state,
|
|
181
167
|
{ intercept, sourceUrl: interceptSourceUrl },
|
|
182
168
|
serverState,
|
|
183
169
|
);
|
|
184
170
|
|
|
185
|
-
// Snapshot old state before pushState/replaceState overwrites it.
|
|
186
|
-
// Used to detect when location state is being cleared.
|
|
187
171
|
const oldState = window.history.state;
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
if (replace) {
|
|
191
|
-
window.history.replaceState(historyState, "", url);
|
|
192
|
-
} else {
|
|
193
|
-
window.history.pushState(historyState, "", url);
|
|
194
|
-
}
|
|
195
|
-
// Ensure new history entry has a scroll restoration key
|
|
173
|
+
pushHistoryWithIdx(historyState, url, replace ?? false);
|
|
196
174
|
ensureHistoryKey();
|
|
197
175
|
|
|
198
|
-
// Notify location state hooks when either old or new state carries
|
|
199
|
-
// location state. This covers both "set new state" and "clear old state"
|
|
200
|
-
// for same-page navigations where components don't remount.
|
|
201
176
|
if (hasLocationState(oldState) || hasLocationState(historyState)) {
|
|
202
177
|
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
203
178
|
}
|
|
204
179
|
|
|
205
|
-
// Complete the navigation in event controller (sets idle state, updates location)
|
|
206
180
|
handle.complete(parsedUrl);
|
|
207
181
|
|
|
208
|
-
// Handle scroll after navigation
|
|
209
|
-
handleNavigationEnd({ scroll });
|
|
210
|
-
|
|
211
182
|
debugLog(
|
|
212
183
|
"[Browser] Navigation committed, historyKey:",
|
|
213
184
|
historyKey,
|
|
214
185
|
intercept ? "(intercept)" : "",
|
|
215
186
|
);
|
|
187
|
+
|
|
188
|
+
return { scroll };
|
|
216
189
|
}
|
|
217
190
|
|
|
218
191
|
return {
|
|
219
192
|
handle,
|
|
220
193
|
commit,
|
|
221
194
|
|
|
222
|
-
/**
|
|
223
|
-
* Create a bound transaction with pre-configured URL options
|
|
224
|
-
* segmentIds and segments provided at commit time (after they're resolved)
|
|
225
|
-
*/
|
|
226
195
|
with(
|
|
227
196
|
opts: Omit<CommitOptions, "segmentIds" | "segments">,
|
|
228
197
|
): BoundTransaction {
|
|
@@ -238,32 +207,18 @@ export function createNavigationTransaction(
|
|
|
238
207
|
segments: ResolvedSegment[],
|
|
239
208
|
overrides?: BoundCommitOverrides,
|
|
240
209
|
) => {
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
// Allow overrides to force replace (e.g., for intercepts)
|
|
245
|
-
const finalReplace =
|
|
246
|
-
overrides?.replace !== undefined ? overrides.replace : opts.replace;
|
|
247
|
-
// Intercept info: overrides take precedence, fallback to opts
|
|
248
|
-
const intercept =
|
|
249
|
-
overrides?.intercept !== undefined
|
|
250
|
-
? overrides.intercept
|
|
251
|
-
: opts.intercept;
|
|
210
|
+
const finalScroll = overrides?.scroll ?? opts.scroll;
|
|
211
|
+
const finalReplace = overrides?.replace ?? opts.replace;
|
|
212
|
+
const intercept = overrides?.intercept ?? opts.intercept;
|
|
252
213
|
const interceptSourceUrl =
|
|
253
|
-
overrides?.interceptSourceUrl
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
const cacheOnly =
|
|
258
|
-
overrides?.cacheOnly !== undefined
|
|
259
|
-
? overrides.cacheOnly
|
|
260
|
-
: opts.cacheOnly;
|
|
261
|
-
// User state: overrides take precedence, fallback to opts
|
|
214
|
+
overrides?.interceptSourceUrl ?? opts.interceptSourceUrl;
|
|
215
|
+
const cacheOnly = overrides?.cacheOnly ?? opts.cacheOnly;
|
|
216
|
+
// state is `unknown` (null is meaningful) so `??` would wrongly drop a
|
|
217
|
+
// null override; serverState always comes from overrides, never opts.
|
|
262
218
|
const state =
|
|
263
219
|
overrides?.state !== undefined ? overrides.state : opts.state;
|
|
264
|
-
// Server-set location state: only from overrides (set by partial-update)
|
|
265
220
|
const serverState = overrides?.serverState;
|
|
266
|
-
commit({
|
|
221
|
+
return commit({
|
|
267
222
|
...opts,
|
|
268
223
|
segmentIds,
|
|
269
224
|
segments,
|
|
@@ -280,13 +235,10 @@ export function createNavigationTransaction(
|
|
|
280
235
|
},
|
|
281
236
|
|
|
282
237
|
[Symbol.dispose]() {
|
|
283
|
-
// Superseded: another navigation took over.
|
|
284
238
|
if (handle.signal.aborted) {
|
|
285
239
|
return;
|
|
286
240
|
}
|
|
287
241
|
|
|
288
|
-
// Failed (not committed): keep the target URL -- the error UI owns it.
|
|
289
|
-
// Just reset the event controller to idle.
|
|
290
242
|
if (!committed) {
|
|
291
243
|
handle[Symbol.dispose]();
|
|
292
244
|
}
|