@rangojs/router 0.17.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/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -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 +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- 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/prerender/build-shell-capture.d.ts +11 -4
- 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/instrument.d.ts +8 -0
- 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/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- 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 +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- 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-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- 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 +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- 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 +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -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 +9 -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 +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- 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 +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- 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 +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- 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 +47 -3
- package/src/rsc/handler.ts +24 -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 +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -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 +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- 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 +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -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 +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
* model is the held-navigation useLoader().isLoading: a `transition` spec
|
|
31
31
|
* plus a pending Promise seeded via navigate(url, { loaders }) commits
|
|
32
32
|
* through production's commitInTransition (browser/partial-update.ts).
|
|
33
|
+
* - A spec's transition({ when }) decides every commit with production's
|
|
34
|
+
* browser decision: navigate() and useRouter().push model kind "push",
|
|
35
|
+
* useRouter().replace kind "replace", refresh() kind "revalidate".
|
|
36
|
+
* Back/forward, action and optimistic clientUrls() swap decisions are
|
|
37
|
+
* e2e territory.
|
|
33
38
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
34
39
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
35
40
|
* useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
|
|
@@ -42,6 +47,7 @@
|
|
|
42
47
|
* (the segment chain is wrapped in a MountContext exactly as in production).
|
|
43
48
|
*/
|
|
44
49
|
|
|
50
|
+
import { decideTransitionGatedOff } from "../browser/transition-when.js";
|
|
45
51
|
import { useEffect, type ReactNode, type ComponentType } from "react";
|
|
46
52
|
import type { RenderResult } from "@testing-library/react";
|
|
47
53
|
import { renderSegments } from "../segment-system.js";
|
|
@@ -56,6 +62,7 @@ import {
|
|
|
56
62
|
shouldStartViewTransition,
|
|
57
63
|
} from "../browser/partial-update.js";
|
|
58
64
|
import type {
|
|
65
|
+
NavigateOptionsInternal,
|
|
59
66
|
NavigationStore,
|
|
60
67
|
NavigationBridge,
|
|
61
68
|
UpdateSubscriber,
|
|
@@ -68,8 +75,19 @@ import {
|
|
|
68
75
|
buildParamsFromMatch,
|
|
69
76
|
} from "../router/pattern-matching.js";
|
|
70
77
|
import { normalizeBasename } from "../router/basename.js";
|
|
71
|
-
import type {
|
|
78
|
+
import type {
|
|
79
|
+
LoaderDefinition,
|
|
80
|
+
TransitionConfig,
|
|
81
|
+
TransitionWhenKind,
|
|
82
|
+
} from "../types.js";
|
|
72
83
|
import type { LocationStateDefinition } from "../browser/react/location-state-shared.js";
|
|
84
|
+
import {
|
|
85
|
+
buildHistoryState,
|
|
86
|
+
hasLocationState,
|
|
87
|
+
pushHistoryWithIdx,
|
|
88
|
+
resolveNavigationState,
|
|
89
|
+
} from "../browser/history-state.js";
|
|
90
|
+
import { withLocationStateKey } from "./location-state-key.js";
|
|
73
91
|
import type { Handle } from "../handle.js";
|
|
74
92
|
import type { ThemeConfig } from "../theme/types.js";
|
|
75
93
|
import { resolveThemeConfig } from "../theme/constants.js";
|
|
@@ -93,18 +111,15 @@ export type HandleDataSeed = Record<string, Record<string, unknown[]>>;
|
|
|
93
111
|
const syntheticIds = new WeakMap<object, string>();
|
|
94
112
|
let syntheticIdCounter = 0;
|
|
95
113
|
|
|
96
|
-
function ensureSyntheticId(
|
|
97
|
-
|
|
98
|
-
field: "$$id" | "__rsc_ls_key",
|
|
99
|
-
): string {
|
|
100
|
-
const existing = (handle as Record<string, string>)[field];
|
|
114
|
+
function ensureSyntheticId(loader: object): string {
|
|
115
|
+
const existing = (loader as { $$id?: string }).$$id;
|
|
101
116
|
if (existing) return existing;
|
|
102
|
-
let id = syntheticIds.get(
|
|
117
|
+
let id = syntheticIds.get(loader);
|
|
103
118
|
if (!id) {
|
|
104
119
|
id = `__rango_test_id_${syntheticIdCounter++}`;
|
|
105
|
-
syntheticIds.set(
|
|
120
|
+
syntheticIds.set(loader, id);
|
|
106
121
|
}
|
|
107
|
-
(
|
|
122
|
+
(loader as { $$id?: string }).$$id = id;
|
|
108
123
|
return id;
|
|
109
124
|
}
|
|
110
125
|
|
|
@@ -337,11 +352,20 @@ export interface TestRouterHandle {
|
|
|
337
352
|
* // reader: isLoading true, old data
|
|
338
353
|
* await act(async () => resolve({ name: "Product 2" }));
|
|
339
354
|
* // reader: isLoading false, new data
|
|
355
|
+
*
|
|
356
|
+
* `options.transition: false` is the per-navigation opt-out
|
|
357
|
+
* (`router.push(url, { transition: false })`, `<Link transition={false}>`):
|
|
358
|
+
* the commit is urgent and no transition({ when }) predicate is called.
|
|
340
359
|
*/
|
|
341
360
|
navigate(
|
|
342
361
|
url: string,
|
|
343
|
-
options?: Pick<RenderRouteOptions, "loaders"
|
|
362
|
+
options?: Pick<RenderRouteOptions, "loaders"> & { transition?: boolean },
|
|
344
363
|
): Promise<void>;
|
|
364
|
+
/**
|
|
365
|
+
* Re-render the current location, as router.refresh() does: a spec's
|
|
366
|
+
* transition({ when }) decides it with kind "revalidate" (`to` is `from`).
|
|
367
|
+
*/
|
|
368
|
+
refresh(): Promise<void>;
|
|
345
369
|
/** The current committed pathname. */
|
|
346
370
|
pathname(): string;
|
|
347
371
|
/** The current committed params. */
|
|
@@ -508,7 +532,7 @@ export async function renderRoute(
|
|
|
508
532
|
): Record<string, unknown> => {
|
|
509
533
|
const out = { ...base };
|
|
510
534
|
for (const [loader, data] of loaders ?? []) {
|
|
511
|
-
out[ensureSyntheticId(loader
|
|
535
|
+
out[ensureSyntheticId(loader)] = data;
|
|
512
536
|
}
|
|
513
537
|
return out;
|
|
514
538
|
};
|
|
@@ -517,7 +541,7 @@ export async function renderRoute(
|
|
|
517
541
|
if (typeof window !== "undefined") {
|
|
518
542
|
const stateObj: Record<string, unknown> = {};
|
|
519
543
|
for (const [def, value] of options.locationState ?? []) {
|
|
520
|
-
stateObj[
|
|
544
|
+
stateObj[withLocationStateKey(def).__rsc_ls_key] = value;
|
|
521
545
|
}
|
|
522
546
|
window.history.replaceState(stateObj, "");
|
|
523
547
|
}
|
|
@@ -597,8 +621,30 @@ export async function renderRoute(
|
|
|
597
621
|
let warnedNavLifecycle = false;
|
|
598
622
|
const navigate = async (
|
|
599
623
|
target: string,
|
|
600
|
-
navOptions?: Pick<RenderRouteOptions, "loaders"
|
|
624
|
+
navOptions?: Pick<RenderRouteOptions, "loaders"> & {
|
|
625
|
+
history?: Pick<
|
|
626
|
+
NavigateOptionsInternal,
|
|
627
|
+
"state" | "replace" | "transition"
|
|
628
|
+
>;
|
|
629
|
+
kind?: TransitionWhenKind;
|
|
630
|
+
transition?: boolean;
|
|
631
|
+
},
|
|
601
632
|
): Promise<void> => {
|
|
633
|
+
// A useRouter().push/replace or <Link> navigation (`history`) writes its
|
|
634
|
+
// entry the way production does (navigation-bridge.ts navigate ->
|
|
635
|
+
// navigation-transaction.ts commit): the dev state check, typed entries
|
|
636
|
+
// spread onto history.state, the idx stamp, and the __rsc_locationstate
|
|
637
|
+
// event when the old or new entry carries location state. The URL stays
|
|
638
|
+
// put: renderRoute tracks location on the event controller, not
|
|
639
|
+
// window.location. router.navigate() and refresh() leave history alone.
|
|
640
|
+
const history = navOptions?.history;
|
|
641
|
+
const historyState = history
|
|
642
|
+
? buildHistoryState(
|
|
643
|
+
history.state !== undefined
|
|
644
|
+
? resolveNavigationState(history.state)
|
|
645
|
+
: undefined,
|
|
646
|
+
)
|
|
647
|
+
: undefined;
|
|
602
648
|
// No server fetch, so the navigation lifecycle never starts: the state
|
|
603
649
|
// useNavigation()/useLinkStatus()/useAction() read stays "idle" — asserting
|
|
604
650
|
// a pending/loading/submitting state here proves nothing. Warn once (per
|
|
@@ -623,30 +669,76 @@ export async function renderRoute(
|
|
|
623
669
|
mount,
|
|
624
670
|
);
|
|
625
671
|
const metadata = makeMetadata(nextUrl.pathname, segments, match.params);
|
|
672
|
+
// Production's browser-run transition({ when }) decision
|
|
673
|
+
// (browser/partial-update.ts): the committed location is the source.
|
|
674
|
+
const kind: TransitionWhenKind =
|
|
675
|
+
navOptions?.kind ?? (history?.replace ? "replace" : "push");
|
|
676
|
+
// `transition: false` gates off without calling a predicate
|
|
677
|
+
// (navigation-bridge.ts navigate).
|
|
678
|
+
const transitionOptOut =
|
|
679
|
+
(navOptions?.transition ?? history?.transition) === false;
|
|
680
|
+
const gatedOff =
|
|
681
|
+
transitionOptOut ||
|
|
682
|
+
decideTransitionGatedOff(segments, () => ({
|
|
683
|
+
kind,
|
|
684
|
+
from: {
|
|
685
|
+
url: eventController.getLocation().href,
|
|
686
|
+
params: eventController.getParams(),
|
|
687
|
+
routeName: leaf.name,
|
|
688
|
+
state: window.history.state,
|
|
689
|
+
},
|
|
690
|
+
to: {
|
|
691
|
+
url: nextUrl,
|
|
692
|
+
params: match.params,
|
|
693
|
+
routeName: leaf.name,
|
|
694
|
+
state: historyState ?? null,
|
|
695
|
+
},
|
|
696
|
+
}));
|
|
626
697
|
const root = await renderSegments(segments, {
|
|
627
698
|
outletPending: options.outletPending,
|
|
699
|
+
transitionGatedOff: gatedOff,
|
|
628
700
|
});
|
|
629
701
|
eventController.setLocation(nextUrl);
|
|
630
702
|
eventController.setParams(match.params);
|
|
631
703
|
store.setCurrentUrl(nextUrl.href);
|
|
632
704
|
store.setSegmentIds(segments.map((s) => s.id));
|
|
705
|
+
let notifyLocationState = false;
|
|
706
|
+
if (history) {
|
|
707
|
+
notifyLocationState =
|
|
708
|
+
hasLocationState(window.history.state) ||
|
|
709
|
+
hasLocationState(historyState);
|
|
710
|
+
pushHistoryWithIdx(
|
|
711
|
+
historyState ?? null,
|
|
712
|
+
window.location.href,
|
|
713
|
+
history.replace ?? false,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
633
716
|
const emit: UpdateSubscriber = (update) => store.emitUpdate(update);
|
|
634
717
|
await act(async () => {
|
|
635
718
|
// Production's transition() lane (browser/partial-update.ts). Its
|
|
636
719
|
// same-structure / fully-prefetched / optimistic hold lanes are not
|
|
637
720
|
// modeled: without transition() the commit stays urgent.
|
|
638
|
-
if (shouldStartViewTransition(segments)) {
|
|
721
|
+
if (shouldStartViewTransition(segments, gatedOff)) {
|
|
639
722
|
commitInTransition(emit, segments, { root, metadata }, ["navigation"]);
|
|
640
723
|
} else {
|
|
641
724
|
emit({ root, metadata });
|
|
642
725
|
}
|
|
726
|
+
if (notifyLocationState) {
|
|
727
|
+
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
728
|
+
}
|
|
643
729
|
});
|
|
644
730
|
};
|
|
645
731
|
|
|
732
|
+
const refresh = (): Promise<void> => {
|
|
733
|
+
const current = new URL(eventController.getLocation().href);
|
|
734
|
+
return navigate(current.pathname + current.search, { kind: "revalidate" });
|
|
735
|
+
};
|
|
736
|
+
|
|
646
737
|
let prefetchRoot: HTMLElement | undefined;
|
|
647
738
|
const bridge: NavigationBridge = {
|
|
648
|
-
navigate: (target) =>
|
|
649
|
-
|
|
739
|
+
navigate: (target, navigateOptions) =>
|
|
740
|
+
navigate(target, { history: navigateOptions ?? {} }),
|
|
741
|
+
refresh: () => refresh(),
|
|
650
742
|
handlePopstate: async () => {},
|
|
651
743
|
registerLinkInterception: () => () => {},
|
|
652
744
|
registerDelegatedPrefetch: () =>
|
|
@@ -704,6 +796,7 @@ export async function renderRoute(
|
|
|
704
796
|
|
|
705
797
|
const router: TestRouterHandle = {
|
|
706
798
|
navigate,
|
|
799
|
+
refresh,
|
|
707
800
|
pathname: () => new URL(eventController.getLocation().href).pathname,
|
|
708
801
|
params: () => eventController.getParams(),
|
|
709
802
|
store,
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* Synchronous: client revalidate functions must be sync.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { makeIsAction
|
|
13
|
+
import { makeIsAction } from "../router/is-action.js";
|
|
14
|
+
import { resolveTestActionId } from "./internal/action-id.js";
|
|
14
15
|
import {
|
|
15
16
|
lockedClientDefault,
|
|
16
17
|
runClientRevalidateChain,
|
|
@@ -23,23 +24,6 @@ import type {
|
|
|
23
24
|
|
|
24
25
|
const DEFAULT_URL = "http://localhost/";
|
|
25
26
|
|
|
26
|
-
function resolveActionId(
|
|
27
|
-
action: ((...args: never[]) => unknown) | string | undefined,
|
|
28
|
-
): string | undefined {
|
|
29
|
-
if (action === undefined) return undefined;
|
|
30
|
-
if (typeof action === "string") return action;
|
|
31
|
-
const id = resolveActionRefId(action);
|
|
32
|
-
if (id === undefined) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
"runClientRevalidate: `action` must be a single imported server action " +
|
|
35
|
-
"(carrying its build-injected id) or an actionId string. The passed " +
|
|
36
|
-
"function has no $id/$$id — outside a built app, pass the id string " +
|
|
37
|
-
'your predicate should match (e.g. "src/actions/cart.ts#addToCart").',
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
return id;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
27
|
/**
|
|
44
28
|
* Options for {@link runClientRevalidate}. Defaults model a same-URL
|
|
45
29
|
* navigation with no action (locked default `false`).
|
|
@@ -80,7 +64,7 @@ export function runClientRevalidate(
|
|
|
80
64
|
const currentParams = opts.currentParams ?? {};
|
|
81
65
|
const nextParams = opts.nextParams ?? currentParams;
|
|
82
66
|
const inAction = opts.action !== undefined;
|
|
83
|
-
const actionId =
|
|
67
|
+
const actionId = resolveTestActionId(opts.action, "runClientRevalidate");
|
|
84
68
|
const defaultShouldRevalidate = lockedClientDefault({
|
|
85
69
|
actionRequest: opts.actionRequest ?? inAction,
|
|
86
70
|
currentParams,
|
|
@@ -48,6 +48,10 @@ import { collectHandle } from "./collect-handle.js";
|
|
|
48
48
|
import type { ThemeConfig } from "../theme/types.js";
|
|
49
49
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
50
50
|
import type { CacheProfile } from "../cache/profile-registry.js";
|
|
51
|
+
import type { PartialCacheOptions } from "../types/cache-types.js";
|
|
52
|
+
import type { HandlerContext } from "../types/handler-context.js";
|
|
53
|
+
import { resolveLoaderData } from "../router/segment-resolution/loader-cache.js";
|
|
54
|
+
import { armLoaderTagSets } from "../cache/cache-tag.js";
|
|
51
55
|
import {
|
|
52
56
|
createTestRequestContext,
|
|
53
57
|
buildRunSnapshot,
|
|
@@ -111,9 +115,11 @@ export interface RunLoaderOptions<TEnv = any> {
|
|
|
111
115
|
basename?: string;
|
|
112
116
|
/**
|
|
113
117
|
* Theme config in the same shape `createRouter({ theme })` takes (e.g. `true`
|
|
114
|
-
* or `{ themes: [...] }`). Seeds the request's theme config so
|
|
115
|
-
*
|
|
116
|
-
*
|
|
118
|
+
* or `{ themes: [...] }`). Seeds the request's theme config, so the loader
|
|
119
|
+
* body reads the visitor's theme (the Cookie on `request`, or the default)
|
|
120
|
+
* with `getRequestContext().theme`. That read is guarded like `cookies()`: it
|
|
121
|
+
* throws inside a `"use cache"` function (#971). The loader ctx has no
|
|
122
|
+
* `theme`/`setTheme` (those are handler/middleware-only).
|
|
117
123
|
*/
|
|
118
124
|
theme?: ThemeConfig | true;
|
|
119
125
|
/** Environment bindings surfaced as `ctx.env`. */
|
|
@@ -151,6 +157,18 @@ export interface RunLoaderOptions<TEnv = any> {
|
|
|
151
157
|
cacheStore?: SegmentCacheStore;
|
|
152
158
|
/** Cache profiles (the `createRouter({ cacheProfiles })` shape). */
|
|
153
159
|
cacheProfiles?: Record<string, CacheProfile>;
|
|
160
|
+
/**
|
|
161
|
+
* Run the loader through the `cache()` a route binds it with — the options
|
|
162
|
+
* `loader(Loader, () => [cache({...})])` takes — using the production
|
|
163
|
+
* read-through against `cache.store`, else `cacheStore`. A HIT returns the
|
|
164
|
+
* stored value without running the body and records the tags the body
|
|
165
|
+
* recorded when the entry was written, so `updateTag()` of any of them drops
|
|
166
|
+
* the entry. The write is a background task the call does not await (spy
|
|
167
|
+
* on the store's `setItem` to wait for it). A raw loader body is keyed by
|
|
168
|
+
* its function reference. `handlePushes` lists the body's pushes, so a HIT
|
|
169
|
+
* reports none: the replay into the page's handle store is not modeled.
|
|
170
|
+
*/
|
|
171
|
+
cache?: PartialCacheOptions;
|
|
154
172
|
/**
|
|
155
173
|
* Customize the rango state cookie a loader that calls
|
|
156
174
|
* `invalidateClientCache()` rotates (the name is always seeded — default
|
|
@@ -352,6 +370,50 @@ function runWithLoaderContext<R>(
|
|
|
352
370
|
});
|
|
353
371
|
}
|
|
354
372
|
|
|
373
|
+
const rawLoaderIds = new WeakMap<object, string>();
|
|
374
|
+
let rawLoaderSeq = 0;
|
|
375
|
+
|
|
376
|
+
function cacheLoaderId(loader: RunnableLoader<unknown>): string {
|
|
377
|
+
if (typeof loader !== "function") return loader.$$id;
|
|
378
|
+
let id = rawLoaderIds.get(loader);
|
|
379
|
+
if (id === undefined) {
|
|
380
|
+
id = `runLoader#${++rawLoaderSeq}`;
|
|
381
|
+
rawLoaderIds.set(loader, id);
|
|
382
|
+
}
|
|
383
|
+
return id;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Call the loader: directly, or through its `cache()` binding (the funnel a
|
|
388
|
+
* route's DSL loaders take, loader-cache.ts resolveLoaderData), whose MISS
|
|
389
|
+
* reads the binding through the handler ctx's use(). A binding arms the
|
|
390
|
+
* per-execution loader tag sets first, as a match that resolves one does
|
|
391
|
+
* (match-api.ts, bindsLoaderCache).
|
|
392
|
+
*/
|
|
393
|
+
function invokeLoader<T>(
|
|
394
|
+
loader: RunnableLoader<T>,
|
|
395
|
+
loaderFn: (ctx: TestLoaderContext) => Promise<T> | T,
|
|
396
|
+
opts: RunLoaderOptions,
|
|
397
|
+
loaderCtx: TestLoaderContext,
|
|
398
|
+
): Promise<T> {
|
|
399
|
+
const run = () => Promise.resolve(loaderFn(loaderCtx));
|
|
400
|
+
if (!opts.cache) return run();
|
|
401
|
+
armLoaderTagSets();
|
|
402
|
+
const handlerCtx = {
|
|
403
|
+
params: loaderCtx.params,
|
|
404
|
+
use: run,
|
|
405
|
+
} as unknown as HandlerContext<any, any>;
|
|
406
|
+
return resolveLoaderData(
|
|
407
|
+
{
|
|
408
|
+
loader: { __brand: "loader", $$id: cacheLoaderId(loader) },
|
|
409
|
+
revalidate: [],
|
|
410
|
+
cache: { options: opts.cache },
|
|
411
|
+
},
|
|
412
|
+
handlerCtx,
|
|
413
|
+
loaderCtx.pathname,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
355
417
|
export async function runLoader<T>(
|
|
356
418
|
loader: RunnableLoader<T>,
|
|
357
419
|
opts: RunLoaderOptions = {},
|
|
@@ -359,7 +421,7 @@ export async function runLoader<T>(
|
|
|
359
421
|
const loaderFn = resolveLoaderFn(loader);
|
|
360
422
|
const { ctx } = createTestRequestContext(buildLoaderCtxOpts(opts));
|
|
361
423
|
return runWithLoaderContext(ctx as RequestContext<any>, opts, (loaderCtx) =>
|
|
362
|
-
|
|
424
|
+
invokeLoader(loader, loaderFn, opts, loaderCtx),
|
|
363
425
|
);
|
|
364
426
|
}
|
|
365
427
|
|
|
@@ -413,7 +475,7 @@ export async function runLoaderResult<T>(
|
|
|
413
475
|
result = await runWithLoaderContext(
|
|
414
476
|
reqCtx,
|
|
415
477
|
opts,
|
|
416
|
-
(loaderCtx) =>
|
|
478
|
+
(loaderCtx) => invokeLoader(loader, loaderFn, opts, loaderCtx),
|
|
417
479
|
handlePushes,
|
|
418
480
|
);
|
|
419
481
|
} catch (error) {
|