@rangojs/router 0.0.0-experimental.002d056c
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 +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NavigateOptions,
|
|
3
|
+
NavigationStore,
|
|
4
|
+
ResolvedSegment,
|
|
5
|
+
StreamingToken,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
import { generateHistoryKey } from "./navigation-store.js";
|
|
8
|
+
import {
|
|
9
|
+
handleNavigationStart,
|
|
10
|
+
ensureHistoryKey,
|
|
11
|
+
} from "./scroll-restoration.js";
|
|
12
|
+
import type { EventController, NavigationHandle } from "./event-controller.js";
|
|
13
|
+
import { debugLog } from "./logging.js";
|
|
14
|
+
import { buildHistoryState } from "./history-state.js";
|
|
15
|
+
|
|
16
|
+
// Re-export for consumers that import from navigation-transaction
|
|
17
|
+
export { resolveNavigationState } from "./history-state.js";
|
|
18
|
+
|
|
19
|
+
/** Check if a history state object contains location state keys. */
|
|
20
|
+
function hasLocationState(state: unknown): boolean {
|
|
21
|
+
if (!state || typeof state !== "object") return false;
|
|
22
|
+
return (
|
|
23
|
+
"state" in state ||
|
|
24
|
+
Object.keys(state).some((k) => k.startsWith("__rsc_ls_"))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Polyfill Symbol.dispose for Safari and older browsers
|
|
29
|
+
if (typeof Symbol.dispose === "undefined") {
|
|
30
|
+
(Symbol as any).dispose = Symbol("Symbol.dispose");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for committing a navigation transaction
|
|
35
|
+
*/
|
|
36
|
+
interface CommitOptions {
|
|
37
|
+
url: string;
|
|
38
|
+
segmentIds: string[];
|
|
39
|
+
segments: ResolvedSegment[];
|
|
40
|
+
replace?: boolean;
|
|
41
|
+
scroll?: boolean;
|
|
42
|
+
/** User-provided state to store in history.state */
|
|
43
|
+
state?: unknown;
|
|
44
|
+
/** If true, only update store without changing URL/history (for server actions) */
|
|
45
|
+
storeOnly?: boolean;
|
|
46
|
+
/** If true, this is an intercept route - store in history.state for popstate handling */
|
|
47
|
+
intercept?: boolean;
|
|
48
|
+
/** Source URL where the intercept was triggered from (stored in history.state) */
|
|
49
|
+
interceptSourceUrl?: string;
|
|
50
|
+
/** If true, only update cache without touching store or history (for background stale revalidation) */
|
|
51
|
+
cacheOnly?: boolean;
|
|
52
|
+
/** Server-set location state to merge into history.pushState */
|
|
53
|
+
serverState?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Options that can override the pre-configured commit settings
|
|
58
|
+
*/
|
|
59
|
+
interface BoundCommitOverrides {
|
|
60
|
+
/** Override scroll behavior (e.g., disable for intercepts) */
|
|
61
|
+
scroll?: boolean;
|
|
62
|
+
/** Override replace behavior (e.g., force replace for intercepts) */
|
|
63
|
+
replace?: boolean;
|
|
64
|
+
/** Override user-provided state */
|
|
65
|
+
state?: unknown;
|
|
66
|
+
/** Mark this as an intercept route */
|
|
67
|
+
intercept?: boolean;
|
|
68
|
+
/** Source URL where intercept was triggered from */
|
|
69
|
+
interceptSourceUrl?: string;
|
|
70
|
+
/** If true, only update cache (for stale revalidation) */
|
|
71
|
+
cacheOnly?: boolean;
|
|
72
|
+
/** Server-set location state to merge into history.pushState */
|
|
73
|
+
serverState?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Bound transaction with pre-configured commit options (without segmentIds/segments)
|
|
78
|
+
*/
|
|
79
|
+
export interface BoundTransaction {
|
|
80
|
+
readonly currentUrl: string;
|
|
81
|
+
/** Start streaming and get a token to end it when the stream completes */
|
|
82
|
+
startStreaming(): StreamingToken;
|
|
83
|
+
/** Commit the navigation. Returns the effective scroll option for the caller to handle. */
|
|
84
|
+
commit(
|
|
85
|
+
segmentIds: string[],
|
|
86
|
+
segments: ResolvedSegment[],
|
|
87
|
+
overrides?: BoundCommitOverrides,
|
|
88
|
+
): { scroll?: boolean };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Navigation transaction for managing state during navigation
|
|
93
|
+
* Uses the event controller handle for lifecycle management
|
|
94
|
+
*/
|
|
95
|
+
interface NavigationTransaction extends Disposable {
|
|
96
|
+
commit(options: CommitOptions): { scroll?: boolean };
|
|
97
|
+
with(
|
|
98
|
+
options: Omit<CommitOptions, "segmentIds" | "segments">,
|
|
99
|
+
): BoundTransaction;
|
|
100
|
+
/** The navigation handle from the event controller */
|
|
101
|
+
handle: NavigationHandle;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a navigation transaction that coordinates with the event controller.
|
|
106
|
+
* Handles loading state transitions and cleanup on completion/abort.
|
|
107
|
+
*/
|
|
108
|
+
export function createNavigationTransaction(
|
|
109
|
+
store: NavigationStore,
|
|
110
|
+
eventController: EventController,
|
|
111
|
+
url: string,
|
|
112
|
+
options?: NavigateOptions & { skipLoadingState?: boolean },
|
|
113
|
+
): NavigationTransaction {
|
|
114
|
+
let committed = false;
|
|
115
|
+
const currentUrl = window.location.href;
|
|
116
|
+
|
|
117
|
+
// Start navigation in event controller (this sets loading state)
|
|
118
|
+
const handle = eventController.startNavigation(url, options);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Commit the navigation - updates store and URL atomically
|
|
122
|
+
*/
|
|
123
|
+
function commit(opts: CommitOptions): { scroll?: boolean } {
|
|
124
|
+
committed = true;
|
|
125
|
+
|
|
126
|
+
const {
|
|
127
|
+
url,
|
|
128
|
+
segmentIds,
|
|
129
|
+
segments,
|
|
130
|
+
replace,
|
|
131
|
+
scroll,
|
|
132
|
+
storeOnly,
|
|
133
|
+
intercept,
|
|
134
|
+
interceptSourceUrl,
|
|
135
|
+
cacheOnly,
|
|
136
|
+
serverState,
|
|
137
|
+
} = opts;
|
|
138
|
+
|
|
139
|
+
const parsedUrl = new URL(url, window.location.origin);
|
|
140
|
+
|
|
141
|
+
// Generate history key from URL (with intercept suffix for separate caching)
|
|
142
|
+
const historyKey = generateHistoryKey(url, { intercept });
|
|
143
|
+
|
|
144
|
+
// For cache-only commits (stale revalidation), only update cache and return
|
|
145
|
+
// Don't touch store state or history - user may have navigated elsewhere
|
|
146
|
+
if (cacheOnly) {
|
|
147
|
+
const currentHandleData = eventController.getHandleState().data;
|
|
148
|
+
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
149
|
+
// Complete the navigation handle so currentNavigation is cleared.
|
|
150
|
+
// Without this, the entry lingers and weakens state-machine invariants.
|
|
151
|
+
handle.complete(parsedUrl);
|
|
152
|
+
debugLog("[Browser] Cache-only commit, historyKey:", historyKey);
|
|
153
|
+
return { scroll: false };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Save current scroll position before navigating
|
|
157
|
+
handleNavigationStart();
|
|
158
|
+
|
|
159
|
+
// Update segment state atomically
|
|
160
|
+
store.setSegmentIds(segmentIds);
|
|
161
|
+
store.setCurrentUrl(url);
|
|
162
|
+
store.setPath(parsedUrl.pathname);
|
|
163
|
+
|
|
164
|
+
store.setHistoryKey(historyKey);
|
|
165
|
+
|
|
166
|
+
// Cache segments with current handleData for this history entry
|
|
167
|
+
const currentHandleData = eventController.getHandleState().data;
|
|
168
|
+
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
169
|
+
|
|
170
|
+
// For server actions, skip URL/history updates but still complete navigation
|
|
171
|
+
if (storeOnly) {
|
|
172
|
+
debugLog("[Browser] Store updated (action)");
|
|
173
|
+
// Complete navigation to clear loading state
|
|
174
|
+
handle.complete(parsedUrl);
|
|
175
|
+
return { scroll: false };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Build history state - include user state, intercept info, and server-set state
|
|
179
|
+
const historyState = buildHistoryState(
|
|
180
|
+
opts.state,
|
|
181
|
+
{ intercept, sourceUrl: interceptSourceUrl },
|
|
182
|
+
serverState,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Snapshot old state before pushState/replaceState overwrites it.
|
|
186
|
+
// Used to detect when location state is being cleared.
|
|
187
|
+
const oldState = window.history.state;
|
|
188
|
+
|
|
189
|
+
// Update browser URL
|
|
190
|
+
if (replace) {
|
|
191
|
+
window.history.replaceState(historyState, "", url);
|
|
192
|
+
} else {
|
|
193
|
+
window.history.pushState(historyState, "", url);
|
|
194
|
+
}
|
|
195
|
+
// Ensure new history entry has a scroll restoration key
|
|
196
|
+
ensureHistoryKey();
|
|
197
|
+
|
|
198
|
+
// Notify location state hooks when either old or new state carries
|
|
199
|
+
// location state. This covers both "set new state" and "clear old state"
|
|
200
|
+
// for same-page navigations where components don't remount.
|
|
201
|
+
if (hasLocationState(oldState) || hasLocationState(historyState)) {
|
|
202
|
+
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Complete the navigation in event controller (sets idle state, updates location)
|
|
206
|
+
handle.complete(parsedUrl);
|
|
207
|
+
|
|
208
|
+
// NOTE: Scroll is NOT handled here. The caller (partial-update.ts) handles
|
|
209
|
+
// scroll AFTER onUpdate() so React has the new content before we scroll.
|
|
210
|
+
|
|
211
|
+
debugLog(
|
|
212
|
+
"[Browser] Navigation committed, historyKey:",
|
|
213
|
+
historyKey,
|
|
214
|
+
intercept ? "(intercept)" : "",
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return { scroll };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
handle,
|
|
222
|
+
commit,
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create a bound transaction with pre-configured URL options
|
|
226
|
+
* segmentIds and segments provided at commit time (after they're resolved)
|
|
227
|
+
*/
|
|
228
|
+
with(
|
|
229
|
+
opts: Omit<CommitOptions, "segmentIds" | "segments">,
|
|
230
|
+
): BoundTransaction {
|
|
231
|
+
return {
|
|
232
|
+
get currentUrl() {
|
|
233
|
+
return currentUrl;
|
|
234
|
+
},
|
|
235
|
+
startStreaming() {
|
|
236
|
+
return handle.startStreaming();
|
|
237
|
+
},
|
|
238
|
+
commit: (
|
|
239
|
+
segmentIds: string[],
|
|
240
|
+
segments: ResolvedSegment[],
|
|
241
|
+
overrides?: BoundCommitOverrides,
|
|
242
|
+
) => {
|
|
243
|
+
// Allow overrides to disable scroll (e.g., for intercepts)
|
|
244
|
+
const finalScroll =
|
|
245
|
+
overrides?.scroll !== undefined ? overrides.scroll : opts.scroll;
|
|
246
|
+
// Allow overrides to force replace (e.g., for intercepts)
|
|
247
|
+
const finalReplace =
|
|
248
|
+
overrides?.replace !== undefined ? overrides.replace : opts.replace;
|
|
249
|
+
// Intercept info: overrides take precedence, fallback to opts
|
|
250
|
+
const intercept =
|
|
251
|
+
overrides?.intercept !== undefined
|
|
252
|
+
? overrides.intercept
|
|
253
|
+
: opts.intercept;
|
|
254
|
+
const interceptSourceUrl =
|
|
255
|
+
overrides?.interceptSourceUrl !== undefined
|
|
256
|
+
? overrides.interceptSourceUrl
|
|
257
|
+
: opts.interceptSourceUrl;
|
|
258
|
+
// Cache-only mode: overrides take precedence, fallback to opts
|
|
259
|
+
const cacheOnly =
|
|
260
|
+
overrides?.cacheOnly !== undefined
|
|
261
|
+
? overrides.cacheOnly
|
|
262
|
+
: opts.cacheOnly;
|
|
263
|
+
// User state: overrides take precedence, fallback to opts
|
|
264
|
+
const state =
|
|
265
|
+
overrides?.state !== undefined ? overrides.state : opts.state;
|
|
266
|
+
// Server-set location state: only from overrides (set by partial-update)
|
|
267
|
+
const serverState = overrides?.serverState;
|
|
268
|
+
return commit({
|
|
269
|
+
...opts,
|
|
270
|
+
segmentIds,
|
|
271
|
+
segments,
|
|
272
|
+
scroll: finalScroll,
|
|
273
|
+
replace: finalReplace,
|
|
274
|
+
state,
|
|
275
|
+
intercept,
|
|
276
|
+
interceptSourceUrl,
|
|
277
|
+
cacheOnly,
|
|
278
|
+
serverState,
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
[Symbol.dispose]() {
|
|
285
|
+
// Superseded: another navigation took over.
|
|
286
|
+
if (handle.signal.aborted) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Failed (not committed): keep the target URL -- the error UI owns it.
|
|
291
|
+
// Just reset the event controller to idle.
|
|
292
|
+
if (!committed) {
|
|
293
|
+
handle[Symbol.dispose]();
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NetworkError, isNetworkError } from "../errors.js";
|
|
2
|
+
import { NetworkErrorThrower } from "../network-error-thrower.js";
|
|
3
|
+
import type { UpdateSubscriber } from "./types.js";
|
|
4
|
+
import { createElement, startTransition } from "react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert an unknown error to a NetworkError, or return null if not network-related.
|
|
8
|
+
*
|
|
9
|
+
* Pure function: extracts the "is this a network error?" decision from
|
|
10
|
+
* navigation-bridge and server-action-bridge so it can be unit tested.
|
|
11
|
+
*/
|
|
12
|
+
export function toNetworkError(
|
|
13
|
+
error: unknown,
|
|
14
|
+
context: { url: string; operation: "action" | "navigation" | "revalidation" },
|
|
15
|
+
): NetworkError | null {
|
|
16
|
+
if (error instanceof NetworkError) return error;
|
|
17
|
+
if (isNetworkError(error)) {
|
|
18
|
+
return new NetworkError(
|
|
19
|
+
"Unable to connect to server. Please check your connection.",
|
|
20
|
+
{ cause: error, url: context.url, operation: context.operation },
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Emit a NetworkError to the UI via the onUpdate subscriber.
|
|
28
|
+
* Wraps in startTransition and renders a NetworkErrorThrower component
|
|
29
|
+
* that throws during render to trigger the nearest error boundary.
|
|
30
|
+
*/
|
|
31
|
+
export function emitNetworkError(
|
|
32
|
+
onUpdate: UpdateSubscriber,
|
|
33
|
+
error: NetworkError,
|
|
34
|
+
pathname: string,
|
|
35
|
+
): void {
|
|
36
|
+
startTransition(() => {
|
|
37
|
+
onUpdate({
|
|
38
|
+
root: createElement(NetworkErrorThrower, { error }),
|
|
39
|
+
metadata: {
|
|
40
|
+
pathname,
|
|
41
|
+
segments: [],
|
|
42
|
+
isError: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if an error is safe to suppress in background operations.
|
|
50
|
+
*
|
|
51
|
+
* Background revalidation (SWR, navigated-away refetch) should silently
|
|
52
|
+
* swallow AbortErrors and network errors since the user has already moved
|
|
53
|
+
* on and showing an error would be disruptive.
|
|
54
|
+
*
|
|
55
|
+
* Pure function, easily unit tested.
|
|
56
|
+
*/
|
|
57
|
+
export function isBackgroundSuppressible(error: unknown): boolean {
|
|
58
|
+
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
59
|
+
if (error instanceof NetworkError || isNetworkError(error)) return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|