@rangojs/router 0.24.4 → 0.26.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 +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/handle-snapshot.d.ts +2 -2
- package/dist/types/cache/types.d.ts +16 -6
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
- package/dist/types/client-urls/client-urls.d.ts +6 -2
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +9 -5
- package/dist/types/client-urls/types.d.ts +29 -16
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/handles/deferred-resolution.d.ts +3 -2
- package/dist/types/handles/meta.d.ts +5 -0
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/prerender/build-shell-capture.d.ts +1 -2
- package/dist/types/prerender/writable-store.d.ts +2 -3
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -11
- package/dist/types/router/router-options.d.ts +4 -27
- package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +3 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/router-versions.d.ts +8 -6
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +14 -4
- package/dist/types/rsc/shell-capture.d.ts +23 -2
- package/dist/types/rsc/types.d.ts +5 -8
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/build-version-table.d.ts +10 -13
- package/dist/types/server/context.d.ts +90 -6
- package/dist/types/server/handle-store.d.ts +17 -6
- package/dist/types/server/request-context.d.ts +45 -7
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/build-versions.d.ts +0 -3
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/internal/seed-vars.d.ts +4 -4
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +42 -5
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +10 -4
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6819 -6555
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/cache-guide/SKILL.md +4 -8
- package/skills/caching/SKILL.md +6 -6
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +185 -32
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/host-router/SKILL.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +42 -19
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +103 -52
- package/skills/prerender/SKILL.md +4 -5
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/router-setup/SKILL.md +0 -2
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/render-handler.md +17 -17
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +86 -22
- package/skills/testing/server-actions.md +16 -16
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +112 -84
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +6 -7
- package/src/cache/cf/cf-cache-types.ts +0 -17
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/handle-snapshot.ts +2 -2
- package/src/cache/types.ts +16 -6
- package/src/cache/vercel/vercel-cache-store.ts +8 -19
- package/src/client-urls/client-urls.ts +202 -153
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +137 -66
- package/src/client-urls/types.ts +30 -15
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/handles/deferred-resolution.ts +3 -2
- package/src/handles/meta.ts +5 -0
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/prerender/build-shell-capture.ts +1 -2
- package/src/prerender/writable-store.ts +2 -3
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +41 -21
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +15 -10
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -13
- package/src/router/router-options.ts +4 -28
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/mask-nested.ts +5 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +3 -2
- package/src/router/tracing.ts +2 -2
- package/src/router-versions.ts +7 -7
- package/src/router.ts +5 -17
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +86 -48
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +26 -3
- package/src/rsc/shell-capture.ts +137 -51
- package/src/rsc/types.ts +5 -9
- package/src/segment-system.tsx +125 -119
- package/src/server/build-version-table.ts +12 -24
- package/src/server/context.ts +160 -7
- package/src/server/handle-store.ts +80 -12
- package/src/server/request-context.ts +46 -6
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/build-versions.ts +0 -3
- package/src/testing/dispatch.ts +5 -8
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/internal/seed-vars.ts +4 -4
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +79 -9
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +10 -4
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/shell-prerender-phase.ts +1 -5
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -16,9 +16,10 @@ import type { NavigationStoreContextValue } from "../browser/react/context.js";
|
|
|
16
16
|
import type { HandleData } from "../browser/types.js";
|
|
17
17
|
import type { ResolvedSegment } from "../types.js";
|
|
18
18
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_ACTION_STATE,
|
|
21
|
+
type EventController,
|
|
22
|
+
type DerivedNavigationState,
|
|
22
23
|
} from "../browser/event-controller.js";
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -111,16 +112,11 @@ function createSsrEventController(opts: {
|
|
|
111
112
|
getState: () => state,
|
|
112
113
|
getLocation: () => location,
|
|
113
114
|
subscribe: () => () => {},
|
|
114
|
-
getActionState: () =>
|
|
115
|
-
state: "idle",
|
|
116
|
-
actionId: null,
|
|
117
|
-
payload: null,
|
|
118
|
-
error: null,
|
|
119
|
-
result: null,
|
|
120
|
-
}),
|
|
115
|
+
getActionState: () => DEFAULT_ACTION_STATE,
|
|
121
116
|
subscribeToAction: () => () => {},
|
|
122
117
|
subscribeToHandles: () => () => {},
|
|
123
118
|
flushRouteState: () => {},
|
|
119
|
+
releaseRouteState: () => {},
|
|
124
120
|
setHandleData: () => {},
|
|
125
121
|
getHandleState: () => handleState,
|
|
126
122
|
lockHydration: () => {},
|
|
@@ -35,9 +35,6 @@ export interface BuildVersions extends RouterVersions {
|
|
|
35
35
|
* Install the cache versions of a simulated build, or remove them with no
|
|
36
36
|
* argument (routers serve unversioned again, the default in tests).
|
|
37
37
|
*
|
|
38
|
-
* A router that sets `createRouter({ version })` keeps that value for both
|
|
39
|
-
* versions, exactly as in production.
|
|
40
|
-
*
|
|
41
38
|
* @example A deploy that changes the client only keeps cached data
|
|
42
39
|
* ```ts
|
|
43
40
|
* setBuildVersions({ data: "d1", document: "h1" });
|
package/src/testing/dispatch.ts
CHANGED
|
@@ -129,7 +129,7 @@ import type { CacheProfile } from "../cache/profile-registry.js";
|
|
|
129
129
|
// onto the whole testing barrel's eager graph and break every consumer suite
|
|
130
130
|
// that imports `@rangojs/router/testing` without mocking plugin-rsc.
|
|
131
131
|
import type { EntryData } from "../server/context.js";
|
|
132
|
-
import {
|
|
132
|
+
import { ensureRouterTrie } from "../rsc/manifest-init.js";
|
|
133
133
|
import { RESPONSE_TYPE_MIME } from "../router/content-negotiation.js";
|
|
134
134
|
import { RouterError } from "../errors.js";
|
|
135
135
|
import { createProblemDetails } from "../rsc/response-error.js";
|
|
@@ -167,9 +167,9 @@ import { applyStreamIdleTimeout } from "../rsc/stream-idle.js";
|
|
|
167
167
|
interface DispatchableRouter<TEnv> {
|
|
168
168
|
id?: string;
|
|
169
169
|
routerId?: string;
|
|
170
|
-
/** createRouter({ version }), the consumer-set version. */
|
|
171
|
-
version?: string;
|
|
172
170
|
routeMap: Record<string, unknown>;
|
|
171
|
+
/** The routes() patterns the dev trie is rebuilt from (rsc/manifest-init.ts). */
|
|
172
|
+
urlpatterns?: unknown;
|
|
173
173
|
middleware: MiddlewareEntry<TEnv>[];
|
|
174
174
|
onError?: OnErrorCallback<TEnv>;
|
|
175
175
|
/**
|
|
@@ -409,11 +409,8 @@ export async function dispatch<TEnv = any>(
|
|
|
409
409
|
const url = new URL(req.url);
|
|
410
410
|
const env = (opts.env ?? {}) as TEnv;
|
|
411
411
|
|
|
412
|
-
// Seed the per-router manifest so reverse() resolves during handler execution.
|
|
413
412
|
const routerId = router.id ?? router.routerId;
|
|
414
|
-
|
|
415
|
-
setRouterManifest(routerId, router.routeMap as Record<string, string>);
|
|
416
|
-
}
|
|
413
|
+
await ensureRouterTrie(router);
|
|
417
414
|
|
|
418
415
|
// The visitor's URL is matched before any middleware runs, so an RSC route
|
|
419
416
|
// fails here, before a middleware side effect. A rewrite target is matched
|
|
@@ -449,7 +446,7 @@ export async function dispatch<TEnv = any>(
|
|
|
449
446
|
cacheProfiles: router.cacheProfiles,
|
|
450
447
|
// The store keys with the router's versions, resolved as the production
|
|
451
448
|
// handler resolves them (rsc/handler.ts).
|
|
452
|
-
versions: resolveRouterVersions(routerId
|
|
449
|
+
versions: resolveRouterVersions(routerId),
|
|
453
450
|
});
|
|
454
451
|
// Wire background error reporting so cache degradation (reportCacheError ->
|
|
455
452
|
// _reportBackgroundError) reaches the router's onError, mirroring the production
|
package/src/testing/e2e/index.ts
CHANGED
package/src/testing/index.ts
CHANGED
|
@@ -19,9 +19,10 @@ import type { ShouldRevalidateFn } from "../../types.js";
|
|
|
19
19
|
*
|
|
20
20
|
* A Vitest project runs no hoist plugin, so the source's revalidate()
|
|
21
21
|
* predicates are plain functions. Each is registered under a key named for
|
|
22
|
-
* its reference, route and loader, as the plugin's generated
|
|
23
|
-
* its own, and the projection carries the keys:
|
|
24
|
-
* and the server runs them, as in an app.
|
|
22
|
+
* its reference, route or intercept, and loader, as the plugin's generated
|
|
23
|
+
* module registers its own, and the projection carries the keys:
|
|
24
|
+
* materialization attaches them and the server runs them, as in an app. A
|
|
25
|
+
* boolean rides in the projection as itself, as the plugin leaves a literal.
|
|
25
26
|
*/
|
|
26
27
|
export function installClientUrlsProjection(
|
|
27
28
|
referenceId: string,
|
|
@@ -50,21 +51,40 @@ function withRevalidateKeys(
|
|
|
50
51
|
projection: ClientUrlProjection,
|
|
51
52
|
): ClientUrlProjection {
|
|
52
53
|
const fns: Record<string, ShouldRevalidateFn<any, any>> = {};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// A route's or an intercept's loaders, keyed under `owner`.
|
|
55
|
+
const withKeys = <T extends object>(
|
|
56
|
+
projected: T,
|
|
57
|
+
owner: string,
|
|
58
|
+
loaders: ClientUrlPatterns["routes"][number]["loaders"] | undefined,
|
|
59
|
+
): T => {
|
|
60
|
+
const loaderRevalidate = (loaders ?? []).map(
|
|
56
61
|
({ revalidate }, loaderIndex) =>
|
|
57
62
|
revalidate.map((entry, index) => {
|
|
58
63
|
if (isHoistedRevalidateRef(entry)) return entry.$$rangoRevalidate;
|
|
59
|
-
|
|
64
|
+
if (typeof entry === "boolean") return entry;
|
|
65
|
+
const key = `${referenceId}:${owner}:${loaderIndex}:${index}`;
|
|
60
66
|
fns[key] = entry;
|
|
61
67
|
return key;
|
|
62
68
|
}),
|
|
63
69
|
);
|
|
64
70
|
return loaderRevalidate.some((keys) => keys.length > 0)
|
|
65
|
-
? { ...
|
|
66
|
-
:
|
|
67
|
-
}
|
|
71
|
+
? { ...projected, loaderRevalidate }
|
|
72
|
+
: projected;
|
|
73
|
+
};
|
|
74
|
+
const routes = projection.routes.map((route) =>
|
|
75
|
+
withKeys(
|
|
76
|
+
route,
|
|
77
|
+
route.id,
|
|
78
|
+
definition.routes.find(({ id }) => id === route.id)?.loaders,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
const intercepts = projection.intercepts?.map((intercept, index) =>
|
|
82
|
+
withKeys(
|
|
83
|
+
intercept,
|
|
84
|
+
`intercept${index}`,
|
|
85
|
+
definition.intercepts[index]?.loaders,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
68
88
|
registerClientUrlRevalidators(fns);
|
|
69
|
-
return { ...projection, routes };
|
|
89
|
+
return { ...projection, routes, ...(intercepts && { intercepts }) };
|
|
70
90
|
}
|
|
@@ -23,10 +23,10 @@ export interface StateCookieSeed {
|
|
|
23
23
|
*/
|
|
24
24
|
routerId?: string;
|
|
25
25
|
/**
|
|
26
|
-
* The app's version
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* The app's version: the rotated value's prefix (`{version}:{timestamp}`)
|
|
27
|
+
* and the request's cache versions, which a store keys with. Defaults to
|
|
28
|
+
* `"0"` for the prefix (resolved inside createRequestContext) and to the
|
|
29
|
+
* build versions for the store.
|
|
30
30
|
*/
|
|
31
31
|
version?: string;
|
|
32
32
|
}
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
* SEEDED directly into client context (see the `loaders` / `locationState` /
|
|
24
24
|
* `handles` / `outletPending` options) — nothing is executed on the server.
|
|
25
25
|
* This exercises the context read path, not the run path.
|
|
26
|
-
* - navigate()
|
|
27
|
-
*
|
|
28
|
-
* pending
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
26
|
+
* - navigate() drives the navigation lifecycle as a click does:
|
|
27
|
+
* useNavigation() reads loading from the call and idle with the commit,
|
|
28
|
+
* and useLinkStatus().pending is true for a link to the destination until
|
|
29
|
+
* the commit is handed to React. A `transition` spec plus a pending
|
|
30
|
+
* Promise seeded via navigate(url, { loaders }) commits through
|
|
31
|
+
* production's commitInTransition (browser/partial-update.ts), which React
|
|
32
|
+
* holds: useNavigation() stays loading and the held reader's
|
|
33
|
+
* useLoader().isLoading reads true until the data lands. Without
|
|
34
|
+
* `transition` the commit is urgent (production's same-structure hold is
|
|
35
|
+
* not modeled). useAction().state stays idle: no action runs.
|
|
33
36
|
* - A spec's transition({ when }) decides every commit with production's
|
|
34
37
|
* browser decision: navigate() and useRouter().push model kind "push",
|
|
35
38
|
* useRouter().replace kind "replace", refresh() kind "revalidate".
|
|
@@ -81,7 +84,10 @@ import type {
|
|
|
81
84
|
NavigationBridge,
|
|
82
85
|
UpdateSubscriber,
|
|
83
86
|
} from "../browser/types.js";
|
|
84
|
-
import type {
|
|
87
|
+
import type {
|
|
88
|
+
EventController,
|
|
89
|
+
NavigationHandle,
|
|
90
|
+
} from "../browser/event-controller.js";
|
|
85
91
|
import type { ResolvedSegment, RscMetadata } from "../browser/types.js";
|
|
86
92
|
import { NavigationProvider } from "../browser/react/NavigationProvider.js";
|
|
87
93
|
import {
|
|
@@ -407,9 +413,10 @@ export interface TestRouterHandle {
|
|
|
407
413
|
* pending Promise suspends the read like a streaming Flight chunk. When a
|
|
408
414
|
* spec in the chain has `transition`, the commit goes through production's
|
|
409
415
|
* commitInTransition: the reader stays on screen with
|
|
410
|
-
* `useLoader().isLoading === true
|
|
411
|
-
* inside RTL's `act()` to
|
|
412
|
-
*
|
|
416
|
+
* `useLoader().isLoading === true`, and `useNavigation().state` reads
|
|
417
|
+
* "loading", until the promise settles. Settle it inside RTL's `act()` to
|
|
418
|
+
* flush the commit. Without `transition` the commit is urgent and the read
|
|
419
|
+
* suspends to the nearest Suspense boundary.
|
|
413
420
|
*
|
|
414
421
|
* @example
|
|
415
422
|
* await router.navigate("/products/2", { loaders: [[ProductLoader, next]] });
|
|
@@ -429,10 +436,16 @@ export interface TestRouterHandle {
|
|
|
429
436
|
* location state as a production replace does). With neither, history is
|
|
430
437
|
* left alone and readers keep the state they have: `navigate(url)` and
|
|
431
438
|
* `navigate(url, { replace: false })` are the same call.
|
|
439
|
+
*
|
|
440
|
+
* `options.handles` are the destination's handle pushes (`[handle,
|
|
441
|
+
* values]`, attached to its leaf route as in `renderRoute`'s own
|
|
442
|
+
* `handles`). They arrive on the response's handle stream, so `useHandle`
|
|
443
|
+
* readers take them with the page's commit: while React holds the page,
|
|
444
|
+
* readers keep the values of the page being left.
|
|
432
445
|
*/
|
|
433
446
|
navigate(
|
|
434
447
|
url: string,
|
|
435
|
-
options?: Pick<RenderRouteOptions, "loaders"> &
|
|
448
|
+
options?: Pick<RenderRouteOptions, "loaders" | "handles"> &
|
|
436
449
|
Pick<NavigateOptions, "state" | "replace" | "transition">,
|
|
437
450
|
): Promise<void>;
|
|
438
451
|
/**
|
|
@@ -791,17 +804,36 @@ export async function renderRoute(
|
|
|
791
804
|
};
|
|
792
805
|
}
|
|
793
806
|
|
|
794
|
-
|
|
807
|
+
type NavigateCall = Pick<RenderRouteOptions, "loaders" | "handles"> & {
|
|
808
|
+
history?: Pick<NavigateOptionsInternal, "state" | "replace" | "transition">;
|
|
809
|
+
kind?: TransitionWhenKind;
|
|
810
|
+
transition?: boolean;
|
|
811
|
+
};
|
|
812
|
+
// The navigation's lifecycle, as navigation-transaction.ts drives it:
|
|
813
|
+
// useNavigation() reads loading from the call, delivered before the commit
|
|
814
|
+
// as a click's notification goes out while its response is in flight, and
|
|
815
|
+
// idle with the location at the commit, which React may hold.
|
|
795
816
|
const navigate = async (
|
|
796
817
|
target: string,
|
|
797
|
-
navOptions?:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
818
|
+
navOptions?: NavigateCall,
|
|
819
|
+
): Promise<void> => {
|
|
820
|
+
const handle = eventController.startNavigation(
|
|
821
|
+
new URL(target, TEST_ORIGIN).href,
|
|
822
|
+
);
|
|
823
|
+
// Synchronous, in the caller's task: a <Link> click's own act() scope
|
|
824
|
+
// takes it, and no act() of the caller's can overlap it.
|
|
825
|
+
act(() => eventController.flushRouteState());
|
|
826
|
+
try {
|
|
827
|
+
await commitNavigation(handle, target, navOptions);
|
|
828
|
+
} finally {
|
|
829
|
+
// Back to idle when the commit never came (a render that threw).
|
|
830
|
+
handle[Symbol.dispose]();
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
const commitNavigation = async (
|
|
834
|
+
handle: NavigationHandle,
|
|
835
|
+
target: string,
|
|
836
|
+
navOptions?: NavigateCall,
|
|
805
837
|
): Promise<void> => {
|
|
806
838
|
// A useRouter().push/replace or <Link> navigation (`history`) writes its
|
|
807
839
|
// entry the way production does (navigation-bridge.ts navigate ->
|
|
@@ -819,21 +851,6 @@ export async function renderRoute(
|
|
|
819
851
|
: undefined,
|
|
820
852
|
)
|
|
821
853
|
: undefined;
|
|
822
|
-
// No server fetch, so the navigation lifecycle never starts: the state
|
|
823
|
-
// useNavigation()/useLinkStatus()/useAction() read stays "idle" — asserting
|
|
824
|
-
// a pending/loading/submitting state here proves nothing. Warn once (per
|
|
825
|
-
// render) under the test runner so that false-confidence trap is loud.
|
|
826
|
-
if (isUnderTestRunner() && !warnedNavLifecycle) {
|
|
827
|
-
warnedNavLifecycle = true;
|
|
828
|
-
console.warn(
|
|
829
|
-
"renderRoute: navigate()/useRouter().push do NOT drive the navigation " +
|
|
830
|
-
"lifecycle. useNavigation().state, useLinkStatus().pending, and " +
|
|
831
|
-
'useAction().state stay "idle" here. Assert params/pathname/content ' +
|
|
832
|
-
"after navigate(); use renderServerTree or e2e to assert " +
|
|
833
|
-
"pending/loading/submitting transition states. A held " +
|
|
834
|
-
"useLoader().isLoading is modeled: see navigate(url, { loaders }).",
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
854
|
const nextUrl = new URL(target, TEST_ORIGIN);
|
|
838
855
|
const match = resolve(nextUrl.pathname);
|
|
839
856
|
const segments = buildSegments(
|
|
@@ -843,6 +860,23 @@ export async function renderRoute(
|
|
|
843
860
|
mount,
|
|
844
861
|
);
|
|
845
862
|
const metadata = makeMetadata(nextUrl.pathname, segments, match.params);
|
|
863
|
+
// The destination's handle pushes arrive on the response's handle
|
|
864
|
+
// stream, as production's do, behind the page's commit.
|
|
865
|
+
const navHandles = navOptions?.handles;
|
|
866
|
+
if (navHandles) {
|
|
867
|
+
const leafId =
|
|
868
|
+
[...segments].reverse().find((s) => s.type === "route")?.id ??
|
|
869
|
+
segments[segments.length - 1]?.id;
|
|
870
|
+
const pushed: HandleDataSeed = {};
|
|
871
|
+
for (const [h, values] of navHandles) {
|
|
872
|
+
if (leafId === undefined) continue;
|
|
873
|
+
const id = (h as unknown as { $$id: string }).$$id;
|
|
874
|
+
(pushed[id] ??= {})[leafId] = values;
|
|
875
|
+
}
|
|
876
|
+
metadata.handles = (async function* () {
|
|
877
|
+
yield pushed;
|
|
878
|
+
})();
|
|
879
|
+
}
|
|
846
880
|
// Production's browser-run transition({ when }) decision
|
|
847
881
|
// (browser/partial-update.ts): the committed location is the source.
|
|
848
882
|
const kind: TransitionWhenKind =
|
|
@@ -872,7 +906,10 @@ export async function renderRoute(
|
|
|
872
906
|
outletPending: options.outletPending,
|
|
873
907
|
transitionGatedOff: gatedOff,
|
|
874
908
|
});
|
|
875
|
-
|
|
909
|
+
// A newer navigate() started meanwhile: this one commits nothing, as
|
|
910
|
+
// production's (browser/partial-update.ts) returns on its aborted signal.
|
|
911
|
+
if (handle.signal.aborted) return;
|
|
912
|
+
handle.complete(nextUrl);
|
|
876
913
|
eventController.setParams(match.params);
|
|
877
914
|
store.setCurrentUrl(nextUrl.href);
|
|
878
915
|
store.setSegmentIds(segments.map((s) => s.id));
|
|
@@ -892,9 +929,14 @@ export async function renderRoute(
|
|
|
892
929
|
// same-structure / fully-prefetched / optimistic hold lanes are not
|
|
893
930
|
// modeled: without transition() the commit stays urgent.
|
|
894
931
|
if (shouldStartViewTransition(segments, gatedOff)) {
|
|
895
|
-
commitInTransition(
|
|
896
|
-
|
|
897
|
-
|
|
932
|
+
commitInTransition(
|
|
933
|
+
store.loaders,
|
|
934
|
+
emit,
|
|
935
|
+
segments,
|
|
936
|
+
{ root, metadata },
|
|
937
|
+
kind,
|
|
938
|
+
["navigation"],
|
|
939
|
+
);
|
|
898
940
|
} else {
|
|
899
941
|
emit({ root, metadata });
|
|
900
942
|
}
|
|
@@ -1024,6 +1066,7 @@ export async function renderRoute(
|
|
|
1024
1066
|
navigate: (target, navOptions) =>
|
|
1025
1067
|
navigate(target, {
|
|
1026
1068
|
loaders: navOptions?.loaders,
|
|
1069
|
+
handles: navOptions?.handles,
|
|
1027
1070
|
transition: navOptions?.transition,
|
|
1028
1071
|
history:
|
|
1029
1072
|
navOptions?.state !== undefined || navOptions?.replace
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type RequestContext,
|
|
40
40
|
} from "../server/request-context.js";
|
|
41
41
|
import { createReverseFunction } from "../router/handler-context.js";
|
|
42
|
+
import { scopeReverse, type ReverseScope } from "../server/context.js";
|
|
42
43
|
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
43
44
|
import { defineRewrittenFrom } from "../server/rewrite-trail.js";
|
|
44
45
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
@@ -134,6 +135,13 @@ export interface RunLoaderOptions<TEnv = any> {
|
|
|
134
135
|
routeMap?: Record<string, string>;
|
|
135
136
|
/** Matched route name for scoped `.name` reverse resolution. */
|
|
136
137
|
routeName?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Include name prefix of the module whose `layout()` / `parallel()` /
|
|
140
|
+
* `cache()` declares the loader (`"account"`, or `""` for the root module).
|
|
141
|
+
* `ctx.reverse(".name")` then resolves in that module, as such a loader's
|
|
142
|
+
* does in production, instead of from `routeName`.
|
|
143
|
+
*/
|
|
144
|
+
declaredIn?: string;
|
|
137
145
|
/** HTTP method surfaced as `ctx.method`. Defaults to "GET". */
|
|
138
146
|
method?: string;
|
|
139
147
|
/** Request body surfaced as `ctx.body`. */
|
|
@@ -279,9 +287,21 @@ function runWithLoaderContext<R>(
|
|
|
279
287
|
const loaderSeeds = new Map<unknown, unknown>(opts.loaders ?? []);
|
|
280
288
|
let renderedResolved = false;
|
|
281
289
|
|
|
290
|
+
// A layout's / parallel's loader: `.name` resolves in its declaring module.
|
|
291
|
+
const reverseScope: ReverseScope | undefined =
|
|
292
|
+
opts.declaredIn === undefined
|
|
293
|
+
? undefined
|
|
294
|
+
: { prefix: opts.declaredIn || undefined, rootScoped: !opts.declaredIn };
|
|
282
295
|
return runWithRequestContext(reqCtx, () => {
|
|
283
296
|
const reverse = opts.routeMap
|
|
284
|
-
?
|
|
297
|
+
? scopeReverse(
|
|
298
|
+
createReverseFunction(
|
|
299
|
+
opts.routeMap,
|
|
300
|
+
opts.routeName,
|
|
301
|
+
opts.params ?? {},
|
|
302
|
+
),
|
|
303
|
+
reverseScope,
|
|
304
|
+
)
|
|
285
305
|
: ((() => {
|
|
286
306
|
throw new Error(
|
|
287
307
|
"ctx.reverse() requires the `routeMap` option in runLoader(). " +
|
|
@@ -289,7 +309,10 @@ function runWithLoaderContext<R>(
|
|
|
289
309
|
);
|
|
290
310
|
}) as TestLoaderContext["reverse"]);
|
|
291
311
|
|
|
292
|
-
const loaderCtx: TestLoaderContext & {
|
|
312
|
+
const loaderCtx: TestLoaderContext & {
|
|
313
|
+
_routeName?: string;
|
|
314
|
+
_reverseScope?: ReverseScope;
|
|
315
|
+
} = {
|
|
293
316
|
params: opts.params ?? {},
|
|
294
317
|
routeParams: (opts.params ?? {}) as Record<string, string>,
|
|
295
318
|
request: reqCtx.request,
|
|
@@ -346,6 +369,7 @@ function runWithLoaderContext<R>(
|
|
|
346
369
|
formData: opts.formData,
|
|
347
370
|
reverse: reverse as TestLoaderContext["reverse"],
|
|
348
371
|
_routeName: reqCtx._routeName,
|
|
372
|
+
_reverseScope: reverseScope,
|
|
349
373
|
rendered:
|
|
350
374
|
opts.rendered !== undefined && opts.rendered !== false
|
|
351
375
|
? async () => {
|
|
@@ -106,6 +106,13 @@ export interface RunMiddlewareRewrite {
|
|
|
106
106
|
pathname: string;
|
|
107
107
|
search: string;
|
|
108
108
|
routeName?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Present, `true`, only when the rewrite was thrown with
|
|
111
|
+
* `.disallowMutations()`: production refuses it for a non-GET, non-HEAD
|
|
112
|
+
* request in global middleware, before the action runs, instead of moving
|
|
113
|
+
* the mutation to the target.
|
|
114
|
+
*/
|
|
115
|
+
mutationsDisallowed?: true;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
/**
|
|
@@ -240,6 +247,9 @@ export async function runMiddleware<TEnv = any>(
|
|
|
240
247
|
pathname: target.pathname,
|
|
241
248
|
search: target.search,
|
|
242
249
|
...("name" in error.target && { routeName: error.target.name }),
|
|
250
|
+
...(error.mutationsDisallowed && {
|
|
251
|
+
mutationsDisallowed: true as const,
|
|
252
|
+
}),
|
|
243
253
|
};
|
|
244
254
|
return new Response(null, {
|
|
245
255
|
status: ctx.res.status,
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* a re-implementation. Pass an array to test a chain in declaration order.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
evaluateRevalidation,
|
|
15
|
+
toShouldRevalidateFn,
|
|
16
|
+
} from "../router/revalidation.js";
|
|
14
17
|
import { runWithRequestContext } from "../server/request-context.js";
|
|
15
18
|
import type { ResolvedSegment, ShouldRevalidateFn } from "../types.js";
|
|
16
19
|
import {
|
|
@@ -81,12 +84,16 @@ export interface RunRevalidateOptions<TEnv = any> {
|
|
|
81
84
|
const DEFAULT_URL = "http://localhost/";
|
|
82
85
|
|
|
83
86
|
/**
|
|
84
|
-
* Run one revalidate()
|
|
85
|
-
* the router does for a request
|
|
86
|
-
*
|
|
87
|
+
* Run one revalidate() argument, or a chain in declaration order, the way
|
|
88
|
+
* the router does for a request: a predicate, or `true` / `false` as
|
|
89
|
+
* `revalidate()` takes them. Resolves to the final decision: true re-runs
|
|
90
|
+
* the segment, false keeps the client's copy.
|
|
87
91
|
*/
|
|
88
92
|
export async function runRevalidate<TEnv = any>(
|
|
89
|
-
fn:
|
|
93
|
+
fn:
|
|
94
|
+
| ShouldRevalidateFn<any, TEnv>
|
|
95
|
+
| boolean
|
|
96
|
+
| readonly (ShouldRevalidateFn<any, TEnv> | boolean)[],
|
|
90
97
|
opts: RunRevalidateOptions<TEnv> = {},
|
|
91
98
|
): Promise<boolean> {
|
|
92
99
|
const currentUrl = toURL(opts.currentUrl, new URL(DEFAULT_URL));
|
|
@@ -124,7 +131,7 @@ export async function runRevalidate<TEnv = any>(
|
|
|
124
131
|
nextUrl,
|
|
125
132
|
revalidations: fns.map((entry, index) => ({
|
|
126
133
|
name: `runRevalidate#${index}`,
|
|
127
|
-
fn: entry,
|
|
134
|
+
fn: toShouldRevalidateFn(entry),
|
|
128
135
|
})),
|
|
129
136
|
routeKey: opts.routeName ?? "",
|
|
130
137
|
context: (opts.context ?? ctx) as RevalidateArgs["context"],
|