@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
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/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- 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 +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- 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 +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- 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 +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- 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/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- 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 +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- 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 +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- 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 +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -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/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 +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- 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 +31 -23
- 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 +63 -9
- package/src/index.ts +64 -9
- 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-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- 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 +0 -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 +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- 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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- 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 +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- 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
|
@@ -1,152 +1,194 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rango State
|
|
3
3
|
*
|
|
4
|
-
* Manages a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Manages a session-cookie-based state value for HTTP cache invalidation. The
|
|
5
|
+
* value is sent as the `X-Rango-State` header on prefetch and navigation
|
|
6
|
+
* requests; the server responds with `Vary: X-Rango-State`, so the browser HTTP
|
|
7
|
+
* cache keys responses by (URL, X-Rango-State value).
|
|
8
8
|
*
|
|
9
9
|
* Value format: `{buildVersion}:{invalidationTimestamp}`
|
|
10
|
-
* - Build version changes on deploy, busting all cached prefetches.
|
|
11
|
-
* - Timestamp
|
|
10
|
+
* - Build version changes on deploy, busting all cached prefetches at boot.
|
|
11
|
+
* - Timestamp rotates on invalidation (server action, invalidateClientCache).
|
|
12
12
|
*
|
|
13
|
-
* Storage
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* Storage is a session cookie named by the server-resolved name passed to
|
|
14
|
+
* initRangoState (`{prefix}_{routerId}`, default prefix `rango-state`). The
|
|
15
|
+
* cookie jar is shared across tabs, so a per-request read IS the cross-tab
|
|
16
|
+
* value sync — no `storage` event is needed. An in-memory mirror is a
|
|
17
|
+
* write-through copy that is authoritative only when the cookie is unreadable
|
|
18
|
+
* (e.g. a sandboxed frame, or site data blocked wholesale): the failure
|
|
19
|
+
* direction is always toward freshness.
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* Precedence is load-bearing: when `document.cookie` is readable, the
|
|
22
|
+
* per-request read wins; the mirror is a fallback, never a cache of the read.
|
|
23
|
+
* Caching the read across requests would reintroduce the staleness this
|
|
24
|
+
* mechanism removes.
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_STATE_COOKIE_PREFIX,
|
|
29
|
+
decodeStateValue,
|
|
30
|
+
getRawCookieValue,
|
|
31
|
+
mintStateValue,
|
|
32
|
+
serializeStateCookie,
|
|
33
|
+
} from "./cookie-name.js";
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
return routerId ? `${LEGACY_STORAGE_KEY}:${routerId}` : LEGACY_STORAGE_KEY;
|
|
28
|
-
}
|
|
35
|
+
let cookieName: string = DEFAULT_STATE_COOKIE_PREFIX;
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// switch). The storage listener filters cross-tab events by this key so
|
|
37
|
-
// events from tabs in a different app are ignored.
|
|
38
|
-
let currentStorageKey: string = LEGACY_STORAGE_KEY;
|
|
39
|
-
|
|
40
|
-
// Cross-tab sync: the `storage` event fires in OTHER tabs when one tab writes
|
|
41
|
-
// to localStorage, keeping cachedState fresh without polling.
|
|
42
|
-
let storageListenerAttached = false;
|
|
43
|
-
|
|
44
|
-
function attachStorageListener(): void {
|
|
45
|
-
if (storageListenerAttached || typeof window === "undefined") return;
|
|
46
|
-
window.addEventListener("storage", (e) => {
|
|
47
|
-
// Only react to events for this tab's current app namespace. Events
|
|
48
|
-
// under other routerId-scoped keys belong to other apps and must not
|
|
49
|
-
// clobber this tab's state.
|
|
50
|
-
if (e.key !== currentStorageKey) return;
|
|
51
|
-
cachedState = e.newValue;
|
|
52
|
-
});
|
|
53
|
-
storageListenerAttached = true;
|
|
54
|
-
}
|
|
37
|
+
let currentVersion = "0";
|
|
38
|
+
|
|
39
|
+
let mirror: string | null = null;
|
|
40
|
+
let cookieBacked = false;
|
|
41
|
+
|
|
42
|
+
let externalRotationObserver: ((value: string) => void) | null = null;
|
|
55
43
|
|
|
56
44
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* each app owns its own `rango-state:{routerId}` key and cannot observe
|
|
61
|
-
* invalidations from sibling apps on the same origin.
|
|
62
|
-
*
|
|
63
|
-
* If localStorage already has a matching-version entry under the key,
|
|
64
|
-
* keeps it (preserves invalidation state across refresh). Otherwise
|
|
65
|
-
* writes a new value.
|
|
45
|
+
* Register the observer invoked when a read detects an EXTERNAL rotation (a
|
|
46
|
+
* sibling tab, a server `Set-Cookie`, or a cookie clear). Self-rotations
|
|
47
|
+
* (invalidateRangoState) update the mirror synchronously and never fire it.
|
|
66
48
|
*/
|
|
67
|
-
export function
|
|
68
|
-
|
|
69
|
-
|
|
49
|
+
export function setRangoStateObserver(
|
|
50
|
+
observer: ((value: string) => void) | null,
|
|
51
|
+
): void {
|
|
52
|
+
externalRotationObserver = observer;
|
|
53
|
+
}
|
|
70
54
|
|
|
71
|
-
|
|
55
|
+
function notifyExternalRotation(value: string): void {
|
|
56
|
+
externalRotationObserver?.(value);
|
|
57
|
+
}
|
|
72
58
|
|
|
59
|
+
interface CookieRead {
|
|
60
|
+
/** False when there is no document or the read threw (sandboxed frame). */
|
|
61
|
+
readable: boolean;
|
|
62
|
+
/** The cookie value, or null when readable but absent. */
|
|
63
|
+
value: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readCookie(name: string): CookieRead {
|
|
67
|
+
if (typeof document === "undefined") return { readable: false, value: null };
|
|
68
|
+
let raw: string;
|
|
73
69
|
try {
|
|
74
|
-
|
|
75
|
-
if (existing) {
|
|
76
|
-
const colonIdx = existing.indexOf(":");
|
|
77
|
-
if (colonIdx > 0) {
|
|
78
|
-
const existingVersion = existing.slice(0, colonIdx);
|
|
79
|
-
if (existingVersion === version) {
|
|
80
|
-
cachedState = existing;
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// New version or first load
|
|
86
|
-
const newState = `${version}:${Date.now()}`;
|
|
87
|
-
localStorage.setItem(currentStorageKey, newState);
|
|
88
|
-
cachedState = newState;
|
|
70
|
+
raw = document.cookie;
|
|
89
71
|
} catch {
|
|
90
|
-
|
|
91
|
-
cachedState = `${version}:${Date.now()}`;
|
|
72
|
+
return { readable: false, value: null };
|
|
92
73
|
}
|
|
74
|
+
return { readable: true, value: getRawCookieValue(raw, name) };
|
|
93
75
|
}
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
77
|
+
function writeCookie(name: string, value: string): void {
|
|
78
|
+
if (typeof document === "undefined") return;
|
|
79
|
+
const secure =
|
|
80
|
+
typeof location !== "undefined" && location.protocol === "https:";
|
|
81
|
+
try {
|
|
82
|
+
document.cookie = serializeStateCookie(name, value, secure);
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
101
85
|
|
|
102
|
-
|
|
86
|
+
function mintValue(): string {
|
|
87
|
+
return mintStateValue(currentVersion, mirror);
|
|
88
|
+
}
|
|
103
89
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Initialize the Rango state cookie at app startup. `version` is the build
|
|
92
|
+
* version; `stateCookieName` is the server-resolved cookie name from payload
|
|
93
|
+
* metadata (falls back to the bare default prefix when a payload arrives
|
|
94
|
+
* without it). Keeps an existing matching-version cookie (preserves the cache
|
|
95
|
+
* key across reloads); mints fresh on a version change or a missing cookie.
|
|
96
|
+
*/
|
|
97
|
+
export function initRangoState(
|
|
98
|
+
version: string,
|
|
99
|
+
stateCookieName?: string,
|
|
100
|
+
): void {
|
|
101
|
+
currentVersion = version;
|
|
102
|
+
cookieName = stateCookieName || DEFAULT_STATE_COOKIE_PREFIX;
|
|
103
|
+
cleanupLegacyStorage();
|
|
104
|
+
|
|
105
|
+
const read = readCookie(cookieName);
|
|
106
|
+
if (!read.readable) {
|
|
107
|
+
// Cookies unreadable: the mirror is the source of truth for this session.
|
|
108
|
+
mirror = mintValue();
|
|
109
|
+
cookieBacked = false;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (read.value !== null) {
|
|
113
|
+
const decoded = decodeStateValue(read.value);
|
|
114
|
+
if (decoded && decoded.version === version) {
|
|
115
|
+
// Keep: a matching-version cookie survives the reload warm.
|
|
116
|
+
mirror = read.value;
|
|
117
|
+
cookieBacked = true;
|
|
118
|
+
return;
|
|
109
119
|
}
|
|
110
|
-
} catch {
|
|
111
|
-
// Fallback for unavailable localStorage
|
|
112
120
|
}
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
// Absent, malformed, or a version change (deploy): mint fresh and write.
|
|
122
|
+
mirror = mintValue();
|
|
123
|
+
cookieBacked = false;
|
|
124
|
+
writeCookie(cookieName, mirror);
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* still in the previous app do not observe a storage event. Rebinds this
|
|
122
|
-
* tab's storage key to the target app's namespace (`rango-state:{routerId}`)
|
|
123
|
-
* so subsequent storage events only reflect the new app. On the next hard
|
|
124
|
-
* reload, initRangoState reconciles localStorage from the server's
|
|
125
|
-
* authoritative version.
|
|
128
|
+
* Get the current Rango state value, used as the `X-Rango-State` header on
|
|
129
|
+
* prefetch and navigation requests. Reads the cookie every call (the read is
|
|
130
|
+
* the cross-tab sync channel) and reconciles the mirror.
|
|
126
131
|
*/
|
|
127
|
-
export function
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
export function getRangoState(): string {
|
|
133
|
+
const read = readCookie(cookieName);
|
|
134
|
+
|
|
135
|
+
if (!read.readable) {
|
|
136
|
+
// Mirror authoritative when the jar is unreadable.
|
|
137
|
+
return mirror ?? "0:0";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (read.value !== null) {
|
|
141
|
+
if (read.value !== mirror) {
|
|
142
|
+
// External rotation (sibling tab / server Set-Cookie): adopt it. The
|
|
143
|
+
// mirror update makes this idempotent across a burst of reads.
|
|
144
|
+
mirror = read.value;
|
|
145
|
+
cookieBacked = true;
|
|
146
|
+
notifyExternalRotation(read.value);
|
|
147
|
+
} else {
|
|
148
|
+
cookieBacked = true;
|
|
149
|
+
}
|
|
150
|
+
return read.value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Readable but absent.
|
|
154
|
+
if (cookieBacked) {
|
|
155
|
+
// present -> absent: an external clear. Mint fresh, write back, and notify
|
|
156
|
+
// once (cookieBacked flips to false so we don't re-fire on the next read).
|
|
157
|
+
mirror = mintValue();
|
|
158
|
+
cookieBacked = false;
|
|
159
|
+
writeCookie(cookieName, mirror);
|
|
160
|
+
notifyExternalRotation(mirror);
|
|
161
|
+
} else if (mirror === null) {
|
|
162
|
+
// First access with no cookie yet (pre-boot): mint silently — there is
|
|
163
|
+
// nothing to invalidate.
|
|
164
|
+
mirror = mintValue();
|
|
165
|
+
writeCookie(cookieName, mirror);
|
|
166
|
+
}
|
|
167
|
+
return mirror;
|
|
130
168
|
}
|
|
131
169
|
|
|
132
170
|
/**
|
|
133
|
-
* Invalidate the Rango state
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
171
|
+
* Invalidate the Rango state (self-rotation). Called when the client clears its
|
|
172
|
+
* prefetch caches (e.g. via the server-action bridge). Rotates the timestamp,
|
|
173
|
+
* keeps the version, writes the cookie, and updates the mirror synchronously so
|
|
174
|
+
* the external-rotation observer is NOT triggered by our own write.
|
|
137
175
|
*/
|
|
138
176
|
export function invalidateRangoState(): void {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
cachedState = newState;
|
|
144
|
-
|
|
145
|
-
if (typeof window === "undefined") return;
|
|
177
|
+
mirror = mintValue();
|
|
178
|
+
cookieBacked = false;
|
|
179
|
+
writeCookie(cookieName, mirror);
|
|
180
|
+
}
|
|
146
181
|
|
|
182
|
+
function cleanupLegacyStorage(): void {
|
|
183
|
+
if (typeof localStorage === "undefined") return;
|
|
147
184
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
185
|
+
const toRemove: string[] = [];
|
|
186
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
187
|
+
const key = localStorage.key(i);
|
|
188
|
+
if (key === "rango-state" || (key && key.startsWith("rango-state:"))) {
|
|
189
|
+
toRemove.push(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const key of toRemove) localStorage.removeItem(key);
|
|
193
|
+
} catch {}
|
|
152
194
|
}
|
|
@@ -39,8 +39,6 @@ import {
|
|
|
39
39
|
unobserveForPrefetch,
|
|
40
40
|
} from "../prefetch/observer.js";
|
|
41
41
|
|
|
42
|
-
// Touch device detection for adaptive strategy.
|
|
43
|
-
// Checked once at module load (Link.tsx is "use client", runs only in browser).
|
|
44
42
|
const isTouchDevice =
|
|
45
43
|
typeof window !== "undefined" && window.matchMedia("(hover: none)").matches;
|
|
46
44
|
|
|
@@ -28,7 +28,8 @@ import { NonceContext } from "./nonce-context.js";
|
|
|
28
28
|
import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
|
|
29
29
|
import { cancelAllPrefetches } from "../prefetch/queue.js";
|
|
30
30
|
import { handleNavigationEnd } from "../scroll-restoration.js";
|
|
31
|
-
import type
|
|
31
|
+
import { createAppShellRef, type AppShellRef } from "../app-shell.js";
|
|
32
|
+
import { debugLog } from "../logging.js";
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Process handles from an async generator, updating the event controller
|
|
@@ -70,7 +71,7 @@ async function processHandles(
|
|
|
70
71
|
// This prevents handle data from cancelled navigations polluting
|
|
71
72
|
// the current route's breadcrumbs (e.g., quick popstate after clicking a link).
|
|
72
73
|
if (historyKey !== store.getHistoryKey()) {
|
|
73
|
-
|
|
74
|
+
debugLog(
|
|
74
75
|
"[NavigationProvider] Stopping handle processing - user navigated away",
|
|
75
76
|
);
|
|
76
77
|
return;
|
|
@@ -157,9 +158,11 @@ export interface NavigationProviderProps {
|
|
|
157
158
|
basename?: string;
|
|
158
159
|
|
|
159
160
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
161
|
+
* App-shell ref. When provided, the context's `basename` and `version` are
|
|
162
|
+
* read through it (live getters) so they don't close over a stale snapshot or
|
|
163
|
+
* invalidate the memoized context value. The shell is set once at init and is
|
|
164
|
+
* not swapped within a session — a cross-app navigation is a full document
|
|
165
|
+
* load (X-RSC-Reload), so the target app establishes its own shell on load.
|
|
163
166
|
*/
|
|
164
167
|
appShellRef?: AppShellRef;
|
|
165
168
|
}
|
|
@@ -217,38 +220,34 @@ export function NavigationProvider({
|
|
|
217
220
|
await bridge.refresh();
|
|
218
221
|
}, []);
|
|
219
222
|
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
223
|
+
// basename/version are always read through a shell ref so the context value
|
|
224
|
+
// has a single shape. Both are set once: a supplied appShellRef is seeded
|
|
225
|
+
// from the init payload (a cross-app navigation reloads, so it is not swapped
|
|
226
|
+
// in-session), and the standalone fallback wraps the mount-time props.
|
|
227
|
+
const fallbackShellRef = useRef<AppShellRef | null>(null);
|
|
228
|
+
if (!fallbackShellRef.current) {
|
|
229
|
+
fallbackShellRef.current = createAppShellRef({ basename, version });
|
|
230
|
+
}
|
|
231
|
+
const shellRef = appShellRef ?? fallbackShellRef.current;
|
|
232
|
+
|
|
224
233
|
const contextValue = useMemo<NavigationStoreContextValue>(() => {
|
|
225
|
-
|
|
226
|
-
const value = {
|
|
227
|
-
store,
|
|
228
|
-
eventController,
|
|
229
|
-
navigate,
|
|
230
|
-
refresh,
|
|
231
|
-
} as NavigationStoreContextValue;
|
|
232
|
-
Object.defineProperty(value, "basename", {
|
|
233
|
-
configurable: true,
|
|
234
|
-
enumerable: true,
|
|
235
|
-
get: () => appShellRef.get().basename,
|
|
236
|
-
});
|
|
237
|
-
Object.defineProperty(value, "version", {
|
|
238
|
-
configurable: true,
|
|
239
|
-
enumerable: true,
|
|
240
|
-
get: () => appShellRef.get().version,
|
|
241
|
-
});
|
|
242
|
-
return value;
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
234
|
+
const value = {
|
|
245
235
|
store,
|
|
246
236
|
eventController,
|
|
247
237
|
navigate,
|
|
248
238
|
refresh,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
239
|
+
} as NavigationStoreContextValue;
|
|
240
|
+
Object.defineProperty(value, "basename", {
|
|
241
|
+
configurable: true,
|
|
242
|
+
enumerable: true,
|
|
243
|
+
get: () => shellRef.get().basename,
|
|
244
|
+
});
|
|
245
|
+
Object.defineProperty(value, "version", {
|
|
246
|
+
configurable: true,
|
|
247
|
+
enumerable: true,
|
|
248
|
+
get: () => shellRef.get().version,
|
|
249
|
+
});
|
|
250
|
+
return value;
|
|
252
251
|
}, []);
|
|
253
252
|
|
|
254
253
|
// Connection warmup: keep TLS alive after idle periods.
|
|
@@ -410,21 +409,15 @@ export function NavigationProvider({
|
|
|
410
409
|
}).catch((err) =>
|
|
411
410
|
console.error("[NavigationProvider] Error consuming handles:", err),
|
|
412
411
|
);
|
|
413
|
-
} else if (update.metadata.cachedHandleData) {
|
|
414
|
-
// For back/forward navigation from cache, restore the cached handleData
|
|
415
|
-
// This restores breadcrumbs to the exact state they were when the page was cached
|
|
416
|
-
eventController.setHandleData(
|
|
417
|
-
update.metadata.cachedHandleData,
|
|
418
|
-
update.metadata.matched,
|
|
419
|
-
false, // full replace - restore entire cached state
|
|
420
|
-
);
|
|
421
412
|
} else if (update.metadata.matched) {
|
|
422
|
-
//
|
|
413
|
+
// cachedHandleData present -> full restore (back/forward); absent ->
|
|
414
|
+
// partial cleanup of segments no longer matched.
|
|
415
|
+
const cached = update.metadata.cachedHandleData;
|
|
423
416
|
eventController.setHandleData(
|
|
424
|
-
{},
|
|
417
|
+
cached ?? {},
|
|
425
418
|
update.metadata.matched,
|
|
426
|
-
|
|
427
|
-
update.metadata.resolvedIds,
|
|
419
|
+
cached === undefined,
|
|
420
|
+
cached === undefined ? update.metadata.resolvedIds : undefined,
|
|
428
421
|
);
|
|
429
422
|
}
|
|
430
423
|
});
|
|
@@ -447,10 +440,10 @@ export function NavigationProvider({
|
|
|
447
440
|
let content = <RootErrorBoundary>{root}</RootErrorBoundary>;
|
|
448
441
|
|
|
449
442
|
// Wrap with ThemeProvider when theme is enabled. The ThemeProvider is
|
|
450
|
-
// document-lifetime: its config comes from the initial load and
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
443
|
+
// document-lifetime: its config comes from the initial load and persists for
|
|
444
|
+
// the session. It sits above the segment tree and is not remounted in-session;
|
|
445
|
+
// a cross-app navigation is a full document load (X-RSC-Reload), so the target
|
|
446
|
+
// app's theme config takes effect on its own load.
|
|
454
447
|
if (themeConfig) {
|
|
455
448
|
content = (
|
|
456
449
|
<ThemeProvider config={themeConfig} initialTheme={initialTheme}>
|
|
@@ -14,17 +14,21 @@ export interface ScrollRestorationProps {
|
|
|
14
14
|
* Return location.pathname to restore scroll based on path
|
|
15
15
|
* (useful for keeping scroll position on the same page).
|
|
16
16
|
*
|
|
17
|
+
* Provide a stable reference: a module-level function or one wrapped in
|
|
18
|
+
* useCallback. The init effect re-runs when getKey's identity changes, and
|
|
19
|
+
* teardown clears in-memory scroll positions — a fresh inline arrow on every
|
|
20
|
+
* parent render would discard unpersisted positions mid-session.
|
|
21
|
+
*
|
|
17
22
|
* @example
|
|
18
23
|
* ```tsx
|
|
24
|
+
* // Stable module-level getKey (recommended)
|
|
25
|
+
* const byPathname = (location) => location.pathname;
|
|
26
|
+
*
|
|
19
27
|
* // Restore based on pathname (same URL = same scroll)
|
|
20
|
-
* <ScrollRestoration
|
|
21
|
-
* getKey={(location) => location.pathname}
|
|
22
|
-
* />
|
|
28
|
+
* <ScrollRestoration getKey={byPathname} />
|
|
23
29
|
*
|
|
24
30
|
* // Restore based on unique history entry (default)
|
|
25
|
-
* <ScrollRestoration
|
|
26
|
-
* getKey={(location) => location.key}
|
|
27
|
-
* />
|
|
31
|
+
* // <ScrollRestoration /> — omit getKey to use location.key
|
|
28
32
|
* ```
|
|
29
33
|
*/
|
|
30
34
|
getKey?: (location: {
|
|
@@ -46,8 +46,6 @@ export function filterSegmentOrder(matched: string[]): string[] {
|
|
|
46
46
|
const slots = slotsByParent.get(id);
|
|
47
47
|
if (slots) result.push(...slots);
|
|
48
48
|
}
|
|
49
|
-
// Defensive: any slot whose parent is missing from the filtered list still
|
|
50
|
-
// gets included rather than silently dropped. Shouldn't happen in practice.
|
|
51
49
|
for (const [parent, slots] of slotsByParent) {
|
|
52
50
|
if (!nonSlotSet.has(parent)) result.push(...slots);
|
|
53
51
|
}
|
|
@@ -1,52 +1,4 @@
|
|
|
1
|
-
// React exports for browser navigation
|
|
2
|
-
|
|
3
|
-
// Hook with Zustand-style selectors
|
|
4
|
-
export { useNavigation } from "./use-navigation.js";
|
|
5
|
-
|
|
6
|
-
// Router actions hook (stable reference, no re-renders)
|
|
7
|
-
export { useRouter } from "./use-router.js";
|
|
8
|
-
|
|
9
|
-
// URL hooks
|
|
10
|
-
export { usePathname } from "./use-pathname.js";
|
|
11
|
-
export { useSearchParams } from "./use-search-params.js";
|
|
12
|
-
export { useParams } from "./use-params.js";
|
|
13
|
-
|
|
14
|
-
// Action state tracking hook
|
|
15
|
-
export { useAction, type TrackedActionState } from "./use-action.js";
|
|
16
|
-
|
|
17
|
-
// Segments state hook
|
|
18
|
-
export { useSegments, type SegmentsState } from "./use-segments.js";
|
|
19
|
-
|
|
20
|
-
// Handle data hook
|
|
21
|
-
export { useHandle } from "./use-handle.js";
|
|
22
|
-
|
|
23
|
-
// Client cache controls hook
|
|
24
|
-
export {
|
|
25
|
-
useClientCache,
|
|
26
|
-
type ClientCacheControls,
|
|
27
|
-
} from "./use-client-cache.js";
|
|
28
|
-
|
|
29
|
-
// Provider
|
|
30
1
|
export {
|
|
31
2
|
NavigationProvider,
|
|
32
3
|
type NavigationProviderProps,
|
|
33
4
|
} from "./NavigationProvider.js";
|
|
34
|
-
|
|
35
|
-
// Context (for advanced usage)
|
|
36
|
-
export {
|
|
37
|
-
NavigationStoreContext,
|
|
38
|
-
type NavigationStoreContextValue,
|
|
39
|
-
} from "./context.js";
|
|
40
|
-
|
|
41
|
-
// Link component
|
|
42
|
-
export { Link, type LinkProps, type PrefetchStrategy } from "./Link.js";
|
|
43
|
-
|
|
44
|
-
// Link status hook
|
|
45
|
-
export { useLinkStatus, type LinkStatus } from "./use-link-status.js";
|
|
46
|
-
|
|
47
|
-
// Scroll restoration
|
|
48
|
-
export {
|
|
49
|
-
ScrollRestoration,
|
|
50
|
-
useScrollRestoration,
|
|
51
|
-
type ScrollRestorationProps,
|
|
52
|
-
} from "./ScrollRestoration.js";
|