@rangojs/router 0.0.0-experimental.0f44aca1

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.
Files changed (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,295 @@
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
+ handleNavigationEnd,
11
+ ensureHistoryKey,
12
+ } from "./scroll-restoration.js";
13
+ import type { EventController, NavigationHandle } from "./event-controller.js";
14
+ import { debugLog } from "./logging.js";
15
+ import { buildHistoryState } from "./history-state.js";
16
+
17
+ // Re-export for consumers that import from navigation-transaction
18
+ export { resolveNavigationState } from "./history-state.js";
19
+
20
+ /** Check if a history state object contains location state keys. */
21
+ function hasLocationState(state: unknown): boolean {
22
+ if (!state || typeof state !== "object") return false;
23
+ return (
24
+ "state" in state ||
25
+ Object.keys(state).some((k) => k.startsWith("__rsc_ls_"))
26
+ );
27
+ }
28
+
29
+ // Polyfill Symbol.dispose for Safari and older browsers
30
+ if (typeof Symbol.dispose === "undefined") {
31
+ (Symbol as any).dispose = Symbol("Symbol.dispose");
32
+ }
33
+
34
+ /**
35
+ * Options for committing a navigation transaction
36
+ */
37
+ interface CommitOptions {
38
+ url: string;
39
+ segmentIds: string[];
40
+ segments: ResolvedSegment[];
41
+ replace?: boolean;
42
+ scroll?: boolean;
43
+ /** User-provided state to store in history.state */
44
+ state?: unknown;
45
+ /** If true, only update store without changing URL/history (for server actions) */
46
+ storeOnly?: boolean;
47
+ /** If true, this is an intercept route - store in history.state for popstate handling */
48
+ intercept?: boolean;
49
+ /** Source URL where the intercept was triggered from (stored in history.state) */
50
+ interceptSourceUrl?: string;
51
+ /** If true, only update cache without touching store or history (for background stale revalidation) */
52
+ cacheOnly?: boolean;
53
+ /** Server-set location state to merge into history.pushState */
54
+ serverState?: Record<string, unknown>;
55
+ }
56
+
57
+ /**
58
+ * Options that can override the pre-configured commit settings
59
+ */
60
+ interface BoundCommitOverrides {
61
+ /** Override scroll behavior (e.g., disable for intercepts) */
62
+ scroll?: boolean;
63
+ /** Override replace behavior (e.g., force replace for intercepts) */
64
+ replace?: boolean;
65
+ /** Override user-provided state */
66
+ state?: unknown;
67
+ /** Mark this as an intercept route */
68
+ intercept?: boolean;
69
+ /** Source URL where intercept was triggered from */
70
+ interceptSourceUrl?: string;
71
+ /** If true, only update cache (for stale revalidation) */
72
+ cacheOnly?: boolean;
73
+ /** Server-set location state to merge into history.pushState */
74
+ serverState?: Record<string, unknown>;
75
+ }
76
+
77
+ /**
78
+ * Bound transaction with pre-configured commit options (without segmentIds/segments)
79
+ */
80
+ export interface BoundTransaction {
81
+ readonly currentUrl: string;
82
+ /** Start streaming and get a token to end it when the stream completes */
83
+ startStreaming(): StreamingToken;
84
+ commit(
85
+ segmentIds: string[],
86
+ segments: ResolvedSegment[],
87
+ overrides?: BoundCommitOverrides,
88
+ ): void;
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): void;
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): void {
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;
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;
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
+ // Handle scroll after navigation
209
+ handleNavigationEnd({ scroll });
210
+
211
+ debugLog(
212
+ "[Browser] Navigation committed, historyKey:",
213
+ historyKey,
214
+ intercept ? "(intercept)" : "",
215
+ );
216
+ }
217
+
218
+ return {
219
+ handle,
220
+ commit,
221
+
222
+ /**
223
+ * Create a bound transaction with pre-configured URL options
224
+ * segmentIds and segments provided at commit time (after they're resolved)
225
+ */
226
+ with(
227
+ opts: Omit<CommitOptions, "segmentIds" | "segments">,
228
+ ): BoundTransaction {
229
+ return {
230
+ get currentUrl() {
231
+ return currentUrl;
232
+ },
233
+ startStreaming() {
234
+ return handle.startStreaming();
235
+ },
236
+ commit: (
237
+ segmentIds: string[],
238
+ segments: ResolvedSegment[],
239
+ overrides?: BoundCommitOverrides,
240
+ ) => {
241
+ // Allow overrides to disable scroll (e.g., for intercepts)
242
+ const finalScroll =
243
+ overrides?.scroll !== undefined ? overrides.scroll : opts.scroll;
244
+ // Allow overrides to force replace (e.g., for intercepts)
245
+ const finalReplace =
246
+ overrides?.replace !== undefined ? overrides.replace : opts.replace;
247
+ // Intercept info: overrides take precedence, fallback to opts
248
+ const intercept =
249
+ overrides?.intercept !== undefined
250
+ ? overrides.intercept
251
+ : opts.intercept;
252
+ const interceptSourceUrl =
253
+ overrides?.interceptSourceUrl !== undefined
254
+ ? overrides.interceptSourceUrl
255
+ : opts.interceptSourceUrl;
256
+ // Cache-only mode: overrides take precedence, fallback to opts
257
+ const cacheOnly =
258
+ overrides?.cacheOnly !== undefined
259
+ ? overrides.cacheOnly
260
+ : opts.cacheOnly;
261
+ // User state: overrides take precedence, fallback to opts
262
+ const state =
263
+ overrides?.state !== undefined ? overrides.state : opts.state;
264
+ // Server-set location state: only from overrides (set by partial-update)
265
+ const serverState = overrides?.serverState;
266
+ commit({
267
+ ...opts,
268
+ segmentIds,
269
+ segments,
270
+ scroll: finalScroll,
271
+ replace: finalReplace,
272
+ state,
273
+ intercept,
274
+ interceptSourceUrl,
275
+ cacheOnly,
276
+ serverState,
277
+ });
278
+ },
279
+ };
280
+ },
281
+
282
+ [Symbol.dispose]() {
283
+ // Superseded: another navigation took over.
284
+ if (handle.signal.aborted) {
285
+ return;
286
+ }
287
+
288
+ // Failed (not committed): keep the target URL -- the error UI owns it.
289
+ // Just reset the event controller to idle.
290
+ if (!committed) {
291
+ handle[Symbol.dispose]();
292
+ }
293
+ },
294
+ };
295
+ }
@@ -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
+ }