@rangojs/router 0.22.1 → 0.24.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 +10 -4
- package/dist/testing/vitest.js +31 -1
- package/dist/types/browser/defer.d.ts +48 -0
- package/dist/types/browser/document-revealed.d.ts +17 -0
- package/dist/types/browser/event-controller.d.ts +7 -1
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +39 -8
- package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
- package/dist/types/browser/response-adapter.d.ts +7 -0
- package/dist/types/browser/types.d.ts +24 -10
- package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/router-processing.d.ts +1 -1
- package/dist/types/build/route-types/scan-filter.d.ts +22 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
- package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/client-urls.d.ts +1 -1
- package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
- package/dist/types/client-urls/navigation.d.ts +0 -37
- package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
- package/dist/types/client-urls/server-projection.d.ts +29 -1
- package/dist/types/client-urls/types.d.ts +34 -69
- package/dist/types/client.d.ts +0 -1
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +31 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/context.d.ts +21 -1
- package/dist/types/server/handle-store.d.ts +9 -1
- package/dist/types/server/request-context.d.ts +52 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +4 -4
- package/dist/types/testing/internal/action-id.d.ts +10 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
- package/dist/types/testing/internal/test-action.d.ts +8 -0
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-loader.d.ts +2 -1
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/run-revalidate.d.ts +74 -0
- package/dist/types/testing/run-transition-when.d.ts +2 -3
- package/dist/types/testing/serve-shell-request.d.ts +40 -1
- package/dist/types/testing/vitest.d.ts +11 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/boundaries.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +30 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/types/segments.d.ts +21 -4
- package/dist/types/urls/path-helper-types.d.ts +25 -4
- package/dist/types/urls/type-extraction.d.ts +6 -2
- package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
- package/dist/types/vite/discovery/state.d.ts +14 -4
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
- package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
- package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
- package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
- package/dist/types/vite/router-discovery.d.ts +10 -0
- package/dist/vite/index.js +974 -379
- package/package.json +1 -1
- package/skills/caching/SKILL.md +54 -5
- package/skills/catalog.json +3 -3
- package/skills/client-urls/SKILL.md +197 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +16 -3
- package/skills/hooks/data.md +3 -1
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +199 -2
- package/skills/middleware/SKILL.md +183 -2
- package/skills/migrate-nextjs/SKILL.md +2 -2
- package/skills/migrate-react-router/SKILL.md +10 -7
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/ppr/SKILL.md +22 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +13 -7
- package/skills/route/SKILL.md +139 -0
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +2 -1
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +107 -19
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/use-cache/SKILL.md +4 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +46 -32
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +82 -16
- package/src/browser/navigation-client.ts +48 -36
- package/src/browser/navigation-store.ts +13 -0
- package/src/browser/navigation-transaction.ts +25 -8
- package/src/browser/partial-update.ts +554 -146
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +88 -49
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +25 -63
- package/src/browser/types.ts +24 -10
- package/src/browser/validate-redirect-origin.ts +16 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +3 -6
- package/src/build/route-types/scan-filter.ts +45 -11
- package/src/cache/cf/cf-cache-store.ts +59 -21
- package/src/cache/cf/cf-cache-types.ts +24 -2
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +21 -107
- package/src/client-urls/client-urls.ts +22 -6
- package/src/client-urls/hoisted-revalidate.ts +25 -0
- package/src/client-urls/navigation.ts +0 -96
- package/src/client-urls/revalidate-registry.ts +30 -0
- package/src/client-urls/server-projection.ts +151 -65
- package/src/client-urls/types.ts +39 -71
- package/src/client.tsx +9 -5
- package/src/context-var.ts +32 -2
- package/src/errors.ts +34 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +148 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-content-wrapper.tsx +18 -4
- package/src/route-definition/dsl-helpers.ts +27 -4
- package/src/route-definition/helpers-types.ts +27 -1
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +35 -16
- package/src/router/internal-params.ts +16 -0
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +85 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +16 -4
- package/src/router/match-middleware/cache-lookup.ts +35 -7
- package/src/router/match-middleware/cache-store.ts +15 -1
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/match-result.ts +14 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/navigation-snapshot.ts +2 -1
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/fresh.ts +22 -18
- package/src/router/segment-resolution/loader-cache.ts +2 -1
- package/src/router/segment-resolution/loader-mask.ts +5 -3
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
- package/src/router/segment-resolution/revalidation.ts +215 -82
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +54 -9
- package/src/rsc/shell-capture.ts +2 -1
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/segment-loader-promise.ts +23 -20
- package/src/segment-system.tsx +60 -26
- package/src/server/context.ts +39 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +152 -25
- package/src/server/request-context.ts +175 -64
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/action-form.ts +2 -11
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +7 -3
- package/src/testing/internal/action-id.ts +16 -1
- package/src/testing/internal/client-urls-projection.ts +70 -0
- package/src/testing/internal/test-action.ts +25 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +4 -1
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +131 -8
- package/src/testing/vitest.ts +67 -1
- package/src/thenable-status.ts +11 -0
- package/src/types/boundaries.ts +12 -0
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +30 -0
- package/src/types/request-scope.ts +8 -0
- package/src/types/segments.ts +26 -4
- package/src/urls/path-helper-types.ts +27 -5
- package/src/urls/type-extraction.ts +30 -10
- package/src/use-loader.tsx +18 -13
- package/src/vite/discovery/client-urls-projection.ts +20 -21
- package/src/vite/discovery/discover-routers.ts +94 -26
- package/src/vite/discovery/state.ts +28 -4
- package/src/vite/discovery/virtual-module-codegen.ts +22 -17
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/plugins/hoist/literal.ts +417 -0
- package/src/vite/plugins/hoist/scope.ts +370 -0
- package/src/vite/plugins/hoist/source.ts +84 -0
- package/src/vite/plugins/transition-when-hoist.ts +113 -779
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +116 -53
- package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
- package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
- package/dist/types/testing/run-client-revalidate.d.ts +0 -43
- package/src/client-urls/revalidate-chain.ts +0 -83
- package/src/client-urls/revalidation-protocol.ts +0 -56
- package/src/testing/run-client-revalidate.ts +0 -92
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { requestHeaders } from "../server/request-headers.js";
|
|
17
|
+
import { FILL_PARAM } from "../prefetch-markers.js";
|
|
17
18
|
import type { MiddlewareFn, MiddlewareContext } from "../router/middleware.js";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
documentCacheStoresRender,
|
|
21
|
+
shouldCacheResponse,
|
|
22
|
+
} from "./document-cache-policy.js";
|
|
19
23
|
import {
|
|
20
24
|
getRequestContext,
|
|
21
25
|
runWithRequestContext,
|
|
@@ -36,11 +40,14 @@ import {
|
|
|
36
40
|
predatesInvalidation,
|
|
37
41
|
} from "./tag-invalidation.js";
|
|
38
42
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
43
|
+
import { isRewrite } from "../route-definition/rewrite.js";
|
|
39
44
|
import {
|
|
40
45
|
SEGMENT_FRAGMENT_CAPABILITY_HEADER,
|
|
41
46
|
SEGMENT_FRAGMENT_RECOVERY_HEADER,
|
|
42
47
|
} from "../segment-fragments.js";
|
|
43
48
|
|
|
49
|
+
export { documentCacheStoresRender };
|
|
50
|
+
|
|
44
51
|
const CACHE_STATUS_HEADER = "x-document-cache-status";
|
|
45
52
|
|
|
46
53
|
function collectRequestTags(
|
|
@@ -61,112 +68,6 @@ function hashSegmentIds(segmentIds: string): string {
|
|
|
61
68
|
return Math.abs(hash).toString(36);
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
// ============================================================================
|
|
65
|
-
|
|
66
|
-
interface CacheDirectives {
|
|
67
|
-
sMaxAge?: number;
|
|
68
|
-
staleWhileRevalidate?: number;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Parse Cache-Control header for s-maxage and stale-while-revalidate directives
|
|
73
|
-
*/
|
|
74
|
-
function parseCacheControl(header: string | null): CacheDirectives | null {
|
|
75
|
-
if (!header) return null;
|
|
76
|
-
|
|
77
|
-
// RFC 7234: in a SHARED cache, `private` and `no-store` forbid storage and
|
|
78
|
-
// MUST win over `s-maxage` even though `private, s-maxage` is contradictory.
|
|
79
|
-
// The document cache is a shared app store, so refuse both regardless of any
|
|
80
|
-
// s-maxage / stale-while-revalidate also present. Match standalone directive
|
|
81
|
-
// tokens (start/end, whitespace, comma, semicolon, or `=` bounded), not a
|
|
82
|
-
// substring, so a value containing "private" cannot false-veto.
|
|
83
|
-
if (/(^|[\s,;])(private|no-store)(?=$|[\s,;=])/i.test(header)) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// RFC 7234 §5.2.2.2: a shared cache MUST NOT serve a stored `no-cache`
|
|
88
|
-
// response without successful origin validation. This store's hit path has no
|
|
89
|
-
// validation step, so serving a stored no-cache response within s-maxage
|
|
90
|
-
// would hand the client content the origin marked must-revalidate. Refuse to
|
|
91
|
-
// store it. Only UNqualified `no-cache` (no `=`) vetoes — the field-name-
|
|
92
|
-
// scoped `no-cache="set-cookie"` form IS storable per the RFC, so the `=`
|
|
93
|
-
// boundary is excluded from the lookahead (unlike private/no-store above).
|
|
94
|
-
if (/(^|[\s,;])no-cache(?=$|[\s,;])/i.test(header)) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const directives: CacheDirectives = {};
|
|
99
|
-
|
|
100
|
-
// Parse s-maxage
|
|
101
|
-
const sMaxAgeMatch = header.match(/s-maxage\s*=\s*(\d+)/i);
|
|
102
|
-
if (sMaxAgeMatch) {
|
|
103
|
-
directives.sMaxAge = parseInt(sMaxAgeMatch[1], 10);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Parse stale-while-revalidate
|
|
107
|
-
const swrMatch = header.match(/stale-while-revalidate\s*=\s*(\d+)/i);
|
|
108
|
-
if (swrMatch) {
|
|
109
|
-
directives.staleWhileRevalidate = parseInt(swrMatch[1], 10);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Only return if we have at least s-maxage (required for document caching)
|
|
113
|
-
if (directives.sMaxAge !== undefined) {
|
|
114
|
-
return directives;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if response should be cached based on Cache-Control headers
|
|
122
|
-
*/
|
|
123
|
-
function shouldCacheResponse(
|
|
124
|
-
response: Response,
|
|
125
|
-
requestCtx?: Pick<RequestContext, "_payloadVisitorTheme">,
|
|
126
|
-
): CacheDirectives | null {
|
|
127
|
-
// Only cache successful responses
|
|
128
|
-
if (response.status !== 200) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Never cache a per-client signal into a SHARED response store. A Set-Cookie
|
|
133
|
-
// (e.g. a rango state rotation from invalidateClientCache(), or any cookie a
|
|
134
|
-
// loader set) would be replayed to every client on a hit — pinning them to
|
|
135
|
-
// one value and even rolling a rotated client back to a prior one. The
|
|
136
|
-
// x-rango-keep-cache directive header is the mirror image: a replayed "keep"
|
|
137
|
-
// would suppress invalidation for every replayed client. Refuse both.
|
|
138
|
-
if (hasPerClientSignal(response.headers)) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// The payload carries the visitor's theme (#978): the render ran before the
|
|
143
|
-
// response opted in (a Cache-Control written after next()), so
|
|
144
|
-
// payloadInitialTheme could not render the default.
|
|
145
|
-
if (requestCtx?._payloadVisitorTheme) {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const cacheControl = response.headers.get("Cache-Control");
|
|
150
|
-
return parseCacheControl(cacheControl);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Whether this cache will store the response the current render produces, as
|
|
155
|
-
* far as the render can tell: the middleware runs it (a MISS or a stale
|
|
156
|
-
* refresh) and the response stub already opts in. payloadInitialTheme
|
|
157
|
-
* (rsc/full-payload.ts) then renders the no-cookie default theme (#978). A
|
|
158
|
-
* Cache-Control written after next() is not on the stub yet; for that render
|
|
159
|
-
* shouldCacheResponse refuses a payload marked `_payloadVisitorTheme`.
|
|
160
|
-
*/
|
|
161
|
-
export function documentCacheStoresRender(
|
|
162
|
-
reqCtx: Pick<RequestContext, "_documentCacheRender" | "res">,
|
|
163
|
-
): boolean {
|
|
164
|
-
return (
|
|
165
|
-
reqCtx._documentCacheRender === true &&
|
|
166
|
-
shouldCacheResponse(reqCtx.res) !== null
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
71
|
/**
|
|
171
72
|
* Throw the first error the drained render reported through Flight or Fizz
|
|
172
73
|
* onError. A component that throws after the 200 committed does not fail the
|
|
@@ -337,6 +238,14 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
337
238
|
return next();
|
|
338
239
|
}
|
|
339
240
|
|
|
241
|
+
// Skip fill requests (prefetch: false): a fill is the second half of one
|
|
242
|
+
// adoption, rendered against what that client holds. Never served from a
|
|
243
|
+
// stored body and never stored. Asked of the raw URL like the two skips
|
|
244
|
+
// above: the request context is not read yet.
|
|
245
|
+
if (rawUrl.searchParams.has(FILL_PARAM)) {
|
|
246
|
+
return next();
|
|
247
|
+
}
|
|
248
|
+
|
|
340
249
|
// Skip configured paths
|
|
341
250
|
if (skipPaths.some((path) => url.pathname.startsWith(path))) {
|
|
342
251
|
return next();
|
|
@@ -492,6 +401,9 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
492
401
|
),
|
|
493
402
|
);
|
|
494
403
|
} catch (error) {
|
|
404
|
+
// The route now rewrites (route middleware): nothing to refresh
|
|
405
|
+
// this entry with.
|
|
406
|
+
if (isRewrite(error)) return;
|
|
495
407
|
// Pass requestCtx explicitly: this runs in a detached waitUntil task
|
|
496
408
|
// where the ALS context is gone, so onError only fires if we hand it
|
|
497
409
|
// the captured context (reportCacheError falls back to _getRequestContext
|
|
@@ -579,6 +491,8 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
579
491
|
noteWarmDocument(requestCtx, "not-cacheable");
|
|
580
492
|
return originalResponse;
|
|
581
493
|
} catch (error) {
|
|
494
|
+
// A rewrite() thrown downstream re-dispatches the request.
|
|
495
|
+
if (isRewrite(error)) throw error;
|
|
582
496
|
reportCacheError(error, "cache-read", "[DocumentCache] middleware");
|
|
583
497
|
if (handlerCalled) {
|
|
584
498
|
// Post-handler failure (e.g. body.tee()): do not call next() again
|
|
@@ -3,9 +3,9 @@ import { validateUserRouteName } from "../route-name.js";
|
|
|
3
3
|
import { buildRouteTrie } from "../router/route-trie-builder.js";
|
|
4
4
|
import { tryTrieMatch } from "../router/trie-matching.js";
|
|
5
5
|
import type { LoaderDefinition } from "../types.js";
|
|
6
|
+
import { isHoistedRevalidateRef } from "./hoisted-revalidate.js";
|
|
6
7
|
import type {
|
|
7
8
|
ClientPathOptions,
|
|
8
|
-
ClientRevalidateFn,
|
|
9
9
|
ClientTransitionConfig,
|
|
10
10
|
ClientUrlBuilder,
|
|
11
11
|
ClientUrlHelpers,
|
|
@@ -137,13 +137,14 @@ interface TransitionItem extends ItemBase {
|
|
|
137
137
|
interface LoaderItem extends ItemBase {
|
|
138
138
|
readonly type: "loader";
|
|
139
139
|
readonly definition: LoaderDefinition<any, any>;
|
|
140
|
-
readonly revalidate:
|
|
140
|
+
readonly revalidate: ClientUrlLoaderRecord["revalidate"];
|
|
141
141
|
readonly ssr?: false;
|
|
142
|
+
readonly prefetch?: false;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
interface RevalidateItem extends ItemBase {
|
|
145
146
|
readonly type: "revalidate";
|
|
146
|
-
readonly fn:
|
|
147
|
+
readonly fn: ClientUrlLoaderRecord["revalidate"][number];
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
interface LoadingItem extends ItemBase {
|
|
@@ -386,11 +387,22 @@ function createHelpers(): ClientUrlHelpers {
|
|
|
386
387
|
"clientUrls() loader() stream was replaced: use loader(Def, { ssr: false }) — the same knob as loading(fallback, { ssr: false }) — to await the loader before first flush on document requests.",
|
|
387
388
|
);
|
|
388
389
|
}
|
|
390
|
+
// Inline, not errors.ts assertBooleanOption: this module is in the
|
|
391
|
+
// client router chunk, where the helper costs 20 B gzip.
|
|
389
392
|
if (options?.ssr !== undefined && typeof options.ssr !== "boolean") {
|
|
390
393
|
throw new Error(
|
|
391
394
|
`clientUrls() loader() ssr must be a boolean (got ${JSON.stringify(options.ssr)}). Omit it (or pass true) to stream on every render.`,
|
|
392
395
|
);
|
|
393
396
|
}
|
|
397
|
+
if (
|
|
398
|
+
process.env.NODE_ENV !== "production" &&
|
|
399
|
+
options?.prefetch !== undefined &&
|
|
400
|
+
typeof options.prefetch !== "boolean"
|
|
401
|
+
) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`clientUrls() loader() prefetch must be a boolean (got ${JSON.stringify(options.prefetch)}). Omit it (or pass true) to run the loader in prefetches.`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
394
406
|
const items = use ? runUse(use, "loader use") : [];
|
|
395
407
|
rejectItems(items, new Set(["revalidate"]), "loader");
|
|
396
408
|
return toPublicItem({
|
|
@@ -404,11 +416,14 @@ function createHelpers(): ClientUrlHelpers {
|
|
|
404
416
|
)
|
|
405
417
|
.map((item) => item.fn),
|
|
406
418
|
...(options?.ssr === false ? { ssr: false as const } : {}),
|
|
419
|
+
...(options?.prefetch === false ? { prefetch: false as const } : {}),
|
|
407
420
|
});
|
|
408
421
|
};
|
|
409
422
|
|
|
410
423
|
const revalidate: ClientUrlHelpers["revalidate"] = (fn) => {
|
|
411
|
-
|
|
424
|
+
// The build leaves a marker in place of the predicate it moved to the
|
|
425
|
+
// server (vite/plugins/client-urls-revalidate-hoist.ts).
|
|
426
|
+
if (typeof fn !== "function" && !isHoistedRevalidateRef(fn)) {
|
|
412
427
|
throw new Error("clientUrls() revalidate() expects a predicate function");
|
|
413
428
|
}
|
|
414
429
|
return toPublicItem({
|
|
@@ -563,6 +578,7 @@ function applyConfig(
|
|
|
563
578
|
loader: item.definition,
|
|
564
579
|
revalidate: Object.freeze([...item.revalidate]),
|
|
565
580
|
...(item.ssr === false ? { ssr: false as const } : {}),
|
|
581
|
+
...(item.prefetch === false ? { prefetch: false as const } : {}),
|
|
566
582
|
}),
|
|
567
583
|
);
|
|
568
584
|
} else if (item.type === "loading") {
|
|
@@ -675,6 +691,8 @@ function collectIntercepts(
|
|
|
675
691
|
let loading: ReactNode | undefined;
|
|
676
692
|
for (const useItem of item.use) {
|
|
677
693
|
if (useItem.type === "loader") {
|
|
694
|
+
// `prefetch` is not carried: an intercept prefetch defers nothing,
|
|
695
|
+
// so the flag is accepted and ignored, as on a urls() intercept().
|
|
678
696
|
loaders.push(
|
|
679
697
|
Object.freeze({
|
|
680
698
|
loader: useItem.definition,
|
|
@@ -762,8 +780,6 @@ export function clientUrls<const TItems extends ClientUrlItems>(
|
|
|
762
780
|
}
|
|
763
781
|
|
|
764
782
|
export type {
|
|
765
|
-
ClientRevalidateArgs,
|
|
766
|
-
ClientRevalidateFn,
|
|
767
783
|
ClientTransitionConfig,
|
|
768
784
|
ClientUrlBuilder,
|
|
769
785
|
ClientUrlHelpers,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value the build leaves in place of a clientUrls() `revalidate()`
|
|
3
|
+
* predicate in the browser and SSR copies of a "use client" module
|
|
4
|
+
* (vite/plugins/client-urls-revalidate-hoist.ts): the predicate itself moves
|
|
5
|
+
* to a server module, registered under the same content key
|
|
6
|
+
* (revalidate-registry.ts). The projection carries the key; the materialized
|
|
7
|
+
* loader stub calls the registered predicate.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const HOISTED_REVALIDATE_KEY = "$$rangoRevalidate";
|
|
11
|
+
|
|
12
|
+
export interface HoistedRevalidateRef {
|
|
13
|
+
readonly $$rangoRevalidate: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isHoistedRevalidateRef(
|
|
17
|
+
value: unknown,
|
|
18
|
+
): value is HoistedRevalidateRef {
|
|
19
|
+
return (
|
|
20
|
+
typeof value === "object" &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
typeof (value as Partial<HoistedRevalidateRef>)[HOISTED_REVALIDATE_KEY] ===
|
|
23
|
+
"string"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { startTransition } from "react";
|
|
4
|
-
import { makeIsAction } from "../router/is-action.js";
|
|
5
|
-
import {
|
|
6
|
-
lockedClientDefault,
|
|
7
|
-
runClientRevalidateChain,
|
|
8
|
-
} from "./revalidate-chain.js";
|
|
9
|
-
import { encodeClientRevalidationDecisions } from "./revalidation-protocol.js";
|
|
10
4
|
import type { ClientUrlPatterns } from "./types.js";
|
|
11
5
|
import type { TransitionWhenFn } from "../types/segments.js";
|
|
12
6
|
|
|
@@ -210,96 +204,6 @@ export function beginClientUrlNavigation(
|
|
|
210
204
|
};
|
|
211
205
|
}
|
|
212
206
|
|
|
213
|
-
/**
|
|
214
|
-
* Run the held clientUrls route's per-loader revalidate() predicates and
|
|
215
|
-
* encode their decisions for the request header. Predicates are CLIENT code
|
|
216
|
-
* (declared in the "use client" definition module); only decisions cross the
|
|
217
|
-
* wire — the synthesized per-loader revalidate() on every materialized stub
|
|
218
|
-
* (server-projection.ts) reads them back by loader $$id.
|
|
219
|
-
*
|
|
220
|
-
* Decisions exist only for the loaders the client currently HOLDS: the local
|
|
221
|
-
* match of currentUrl. They matter when the server would re-evaluate those
|
|
222
|
-
* held loader segments — same-route param/search navs, actions, and stale
|
|
223
|
-
* restores. Cross-route targets render new segments unconditionally, so a
|
|
224
|
-
* decision would be ignored; we still compute against the current route since
|
|
225
|
-
* only its held segments are addressable.
|
|
226
|
-
*
|
|
227
|
-
* Returns null when no group is active, the current location is not a client
|
|
228
|
-
* route, or no predicate produced a decision that differs from the locked
|
|
229
|
-
* default — the server then applies defaults, which is also the behavior for
|
|
230
|
-
* requests that cannot carry decisions (no-JS, PE, prefetch, document loads).
|
|
231
|
-
*/
|
|
232
|
-
export function collectClientRevalidationDecisions(options: {
|
|
233
|
-
currentUrl: URL;
|
|
234
|
-
nextUrl: URL;
|
|
235
|
-
/**
|
|
236
|
-
* True only when the decisions ride the action POST itself — the one
|
|
237
|
-
* request the server evaluates with actionContext (locked default true).
|
|
238
|
-
* Action-triggered refetch GETs (partial-update terminals) pass false:
|
|
239
|
-
* the server gives those navigation defaults, and the delta gate below
|
|
240
|
-
* must diff against the default the SERVER will use, or a force decision
|
|
241
|
-
* on the refetch would be silently swallowed as "equals default".
|
|
242
|
-
*/
|
|
243
|
-
actionRequest: boolean;
|
|
244
|
-
/** Action TRUTH for the predicates' isAction() matcher; may be true on
|
|
245
|
-
* refetch GETs where actionRequest is false. */
|
|
246
|
-
isAction: boolean;
|
|
247
|
-
actionId?: string;
|
|
248
|
-
stale: boolean;
|
|
249
|
-
}): string | null {
|
|
250
|
-
const group = activeGroup;
|
|
251
|
-
if (!group) return null;
|
|
252
|
-
|
|
253
|
-
const { currentUrl, nextUrl, actionRequest, isAction, actionId, stale } =
|
|
254
|
-
options;
|
|
255
|
-
const currentLocal = stripMountPrefix(currentUrl.pathname, group.mount);
|
|
256
|
-
if (currentLocal === null) return null;
|
|
257
|
-
const currentMatch = group.definition.match(currentLocal);
|
|
258
|
-
if (!currentMatch || currentMatch.redirectTo) return null;
|
|
259
|
-
const record = group.definition.routes.find(
|
|
260
|
-
(candidate) => candidate.id === currentMatch.routeKey,
|
|
261
|
-
);
|
|
262
|
-
if (!record) return null;
|
|
263
|
-
|
|
264
|
-
const nextLocal = stripMountPrefix(nextUrl.pathname, group.mount);
|
|
265
|
-
const nextMatch =
|
|
266
|
-
nextLocal === null ? null : group.definition.match(nextLocal);
|
|
267
|
-
const nextParams = nextMatch?.params ?? {};
|
|
268
|
-
|
|
269
|
-
const defaultShouldRevalidate = lockedClientDefault({
|
|
270
|
-
actionRequest,
|
|
271
|
-
currentParams: currentMatch.params,
|
|
272
|
-
nextParams,
|
|
273
|
-
currentUrl,
|
|
274
|
-
nextUrl,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const baseArgs = {
|
|
278
|
-
currentUrl,
|
|
279
|
-
nextUrl,
|
|
280
|
-
currentParams: currentMatch.params,
|
|
281
|
-
nextParams,
|
|
282
|
-
stale,
|
|
283
|
-
isAction: makeIsAction(actionId, isAction),
|
|
284
|
-
...(actionId !== undefined ? { actionId } : {}),
|
|
285
|
-
};
|
|
286
|
-
const skip: string[] = [];
|
|
287
|
-
const force: string[] = [];
|
|
288
|
-
for (const { loader, revalidate } of record.loaders) {
|
|
289
|
-
if (revalidate.length === 0) continue;
|
|
290
|
-
const decision = runClientRevalidateChain(
|
|
291
|
-
revalidate,
|
|
292
|
-
baseArgs,
|
|
293
|
-
defaultShouldRevalidate,
|
|
294
|
-
`loader "${loader.$$id}"`,
|
|
295
|
-
);
|
|
296
|
-
if (decision === defaultShouldRevalidate) continue;
|
|
297
|
-
(decision ? force : skip).push(loader.$$id);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return encodeClientRevalidationDecisions({ skip, force });
|
|
301
|
-
}
|
|
302
|
-
|
|
303
207
|
export function clearClientUrlNavigationRegistry(): void {
|
|
304
208
|
activeGroup = null;
|
|
305
209
|
activeInterceptTargets = new Set();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ShouldRevalidateFn } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hoisted clientUrls() `revalidate()` predicates by content key. The
|
|
5
|
+
* generated `<file>?rango-revalidate` module of a group fills it when the
|
|
6
|
+
* group's module loads in the RSC realm
|
|
7
|
+
* (vite/plugins/client-urls-revalidate-hoist.ts); materialized loader stubs
|
|
8
|
+
* read it (server-projection.ts). A key hashes the file and the predicate's
|
|
9
|
+
* syntax, so a re-registration under one key is the same predicate.
|
|
10
|
+
*
|
|
11
|
+
* Server-only build output, not router state: no router hook reads it
|
|
12
|
+
* (AGENTS.md Hard rule 8).
|
|
13
|
+
*/
|
|
14
|
+
const revalidators = new Map<string, ShouldRevalidateFn<any, any>>();
|
|
15
|
+
|
|
16
|
+
export function registerClientUrlRevalidators(
|
|
17
|
+
fns: Readonly<Record<string, ShouldRevalidateFn<any, any>>>,
|
|
18
|
+
): void {
|
|
19
|
+
for (const [key, fn] of Object.entries(fns)) revalidators.set(key, fn);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getClientUrlRevalidator(
|
|
23
|
+
key: string,
|
|
24
|
+
): ShouldRevalidateFn<any, any> | undefined {
|
|
25
|
+
return revalidators.get(key);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function clearClientUrlRevalidators(): void {
|
|
29
|
+
revalidators.clear();
|
|
30
|
+
}
|