@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17
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/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +192 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +64 -25
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +348 -128
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Classification
|
|
3
|
+
*
|
|
4
|
+
* Replaces the implicit "preview then match again" model with a clean
|
|
5
|
+
* two-stage architecture:
|
|
6
|
+
*
|
|
7
|
+
* 1. Classification — classifyRequest() produces a RequestPlan that answers
|
|
8
|
+
* all routing questions once: target route, request mode, route middleware,
|
|
9
|
+
* response-route info, negotiation state.
|
|
10
|
+
*
|
|
11
|
+
* 2. Execution — executeRequest() dispatches on the plan to the appropriate
|
|
12
|
+
* handler (response route, loader fetch, full render, partial render,
|
|
13
|
+
* action revalidation, PE render).
|
|
14
|
+
*
|
|
15
|
+
* Builds on RouteSnapshot from route-snapshot.ts.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { RouteNotFoundError } from "../errors.js";
|
|
19
|
+
import type { EntryData } from "../server/context.js";
|
|
20
|
+
import type { CollectedMiddleware } from "./middleware-types.js";
|
|
21
|
+
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
22
|
+
import { negotiateRoute } from "./content-negotiation.js";
|
|
23
|
+
import { stripInternalParams } from "./handler-context.js";
|
|
24
|
+
import { resolveRoute, type RouteSnapshot } from "./route-snapshot.js";
|
|
25
|
+
|
|
26
|
+
interface RedirectPlan<TEnv = any> {
|
|
27
|
+
mode: "redirect";
|
|
28
|
+
route: RouteSnapshot<TEnv>;
|
|
29
|
+
redirectUrl: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface VersionMismatchPlan<TEnv = any> {
|
|
33
|
+
mode: "version-mismatch";
|
|
34
|
+
/** May be undefined when version mismatch is detected before route resolution */
|
|
35
|
+
route?: RouteSnapshot<TEnv>;
|
|
36
|
+
reloadUrl: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface AppSwitchReloadPlan {
|
|
40
|
+
mode: "app-switch";
|
|
41
|
+
/** Clean target URL (internal _rsc_* params stripped) to navigate to. */
|
|
42
|
+
reloadUrl: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ResponseRoutePlan<TEnv = any> {
|
|
46
|
+
mode: "response";
|
|
47
|
+
route: RouteSnapshot<TEnv>;
|
|
48
|
+
handler: Function;
|
|
49
|
+
responseType: string;
|
|
50
|
+
negotiated: boolean;
|
|
51
|
+
manifestEntry: EntryData;
|
|
52
|
+
routeMiddleware: CollectedMiddleware[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface LoaderFetchPlan<TEnv = any> {
|
|
56
|
+
mode: "loader";
|
|
57
|
+
route: RouteSnapshot<TEnv>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface PeRenderPlan<TEnv = any> {
|
|
61
|
+
mode: "pe-render";
|
|
62
|
+
route: RouteSnapshot<TEnv>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ActionPlan<TEnv = any> {
|
|
66
|
+
mode: "action";
|
|
67
|
+
route: RouteSnapshot<TEnv>;
|
|
68
|
+
actionId: string;
|
|
69
|
+
negotiated: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface FullRenderPlan<TEnv = any> {
|
|
73
|
+
mode: "full-render";
|
|
74
|
+
route: RouteSnapshot<TEnv>;
|
|
75
|
+
negotiated: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface PartialRenderPlan<TEnv = any> {
|
|
79
|
+
mode: "partial-render";
|
|
80
|
+
route: RouteSnapshot<TEnv>;
|
|
81
|
+
negotiated: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The output of request classification. A discriminated union where each
|
|
86
|
+
* variant carries exactly the fields needed for its execution path.
|
|
87
|
+
*/
|
|
88
|
+
export type RequestPlan<TEnv = any> =
|
|
89
|
+
| RedirectPlan<TEnv>
|
|
90
|
+
| VersionMismatchPlan<TEnv>
|
|
91
|
+
| AppSwitchReloadPlan
|
|
92
|
+
| ResponseRoutePlan<TEnv>
|
|
93
|
+
| LoaderFetchPlan<TEnv>
|
|
94
|
+
| PeRenderPlan<TEnv>
|
|
95
|
+
| ActionPlan<TEnv>
|
|
96
|
+
| FullRenderPlan<TEnv>
|
|
97
|
+
| PartialRenderPlan<TEnv>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Plans that have passed the terminal-check gate (version-mismatch and
|
|
101
|
+
* app-switch reloads handled) and are ready for execution. Always have a
|
|
102
|
+
* `route` field.
|
|
103
|
+
*/
|
|
104
|
+
export type ExecutableRequestPlan<TEnv = any> = Exclude<
|
|
105
|
+
RequestPlan<TEnv>,
|
|
106
|
+
VersionMismatchPlan<TEnv> | AppSwitchReloadPlan
|
|
107
|
+
>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Re-export individual plan types for consumers that need to narrow.
|
|
111
|
+
*/
|
|
112
|
+
export type {
|
|
113
|
+
RedirectPlan,
|
|
114
|
+
VersionMismatchPlan,
|
|
115
|
+
ResponseRoutePlan,
|
|
116
|
+
LoaderFetchPlan,
|
|
117
|
+
PeRenderPlan,
|
|
118
|
+
ActionPlan,
|
|
119
|
+
FullRenderPlan,
|
|
120
|
+
PartialRenderPlan,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export interface ClassifyRequestDeps<TEnv = any> {
|
|
124
|
+
findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
|
|
125
|
+
routerVersion: string;
|
|
126
|
+
routerId: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Classify an incoming request into a RequestPlan.
|
|
131
|
+
*
|
|
132
|
+
* This is the single source of truth for request mode detection. It replaces
|
|
133
|
+
* the scattered previewMatch + isAction/isLoaderFetch/isPartial checks in
|
|
134
|
+
* handler.ts.
|
|
135
|
+
*
|
|
136
|
+
* Classification order:
|
|
137
|
+
* 1. Route resolution (findMatch + loadManifest via resolveRoute lite)
|
|
138
|
+
* 2. Redirect detection
|
|
139
|
+
* 3. Version mismatch
|
|
140
|
+
* 4. Response route + content negotiation
|
|
141
|
+
* 5. Mode detection from headers/params
|
|
142
|
+
*/
|
|
143
|
+
export async function classifyRequest<TEnv = any>(
|
|
144
|
+
request: Request,
|
|
145
|
+
url: URL,
|
|
146
|
+
deps: ClassifyRequestDeps<TEnv>,
|
|
147
|
+
): Promise<RequestPlan<TEnv>> {
|
|
148
|
+
const pathname = url.pathname;
|
|
149
|
+
const isAction =
|
|
150
|
+
request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
|
|
151
|
+
|
|
152
|
+
const clientVersion = url.searchParams.get("_rsc_v");
|
|
153
|
+
if (
|
|
154
|
+
deps.routerVersion &&
|
|
155
|
+
clientVersion &&
|
|
156
|
+
clientVersion !== deps.routerVersion
|
|
157
|
+
) {
|
|
158
|
+
let reloadUrl = stripInternalParams(url).toString();
|
|
159
|
+
if (isAction) {
|
|
160
|
+
const referer = request.headers.get("referer");
|
|
161
|
+
if (referer) {
|
|
162
|
+
try {
|
|
163
|
+
const refererUrl = new URL(referer);
|
|
164
|
+
if (refererUrl.origin === url.origin) {
|
|
165
|
+
reloadUrl = referer;
|
|
166
|
+
}
|
|
167
|
+
} catch {}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
mode: "version-mismatch",
|
|
173
|
+
reloadUrl,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
178
|
+
if (
|
|
179
|
+
clientRouterId &&
|
|
180
|
+
clientRouterId !== deps.routerId &&
|
|
181
|
+
url.searchParams.has("_rsc_partial")
|
|
182
|
+
) {
|
|
183
|
+
return {
|
|
184
|
+
mode: "app-switch",
|
|
185
|
+
reloadUrl: stripInternalParams(url).toString(),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const result = await resolveRoute<TEnv>(pathname, {
|
|
190
|
+
findMatch: deps.findMatch,
|
|
191
|
+
lite: true,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (!result) {
|
|
195
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
196
|
+
cause: { pathname, method: request.method },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (result.type === "redirect") {
|
|
201
|
+
const snapshot: RouteSnapshot<TEnv> = {
|
|
202
|
+
matched: result as any,
|
|
203
|
+
manifestEntry: null as any,
|
|
204
|
+
entries: [],
|
|
205
|
+
routeKey: "",
|
|
206
|
+
localRouteName: "",
|
|
207
|
+
params: {},
|
|
208
|
+
routeMiddleware: [],
|
|
209
|
+
cacheScope: null,
|
|
210
|
+
isPassthrough: false,
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
mode: "redirect",
|
|
214
|
+
route: snapshot,
|
|
215
|
+
redirectUrl: result.redirectTo + url.search,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const snapshot = result.snapshot;
|
|
220
|
+
|
|
221
|
+
const responseResult = await classifyResponseRoute(
|
|
222
|
+
request,
|
|
223
|
+
pathname,
|
|
224
|
+
snapshot,
|
|
225
|
+
);
|
|
226
|
+
if (responseResult) {
|
|
227
|
+
return responseResult;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const actionId =
|
|
231
|
+
request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
|
|
232
|
+
const isLoaderFetch = url.searchParams.has("_rsc_loader");
|
|
233
|
+
|
|
234
|
+
const hasVariants =
|
|
235
|
+
snapshot.matched.negotiateVariants &&
|
|
236
|
+
snapshot.matched.negotiateVariants.length > 0;
|
|
237
|
+
const negotiated = !!hasVariants;
|
|
238
|
+
|
|
239
|
+
if (isAction && actionId) {
|
|
240
|
+
return { mode: "action", route: snapshot, actionId, negotiated };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (isLoaderFetch) {
|
|
244
|
+
return { mode: "loader", route: snapshot };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const contentType = request.headers.get("content-type") || "";
|
|
248
|
+
const isFormSubmission =
|
|
249
|
+
contentType.includes("multipart/form-data") ||
|
|
250
|
+
contentType.includes("application/x-www-form-urlencoded");
|
|
251
|
+
if (request.method === "POST" && !isAction && isFormSubmission) {
|
|
252
|
+
return { mode: "pe-render", route: snapshot };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (url.searchParams.has("_rsc_partial")) {
|
|
256
|
+
return { mode: "partial-render", route: snapshot, negotiated };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { mode: "full-render", route: snapshot, negotiated };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if the route is a response route and perform content negotiation
|
|
264
|
+
* if negotiate variants exist. Returns a ResponseRoutePlan if the route
|
|
265
|
+
* is a response route, null otherwise (RSC route).
|
|
266
|
+
*/
|
|
267
|
+
async function classifyResponseRoute<TEnv>(
|
|
268
|
+
request: Request,
|
|
269
|
+
pathname: string,
|
|
270
|
+
snapshot: RouteSnapshot<TEnv>,
|
|
271
|
+
): Promise<ResponseRoutePlan<TEnv> | null> {
|
|
272
|
+
const negotiation = await negotiateRoute(request, pathname, snapshot);
|
|
273
|
+
return negotiation
|
|
274
|
+
? { mode: "response", route: snapshot, ...negotiation }
|
|
275
|
+
: null;
|
|
276
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Evaluates whether segments should revalidate based on params, actions, and custom functions.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ResolvedSegment, HandlerContext } from "../types";
|
|
7
|
+
import type { ResolvedSegment, HandlerContext, ActionRef } from "../types";
|
|
8
8
|
import type { ActionContext } from "./types";
|
|
9
9
|
import {
|
|
10
10
|
debugLog,
|
|
@@ -14,21 +14,52 @@ import {
|
|
|
14
14
|
import type { RevalidationTraceEntry } from "./logging.js";
|
|
15
15
|
import { _getRequestContext } from "../server/request-context.js";
|
|
16
16
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
17
|
+
import { paramsEqual } from "./params-util.js";
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
):
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a server-action reference's stable id, mirroring how the action
|
|
21
|
+
* boundary derives `actionContext.actionId` in `rsc/server-action.ts`
|
|
22
|
+
* (`$id ?? $$id`): the file-path `$id` set by the expose-action-id plugin in a
|
|
23
|
+
* production RSC build when present, otherwise React's `$$id`. Resolving both
|
|
24
|
+
* the incoming `actionId` and the reference with the same precedence makes
|
|
25
|
+
* `isAction()` form-agnostic across dev and production.
|
|
26
|
+
*/
|
|
27
|
+
function resolveActionRefId(ref: unknown): string | undefined {
|
|
28
|
+
if (ref == null) return undefined;
|
|
29
|
+
const r = ref as { $id?: unknown; $$id?: unknown };
|
|
30
|
+
if (typeof r.$id === "string") return r.$id;
|
|
31
|
+
if (typeof r.$$id === "string") return r.$$id;
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Build the `isAction()` helper bound to the current action's id. Called with no
|
|
37
|
+
* arguments it answers "is this request an action at all?" (any action) — `true`
|
|
38
|
+
* during action handling, `false` on plain navigation. Called with one or more
|
|
39
|
+
* action references it narrows to those: a single imported action, several
|
|
40
|
+
* (variadic), or any export of a namespace import (`import * as Mod`). Returns
|
|
41
|
+
* `false` when there is no action (plain navigation) or nothing matches.
|
|
42
|
+
*/
|
|
43
|
+
function makeIsAction(
|
|
44
|
+
currentActionId: string | undefined,
|
|
45
|
+
): (...actions: ActionRef[]) => boolean {
|
|
46
|
+
return (...actions: ActionRef[]): boolean => {
|
|
47
|
+
if (!currentActionId) return false;
|
|
48
|
+
// Bare isAction(): an action is in flight (currentActionId is set) and the
|
|
49
|
+
// caller did not narrow to a specific action, so this is "any action".
|
|
50
|
+
if (actions.length === 0) return true;
|
|
51
|
+
for (const action of actions) {
|
|
52
|
+
if (typeof action === "function") {
|
|
53
|
+
if (resolveActionRefId(action) === currentActionId) return true;
|
|
54
|
+
} else if (action && typeof action === "object") {
|
|
55
|
+
// Namespace import: match any export of the module.
|
|
56
|
+
for (const value of Object.values(action)) {
|
|
57
|
+
if (resolveActionRefId(value) === currentActionId) return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
32
63
|
}
|
|
33
64
|
|
|
34
65
|
/**
|
|
@@ -59,6 +90,14 @@ interface EvaluateRevalidationOptions<TEnv> {
|
|
|
59
90
|
stale?: boolean;
|
|
60
91
|
/** Trace source hint for the revalidation trace */
|
|
61
92
|
traceSource?: RevalidationTraceEntry["source"];
|
|
93
|
+
/**
|
|
94
|
+
* Override the segment-type-derived default. When set, the value is used as
|
|
95
|
+
* the seed `defaultShouldRevalidate` passed to user revalidate fns and the
|
|
96
|
+
* reason flows into the trace. Callers use this when client-knowledge
|
|
97
|
+
* (e.g. parallel slot not in clientSegmentIds) should dictate the seed
|
|
98
|
+
* instead of the params/method-based heuristic.
|
|
99
|
+
*/
|
|
100
|
+
defaultOverride?: { value: boolean; reason: string };
|
|
62
101
|
}
|
|
63
102
|
|
|
64
103
|
/**
|
|
@@ -81,13 +120,12 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
81
120
|
actionContext,
|
|
82
121
|
stale,
|
|
83
122
|
traceSource,
|
|
123
|
+
defaultOverride,
|
|
84
124
|
} = options;
|
|
85
125
|
const nextParams = segment.params || {};
|
|
86
126
|
const paramsChanged = !paramsEqual(nextParams, prevParams);
|
|
87
127
|
const searchChanged = prevUrl.search !== nextUrl.search;
|
|
88
128
|
|
|
89
|
-
// Trace helper: push a structured entry to the request-scoped trace buffer.
|
|
90
|
-
// Guarded by isTraceActive() so object construction is skipped in production.
|
|
91
129
|
function pushTrace(
|
|
92
130
|
defaultVal: boolean,
|
|
93
131
|
finalVal: boolean,
|
|
@@ -106,38 +144,28 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
106
144
|
});
|
|
107
145
|
}
|
|
108
146
|
|
|
109
|
-
// Calculate default revalidation based on segment type and request method
|
|
110
147
|
let defaultShouldRevalidate: boolean;
|
|
111
148
|
let defaultReason: string;
|
|
112
149
|
|
|
113
|
-
if (
|
|
114
|
-
|
|
150
|
+
if (defaultOverride) {
|
|
151
|
+
defaultShouldRevalidate = defaultOverride.value;
|
|
152
|
+
defaultReason = defaultOverride.reason;
|
|
153
|
+
} else if (request.method === "POST") {
|
|
115
154
|
if (segment.type === "route") {
|
|
116
|
-
// Route segment always revalidates on actions
|
|
117
155
|
defaultShouldRevalidate = true;
|
|
118
156
|
defaultReason = "action:route-segment";
|
|
119
157
|
} else if (segment.type === "loader") {
|
|
120
|
-
// Loaders always revalidate on actions - they often contain action-sensitive data
|
|
121
|
-
// (e.g., cart count after add-to-cart action)
|
|
122
158
|
defaultShouldRevalidate = true;
|
|
123
159
|
defaultReason = "action:loader-segment";
|
|
124
160
|
} else if (segment.belongsToRoute) {
|
|
125
|
-
// Segment belongs to route (orphan layouts/parallels) - revalidate
|
|
126
161
|
defaultShouldRevalidate = true;
|
|
127
162
|
defaultReason = "action:belongs-to-route";
|
|
128
163
|
} else {
|
|
129
|
-
// Parent chain segment (shared layouts/parallels) - don't revalidate
|
|
130
164
|
defaultShouldRevalidate = false;
|
|
131
165
|
defaultReason = "action:parent-chain-skip";
|
|
132
166
|
}
|
|
133
167
|
} else {
|
|
134
|
-
// Navigation (GET): Conservative defaults to minimize unnecessary revalidations
|
|
135
|
-
// Only the route segment revalidates by default - all others require explicit opt-in
|
|
136
|
-
|
|
137
168
|
if (segment.type === "route") {
|
|
138
|
-
// Route segments revalidate when path params OR search params change.
|
|
139
|
-
// Search params (e.g., ?page=2&sort=price) are server-parsed via ctx.search,
|
|
140
|
-
// so the handler must re-execute to produce updated content.
|
|
141
169
|
const routeChanged = paramsChanged || searchChanged;
|
|
142
170
|
defaultShouldRevalidate = routeChanged;
|
|
143
171
|
defaultReason = paramsChanged
|
|
@@ -153,8 +181,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
153
181
|
});
|
|
154
182
|
}
|
|
155
183
|
} else if (segment.belongsToRoute && (paramsChanged || searchChanged)) {
|
|
156
|
-
// Children of the route path (loaders, orphan layouts/parallels)
|
|
157
|
-
// revalidate when path params or search params change
|
|
158
184
|
defaultShouldRevalidate = true;
|
|
159
185
|
defaultReason = paramsChanged
|
|
160
186
|
? "nav:route-child-params-changed"
|
|
@@ -166,9 +192,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
166
192
|
searchChanged,
|
|
167
193
|
});
|
|
168
194
|
} else {
|
|
169
|
-
// Parent layouts and parallels default to no revalidation
|
|
170
|
-
// Cannot assume these segments depend on params without explicit declaration
|
|
171
|
-
// Use custom revalidation functions to opt-in when needed
|
|
172
195
|
defaultShouldRevalidate = false;
|
|
173
196
|
defaultReason = "nav:non-route-skip";
|
|
174
197
|
debugLog("revalidation", "non-route segment skipped by default", {
|
|
@@ -178,7 +201,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
178
201
|
}
|
|
179
202
|
}
|
|
180
203
|
|
|
181
|
-
// No custom revalidations defined - return default behavior without prev segment
|
|
182
204
|
if (revalidations.length === 0) {
|
|
183
205
|
if (defaultShouldRevalidate) {
|
|
184
206
|
debugLog("revalidation", "default revalidate=true", {
|
|
@@ -195,14 +217,10 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
195
217
|
return defaultShouldRevalidate;
|
|
196
218
|
}
|
|
197
219
|
|
|
198
|
-
// Custom revalidations exist - may need full prev segment
|
|
199
|
-
// Lazy load prev segment only if getPrevSegment provided
|
|
200
220
|
const prevSegment = getPrevSegment ? await getPrevSegment() : null;
|
|
201
221
|
|
|
202
|
-
// Execute revalidation functions with soft/hard decision pattern
|
|
203
222
|
let currentSuggestion = defaultShouldRevalidate;
|
|
204
223
|
|
|
205
|
-
// Compute public route names (filtered: undefined for auto-generated routes)
|
|
206
224
|
const toRouteName =
|
|
207
225
|
routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
208
226
|
const reqCtx = _getRequestContext();
|
|
@@ -226,23 +244,18 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
226
244
|
slotName: segment.slot,
|
|
227
245
|
// Action context (only populated when triggered by server action)
|
|
228
246
|
actionId: actionContext?.actionId,
|
|
247
|
+
isAction: makeIsAction(actionContext?.actionId),
|
|
229
248
|
actionUrl: actionContext?.actionUrl,
|
|
230
249
|
actionResult: actionContext?.actionResult,
|
|
231
250
|
formData: actionContext?.formData,
|
|
232
|
-
method: request.method,
|
|
233
|
-
routeName: toRouteName,
|
|
234
|
-
fromRouteName,
|
|
235
|
-
toRouteName,
|
|
236
|
-
// Stale cache context (only true for background revalidation after stale cache render)
|
|
251
|
+
method: request.method,
|
|
252
|
+
routeName: toRouteName,
|
|
253
|
+
fromRouteName,
|
|
254
|
+
toRouteName,
|
|
237
255
|
stale,
|
|
238
256
|
});
|
|
239
257
|
|
|
240
|
-
// Check return type:
|
|
241
|
-
// - boolean: hard decision, short-circuit immediately
|
|
242
|
-
// - { defaultShouldRevalidate: boolean }: soft decision, update suggestion and continue
|
|
243
|
-
// - null/undefined: use default behavior (equivalent to returning { defaultShouldRevalidate })
|
|
244
258
|
if (typeof result === "boolean") {
|
|
245
|
-
// Hard decision - short-circuit
|
|
246
259
|
debugLog("revalidation", "hard decision", {
|
|
247
260
|
segmentId: segment.id,
|
|
248
261
|
revalidator: name,
|
|
@@ -255,7 +268,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
255
268
|
typeof result === "object" &&
|
|
256
269
|
"defaultShouldRevalidate" in result
|
|
257
270
|
) {
|
|
258
|
-
// Soft decision - update suggestion and continue
|
|
259
271
|
currentSuggestion = result.defaultShouldRevalidate;
|
|
260
272
|
debugLog("revalidation", "soft decision", {
|
|
261
273
|
segmentId: segment.id,
|
|
@@ -263,18 +275,14 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
263
275
|
revalidate: currentSuggestion,
|
|
264
276
|
});
|
|
265
277
|
} else if (result === null || result === undefined) {
|
|
266
|
-
// Defer to default - equivalent to { defaultShouldRevalidate: currentSuggestion }
|
|
267
|
-
// This means "I don't care, use whatever the default is"
|
|
268
278
|
debugLog("revalidation", "deferred to current default", {
|
|
269
279
|
segmentId: segment.id,
|
|
270
280
|
revalidator: name,
|
|
271
281
|
revalidate: currentSuggestion,
|
|
272
282
|
});
|
|
273
|
-
// currentSuggestion stays the same, continue to next function
|
|
274
283
|
}
|
|
275
284
|
}
|
|
276
285
|
|
|
277
|
-
// All revalidators completed - use final suggestion
|
|
278
286
|
debugLog("revalidation", "final decision", {
|
|
279
287
|
segmentId: segment.id,
|
|
280
288
|
revalidate: currentSuggestion,
|