@rangojs/router 0.18.0 → 0.19.0
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/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +24 -4
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +89 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +960 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +64 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +197 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +105 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +28 -3
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +150 -25
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +189 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +123 -84
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -13,30 +13,23 @@ import React, {
|
|
|
13
13
|
} from "react";
|
|
14
14
|
import { NavigationStoreContext } from "./context.js";
|
|
15
15
|
import { LinkContext } from "./use-link-status.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type { HistoryState } from "../types.js";
|
|
17
17
|
import {
|
|
18
18
|
isHashOnlyNavigation,
|
|
19
19
|
isPrefetchScopeDisabled,
|
|
20
20
|
subscribeToPrefetchScopeChange,
|
|
21
21
|
} from "../link-interceptor.js";
|
|
22
22
|
import { subscribeToLocationChange } from "../event-controller.js";
|
|
23
|
-
import {
|
|
24
|
-
isLocationStateEntry,
|
|
25
|
-
type LocationStateEntry,
|
|
26
|
-
resolveLocationStateEntries,
|
|
27
|
-
} from "./location-state.js";
|
|
23
|
+
import { isLocationStateDefinition } from "./location-state-shared.js";
|
|
28
24
|
|
|
29
|
-
/**
|
|
30
|
-
* State prop type for Link component.
|
|
31
|
-
* - LocationStateEntry[]: Type-safe state entries via createLocationState()
|
|
32
|
-
* - StateOrGetter: Plain state object or click-time getter function
|
|
33
|
-
* - Record<string, unknown>: Plain state object passed to history.pushState
|
|
34
|
-
*/
|
|
35
25
|
export type StateOrGetter<T = unknown> = T | (() => T);
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
/**
|
|
28
|
+
* State prop type for Link component: the `router.push()` state type
|
|
29
|
+
* ({@link HistoryState}: `LocationStateEntry[]` from createLocationState(), or
|
|
30
|
+
* plain structured-clone-safe state), or a getter for it called at click time.
|
|
31
|
+
*/
|
|
32
|
+
export type LinkState = StateOrGetter<HistoryState>;
|
|
40
33
|
|
|
41
34
|
import {
|
|
42
35
|
prefetchDirect,
|
|
@@ -93,6 +86,14 @@ export interface LinkProps extends Omit<
|
|
|
93
86
|
* @default true
|
|
94
87
|
*/
|
|
95
88
|
revalidate?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Set to `false` to present this navigation without a transition (urgent
|
|
91
|
+
* commit, `<ViewTransition>` classes `"none"`); no `transition({ when })`
|
|
92
|
+
* predicate is called. See `NavigateOptions.transition`.
|
|
93
|
+
*
|
|
94
|
+
* @default true
|
|
95
|
+
*/
|
|
96
|
+
transition?: boolean;
|
|
96
97
|
/**
|
|
97
98
|
* Prefetch strategy for the link destination. When omitted, falls back to
|
|
98
99
|
* the router-wide default (`createRouter({ defaultPrefetch })`: `"none"` in
|
|
@@ -118,8 +119,11 @@ export interface LinkProps extends Omit<
|
|
|
118
119
|
* therefore serve the wrong diff to a navigation from a different
|
|
119
120
|
* source.
|
|
120
121
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
122
|
+
* A route an intercept targets is scoped to the source automatically
|
|
123
|
+
* (server-side `x-rsc-prefetch-scope: source`), whether or not the
|
|
124
|
+
* intercept applies from this page: the modal and the full page are
|
|
125
|
+
* both source-specific. Every other route stays shared, so `":source"`
|
|
126
|
+
* is only needed for custom revalidation logic.
|
|
123
127
|
*
|
|
124
128
|
* @example
|
|
125
129
|
* ```tsx
|
|
@@ -226,6 +230,7 @@ export const Link: ForwardRefExoticComponent<
|
|
|
226
230
|
scroll = true,
|
|
227
231
|
reloadDocument = false,
|
|
228
232
|
revalidate,
|
|
233
|
+
transition,
|
|
229
234
|
prefetch,
|
|
230
235
|
prefetchKey,
|
|
231
236
|
state,
|
|
@@ -334,28 +339,26 @@ export const Link: ForwardRefExoticComponent<
|
|
|
334
339
|
// Stop propagation to prevent link-interceptor from also handling this
|
|
335
340
|
e.stopPropagation();
|
|
336
341
|
|
|
342
|
+
// Call a click-time getter; typed entries are resolved (and, in dev,
|
|
343
|
+
// checked) by the bridge's resolveNavigationState, like router.push().
|
|
344
|
+
// In dev an uncalled definition passes through to that check instead of
|
|
345
|
+
// being called as a getter.
|
|
337
346
|
const currentState = stateRef.current;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
currentState as LocationStateEntry[],
|
|
347
|
-
);
|
|
348
|
-
} else if (typeof currentState === "function") {
|
|
349
|
-
resolvedState = currentState();
|
|
350
|
-
} else if (currentState != null) {
|
|
351
|
-
resolvedState = currentState;
|
|
352
|
-
}
|
|
347
|
+
const state =
|
|
348
|
+
typeof currentState === "function" &&
|
|
349
|
+
!(
|
|
350
|
+
process.env.NODE_ENV !== "production" &&
|
|
351
|
+
isLocationStateDefinition(currentState)
|
|
352
|
+
)
|
|
353
|
+
? currentState()
|
|
354
|
+
: (currentState ?? undefined);
|
|
353
355
|
|
|
354
356
|
ctx.navigate(resolvedTo, {
|
|
355
357
|
replace,
|
|
356
358
|
scroll,
|
|
357
|
-
state
|
|
359
|
+
state,
|
|
358
360
|
revalidate,
|
|
361
|
+
transition,
|
|
359
362
|
});
|
|
360
363
|
},
|
|
361
364
|
[
|
|
@@ -365,6 +368,7 @@ export const Link: ForwardRefExoticComponent<
|
|
|
365
368
|
replace,
|
|
366
369
|
scroll,
|
|
367
370
|
revalidate,
|
|
371
|
+
transition,
|
|
368
372
|
ctx,
|
|
369
373
|
onClick,
|
|
370
374
|
],
|
|
@@ -487,6 +491,7 @@ export const Link: ForwardRefExoticComponent<
|
|
|
487
491
|
data-scroll={scroll === false ? "false" : undefined}
|
|
488
492
|
data-replace={replace ? "true" : undefined}
|
|
489
493
|
data-revalidate={revalidate === false ? "false" : undefined}
|
|
494
|
+
data-transition={transition === false ? "false" : undefined}
|
|
490
495
|
{...props}
|
|
491
496
|
>
|
|
492
497
|
<LinkContext.Provider value={resolvedTo}>{children}</LinkContext.Provider>
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import type {
|
|
18
18
|
NavigationStore,
|
|
19
19
|
NavigationUpdate,
|
|
20
|
-
|
|
20
|
+
NavigateOptionsInternal,
|
|
21
21
|
NavigationBridge,
|
|
22
22
|
} from "../types.js";
|
|
23
23
|
import type { EventController } from "../event-controller.js";
|
|
@@ -320,7 +320,7 @@ export function NavigationProvider({
|
|
|
320
320
|
* Navigate to a URL (delegates to bridge)
|
|
321
321
|
*/
|
|
322
322
|
const navigate = useCallback(
|
|
323
|
-
async (url: string, options?:
|
|
323
|
+
async (url: string, options?: NavigateOptionsInternal): Promise<void> => {
|
|
324
324
|
await bridge.navigate(url, options);
|
|
325
325
|
},
|
|
326
326
|
[],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { createContext, type Context } from "react";
|
|
4
|
-
import type { NavigationStore,
|
|
4
|
+
import type { NavigationStore, NavigateOptionsInternal } from "../types.js";
|
|
5
5
|
import type { EventController } from "../event-controller.js";
|
|
6
6
|
import type { PrefetchStrategy } from "../../router/prefetch-default.js";
|
|
7
7
|
|
|
@@ -34,7 +34,7 @@ export interface NavigationStoreContextValue {
|
|
|
34
34
|
* @param options - Navigation options (replace, scroll)
|
|
35
35
|
* @returns Promise that resolves when navigation is complete
|
|
36
36
|
*/
|
|
37
|
-
navigate: (url: string, options?:
|
|
37
|
+
navigate: (url: string, options?: NavigateOptionsInternal) => Promise<void>;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Refresh the current route
|
|
@@ -27,17 +27,64 @@ type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
|
27
27
|
type IsUnknown<T> =
|
|
28
28
|
IsAny<T> extends true ? false : unknown extends T ? true : false;
|
|
29
29
|
|
|
30
|
+
type UnknownReason =
|
|
31
|
+
"`unknown` cannot be verified as serializable; give it a concrete type";
|
|
32
|
+
type FunctionReason = "functions cannot be stored in location state";
|
|
33
|
+
type ConstructorReason =
|
|
34
|
+
"class constructors cannot be stored in location state";
|
|
35
|
+
type SymbolReason = "symbols cannot be stored in location state";
|
|
36
|
+
type ReactReason =
|
|
37
|
+
"React/RSC content cannot be stored in location state; store plain data and render it on arrival";
|
|
38
|
+
type DepthReason =
|
|
39
|
+
"nested too deep to locate the unsafe field; it is below this path";
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* Branded error surfaced when a value that cannot live in location state is
|
|
32
43
|
* used. Location state is written into `history.state`, which uses the
|
|
33
44
|
* structured clone algorithm; React elements, functions, and symbols throw a
|
|
34
|
-
* `DataCloneError` at runtime. Carries
|
|
35
|
-
*
|
|
45
|
+
* `DataCloneError` at runtime. Carries the reason and the offending field's
|
|
46
|
+
* path (`items[].info.values`; `<root>` for the value itself) so the compile
|
|
47
|
+
* error names the field to fix.
|
|
36
48
|
*/
|
|
37
|
-
export type LocationStateUnsafe<
|
|
49
|
+
export type LocationStateUnsafe<
|
|
50
|
+
Reason extends string,
|
|
51
|
+
Path extends string = "<root>",
|
|
52
|
+
> = {
|
|
38
53
|
readonly __rango_location_state_unsafe: Reason;
|
|
54
|
+
readonly __rango_location_state_path: Path;
|
|
39
55
|
};
|
|
40
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Why a value cannot be structured-cloned, for the kinds that fail on their
|
|
59
|
+
* own (not through something nested in them), or `never`. Distributes over a
|
|
60
|
+
* union, one reason per unsafe member.
|
|
61
|
+
*/
|
|
62
|
+
type UnsafeReason<T> = T extends LocationStateUnsafeFn
|
|
63
|
+
? FunctionReason
|
|
64
|
+
: T extends LocationStateUnsafeCtor
|
|
65
|
+
? ConstructorReason
|
|
66
|
+
: T extends symbol
|
|
67
|
+
? SymbolReason
|
|
68
|
+
: T extends ReactElement
|
|
69
|
+
? ReactReason
|
|
70
|
+
: never;
|
|
71
|
+
|
|
72
|
+
/** Primitives and structured-clone built-ins, stored as-is. */
|
|
73
|
+
type LocationStatePassthrough =
|
|
74
|
+
| string
|
|
75
|
+
| number
|
|
76
|
+
| boolean
|
|
77
|
+
| bigint
|
|
78
|
+
| null
|
|
79
|
+
| undefined
|
|
80
|
+
| Date
|
|
81
|
+
| RegExp
|
|
82
|
+
| ArrayBuffer
|
|
83
|
+
| ArrayBufferView
|
|
84
|
+
| Blob
|
|
85
|
+
| File
|
|
86
|
+
| FormData;
|
|
87
|
+
|
|
41
88
|
/**
|
|
42
89
|
* Maps `T` to itself when it is safe to store in location state, or to a branded
|
|
43
90
|
* {@link LocationStateUnsafe} error for the disallowed parts: `unknown`, React
|
|
@@ -50,48 +97,134 @@ export type LocationStateUnsafe<Reason extends string> = {
|
|
|
50
97
|
*/
|
|
51
98
|
export type LocationStateSafe<T> =
|
|
52
99
|
IsUnknown<T> extends true
|
|
53
|
-
? LocationStateUnsafe<
|
|
54
|
-
: T extends
|
|
55
|
-
?
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
? LocationStateUnsafe<UnknownReason>
|
|
101
|
+
: T extends unknown
|
|
102
|
+
? [UnsafeReason<T>] extends [never]
|
|
103
|
+
? T extends LocationStatePassthrough
|
|
104
|
+
? T
|
|
105
|
+
: T extends ReadonlyMap<infer K, infer V>
|
|
106
|
+
? ReadonlyMap<LocationStateSafe<K>, LocationStateSafe<V>>
|
|
107
|
+
: T extends ReadonlySet<infer V>
|
|
108
|
+
? ReadonlySet<LocationStateSafe<V>>
|
|
109
|
+
: T extends object
|
|
110
|
+
? { [K in keyof T]: LocationStateSafe<T[K]> }
|
|
111
|
+
: T
|
|
112
|
+
: LocationStateUnsafe<UnsafeReason<T>>
|
|
113
|
+
: never;
|
|
114
|
+
|
|
115
|
+
type AtPath<P extends string> = P extends "" ? "<root>" : P;
|
|
116
|
+
|
|
117
|
+
/** `a.b` for a named key; `a[string]` / `a[number]` for an index signature. */
|
|
118
|
+
type ChildPath<P extends string, K> = string extends K
|
|
119
|
+
? `${P}[string]`
|
|
120
|
+
: number extends K
|
|
121
|
+
? `${P}[number]`
|
|
122
|
+
: K extends string | number
|
|
123
|
+
? P extends ""
|
|
124
|
+
? `${K}`
|
|
125
|
+
: `${P}.${K}`
|
|
126
|
+
: `${P}[symbol]`;
|
|
127
|
+
|
|
128
|
+
type UnsafeAt<R, P extends string> = R extends string
|
|
129
|
+
? LocationStateUnsafe<R, AtPath<P>>
|
|
130
|
+
: never;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Walks `T` to the fields {@link LocationStateSafe} rejects and returns one
|
|
134
|
+
* {@link LocationStateUnsafe} brand per offending path (a union when several
|
|
135
|
+
* fail). Reports the shallowest failures: a value that is itself unsafe stops
|
|
136
|
+
* the walk, and an object with unsafe fields of its own reports only those, so
|
|
137
|
+
* a class instance or DOM node stops at its methods instead of walking its
|
|
138
|
+
* whole object graph (`{ el: HTMLElement }` took 8.4M instantiations and hit
|
|
139
|
+
* TS2589 before). Descends only into children that fail the cached
|
|
140
|
+
* `LocationStateSafe` check, and stops at depth 8.
|
|
141
|
+
*/
|
|
142
|
+
type LocateUnsafe<
|
|
143
|
+
T,
|
|
144
|
+
P extends string,
|
|
145
|
+
D extends unknown[],
|
|
146
|
+
> = D["length"] extends 8
|
|
147
|
+
? LocationStateUnsafe<DepthReason, AtPath<P>>
|
|
148
|
+
: IsUnknown<T> extends true
|
|
149
|
+
? LocationStateUnsafe<UnknownReason, AtPath<P>>
|
|
150
|
+
: [UnsafeReason<T>] extends [never]
|
|
151
|
+
? LocateNested<T, P, D>
|
|
152
|
+
: UnsafeAt<UnsafeReason<T>, P>;
|
|
153
|
+
|
|
154
|
+
type LocateNested<
|
|
155
|
+
T,
|
|
156
|
+
P extends string,
|
|
157
|
+
D extends unknown[],
|
|
158
|
+
> = T extends LocationStatePassthrough
|
|
159
|
+
? never
|
|
160
|
+
: T extends ReadonlyMap<infer K, infer V>
|
|
161
|
+
? LocateChild<K, `${P}<key>`, D> | LocateChild<V, `${P}<value>`, D>
|
|
162
|
+
: T extends ReadonlySet<infer V>
|
|
163
|
+
? LocateChild<V, `${P}[]`, D>
|
|
164
|
+
: T extends readonly unknown[]
|
|
165
|
+
? number extends T["length"]
|
|
166
|
+
? LocateChild<T[number], `${P}[]`, D>
|
|
167
|
+
: {
|
|
168
|
+
[K in keyof T & `${number}`]-?: LocateChild<
|
|
169
|
+
T[K],
|
|
170
|
+
`${P}[${K}]`,
|
|
171
|
+
D
|
|
172
|
+
>;
|
|
173
|
+
}[keyof T & `${number}`]
|
|
174
|
+
: T extends object
|
|
175
|
+
? LocateFields<T, P, D, OwnUnsafeKeys<T>>
|
|
176
|
+
: never;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Keys whose value is unsafe itself, not through something nested in it.
|
|
180
|
+
* `any` is never one: it passes LocationStateSafe, so reporting it would
|
|
181
|
+
* leave nothing to report and hide its siblings' paths.
|
|
182
|
+
*/
|
|
183
|
+
type OwnUnsafeKeys<T> = {
|
|
184
|
+
[K in keyof T]-?: IsAny<T[K]> extends true
|
|
185
|
+
? never
|
|
186
|
+
: IsUnknown<T[K]> extends true
|
|
187
|
+
? K
|
|
188
|
+
: [UnsafeReason<T[K]>] extends [never]
|
|
189
|
+
? never
|
|
190
|
+
: K;
|
|
191
|
+
}[keyof T];
|
|
192
|
+
|
|
193
|
+
type LocateFields<
|
|
194
|
+
T,
|
|
195
|
+
P extends string,
|
|
196
|
+
D extends unknown[],
|
|
197
|
+
Own extends keyof T,
|
|
198
|
+
> = [Own] extends [never]
|
|
199
|
+
? { [K in keyof T]-?: LocateChild<T[K], ChildPath<P, K>, D> }[keyof T]
|
|
200
|
+
: { [K in Own]-?: LocateChild<T[K], ChildPath<P, K>, D> }[Own];
|
|
201
|
+
|
|
202
|
+
type LocateChild<T, P extends string, D extends unknown[]> = [T] extends [
|
|
203
|
+
LocationStateSafe<T>,
|
|
204
|
+
]
|
|
205
|
+
? never
|
|
206
|
+
: LocateUnsafe<T, P, [...D, unknown]>;
|
|
207
|
+
|
|
208
|
+
type GenericReason =
|
|
209
|
+
"location state must be serializable: React/RSC content, functions, and symbols cannot be stored — pass plain data and render it on arrival";
|
|
82
210
|
|
|
83
211
|
/**
|
|
84
212
|
* `unknown` (a no-op) when `T` is safe to store in location state, otherwise a
|
|
85
|
-
* branded {@link LocationStateUnsafe} object
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
213
|
+
* branded {@link LocationStateUnsafe} object naming the reason and the path of
|
|
214
|
+
* each offending field, e.g. `LocationStateUnsafe<"...", "items[].info.values">`.
|
|
215
|
+
* Intersected into the value parameter of a definition's call and `write()` so
|
|
216
|
+
* POSTING RSC content (or any non-serializable value) is a compile error whose
|
|
217
|
+
* text carries the reason and the field — without a `TState extends ...`
|
|
218
|
+
* self-constraint, which TypeScript rejects as circular (TS2313). For safe `T`,
|
|
219
|
+
* `value & unknown` collapses back to `value`, so valid usage is unchanged.
|
|
91
220
|
*/
|
|
92
221
|
export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>]
|
|
93
222
|
? unknown
|
|
94
|
-
:
|
|
223
|
+
: OrGenericUnsafe<LocateUnsafe<T, "", []>>;
|
|
224
|
+
|
|
225
|
+
type OrGenericUnsafe<U> = [U] extends [never]
|
|
226
|
+
? LocationStateUnsafe<GenericReason>
|
|
227
|
+
: U;
|
|
95
228
|
|
|
96
229
|
/**
|
|
97
230
|
* Type-safe location state definition
|
|
@@ -101,7 +234,10 @@ export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>]
|
|
|
101
234
|
*/
|
|
102
235
|
export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
103
236
|
(...args: TArgs): LocationStateEntry;
|
|
104
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* Injected by Vite plugin - do not set manually. Unit tests without the
|
|
239
|
+
* plugin use withLocationStateKey() from @rangojs/router/testing.
|
|
240
|
+
*/
|
|
105
241
|
__rsc_ls_key: string;
|
|
106
242
|
/** Whether this state auto-clears after first read */
|
|
107
243
|
readonly __rsc_ls_flash: boolean;
|
|
@@ -117,6 +253,13 @@ export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
|
117
253
|
* want React to manage subscription/hydration for you.
|
|
118
254
|
*/
|
|
119
255
|
read(): TState | undefined;
|
|
256
|
+
/**
|
|
257
|
+
* Read this definition's slot from a location snapshot instead of the
|
|
258
|
+
* current entry: a transition({ when }) context's `from` / `to`, or any
|
|
259
|
+
* `{ state }` holding a `history.state` object. A snapshot read never clears
|
|
260
|
+
* flash state.
|
|
261
|
+
*/
|
|
262
|
+
read(location: { readonly state: unknown }): TState | undefined;
|
|
120
263
|
/**
|
|
121
264
|
* Statically write the value into the current history entry under this
|
|
122
265
|
* definition's key, preserving any other keys already on history.state
|
|
@@ -187,6 +330,26 @@ export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
|
187
330
|
* ProductState.delete();
|
|
188
331
|
* ```
|
|
189
332
|
*/
|
|
333
|
+
let onCurrentStateReplaced: (() => void) | undefined;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* @internal The navigation store registers here (navigation-store.ts) to
|
|
337
|
+
* refresh its per-entry memory: back/forward reads the entry being left from
|
|
338
|
+
* that memory (at popstate history.state already belongs to the destination),
|
|
339
|
+
* so a write after the entry's commit must reach it.
|
|
340
|
+
*/
|
|
341
|
+
export function setCurrentHistoryStateListener(
|
|
342
|
+
listener: (() => void) | undefined,
|
|
343
|
+
): void {
|
|
344
|
+
onCurrentStateReplaced = listener;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Replace the current entry's history.state in place (same URL). */
|
|
348
|
+
export function replaceCurrentHistoryState(state: unknown): void {
|
|
349
|
+
window.history.replaceState(state, "", window.location.href);
|
|
350
|
+
onCurrentStateReplaced?.();
|
|
351
|
+
}
|
|
352
|
+
|
|
190
353
|
export function createLocationState<TState>(
|
|
191
354
|
options?: LocationStateOptions,
|
|
192
355
|
): LocationStateDefinition<
|
|
@@ -196,12 +359,17 @@ export function createLocationState<TState>(
|
|
|
196
359
|
const flash = options?.flash ?? false;
|
|
197
360
|
let _key: string | undefined;
|
|
198
361
|
|
|
362
|
+
// Dev and test throw; production folds the check away (the plugin always
|
|
363
|
+
// injects the key there). Without it, reads and writes silently target
|
|
364
|
+
// history.state["undefined"].
|
|
199
365
|
function getKey(): string {
|
|
200
|
-
if (!_key && process.env.NODE_ENV
|
|
366
|
+
if (!_key && process.env.NODE_ENV !== "production") {
|
|
201
367
|
throw new Error(
|
|
202
368
|
"[rango] createLocationState key not set. " +
|
|
203
369
|
"Make sure the exposeInternalIds Vite plugin is enabled and " +
|
|
204
|
-
"the state is exported with: export const MyState = createLocationState(...)"
|
|
370
|
+
"the state is exported with: export const MyState = createLocationState(...). " +
|
|
371
|
+
"In a unit test without the plugin, assign one with " +
|
|
372
|
+
"withLocationStateKey(MyState) from @rangojs/router/testing.",
|
|
205
373
|
);
|
|
206
374
|
}
|
|
207
375
|
return _key!;
|
|
@@ -239,7 +407,13 @@ export function createLocationState<TState>(
|
|
|
239
407
|
});
|
|
240
408
|
|
|
241
409
|
Object.defineProperty(fn, "read", {
|
|
242
|
-
value: (): TState | undefined => {
|
|
410
|
+
value: (location?: { readonly state: unknown }): TState | undefined => {
|
|
411
|
+
if (location !== undefined) {
|
|
412
|
+
const state = location.state;
|
|
413
|
+
return state !== null && typeof state === "object"
|
|
414
|
+
? ((state as Record<string, unknown>)[getKey()] as TState | undefined)
|
|
415
|
+
: undefined;
|
|
416
|
+
}
|
|
243
417
|
if (typeof window === "undefined") return undefined;
|
|
244
418
|
return window.history.state?.[getKey()] as TState | undefined;
|
|
245
419
|
},
|
|
@@ -263,11 +437,7 @@ export function createLocationState<TState>(
|
|
|
263
437
|
const existing = window.history.state;
|
|
264
438
|
const current =
|
|
265
439
|
existing !== null && typeof existing === "object" ? existing : {};
|
|
266
|
-
|
|
267
|
-
{ ...current, [key]: value },
|
|
268
|
-
"",
|
|
269
|
-
window.location.href,
|
|
270
|
-
);
|
|
440
|
+
replaceCurrentHistoryState({ ...current, [key]: value });
|
|
271
441
|
},
|
|
272
442
|
enumerable: true,
|
|
273
443
|
});
|
|
@@ -290,7 +460,7 @@ export function createLocationState<TState>(
|
|
|
290
460
|
return;
|
|
291
461
|
const next = { ...current };
|
|
292
462
|
delete next[key];
|
|
293
|
-
|
|
463
|
+
replaceCurrentHistoryState(next);
|
|
294
464
|
},
|
|
295
465
|
enumerable: true,
|
|
296
466
|
});
|
|
@@ -301,6 +471,28 @@ export function createLocationState<TState>(
|
|
|
301
471
|
>;
|
|
302
472
|
}
|
|
303
473
|
|
|
474
|
+
/**
|
|
475
|
+
* A definition's key, or undefined when none is set, without the missing-key
|
|
476
|
+
* throw of the `__rsc_ls_key` getter. For dev checks and test helpers.
|
|
477
|
+
*/
|
|
478
|
+
export function peekLocationStateKey(definition: object): string | undefined {
|
|
479
|
+
try {
|
|
480
|
+
return (definition as { __rsc_ls_key?: string }).__rsc_ls_key || undefined;
|
|
481
|
+
} catch {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Check if a value is a LocationStateDefinition (a createLocationState()
|
|
488
|
+
* result, callable, not an entry).
|
|
489
|
+
*/
|
|
490
|
+
export function isLocationStateDefinition(
|
|
491
|
+
value: unknown,
|
|
492
|
+
): value is LocationStateDefinition<unknown[], unknown> {
|
|
493
|
+
return typeof value === "function" && "__rsc_ls_flash" in value;
|
|
494
|
+
}
|
|
495
|
+
|
|
304
496
|
/**
|
|
305
497
|
* Check if a value is a LocationStateEntry
|
|
306
498
|
*/
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef } from "react";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
replaceCurrentHistoryState,
|
|
6
|
+
type LocationStateDefinition,
|
|
7
|
+
} from "./location-state-shared.js";
|
|
5
8
|
|
|
6
9
|
export {
|
|
7
10
|
createLocationState,
|
|
@@ -124,7 +127,7 @@ export function useLocationState<TArgs extends unknown[], TState>(
|
|
|
124
127
|
if (isFlash && key && state !== undefined) {
|
|
125
128
|
const cleaned = { ...window.history.state };
|
|
126
129
|
delete cleaned[key];
|
|
127
|
-
|
|
130
|
+
replaceCurrentHistoryState(cleaned);
|
|
128
131
|
}
|
|
129
132
|
}, [isFlash, key, state]);
|
|
130
133
|
|
|
@@ -226,6 +226,10 @@ export async function initBrowserApp(
|
|
|
226
226
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
// The document's entry: the transition({ when }) source of the first
|
|
230
|
+
// navigation, and of a later back/forward that leaves it.
|
|
231
|
+
store.rememberDisplayedEntry(initialPayload.metadata?.routeName);
|
|
232
|
+
|
|
229
233
|
// Register the active store on the module-level handle and wire the
|
|
230
234
|
// jar-divergence observer before any getRangoState() read can detect a
|
|
231
235
|
// cross-tab/server rotation. There is no global store singleton, so this
|