@rangojs/router 0.28.0 → 0.29.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/bin/rango.js +1 -1
- package/dist/types/browser/action-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +6 -1
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/use-action.d.ts +7 -2
- package/dist/types/browser/react/use-handle.d.ts +19 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/segment-reconciler.d.ts +26 -0
- package/dist/types/browser/types.d.ts +22 -4
- package/dist/types/browser/wire-protocol.d.ts +1 -0
- package/dist/types/cache/tag-invalidation.d.ts +0 -10
- package/dist/types/router/error-handling.d.ts +7 -1
- package/dist/types/router/match-context.d.ts +2 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +3 -2
- package/dist/types/router/navigation-snapshot.d.ts +7 -0
- package/dist/types/router/segment-resolution/helpers.d.ts +19 -3
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +2 -0
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- package/dist/types/router/types.d.ts +1 -1
- package/dist/types/server/request-context.d.ts +30 -1
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/error-types.d.ts +27 -8
- package/dist/types/wire-params.d.ts +5 -0
- package/dist/vite/index.js +15 -19
- package/package.json +1 -1
- package/skills/client-urls/SKILL.md +4 -2
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +33 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +16 -5
- package/skills/intercept/SKILL.md +6 -0
- package/skills/observability/SKILL.md +30 -0
- package/skills/server-actions/SKILL.md +8 -1
- package/skills/testing/client-components.md +25 -18
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/event-controller.ts +32 -10
- package/src/browser/navigation-bridge.ts +45 -13
- package/src/browser/navigation-client.ts +2 -0
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- package/src/browser/react/use-action.ts +24 -48
- package/src/browser/react/use-handle.ts +22 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +92 -41
- package/src/browser/segment-reconciler.ts +43 -0
- package/src/browser/server-action-bridge.ts +35 -8
- package/src/browser/types.ts +23 -4
- package/src/browser/wire-protocol.ts +2 -0
- package/src/cache/cache-runtime.ts +28 -31
- package/src/cache/document-cache.ts +34 -37
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/tag-invalidation.ts +3 -18
- package/src/router/error-handling.ts +18 -6
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/match-api.ts +1 -0
- package/src/router/match-context.ts +3 -0
- package/src/router/match-middleware/background-revalidation.ts +5 -4
- package/src/router/match-middleware/cache-lookup.ts +5 -3
- package/src/router/navigation-snapshot.ts +10 -0
- package/src/router/segment-resolution/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/resolution-pass.ts +2 -0
- package/src/router/segment-resolution/revalidation.ts +57 -28
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- package/src/router/types.ts +1 -1
- package/src/router.ts +7 -4
- package/src/rsc/request-stages.ts +4 -2
- package/src/rsc/shell-capture.ts +63 -58
- package/src/server/request-context.ts +49 -0
- package/src/testing/dispatch.ts +3 -0
- package/src/testing/render-route.tsx +199 -13
- package/src/testing/serve-shell-request.ts +19 -10
- package/src/types/error-types.ts +27 -8
- package/src/wire-params.ts +5 -0
package/dist/bin/rango.js
CHANGED
|
@@ -1644,7 +1644,7 @@ var init_package = __esm({
|
|
|
1644
1644
|
"package.json"() {
|
|
1645
1645
|
package_default = {
|
|
1646
1646
|
name: "@rangojs/router",
|
|
1647
|
-
version: "0.
|
|
1647
|
+
version: "0.29.0",
|
|
1648
1648
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1649
1649
|
keywords: [
|
|
1650
1650
|
"react",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
*
|
|
21
21
|
* The drop runs the batch's follow-up: when an action of the batch did not
|
|
22
22
|
* land on the page on screen, the action bridge refetches that page once,
|
|
23
|
-
* whole
|
|
23
|
+
* whole (or whole above an action's error boundary on screen, which it
|
|
24
|
+
* holds), after the navigation the user started, unless an intercept is on
|
|
24
25
|
* screen (server-action-bridge.ts followUp; the rules are R1-R7 in
|
|
25
26
|
* docs/internal/execution-model.md).
|
|
26
27
|
*
|
|
@@ -59,6 +59,11 @@ export interface ActionEntry {
|
|
|
59
59
|
startedAt: number;
|
|
60
60
|
/** Whether action processing is complete (may still be streaming) */
|
|
61
61
|
completed?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Set when a later call started (see startAction): "id" hides this call
|
|
64
|
+
* from every reader, "name" from readers that match by export name.
|
|
65
|
+
*/
|
|
66
|
+
supersededBy?: "id" | "name";
|
|
62
67
|
}
|
|
63
68
|
/**
|
|
64
69
|
* Derived navigation state (computed from source of truth)
|
|
@@ -70,7 +75,7 @@ export interface DerivedNavigationState {
|
|
|
70
75
|
isStreaming: boolean;
|
|
71
76
|
/** Whether a navigation is active (fetching or streaming, before commit) */
|
|
72
77
|
isNavigating: boolean;
|
|
73
|
-
/**
|
|
78
|
+
/** The location of the last page flushed to React (useNavigation keeps the page on screen) */
|
|
74
79
|
location: NavigationLocation;
|
|
75
80
|
/** URL being navigated to (null if idle) */
|
|
76
81
|
pendingUrl: string | null;
|
|
@@ -112,18 +112,29 @@ export type UpdateMode = {
|
|
|
112
112
|
* transition({ when }) sees kind "revalidate", not "replace".
|
|
113
113
|
*/
|
|
114
114
|
refresh?: boolean;
|
|
115
|
+
/** The cross-tab refresh's held boundary (see `action`). */
|
|
116
|
+
boundary?: string;
|
|
115
117
|
} | {
|
|
116
118
|
type: "leave-intercept";
|
|
117
119
|
interceptSourceUrl?: string;
|
|
120
|
+
boundary?: never;
|
|
118
121
|
} | {
|
|
119
122
|
type: "stale-revalidation";
|
|
120
123
|
interceptSourceUrl?: string;
|
|
124
|
+
/** Back/Forward's held boundary (see `action`). */
|
|
125
|
+
boundary?: string;
|
|
121
126
|
} | {
|
|
122
127
|
type: "action";
|
|
123
128
|
interceptSourceUrl?: string;
|
|
124
129
|
actionId?: string;
|
|
125
130
|
/** The action's FormData and result, for transition({ when }) `action`. */
|
|
126
131
|
action?: TransitionWhenActionInput;
|
|
132
|
+
/**
|
|
133
|
+
* An action's error boundary the page shows (actionBoundaryAt): the
|
|
134
|
+
* server renders above it, its cached segment stays, and the page
|
|
135
|
+
* holds only the ids above it.
|
|
136
|
+
*/
|
|
137
|
+
boundary?: string;
|
|
127
138
|
};
|
|
128
139
|
/**
|
|
129
140
|
* Type for the fetchPartialUpdate function. In `action` mode it resolves with
|
|
@@ -18,14 +18,19 @@ export type ServerActionFunction = ((...args: any[]) => Promise<any>) & {
|
|
|
18
18
|
* Features:
|
|
19
19
|
* - Tracks action lifecycle: idle → loading → streaming → idle
|
|
20
20
|
* - Captures result/error locally (React handles cleanup)
|
|
21
|
-
* - If
|
|
21
|
+
* - If the same action fires several times, reports the latest call to
|
|
22
|
+
* start. An earlier call that settles later changes nothing it reports;
|
|
23
|
+
* a latest call that was aborted reports idle with nothing. One call at a
|
|
24
|
+
* time, not one per argument: a reader filtering on `payload` cannot track
|
|
25
|
+
* an earlier call once a later one starts.
|
|
22
26
|
* - Supports selector pattern like useNavigation
|
|
23
27
|
*
|
|
24
28
|
* Matching behavior:
|
|
25
29
|
* - **Function reference**: Uses full $$id for exact matching. This is precise
|
|
26
30
|
* and distinguishes between actions with the same name in different files.
|
|
27
31
|
* - **String**: Matches by suffix (action name after #). This is convenient
|
|
28
|
-
* but may be ambiguous if multiple files export the same action name
|
|
32
|
+
* but may be ambiguous if multiple files export the same action name: it
|
|
33
|
+
* follows the latest call of any action with that export name.
|
|
29
34
|
*
|
|
30
35
|
* @param action - Either a server action function or a string action name.
|
|
31
36
|
* - **Function**: Must be directly imported in the client component.
|
|
@@ -21,21 +21,24 @@ import type { RouteStateSignal } from "../event-controller.js";
|
|
|
21
21
|
export declare function useHandle<T, A>(handle: Handle<T, A>): Rango.FlightSerialize<A>;
|
|
22
22
|
export declare function useHandle<T, A, S>(handle: Handle<T, A>, selector: (data: Rango.FlightSerialize<A>) => S): S;
|
|
23
23
|
/**
|
|
24
|
-
* Route state the controller's handle notifications drive
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* priority, it renders in the page's lane, and the page's effects read it.
|
|
30
|
-
* A page in a transition (signal "anchor") does not: until it commits
|
|
24
|
+
* Route state the controller's handle notifications drive commits with the
|
|
25
|
+
* page it belongs to. NavigationProvider flushes the controller inside every
|
|
26
|
+
* payload update, with signal "anchor" for a page in a transition and
|
|
27
|
+
* "release" for an urgent one, so the page's own change is set in the page's
|
|
28
|
+
* lane and the page's effects read it. From the anchor until the page commits
|
|
31
29
|
* (releaseRouteState() from the provider's layout effect, signal "release"),
|
|
32
|
-
* a
|
|
33
|
-
* page's own commit
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
30
|
+
* a later change waits and is applied in the commit's task, right after the
|
|
31
|
+
* page's own commit: set in a lane of its own, it would commit over the page
|
|
32
|
+
* being left (#1165).
|
|
33
|
+
*
|
|
34
|
+
* `waitForRelease`, set by useHandle only: the anchor's own change waits too,
|
|
35
|
+
* so a handle value lands right after its page's commit, never in its lane. A
|
|
36
|
+
* handle value can carry a promise that suspends, and in the page's lane one
|
|
37
|
+
* that suspends in a revealed <Suspense> would hold the page. Nothing
|
|
38
|
+
* useSegments reads can suspend, so it leaves it off and its path and
|
|
39
|
+
* location commit with their page.
|
|
40
|
+
*
|
|
41
|
+
* A value shallow-equal to the last one sets nothing, so a reader renders only
|
|
42
|
+
* when its value moves.
|
|
40
43
|
*/
|
|
41
|
-
export declare function useCommitAnchoredState<T>(init: () => T): [T, (value: T, signal?: RouteStateSignal) => void];
|
|
44
|
+
export declare function useCommitAnchoredState<T>(init: () => T, waitForRelease?: boolean): [T, (value: T, signal?: RouteStateSignal) => void];
|
|
@@ -14,8 +14,11 @@ export interface LinkStatus {
|
|
|
14
14
|
/**
|
|
15
15
|
* Hook to track the pending state of a Link component
|
|
16
16
|
*
|
|
17
|
-
* Must be used inside a Link component. Returns `{ pending: true }`
|
|
18
|
-
*
|
|
17
|
+
* Must be used inside a Link component. Returns `{ pending: true }` while a
|
|
18
|
+
* navigation to the link's destination is in flight, until the page it
|
|
19
|
+
* brings commits: under transition() while React holds that page, and
|
|
20
|
+
* through a server redirect until the target page commits. A newer
|
|
21
|
+
* navigation ends it at once.
|
|
19
22
|
*
|
|
20
23
|
* Useful for showing inline loading indicators on individual links.
|
|
21
24
|
*
|
|
@@ -71,3 +71,29 @@ export declare function reconcileSegments(input: ReconcileInput): ReconcileResul
|
|
|
71
71
|
* other segment merges as an action's does (mergeCached).
|
|
72
72
|
*/
|
|
73
73
|
export declare function reconcileErrorSegments(cachedSegments: ResolvedSegment[], errorSegments: ResolvedSegment[]): ReconcileResult;
|
|
74
|
+
/**
|
|
75
|
+
* The ids a page holds once an action's error boundary `boundary` shows: the
|
|
76
|
+
* ones above it. Nothing at or below the boundary ran after the action's
|
|
77
|
+
* write (its response and the follow-up cut there), so the next request must
|
|
78
|
+
* not hold it. ShortCodes nest, so those ids start with the boundary's: the
|
|
79
|
+
* boundary, its loaders (`${id}D…`), its slots and every entry under it. A
|
|
80
|
+
* sibling with a longer index (`…R10` beside `…R1`) is kept.
|
|
81
|
+
*/
|
|
82
|
+
export declare function idsAboveBoundary(ids: string[], boundary: string | undefined): string[];
|
|
83
|
+
/**
|
|
84
|
+
* The error segment of every action error boundary committed, by identity
|
|
85
|
+
* (server-action-bridge.ts adds and deletes). An entry shows one while its
|
|
86
|
+
* cached list still holds that object: a later commit on the entry replaces
|
|
87
|
+
* the list, and a refetch that holds the boundary keeps it. Weak, so a
|
|
88
|
+
* replaced boundary is not kept alive. Two routers never share a segment
|
|
89
|
+
* object. No hook reads it.
|
|
90
|
+
*/
|
|
91
|
+
export declare const actionBoundaries: WeakSet<ResolvedSegment>;
|
|
92
|
+
/**
|
|
93
|
+
* The index of the deepest action error boundary in `segments`, -1 for none.
|
|
94
|
+
* It is that action's outcome, which no GET reproduces: a background refetch
|
|
95
|
+
* of the page (the follow-up, Back/Forward's stale revalidation, the
|
|
96
|
+
* cross-tab refresh) holds it and renders what is above it. At 0 nothing is
|
|
97
|
+
* above it.
|
|
98
|
+
*/
|
|
99
|
+
export declare function actionBoundaryAt(segments: readonly ResolvedSegment[]): number;
|
|
@@ -191,11 +191,24 @@ export interface InflightAction {
|
|
|
191
191
|
* @internal This type is an implementation detail. Use PublicNavigationState instead.
|
|
192
192
|
*/
|
|
193
193
|
export interface NavigationState {
|
|
194
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Lifecycle state: `"loading"` while a navigation is in flight and while a
|
|
196
|
+
* server action runs, `"idle"` otherwise
|
|
197
|
+
*/
|
|
195
198
|
state: "idle" | "loading";
|
|
196
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Whether RSC data is currently streaming: a navigation's response, or a
|
|
201
|
+
* server action's response (initial load, navigation or action)
|
|
202
|
+
*/
|
|
197
203
|
isStreaming: boolean;
|
|
198
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* True while a navigation is in flight (fetching or streaming, before its
|
|
206
|
+
* commit); false during a server action, whose `state` is still `"loading"`.
|
|
207
|
+
* Drive a navigation progress bar from this and an action spinner from
|
|
208
|
+
* `state`.
|
|
209
|
+
*/
|
|
210
|
+
isNavigating: boolean;
|
|
211
|
+
/** The page on screen; moves when the next page shows (`pendingUrl` is the page being fetched) */
|
|
199
212
|
location: NavigationLocation;
|
|
200
213
|
/** URL being navigated to (null when idle) */
|
|
201
214
|
pendingUrl: string | null;
|
|
@@ -450,6 +463,8 @@ export interface NavigateOptionsInternal extends Omit<NavigateOptions, "state">
|
|
|
450
463
|
_skipCache?: boolean;
|
|
451
464
|
/** Server redirects already followed by this navigation chain (0 for a user navigation) */
|
|
452
465
|
_redirectHops?: number;
|
|
466
|
+
/** The URL a redirected navigation chain started from: a link to it stays pending (useLinkStatus) */
|
|
467
|
+
_redirectFrom?: string;
|
|
453
468
|
}
|
|
454
469
|
/**
|
|
455
470
|
* Options for useRouter push/replace methods.
|
|
@@ -603,6 +618,8 @@ export interface FetchPartialOptions {
|
|
|
603
618
|
signal?: AbortSignal;
|
|
604
619
|
/** If true, this is a stale cache revalidation request - server should force revalidators */
|
|
605
620
|
staleRevalidation?: boolean;
|
|
621
|
+
/** Sent as `_rsc_boundary` (wire-params.ts WIRE.boundary). */
|
|
622
|
+
boundary?: string;
|
|
606
623
|
interceptSourceUrl?: string;
|
|
607
624
|
/** RSC version for cache invalidation detection */
|
|
608
625
|
version?: string;
|
|
@@ -667,7 +684,8 @@ export interface ServerActionBridgeConfig {
|
|
|
667
684
|
*/
|
|
668
685
|
export interface NavigationBridge {
|
|
669
686
|
navigate(url: string, options?: NavigateOptionsInternal): Promise<void>;
|
|
670
|
-
|
|
687
|
+
/** `crossTab`: another tab's write; an action's error boundary stays. */
|
|
688
|
+
refresh(crossTab?: boolean): Promise<void>;
|
|
671
689
|
/** `event`: the popstate event; a traversal the browser animated itself (hasUAVisualTransition) is not animated again. */
|
|
672
690
|
handlePopstate(event?: PopStateEvent): Promise<void>;
|
|
673
691
|
registerLinkInterception(): () => void;
|
|
@@ -35,19 +35,9 @@
|
|
|
35
35
|
* request, is neither joined nor written (#973, #977): see
|
|
36
36
|
* predatesInvalidation, and docs/design/caching.md "Read-your-own-writes".
|
|
37
37
|
*/
|
|
38
|
-
import { type RequestContext } from "../server/request-context.js";
|
|
39
38
|
import type { SegmentCacheStore } from "./types.js";
|
|
40
39
|
import { type ExecutionStart } from "./invalidation-order.js";
|
|
41
40
|
export { executionStart, invalidatedSince, markResponseStart, type ExecutionStart, } from "./invalidation-order.js";
|
|
42
|
-
/**
|
|
43
|
-
* Run a stale entry's background refresh on the visitor's request context: a
|
|
44
|
-
* "use cache" function's (cache-runtime.ts) or a cached loader's
|
|
45
|
-
* (loader-cache.ts). No visitor made the invalidations in it, so they set no
|
|
46
|
-
* fresh-reads cookie, as in a stale cache() HIT's re-render or a document
|
|
47
|
-
* cache entry's re-run, whose passes take a sink's no-op writer
|
|
48
|
-
* (server/render-pass.ts).
|
|
49
|
-
*/
|
|
50
|
-
export declare function runAsBackgroundRefresh<T>(ctx: RequestContext, fn: () => T): T;
|
|
51
41
|
/**
|
|
52
42
|
* The write gate of every cache execution (#977): whether one of `tags` was
|
|
53
43
|
* invalidated after the execution that produced the value started.
|
|
@@ -25,7 +25,7 @@ export interface InvokeOnErrorContext<TEnv = any> {
|
|
|
25
25
|
isPartial?: boolean;
|
|
26
26
|
handledByBoundary?: boolean;
|
|
27
27
|
metadata?: Record<string, unknown>;
|
|
28
|
-
/**
|
|
28
|
+
/** performance.now() at request entry; the request context's by default. */
|
|
29
29
|
requestStartTime?: number;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
@@ -35,6 +35,12 @@ export interface InvokeOnErrorContext<TEnv = any> {
|
|
|
35
35
|
* This is a shared utility used by both the router and RSC handler to ensure
|
|
36
36
|
* consistent error callback behavior across the codebase.
|
|
37
37
|
*
|
|
38
|
+
* `background` is set inside runAsBackgroundWork (server/request-context.ts),
|
|
39
|
+
* and `duration` and `env` come from the request context on the ALS
|
|
40
|
+
* (`_handlerStart`, `env`) when the call site leaves them out, so no call
|
|
41
|
+
* site has to know. A Prerender pass's ctx.env throws without buildEnv, so
|
|
42
|
+
* the walk never reads it eagerly.
|
|
43
|
+
*
|
|
38
44
|
* @param onError - The onError callback to invoke (may be undefined)
|
|
39
45
|
* @param error - The error that occurred
|
|
40
46
|
* @param phase - The phase where the error occurred
|
|
@@ -126,6 +126,8 @@ export interface MatchContext<TEnv = any> {
|
|
|
126
126
|
clientSegmentIds: string[];
|
|
127
127
|
clientSegmentSet: Set<string>;
|
|
128
128
|
stale: boolean;
|
|
129
|
+
/** NavigationSnapshot.heldBoundary: the walk stops above it. */
|
|
130
|
+
heldBoundary?: string;
|
|
129
131
|
prevUrl: URL;
|
|
130
132
|
prevParams: Record<string, string>;
|
|
131
133
|
prevMatch: RouteMatchResult | null;
|
|
@@ -125,8 +125,9 @@ import { type RequestContext } from "../../server/request-context.js";
|
|
|
125
125
|
* each field and why. The DSL store below has `metrics` unset too (track()
|
|
126
126
|
* reads that one), so nothing lands on the foreground's perf timeline;
|
|
127
127
|
* ctx.Store.run closes over ctx.Store, so the derived store needs its own run.
|
|
128
|
-
*
|
|
129
|
-
* task on workerd loses
|
|
128
|
+
* runAsBackgroundWork also re-establishes the request ALS, which a waitUntil
|
|
129
|
+
* task on workerd loses, and marks the pass's onError reports `background`;
|
|
130
|
+
* the DSL store is a different ALS (build context).
|
|
130
131
|
*/
|
|
131
132
|
export declare function rerenderAndCacheRoute<TEnv>(ctx: MatchContext<TEnv>, requestCtx: RequestContext<TEnv>, cacheScope: CacheScope, routerCtx: RouterContext<TEnv>): Promise<number>;
|
|
132
133
|
/**
|
|
@@ -11,6 +11,13 @@ export interface NavigationSnapshot {
|
|
|
11
11
|
clientSegmentSet: Set<string>;
|
|
12
12
|
filteredSegmentIds: string[];
|
|
13
13
|
stale: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The id of an action's error boundary on screen that a stale request holds
|
|
16
|
+
* (`_rsc_boundary`, the action follow-up): read only beside `stale`, so it
|
|
17
|
+
* has every stale guarantee (a live render, no route-cache or document-cache
|
|
18
|
+
* read or store, no PPR replay).
|
|
19
|
+
*/
|
|
20
|
+
heldBoundary?: string;
|
|
14
21
|
isSameRouteNavigation: boolean;
|
|
15
22
|
effectiveFromUrl: URL;
|
|
16
23
|
effectiveFromMatch: RouteMatchResult | null;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ReactNode } from "react";
|
|
11
11
|
import type { EntryData } from "../../server/context";
|
|
12
|
-
import type { ResolvedSegment, HandlerContext } from "../../types";
|
|
12
|
+
import type { ResolvedSegment, ErrorInfo, HandlerContext } from "../../types";
|
|
13
13
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
14
14
|
import type { TelemetrySink } from "../telemetry.js";
|
|
15
15
|
/** The errorContext shape wrapLoaderPromise expects as its 5th argument. */
|
|
@@ -70,16 +70,32 @@ export interface ErrorReportContext {
|
|
|
70
70
|
routeKey?: string;
|
|
71
71
|
env?: any;
|
|
72
72
|
isPartial?: boolean;
|
|
73
|
-
requestStartTime?: number;
|
|
74
73
|
telemetry?: TelemetrySink;
|
|
75
74
|
}
|
|
75
|
+
type Thrower = {
|
|
76
|
+
id: string;
|
|
77
|
+
type: ErrorInfo["segmentType"];
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Note that `id` threw `error`, for an error its owning entry's boundary
|
|
81
|
+
* catches (a parallel slot's sync throw, an orphan layout's): onError names
|
|
82
|
+
* the segment that threw, as a streamed slot's rejection does. Kept on the
|
|
83
|
+
* request context, never the module: a reused error object (a constant, a
|
|
84
|
+
* memoized rejection) can be thrown by another request, or by a Prerender
|
|
85
|
+
* pass that rethrows before catchSegmentError reads it. Returns `error`, to
|
|
86
|
+
* rethrow.
|
|
87
|
+
*/
|
|
88
|
+
export declare function noteThrower(error: unknown, id: string, type: ErrorInfo["segmentType"]): unknown;
|
|
76
89
|
/**
|
|
77
90
|
* Handle a caught error during segment resolution by creating an
|
|
78
91
|
* error or not-found segment with the nearest boundary.
|
|
79
92
|
*
|
|
80
93
|
* Called by resolveWithErrorBoundary to produce error/notFound segments.
|
|
94
|
+
* `entry` is where the boundary is looked up; `thrower` names the segment
|
|
95
|
+
* that threw when it is not `entry` (an intercept's slot, under its declaring
|
|
96
|
+
* entry), for the report, the server print and the client's ErrorInfo.
|
|
81
97
|
*/
|
|
82
|
-
export declare function catchSegmentError<TEnv>(error: unknown, entry: EntryData, params: Record<string, string>, deps: SegmentResolutionDeps<TEnv>, report?: ErrorReportContext, pathname?: string): ResolvedSegment;
|
|
98
|
+
export declare function catchSegmentError<TEnv>(error: unknown, entry: EntryData, params: Record<string, string>, deps: SegmentResolutionDeps<TEnv>, report?: ErrorReportContext, pathname?: string, thrower?: Thrower): ResolvedSegment;
|
|
83
99
|
/**
|
|
84
100
|
* Generic error boundary wrapper for segment resolution.
|
|
85
101
|
* Catches non-Response errors and produces an error/notFound segment
|
|
@@ -46,6 +46,8 @@ export interface ResolutionPassInput<TEnv = any> {
|
|
|
46
46
|
readonly routeKey: string;
|
|
47
47
|
readonly actionContext?: ActionContext;
|
|
48
48
|
readonly stale?: boolean;
|
|
49
|
+
/** NavigationSnapshot.heldBoundary: the walk cuts there, sending no segment. */
|
|
50
|
+
readonly heldBoundary?: string;
|
|
49
51
|
/**
|
|
50
52
|
* No loader is started and no loader segment is emitted: build time
|
|
51
53
|
* (prerender-match.ts) and the background re-render of a route record
|
|
@@ -32,6 +32,8 @@ export interface StreamedHandlerOptions<TEnv> {
|
|
|
32
32
|
context: HandlerContext<any, TEnv>;
|
|
33
33
|
routeKey?: string;
|
|
34
34
|
params: Record<string, string>;
|
|
35
|
+
/** onError's `isPartial`, as the walk reports a sync handler's error. */
|
|
36
|
+
isPartial: true | undefined;
|
|
35
37
|
/** False where the rejection must reach the caller (prerender, intercept background re-render). */
|
|
36
38
|
recover: boolean;
|
|
37
39
|
}
|
|
@@ -34,13 +34,13 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
34
34
|
params?: Record<string, string>;
|
|
35
35
|
env?: TEnv;
|
|
36
36
|
isPartial?: boolean;
|
|
37
|
-
requestStartTime?: number;
|
|
38
37
|
},
|
|
39
38
|
/** An `ssr: false` loader on a document or capture render. */
|
|
40
39
|
useDefaultErrorBoundary?: boolean) => Promise<LoaderDataResult<T>>;
|
|
41
40
|
trackHandler: <T>(promise: Promise<T>, errorContext?: {
|
|
42
41
|
segmentId?: string;
|
|
43
42
|
segmentType?: string;
|
|
43
|
+
isPartial?: boolean;
|
|
44
44
|
}) => Promise<T>;
|
|
45
45
|
findNearestErrorBoundary: (entry: EntryData | null) => ReactNode | ErrorBoundaryHandler | null;
|
|
46
46
|
findNearestNotFoundBoundary: (entry: EntryData | null) => ReactNode | NotFoundBoundaryHandler | null;
|
|
@@ -683,6 +683,15 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
683
683
|
_renderBarrierGuardClosed?: boolean;
|
|
684
684
|
/** @internal Per-request error dedup set for onError reporting */
|
|
685
685
|
_reportedErrors: WeakSet<object>;
|
|
686
|
+
/**
|
|
687
|
+
* @internal The segment a parallel slot's or orphan layout's error came
|
|
688
|
+
* from, for the owning entry's catch (segment-resolution/helpers.ts
|
|
689
|
+
* noteThrower). Per request: an error object can be thrown by several.
|
|
690
|
+
*/
|
|
691
|
+
_throwers?: WeakMap<object, {
|
|
692
|
+
id: string;
|
|
693
|
+
type: string;
|
|
694
|
+
}>;
|
|
686
695
|
/**
|
|
687
696
|
* @internal The errors this request printed to the server console, so a
|
|
688
697
|
* failure prints once where it originated (server/print-server-error.ts). A derived context shares it through the prototype.
|
|
@@ -786,7 +795,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
786
795
|
* This is the type exported to library consumers. Internal code should
|
|
787
796
|
* use the full RequestContext interface directly.
|
|
788
797
|
*/
|
|
789
|
-
export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_prerender" | "_onDemandProducer" | "_searchParamsFilter" | "_shellCaptureState" | "_shellImplicitCache" | "_shellLoaderSeed" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_prerenderWarm" | "_runAtRequestEntry" | "_shellFragmentPayload" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_payloadDeferred" | "_requestKind" | "_payloadVisitorLocation" | "_locationState" | "_routeName" | "_prevRouteKey" | "_visitorMoved" | "_inActionRevalidation" | "_actionRan" | "_reportedErrors" | "_printedErrors" | "_renderErrors" | "_recoveredHandlerErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_handlerTiming" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
|
|
798
|
+
export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_prerender" | "_onDemandProducer" | "_searchParamsFilter" | "_shellCaptureState" | "_shellImplicitCache" | "_shellLoaderSeed" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_prerenderWarm" | "_runAtRequestEntry" | "_shellFragmentPayload" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_payloadDeferred" | "_requestKind" | "_payloadVisitorLocation" | "_locationState" | "_routeName" | "_prevRouteKey" | "_visitorMoved" | "_inActionRevalidation" | "_actionRan" | "_reportedErrors" | "_throwers" | "_printedErrors" | "_renderErrors" | "_recoveredHandlerErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_handlerTiming" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
|
|
790
799
|
/**
|
|
791
800
|
* Marker for a waitUntil-scheduled fn whose task promise must NOT enter
|
|
792
801
|
* _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
|
|
@@ -816,6 +825,26 @@ export declare function captureRequestEntryContext(): (<T>(fn: () => T) => T) |
|
|
|
816
825
|
* Used by the RSC handler to provide context to server actions
|
|
817
826
|
*/
|
|
818
827
|
export declare function runWithRequestContext<TEnv, T>(context: RequestContext<TEnv>, fn: () => T): T;
|
|
828
|
+
/**
|
|
829
|
+
* Run work whose output goes only to a cache, never to the response of the
|
|
830
|
+
* request that started it, on `context`: a stale entry's refresh (a "use
|
|
831
|
+
* cache" function, a cached loader, a cache() record, a document), a
|
|
832
|
+
* navigation's proactive cache() write, a PPR shell capture. No visitor made
|
|
833
|
+
* the invalidations in it, so they set no fresh-reads cookie
|
|
834
|
+
* (cache/tag-invalidation.ts), and onError marks every error reported inside
|
|
835
|
+
* it `background` (router/error-handling.ts invokeOnError). Its failure is
|
|
836
|
+
* reported inside it too: a catch outside runs on the visitor's frame.
|
|
837
|
+
*/
|
|
838
|
+
export declare function runAsBackgroundWork<TEnv, T>(context: RequestContext<TEnv>, fn: () => T): T;
|
|
839
|
+
/** Whether the caller runs inside runAsBackgroundWork. */
|
|
840
|
+
export declare function isBackgroundWork(): boolean;
|
|
841
|
+
/**
|
|
842
|
+
* The set onError's dedupe records a reported error in. Background work keeps
|
|
843
|
+
* its own: an error object both the visitor's render and background work
|
|
844
|
+
* reach (a memoized rejection, a thrown constant) is reported by each, so a
|
|
845
|
+
* background report never takes the place of the visitor's.
|
|
846
|
+
*/
|
|
847
|
+
export declare function reportedErrorsOf(context: RequestContext<any>): WeakSet<object>;
|
|
819
848
|
/**
|
|
820
849
|
* Get the current request context
|
|
821
850
|
* Throws if called outside of a request context
|
|
@@ -26,24 +26,30 @@
|
|
|
26
26
|
* - navigate() drives the navigation lifecycle as a click does:
|
|
27
27
|
* useNavigation() reads loading from the call and idle with the commit,
|
|
28
28
|
* and useLinkStatus().pending is true for a link to the destination until
|
|
29
|
-
* the commit
|
|
29
|
+
* the commit, which React holds with it. A `transition` spec plus a pending
|
|
30
30
|
* Promise seeded via navigate(url, { loaders }) commits through
|
|
31
31
|
* production's commitInTransition (browser/partial-update.ts), which React
|
|
32
32
|
* holds: useNavigation() stays loading and the held reader's
|
|
33
33
|
* useLoader().isLoading reads true until the data lands. Without
|
|
34
34
|
* `transition` the commit is urgent (production's same-structure hold is
|
|
35
|
-
* not modeled).
|
|
35
|
+
* not modeled). navigate(url, { streamUntil }) gives the navigation a
|
|
36
|
+
* response stream that stays open past the commit (isStreaming).
|
|
37
|
+
* useAction().state stays idle: no action runs.
|
|
36
38
|
* - A spec's transition({ when }) decides every commit with production's
|
|
37
39
|
* browser decision: navigate() and useRouter().push model kind "push",
|
|
38
|
-
* useRouter().replace kind "replace", refresh() kind "revalidate"
|
|
39
|
-
*
|
|
40
|
-
* e2e territory.
|
|
40
|
+
* useRouter().replace kind "replace", refresh() kind "revalidate", and a
|
|
41
|
+
* back/forward that restores a page kind "pop". Action and optimistic
|
|
42
|
+
* clientUrls() swap decisions are e2e territory.
|
|
41
43
|
* - Location state reaches readers the way it does in production:
|
|
42
44
|
* NavigationProvider holds the committed entry's state next to the
|
|
43
45
|
* payload, so a navigation whose loader the test holds keeps the entry
|
|
44
46
|
* being left on screen with its own state. A `popstate` event is a
|
|
45
|
-
* back/forward onto the entry history.state holds
|
|
46
|
-
*
|
|
47
|
+
* back/forward onto the entry history.state holds. When it is the
|
|
48
|
+
* mounted entry or one a navigate() wrote, history still holds the state
|
|
49
|
+
* written there, and another page is on screen, its page is restored
|
|
50
|
+
* from the history cache, with the handle values it had, as production's
|
|
51
|
+
* restore is; otherwise the tree keeps its location. A stale entry's
|
|
52
|
+
* revalidation, a restore that fetches, a server action's state, a
|
|
47
53
|
* redirect's and an intercept's are e2e territory.
|
|
48
54
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
49
55
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
@@ -361,8 +367,37 @@ export interface TestRouterHandle {
|
|
|
361
367
|
* `handles`). They arrive on the response's handle stream, so `useHandle`
|
|
362
368
|
* readers take them with the page's commit: while React holds the page,
|
|
363
369
|
* readers keep the values of the page being left.
|
|
370
|
+
*
|
|
371
|
+
* `options.streamUntil` gives the navigation a response stream, as a real
|
|
372
|
+
* response has: it opens in a task after the call, before the commit (the
|
|
373
|
+
* response arrives, and `useNavigation().pendingUrl` clears), and stays open
|
|
374
|
+
* until the promise settles (a page that streams a tail past its commit:
|
|
375
|
+
* `isStreaming` reads true until then). Without it the navigation has no
|
|
376
|
+
* stream. A navigation that a newer one or a back/forward aborts before its
|
|
377
|
+
* response arrives opens none and commits nothing; one aborted after its
|
|
378
|
+
* response arrived stops counting as streaming at the abort, as in
|
|
379
|
+
* production, where its response streams on and is ignored.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* const tail = deferred();
|
|
383
|
+
* await router.navigate("/products/2", { streamUntil: tail.promise });
|
|
384
|
+
* // useNavigation(): idle, isStreaming true
|
|
385
|
+
* await act(async () => tail.resolve());
|
|
386
|
+
* // useNavigation(): idle, isStreaming false
|
|
387
|
+
*
|
|
388
|
+
* A navigation started while React holds a page goes inside
|
|
389
|
+
* `await act(async () => ...)`. Its first update makes React retry the held
|
|
390
|
+
* page, which suspends again; a synchronous act() scope drops that retry
|
|
391
|
+
* and React renders no later update of the same priority.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* await act(async () => {
|
|
395
|
+
* await router.navigate("/products/3", { transition: false });
|
|
396
|
+
* });
|
|
364
397
|
*/
|
|
365
|
-
navigate(url: string, options?: Pick<RenderRouteOptions, "loaders" | "handles"> & Pick<NavigateOptions, "state" | "replace" | "transition">
|
|
398
|
+
navigate(url: string, options?: Pick<RenderRouteOptions, "loaders" | "handles"> & Pick<NavigateOptions, "state" | "replace" | "transition"> & {
|
|
399
|
+
streamUntil?: Promise<unknown>;
|
|
400
|
+
}): Promise<void>;
|
|
366
401
|
/**
|
|
367
402
|
* Re-render the current location, as router.refresh() does: a spec's
|
|
368
403
|
* transition({ when }) decides it with kind "revalidate" (`to` is `from`).
|
|
@@ -81,7 +81,13 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
81
81
|
* and segments take an action's revalidation defaults; it renders live, as
|
|
82
82
|
* an action does, so neither the route cache nor the document cache answers
|
|
83
83
|
* or stores it, and a `ppr` route's `replayStatus` is
|
|
84
|
-
* `{ outcome: "BYPASS", reason: "stale" }`.
|
|
84
|
+
* `{ outcome: "BYPASS", reason: "stale" }`. `boundary` names the error
|
|
85
|
+
* segment of a thrown action's boundary the page at `from` shows
|
|
86
|
+
* (`_rsc_boundary`), as the follow-up an earlier action owes that page
|
|
87
|
+
* sends it, with `stale: true` and no `segments`: the response renders
|
|
88
|
+
* every segment above the boundary and nothing at or below it, claims the
|
|
89
|
+
* boundary in `matched` and sends no segment for it. The server reads it
|
|
90
|
+
* only beside `stale`.
|
|
85
91
|
*/
|
|
86
92
|
partial?: true | {
|
|
87
93
|
from?: string;
|
|
@@ -91,6 +97,7 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
91
97
|
fill?: boolean;
|
|
92
98
|
interceptSource?: string;
|
|
93
99
|
stale?: boolean;
|
|
100
|
+
boundary?: string;
|
|
94
101
|
};
|
|
95
102
|
/**
|
|
96
103
|
* POST this form as a submit before hydration: the no-JS request the
|
|
@@ -67,19 +67,38 @@ export interface OnErrorContext<TEnv = any> {
|
|
|
67
67
|
actionId?: string;
|
|
68
68
|
/** Environment/bindings (platform context) */
|
|
69
69
|
env?: TEnv;
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Duration from request start to error (milliseconds). For a `background`
|
|
72
|
+
* error it counts from the start of the request that started the work, so
|
|
73
|
+
* it includes that response and any wait before the work ran.
|
|
74
|
+
*/
|
|
71
75
|
duration?: number;
|
|
72
76
|
/**
|
|
73
77
|
* Whether this is a partial/navigation request, on an error the segment
|
|
74
|
-
* walk catches (a handler that throws, a
|
|
75
|
-
* for a navigation, unset for a
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* either render; the `routing` phase reports `false`.
|
|
78
|
+
* walk catches (a handler that throws, a streamed `loading()` handler that
|
|
79
|
+
* rejects, a DSL loader that rejects): `true` for a navigation, unset for a
|
|
80
|
+
* document. A `cache()` record's refresh or proactive write reports the
|
|
81
|
+
* request that started it; a PPR shell capture renders a document. The
|
|
82
|
+
* `routing` phase reports `false`.
|
|
80
83
|
*/
|
|
81
84
|
isPartial?: boolean;
|
|
82
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* `true` when the error came from work whose output goes to a cache and
|
|
87
|
+
* never to the response of the request that started it: a stale `cache()`
|
|
88
|
+
* record's refresh, the write of a navigation that kept a layout mounted, a
|
|
89
|
+
* stale document cache entry's re-run, a PPR shell capture, or a stale
|
|
90
|
+
* `"use cache"` function's or cached loader's refresh, the work's own
|
|
91
|
+
* failure included. `request` and `url` are the starting request's (a
|
|
92
|
+
* shell capture's handler errors name the page it captures). A
|
|
93
|
+
* `router.prerender()` warm's own render is its request's: unset, while a
|
|
94
|
+
* shell capture it makes is `true`. Unset for every other error.
|
|
95
|
+
*/
|
|
96
|
+
background?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Whether an error boundary caught the error and its fallback reached the
|
|
99
|
+
* response. `false` on a `background` error: the failed render is never
|
|
100
|
+
* stored, so no visitor sees the fallback.
|
|
101
|
+
*/
|
|
83
102
|
handledByBoundary?: boolean;
|
|
84
103
|
/** Stack trace (if available) */
|
|
85
104
|
stack?: string;
|