@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
|
@@ -14,11 +14,34 @@ const addTransitionType: ((type: string) => void) | undefined =
|
|
|
14
14
|
import type { RenderSegmentsOptions } from "../segment-system.js";
|
|
15
15
|
import { reconcileSegments } from "./segment-reconciler.js";
|
|
16
16
|
import type { ReconcileActor } from "./segment-reconciler.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
hasActiveIntercept as hasActiveInterceptSlots,
|
|
19
|
+
isInterceptSegment,
|
|
20
|
+
} from "./intercept-utils.js";
|
|
18
21
|
import type { BoundTransaction } from "./navigation-transaction.js";
|
|
19
22
|
import { ServerRedirect } from "../errors.js";
|
|
20
23
|
import { debugLog } from "./logging.js";
|
|
21
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
validateRedirectOrigin,
|
|
26
|
+
validateExternalRedirect,
|
|
27
|
+
} from "./validate-redirect-origin.js";
|
|
28
|
+
import type { NavigationUpdate } from "./types.js";
|
|
29
|
+
|
|
30
|
+
function toScrollPayload(
|
|
31
|
+
scroll: boolean | undefined,
|
|
32
|
+
): NonNullable<NavigationUpdate["scroll"]> {
|
|
33
|
+
return { enabled: scroll !== false ? scroll : false };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function shouldStartViewTransition(segments: ResolvedSegment[]): boolean {
|
|
37
|
+
let hasIntercept = false;
|
|
38
|
+
let hasTransition = false;
|
|
39
|
+
for (const s of segments) {
|
|
40
|
+
if (isInterceptSegment(s)) hasIntercept = true;
|
|
41
|
+
else if (s.transition) hasTransition = true;
|
|
42
|
+
}
|
|
43
|
+
return !hasIntercept && hasTransition;
|
|
44
|
+
}
|
|
22
45
|
|
|
23
46
|
/**
|
|
24
47
|
* Configuration for creating a partial updater
|
|
@@ -31,8 +54,8 @@ export interface PartialUpdateConfig {
|
|
|
31
54
|
segments: ResolvedSegment[],
|
|
32
55
|
options?: RenderSegmentsOptions,
|
|
33
56
|
) => Promise<ReactNode> | ReactNode;
|
|
34
|
-
/** RSC version
|
|
35
|
-
|
|
57
|
+
/** RSC version getter — returns the current version (may change after HMR) */
|
|
58
|
+
getVersion?: () => string | undefined;
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
/**
|
|
@@ -68,7 +91,7 @@ export type UpdateMode =
|
|
|
68
91
|
/** Source URL for intercept restore (popstate cache miss) */
|
|
69
92
|
interceptSourceUrl?: string;
|
|
70
93
|
}
|
|
71
|
-
| { type: "leave-intercept" }
|
|
94
|
+
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
72
95
|
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
73
96
|
| { type: "action"; interceptSourceUrl?: string };
|
|
74
97
|
|
|
@@ -84,35 +107,23 @@ export type PartialUpdater = (
|
|
|
84
107
|
mode?: UpdateMode,
|
|
85
108
|
) => Promise<void>;
|
|
86
109
|
|
|
87
|
-
/**
|
|
88
|
-
* Create a partial updater for fetching and applying RSC partial updates
|
|
89
|
-
*
|
|
90
|
-
* This function is shared between navigation-bridge and server-action-bridge
|
|
91
|
-
* to handle partial RSC updates with HMR resilience.
|
|
92
|
-
*
|
|
93
|
-
* @param config - Partial update configuration
|
|
94
|
-
* @returns fetchPartialUpdate function
|
|
95
|
-
*/
|
|
96
110
|
export function createPartialUpdater(
|
|
97
111
|
config: PartialUpdateConfig,
|
|
98
112
|
): PartialUpdater {
|
|
99
|
-
const {
|
|
113
|
+
const {
|
|
114
|
+
store,
|
|
115
|
+
client,
|
|
116
|
+
onUpdate,
|
|
117
|
+
renderSegments,
|
|
118
|
+
getVersion = () => undefined,
|
|
119
|
+
} = config;
|
|
100
120
|
|
|
101
|
-
/**
|
|
102
|
-
* Get current page's cached segments as an array
|
|
103
|
-
*/
|
|
104
121
|
function getCurrentCachedSegments(): ResolvedSegment[] {
|
|
105
122
|
const currentKey = store.getHistoryKey();
|
|
106
123
|
const cached = store.getCachedSegments(currentKey);
|
|
107
124
|
return cached?.segments || [];
|
|
108
125
|
}
|
|
109
126
|
|
|
110
|
-
/**
|
|
111
|
-
* Fetch partial update and trigger UI update
|
|
112
|
-
*
|
|
113
|
-
* @param tx - Transaction for committing segment state (required)
|
|
114
|
-
* @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
|
|
115
|
-
*/
|
|
116
127
|
async function fetchPartialUpdate(
|
|
117
128
|
targetUrl: string,
|
|
118
129
|
segmentIds: string[] | undefined,
|
|
@@ -124,26 +135,16 @@ export function createPartialUpdater(
|
|
|
124
135
|
const segmentState = store.getSegmentState();
|
|
125
136
|
const url = targetUrl || window.location.href;
|
|
126
137
|
|
|
127
|
-
// Capture history key at start for stale revalidation consistency check
|
|
128
138
|
const historyKeyAtStart = store.getHistoryKey();
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
const interceptSourceUrl =
|
|
132
|
-
mode.type === "stale-revalidation" ||
|
|
133
|
-
mode.type === "action" ||
|
|
134
|
-
mode.type === "navigate"
|
|
135
|
-
? mode.interceptSourceUrl
|
|
136
|
-
: undefined;
|
|
140
|
+
const interceptSourceUrl = mode.interceptSourceUrl;
|
|
137
141
|
|
|
138
|
-
// When leaving intercept, filter out intercept-specific segments
|
|
139
142
|
let segments: string[];
|
|
140
143
|
if (mode.type === "leave-intercept") {
|
|
141
144
|
const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
|
|
142
145
|
const currentCached = getCurrentCachedSegments();
|
|
143
146
|
const interceptIds = new Set(
|
|
144
|
-
currentCached
|
|
145
|
-
.filter((s) => s.namespace?.startsWith("intercept:"))
|
|
146
|
-
.map((s) => s.id),
|
|
147
|
+
currentCached.filter(isInterceptSegment).map((s) => s.id),
|
|
147
148
|
);
|
|
148
149
|
segments = currentSegments.filter((id) => !interceptIds.has(id));
|
|
149
150
|
debugLog(
|
|
@@ -153,9 +154,10 @@ export function createPartialUpdater(
|
|
|
153
154
|
segments = segmentIds ?? segmentState.currentSegmentIds;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
// For intercept revalidation, use the intercept source URL as previousUrl
|
|
157
157
|
const previousUrl =
|
|
158
|
-
|
|
158
|
+
mode.type === "leave-intercept"
|
|
159
|
+
? segmentState.currentUrl || tx.currentUrl
|
|
160
|
+
: interceptSourceUrl || tx.currentUrl || segmentState.currentUrl;
|
|
159
161
|
|
|
160
162
|
debugLog(`\n[Browser] >>> NAVIGATION`);
|
|
161
163
|
debugLog(`[Browser] From: ${previousUrl}`);
|
|
@@ -165,31 +167,26 @@ export function createPartialUpdater(
|
|
|
165
167
|
debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
// Get cached segments for merging with server diff.
|
|
169
|
-
// When navigating with targetCacheSegments, use those for consistency.
|
|
170
|
-
// Otherwise fall back to current page's segments (for same-route revalidation).
|
|
171
170
|
const targetCache =
|
|
172
|
-
mode.type === "navigate"
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
mode.type === "navigate" && mode.targetCacheSegments?.length
|
|
172
|
+
? mode.targetCacheSegments
|
|
173
|
+
: undefined;
|
|
174
|
+
const cachedSegs = targetCache ?? getCurrentCachedSegments();
|
|
175
|
+
const cachedSegsSource = targetCache ? "history-cache" : "current-page";
|
|
176
|
+
debugLog(
|
|
177
|
+
`[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
|
|
178
|
+
);
|
|
177
179
|
|
|
178
|
-
// Fetch partial payload (no abort signal - RSC doesn't support it well)
|
|
179
180
|
let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
|
|
180
181
|
fetchResult = await client.fetchPartial({
|
|
181
182
|
targetUrl: url,
|
|
182
183
|
segmentIds: segments,
|
|
183
184
|
previousUrl,
|
|
184
|
-
// Mark stale when explicitly requested OR when no segments are sent
|
|
185
|
-
// (action redirect sends empty segments for a fresh render).
|
|
186
185
|
staleRevalidation:
|
|
187
186
|
mode.type === "stale-revalidation" || segments.length === 0,
|
|
188
|
-
version,
|
|
187
|
+
version: getVersion(),
|
|
188
|
+
routerId: store.getRouterId?.(),
|
|
189
189
|
});
|
|
190
|
-
// Mark navigation as streaming (response received, now parsing RSC).
|
|
191
|
-
// Called after fetchPartial so pendingUrl stays set during the network wait,
|
|
192
|
-
// allowing useLinkStatus to show per-link pending indicators.
|
|
193
190
|
const streamingToken = tx.startStreaming();
|
|
194
191
|
const { payload, streamComplete: rawStreamComplete } = fetchResult;
|
|
195
192
|
debugLog("payload.metadata", payload.metadata);
|
|
@@ -198,12 +195,43 @@ export function createPartialUpdater(
|
|
|
198
195
|
streamingToken.end();
|
|
199
196
|
});
|
|
200
197
|
|
|
201
|
-
|
|
198
|
+
const currentRouterId = store.getRouterId?.();
|
|
199
|
+
if (
|
|
200
|
+
payload.metadata?.routerId &&
|
|
201
|
+
currentRouterId &&
|
|
202
|
+
payload.metadata.routerId !== currentRouterId
|
|
203
|
+
) {
|
|
204
|
+
console.error(
|
|
205
|
+
`[rango] Partial response router id "${payload.metadata.routerId}" does not ` +
|
|
206
|
+
`match this client ("${currentRouterId}"); discarding it and reloading to re-sync.`,
|
|
207
|
+
);
|
|
208
|
+
window.location.href = url;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
202
212
|
if (payload.metadata?.redirect) {
|
|
203
213
|
if (signal?.aborted) {
|
|
204
214
|
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
205
215
|
return;
|
|
206
216
|
}
|
|
217
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
218
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
219
|
+
// waives the same-origin check the app opted out of, NOT scheme safety, so
|
|
220
|
+
// a forged payload carrying a javascript:/data: URL cannot script via
|
|
221
|
+
// location.assign.
|
|
222
|
+
if (payload.metadata.redirect.external) {
|
|
223
|
+
const externalUrl = validateExternalRedirect(
|
|
224
|
+
payload.metadata.redirect.url,
|
|
225
|
+
window.location.origin,
|
|
226
|
+
);
|
|
227
|
+
if (!externalUrl) {
|
|
228
|
+
debugLog("[Browser] Ignoring blocked external redirect payload");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
debugLog("[Browser] External redirect (hard navigation)");
|
|
232
|
+
window.location.assign(externalUrl);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
207
235
|
const redirectUrl = validateRedirectOrigin(
|
|
208
236
|
payload.metadata.redirect.url,
|
|
209
237
|
window.location.origin,
|
|
@@ -228,7 +256,6 @@ export function createPartialUpdater(
|
|
|
228
256
|
debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
|
|
229
257
|
debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
|
|
230
258
|
|
|
231
|
-
// If diff is empty, nothing changed on server side.
|
|
232
259
|
if (!diff || diff.length === 0) {
|
|
233
260
|
const matchedIds = matched || [];
|
|
234
261
|
const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
|
|
@@ -236,8 +263,7 @@ export function createPartialUpdater(
|
|
|
236
263
|
.map((id: string) => cacheMap.get(id))
|
|
237
264
|
.filter(Boolean) as ResolvedSegment[];
|
|
238
265
|
|
|
239
|
-
|
|
240
|
-
if (mode.type === "navigate" && targetCache && targetCache.length > 0) {
|
|
266
|
+
if (mode.type === "navigate" && targetCache) {
|
|
241
267
|
debugLog(
|
|
242
268
|
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
243
269
|
);
|
|
@@ -246,12 +272,18 @@ export function createPartialUpdater(
|
|
|
246
272
|
forceAwait: true,
|
|
247
273
|
});
|
|
248
274
|
|
|
249
|
-
tx.commit(
|
|
275
|
+
const { scroll: commitScroll } = tx.commit(
|
|
276
|
+
matchedIds,
|
|
277
|
+
existingSegments,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (mode.targetCacheHandleData) {
|
|
281
|
+
store.updateCacheHandleData(
|
|
282
|
+
store.getHistoryKey(),
|
|
283
|
+
mode.targetCacheHandleData,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
250
286
|
|
|
251
|
-
// Include cachedHandleData in metadata so NavigationProvider can restore
|
|
252
|
-
// breadcrumbs and other handle data from cache.
|
|
253
|
-
// Remove `handles` from metadata to prevent NavigationProvider from
|
|
254
|
-
// processing an empty handles stream, which would clear the cached breadcrumbs.
|
|
255
287
|
const { handles: _unusedHandles, ...metadataWithoutHandles } =
|
|
256
288
|
payload.metadata!;
|
|
257
289
|
const cachedUpdate = {
|
|
@@ -260,12 +292,10 @@ export function createPartialUpdater(
|
|
|
260
292
|
...metadataWithoutHandles,
|
|
261
293
|
cachedHandleData: mode.targetCacheHandleData,
|
|
262
294
|
},
|
|
295
|
+
scroll: toScrollPayload(commitScroll),
|
|
263
296
|
};
|
|
264
297
|
|
|
265
|
-
|
|
266
|
-
(s) => s.transition,
|
|
267
|
-
);
|
|
268
|
-
if (cachedHasTransition) {
|
|
298
|
+
if (shouldStartViewTransition(existingSegments)) {
|
|
269
299
|
startTransition(() => {
|
|
270
300
|
if (addTransitionType) {
|
|
271
301
|
addTransitionType("navigation");
|
|
@@ -280,7 +310,6 @@ export function createPartialUpdater(
|
|
|
280
310
|
return;
|
|
281
311
|
}
|
|
282
312
|
|
|
283
|
-
// When leaving intercept, force re-render even with empty diff
|
|
284
313
|
if (mode.type === "leave-intercept") {
|
|
285
314
|
debugLog(
|
|
286
315
|
"[Browser] Leaving intercept - forcing re-render to remove modal",
|
|
@@ -290,18 +319,21 @@ export function createPartialUpdater(
|
|
|
290
319
|
forceAwait: true,
|
|
291
320
|
});
|
|
292
321
|
|
|
293
|
-
tx.commit(
|
|
322
|
+
const { scroll: leaveScroll } = tx.commit(
|
|
323
|
+
matchedIds,
|
|
324
|
+
existingSegments,
|
|
325
|
+
);
|
|
294
326
|
|
|
295
327
|
onUpdate({
|
|
296
328
|
root: newTree,
|
|
297
329
|
metadata: payload.metadata,
|
|
330
|
+
scroll: toScrollPayload(leaveScroll),
|
|
298
331
|
});
|
|
299
332
|
|
|
300
333
|
debugLog("[Browser] Navigation complete (left intercept)");
|
|
301
334
|
return;
|
|
302
335
|
}
|
|
303
336
|
|
|
304
|
-
// Same route revalidation with no changes - skip UI update
|
|
305
337
|
debugLog(
|
|
306
338
|
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
307
339
|
);
|
|
@@ -310,7 +342,6 @@ export function createPartialUpdater(
|
|
|
310
342
|
return;
|
|
311
343
|
}
|
|
312
344
|
|
|
313
|
-
// Reconcile server segments with cached segments (single source of truth)
|
|
314
345
|
const matchedIds = matched || [];
|
|
315
346
|
const actor: ReconcileActor =
|
|
316
347
|
mode.type === "stale-revalidation" || mode.type === "action"
|
|
@@ -326,7 +357,6 @@ export function createPartialUpdater(
|
|
|
326
357
|
insertMissingDiff: true,
|
|
327
358
|
});
|
|
328
359
|
|
|
329
|
-
// HMR RESILIENCE: Check if we're missing any matched segments
|
|
330
360
|
const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
|
|
331
361
|
const missingIds = matchedIds.filter(
|
|
332
362
|
(id: string) => !reconciledIdSet.has(id),
|
|
@@ -354,7 +384,6 @@ export function createPartialUpdater(
|
|
|
354
384
|
`[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
|
|
355
385
|
);
|
|
356
386
|
|
|
357
|
-
// Refetch with empty segments = server sends everything
|
|
358
387
|
return fetchPartialUpdate(url, [], true, signal, tx, mode);
|
|
359
388
|
}
|
|
360
389
|
|
|
@@ -363,7 +392,6 @@ export function createPartialUpdater(
|
|
|
363
392
|
return;
|
|
364
393
|
}
|
|
365
394
|
|
|
366
|
-
// Rebuild tree on client (await for loader data resolution)
|
|
367
395
|
const renderOptions = {
|
|
368
396
|
isAction: mode.type === "action",
|
|
369
397
|
forceAwait: mode.type === "stale-revalidation",
|
|
@@ -386,21 +414,15 @@ export function createPartialUpdater(
|
|
|
386
414
|
])
|
|
387
415
|
: renderSegments(reconciled.mainSegments, renderOptions));
|
|
388
416
|
|
|
389
|
-
// Final abort check before committing - another navigation may have started
|
|
390
417
|
if (signal?.aborted) {
|
|
391
418
|
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
392
419
|
return;
|
|
393
420
|
}
|
|
394
421
|
|
|
395
|
-
// Check if this is an intercept response (any slot is active)
|
|
396
422
|
const isInterceptResponse = hasActiveInterceptSlots(
|
|
397
423
|
payload.metadata?.slots,
|
|
398
424
|
);
|
|
399
425
|
|
|
400
|
-
// Track intercept context (only on navigation, not actions or stale revalidation)
|
|
401
|
-
// Use the authoritative source from mode/history state when restoring an
|
|
402
|
-
// intercept via popstate cache miss; fall back to the current URL for fresh
|
|
403
|
-
// intercept navigations.
|
|
404
426
|
const effectiveInterceptSource =
|
|
405
427
|
interceptSourceUrl || segmentState.currentUrl;
|
|
406
428
|
if (mode.type !== "action" && mode.type !== "stale-revalidation") {
|
|
@@ -411,8 +433,7 @@ export function createPartialUpdater(
|
|
|
411
433
|
}
|
|
412
434
|
}
|
|
413
435
|
|
|
414
|
-
|
|
415
|
-
const allSegmentIds = reconciled.segments.map((s) => s.id);
|
|
436
|
+
const allSegmentIds = matchedIds;
|
|
416
437
|
const serverLocationState = payload.metadata?.locationState;
|
|
417
438
|
const overrides: CommitOverrides | undefined = isInterceptResponse
|
|
418
439
|
? {
|
|
@@ -424,9 +445,12 @@ export function createPartialUpdater(
|
|
|
424
445
|
: serverLocationState
|
|
425
446
|
? { serverState: serverLocationState }
|
|
426
447
|
: undefined;
|
|
427
|
-
tx.commit(
|
|
448
|
+
const { scroll: navScroll } = tx.commit(
|
|
449
|
+
allSegmentIds,
|
|
450
|
+
reconciled.segments,
|
|
451
|
+
overrides,
|
|
452
|
+
);
|
|
428
453
|
|
|
429
|
-
// For stale revalidation: verify history key hasn't changed before updating UI
|
|
430
454
|
if (mode.type === "stale-revalidation") {
|
|
431
455
|
const historyKeyNow = store.getHistoryKey();
|
|
432
456
|
if (historyKeyNow !== historyKeyAtStart) {
|
|
@@ -439,8 +463,8 @@ export function createPartialUpdater(
|
|
|
439
463
|
|
|
440
464
|
debugLog("[partial-update] updating document");
|
|
441
465
|
|
|
442
|
-
|
|
443
|
-
const
|
|
466
|
+
const hasTransition = shouldStartViewTransition(reconciled.segments);
|
|
467
|
+
const scrollPayload = toScrollPayload(navScroll);
|
|
444
468
|
|
|
445
469
|
if (mode.type === "action" || mode.type === "stale-revalidation") {
|
|
446
470
|
startTransition(() => {
|
|
@@ -450,6 +474,7 @@ export function createPartialUpdater(
|
|
|
450
474
|
onUpdate({
|
|
451
475
|
root: newTree,
|
|
452
476
|
metadata: payload.metadata!,
|
|
477
|
+
scroll: scrollPayload,
|
|
453
478
|
});
|
|
454
479
|
});
|
|
455
480
|
} else if (hasTransition) {
|
|
@@ -460,19 +485,20 @@ export function createPartialUpdater(
|
|
|
460
485
|
onUpdate({
|
|
461
486
|
root: newTree,
|
|
462
487
|
metadata: payload.metadata!,
|
|
488
|
+
scroll: scrollPayload,
|
|
463
489
|
});
|
|
464
490
|
});
|
|
465
491
|
} else {
|
|
466
492
|
onUpdate({
|
|
467
493
|
root: newTree,
|
|
468
494
|
metadata: payload.metadata!,
|
|
495
|
+
scroll: scrollPayload,
|
|
469
496
|
});
|
|
470
497
|
}
|
|
471
498
|
|
|
472
499
|
debugLog("[Browser] Navigation complete");
|
|
473
500
|
return;
|
|
474
501
|
} else {
|
|
475
|
-
// Full update (fallback)
|
|
476
502
|
console.warn(`[Browser] Full update (fallback)`);
|
|
477
503
|
|
|
478
504
|
const segments = payload.metadata?.segments || [];
|
|
@@ -492,15 +518,14 @@ export function createPartialUpdater(
|
|
|
492
518
|
}
|
|
493
519
|
|
|
494
520
|
const fullUpdateServerState = payload.metadata?.locationState;
|
|
495
|
-
|
|
496
|
-
tx.commit(segmentIds, segments, {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
521
|
+
const { scroll: fullScroll } = fullUpdateServerState
|
|
522
|
+
? tx.commit(segmentIds, segments, {
|
|
523
|
+
serverState: fullUpdateServerState,
|
|
524
|
+
})
|
|
525
|
+
: tx.commit(segmentIds, segments);
|
|
500
526
|
|
|
501
|
-
const fullHasTransition = segments
|
|
502
|
-
|
|
503
|
-
);
|
|
527
|
+
const fullHasTransition = shouldStartViewTransition(segments);
|
|
528
|
+
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
504
529
|
|
|
505
530
|
if (mode.type === "stale-revalidation") {
|
|
506
531
|
await rawStreamComplete;
|
|
@@ -511,6 +536,7 @@ export function createPartialUpdater(
|
|
|
511
536
|
onUpdate({
|
|
512
537
|
root: newTree,
|
|
513
538
|
metadata: payload.metadata!,
|
|
539
|
+
scroll: fullScrollPayload,
|
|
514
540
|
});
|
|
515
541
|
});
|
|
516
542
|
} else if (mode.type === "action") {
|
|
@@ -521,6 +547,7 @@ export function createPartialUpdater(
|
|
|
521
547
|
onUpdate({
|
|
522
548
|
root: newTree,
|
|
523
549
|
metadata: payload.metadata!,
|
|
550
|
+
scroll: fullScrollPayload,
|
|
524
551
|
});
|
|
525
552
|
});
|
|
526
553
|
} else if (fullHasTransition) {
|
|
@@ -531,12 +558,14 @@ export function createPartialUpdater(
|
|
|
531
558
|
onUpdate({
|
|
532
559
|
root: newTree,
|
|
533
560
|
metadata: payload.metadata!,
|
|
561
|
+
scroll: fullScrollPayload,
|
|
534
562
|
});
|
|
535
563
|
});
|
|
536
564
|
} else {
|
|
537
565
|
onUpdate({
|
|
538
566
|
root: newTree,
|
|
539
567
|
metadata: payload.metadata!,
|
|
568
|
+
scroll: fullScrollPayload,
|
|
540
569
|
});
|
|
541
570
|
}
|
|
542
571
|
|