@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -14,60 +14,43 @@
14
14
  export {
15
15
  Outlet,
16
16
  ParallelOutlet,
17
- OutletProvider,
18
17
  useOutlet,
19
18
  useLoader,
20
19
  ErrorBoundary,
21
20
  type ErrorBoundaryProps,
22
21
  } from "./client.js";
23
22
 
24
- // Re-export the server's createLoader for RSC context
25
- // This version includes the actual loader function
26
23
  export { createLoader } from "./route-definition.js";
27
24
 
28
- // Re-export Link component (can be used in server components)
29
25
  export {
30
26
  Link,
31
27
  type LinkProps,
32
28
  type PrefetchStrategy,
33
29
  } from "./browser/react/Link.js";
34
30
 
35
- // Re-export ScrollRestoration (can be used in server components)
36
31
  export {
37
32
  ScrollRestoration,
38
33
  type ScrollRestorationProps,
39
34
  } from "./browser/react/ScrollRestoration.js";
40
35
 
41
- // Re-export NavigationProvider (needed for setup)
42
36
  export {
43
37
  NavigationProvider,
44
38
  type NavigationProviderProps,
45
39
  } from "./browser/react/NavigationProvider.js";
46
40
 
47
- // Re-export href function (can be used in server components)
48
41
  export { href } from "./href-client.js";
49
42
 
50
- // Mount context re-exports (useMount is client-only, but MountContext can be referenced)
51
43
  export { MountContext } from "./browser/react/mount-context.js";
52
44
 
53
- // Note: useNavigation, useAction, useClientCache are NOT re-exported here
54
- // because they use client-side state and should only be used in client components
45
+ // useNavigation and useAction are NOT re-exported here because they use client-side state
55
46
 
56
- // Handle API - for accumulating data across route segments
57
- // Works in both RSC and client contexts
58
47
  export { createHandle, isHandle, type Handle } from "./handle.js";
59
48
 
60
- // Built-in handles
61
- // Meta handle works in RSC context
62
49
  export { Meta } from "./handles/meta.js";
63
- // MetaTags is a "use client" component that can be imported from RSC
64
50
  export { MetaTags } from "./handles/MetaTags.js";
65
51
  export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
66
- // Breadcrumbs handle works in RSC context
67
52
  export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
68
53
 
69
- // Location state - createLocationState works in RSC (just creates definition)
70
- // useLocationState is NOT exported here as it uses client hooks
71
54
  export {
72
55
  createLocationState,
73
56
  type LocationStateDefinition,
@@ -75,11 +58,13 @@ export {
75
58
  type LocationStateOptions,
76
59
  } from "./browser/react/location-state-shared.js";
77
60
 
78
- // Re-export useHref - it's a "use client" hook
79
61
  export { useHref } from "./browser/react/use-href.js";
80
62
 
81
- // Re-export useHandle - it's a "use client" hook
63
+ export { useReverse } from "./browser/react/use-reverse.js";
64
+
82
65
  export { useHandle } from "./browser/react/use-handle.js";
66
+ // Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
67
+ // (a `ctx.use(Handle).defer()` slot) until it resolves.
68
+ export type { DeferredHandleEntry } from "./defer.js";
83
69
 
84
- // Re-export useLocationState - it's a "use client" hook
85
70
  export { useLocationState } from "./browser/react/location-state.js";
package/src/client.tsx CHANGED
@@ -111,6 +111,11 @@ function useSlotSegment(
111
111
  * the parallel segment with that slot name instead of the default content.
112
112
  * This is used for parallel routes and intercepting routes.
113
113
  *
114
+ * For a named slot, `<Outlet name="@x" />` is equivalent to
115
+ * `<ParallelOutlet name="@x" />` — both run the same resolution + wrapping
116
+ * pipeline. Convention: use bare `<Outlet />` for default content and
117
+ * `<ParallelOutlet name="@x" />` for named slots.
118
+ *
114
119
  * @param name - Optional slot name for parallel/intercept content (must start with @)
115
120
  *
116
121
  * @example
@@ -163,6 +168,9 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
163
168
  * is wrapped in Suspense with the loading component as fallback.
164
169
  * This enables streaming and navigation loading states for parallels.
165
170
  *
171
+ * Equivalent to `<Outlet name="@x" />` for a named slot; ParallelOutlet
172
+ * requires `name` and is named-slot-only, which reads clearer at the call site.
173
+ *
166
174
  * @param name - The slot name (must start with @, e.g., "@modal", "@sidebar")
167
175
  *
168
176
  * @example
@@ -186,10 +194,9 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
186
194
  }
187
195
 
188
196
  // OutletProvider is defined in outlet-provider.tsx to break a circular
189
- // dependency between client.tsx and route-content-wrapper.tsx.
190
- // Imported at the top of this file for local use in Outlet/ParallelOutlet,
191
- // and re-exported here for backwards compatibility.
192
- export { OutletProvider };
197
+ // dependency between client.tsx and route-content-wrapper.tsx. It is imported
198
+ // at the top of this file for local use in Outlet/ParallelOutlet only; it is an
199
+ // internal component and is intentionally not part of the public ./client API.
193
200
 
194
201
  /**
195
202
  * Hook to access outlet content programmatically
@@ -210,10 +217,10 @@ export function useOutlet(): ReactNode {
210
217
  return context?.content ?? null;
211
218
  }
212
219
 
213
- // Loader hooks - re-exported from dedicated file
214
220
  export {
215
221
  useLoader,
216
222
  useFetchLoader,
223
+ useRefreshLoaders,
217
224
  type LoadFunction,
218
225
  type UseLoaderResult,
219
226
  type UseFetchLoaderResult,
@@ -328,12 +335,6 @@ export class ErrorBoundary extends Component<
328
335
  }
329
336
  }
330
337
 
331
- // ============================================================================
332
- // Re-exports from browser/react for convenience
333
- // These are the most commonly used client-side navigation utilities
334
- // ============================================================================
335
-
336
- // Navigation hooks
337
338
  export { useNavigation } from "./browser/react/use-navigation.js";
338
339
  export { useRouter } from "./browser/react/use-router.js";
339
340
  export { usePathname } from "./browser/react/use-pathname.js";
@@ -343,64 +344,55 @@ export type {
343
344
  RouterInstance,
344
345
  RouterNavigateOptions,
345
346
  ReadonlyURLSearchParams,
347
+ ActionState,
348
+ ActionLifecycleState,
346
349
  } from "./browser/types.js";
347
350
 
348
- // Action state tracking hook
349
351
  export {
350
352
  useAction,
351
353
  type ServerActionFunction,
352
354
  } from "./browser/react/use-action.js";
353
355
 
354
- // Segments state hook
355
356
  export {
356
357
  useSegments,
357
358
  type SegmentsState,
358
359
  } from "./browser/react/use-segments.js";
359
360
 
360
- // Client cache controls hook
361
- export {
362
- useClientCache,
363
- type ClientCacheControls,
364
- } from "./browser/react/use-client-cache.js";
365
-
366
- // Provider
367
361
  export {
368
362
  NavigationProvider,
369
363
  type NavigationProviderProps,
370
364
  } from "./browser/react/NavigationProvider.js";
371
365
 
372
- // Link component
373
366
  export {
374
367
  Link,
375
368
  type LinkProps,
376
369
  type PrefetchStrategy,
377
370
  type StateOrGetter,
371
+ type LinkState,
378
372
  } from "./browser/react/Link.js";
379
373
 
380
- // Link status hook
381
374
  export {
382
375
  useLinkStatus,
383
376
  type LinkStatus,
384
377
  } from "./browser/react/use-link-status.js";
385
378
 
386
- // Scroll restoration
387
379
  export {
388
380
  ScrollRestoration,
389
381
  useScrollRestoration,
390
382
  type ScrollRestorationProps,
391
383
  } from "./browser/react/ScrollRestoration.js";
392
384
 
393
- // Handle data hook (client-side only — createHandle/isHandle are server APIs from the root export)
394
385
  export { type Handle } from "./handle.js";
395
386
  export { useHandle } from "./browser/react/use-handle.js";
387
+ // Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
388
+ // (a `ctx.use(Handle).defer()` slot) until it resolves.
389
+ export type { DeferredHandleEntry } from "./defer.js";
396
390
 
397
- // Built-in handles
398
391
  export { Meta } from "./handles/meta.js";
399
392
  export { MetaTags } from "./handles/MetaTags.js";
400
393
  export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
401
394
  export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
402
395
 
403
- // Location state - type-safe navigation state
404
396
  export {
405
397
  createLocationState,
406
398
  useLocationState,
@@ -409,47 +401,19 @@ export {
409
401
  type LocationStateOptions,
410
402
  } from "./browser/react/location-state.js";
411
403
 
412
- // Type-safe href for client-side path validation
413
- export {
414
- href,
415
- type ValidPaths,
416
- type PatternToPath,
417
- type PathResponse,
418
- } from "./href-client.js";
404
+ // Ambient Rango.Path / Rango.PathResponse types (declared in href-client.ts)
405
+ export { href, type PatternToPath } from "./href-client.js";
419
406
 
420
- // Response envelope types for consuming JSON response routes
421
- export type { ResponseEnvelope, ResponseError } from "./urls.js";
407
+ // RFC 9457 error type for JSON response routes
408
+ export type { ProblemDetails } from "./urls.js";
422
409
 
423
- /**
424
- * Type guard for checking if a response envelope contains an error.
425
- *
426
- * @example
427
- * ```typescript
428
- * const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
429
- * if (isResponseError(result)) {
430
- * console.log(result.error.message, result.error.code);
431
- * return;
432
- * }
433
- * result.data // fully typed as Product
434
- * ```
435
- */
436
- export function isResponseError<T>(
437
- result: import("./urls.js").ResponseEnvelope<T>,
438
- ): result is import("./urls.js").ResponseEnvelope<T> & {
439
- error: import("./urls.js").ResponseError;
440
- } {
441
- return result.error !== undefined;
442
- }
443
-
444
- // Mount context for include() scoped components
445
410
  export { useMount } from "./browser/react/use-mount.js";
446
411
  export { MountContext } from "./browser/react/mount-context.js";
447
412
 
448
- // Mount-aware href hook - auto-prefixes paths with include() mount
449
413
  export { useHref } from "./browser/react/use-href.js";
450
414
 
451
- // Type-safe scoped reverse function for scopedReverse<typeof patterns>()
452
- export type { ScopedReverseFunction } from "./reverse.js";
415
+ export { useReverse } from "./browser/react/use-reverse.js";
416
+
417
+ export type { ScopedReverseFunction, LocalReverseFunction } from "./reverse.js";
453
418
 
454
- // Loader definition type - for typing loader props in client components
455
419
  export type { LoaderDefinition } from "./types.js";
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ComponentType } from "react";
8
+ import { isUnderTestRunner } from "./runtime-env.js";
8
9
 
9
10
  /**
10
11
  * Symbol used by React to mark client component references.
@@ -48,11 +49,21 @@ export function isClientComponent(
48
49
  *
49
50
  * @param component - The component to check
50
51
  * @param name - Name to use in error message (e.g., "document")
52
+ * @param opts.allowServerInTest - When true AND running under a test runner
53
+ * (`isUnderTestRunner()`), relax ONLY the "use client" requirement: a server
54
+ * component is accepted. The plugin's "use client" transform does not run in a
55
+ * bare unit test, so a real exported `document` (almost every app sets one) has
56
+ * no client marker and would otherwise throw at `createRouter`, blocking
57
+ * `dispatch`/`assertGeneratedRoutesMatch` against the real router. The document
58
+ * reference is irrelevant to those (no Flight render). The "not a JSX element"
59
+ * guard still fires, and a real dev/build still throws (mirrors the runtime
60
+ * fallback-id gating in handle.ts/loader.ts).
51
61
  * @throws Error if the component is not a client component
52
62
  */
53
63
  export function assertClientComponent(
54
64
  component: ComponentType<unknown> | unknown,
55
65
  name: string,
66
+ opts?: { allowServerInTest?: boolean },
56
67
  ): asserts component is ComponentType<unknown> {
57
68
  if (typeof component !== "function") {
58
69
  throw new Error(
@@ -62,6 +73,14 @@ export function assertClientComponent(
62
73
  );
63
74
  }
64
75
 
76
+ // Under a test runner the "use client" transform did not run, so a real
77
+ // server-rendered `document` has no client marker; accept it (the reference is
78
+ // never serialized in dispatch/route-map checks). Outside a test runner this
79
+ // still throws — the build-time safety net is preserved.
80
+ if (opts?.allowServerInTest && isUnderTestRunner()) {
81
+ return;
82
+ }
83
+
65
84
  if (!isClientComponent(component)) {
66
85
  throw new Error(
67
86
  `${name} must be a client component with "use client" directive at the top of the file. ` +
@@ -12,7 +12,7 @@
12
12
  * interface PaginationData { current: number; total: number }
13
13
  * export const Pagination = createVar<PaginationData>();
14
14
  *
15
- * // Non-cacheable var — throws if set/get inside cache() or "use cache"
15
+ * // Non-cacheable var — ctx.get(User) throws inside a cache() boundary
16
16
  * export const User = createVar<UserData>({ cache: false });
17
17
  *
18
18
  * // handler
@@ -26,7 +26,7 @@
26
26
  export interface ContextVar<T> {
27
27
  readonly __brand: "context-var";
28
28
  readonly key: symbol;
29
- /** When false, the var is non-cacheable — throws inside cache() / "use cache" */
29
+ /** When false, ctx.get(var) throws inside a cache() boundary. */
30
30
  readonly cache: boolean;
31
31
  /** Phantom field to carry the type parameter. Never set at runtime. */
32
32
  readonly __type?: T;
@@ -35,9 +35,9 @@ export interface ContextVar<T> {
35
35
  export interface ContextVarOptions {
36
36
  /**
37
37
  * When false, marks this variable as non-cacheable.
38
- * Setting or getting this var inside a cache() boundary or "use cache"
39
- * function will throw. Use for inherently request-specific data (user
40
- * sessions, auth tokens, etc.) that must never be baked into cached segments.
38
+ * Reading this var with ctx.get() inside a cache() boundary throws. Use for
39
+ * inherently request-specific data (user sessions, auth tokens, etc.) that
40
+ * must never be baked into cached segments.
41
41
  *
42
42
  * @default true
43
43
  */
@@ -70,6 +70,18 @@ export function isContextVar(value: unknown): value is ContextVar<unknown> {
70
70
  );
71
71
  }
72
72
 
73
+ /**
74
+ * Does a variables object hold any entries? Counts both string keys and the
75
+ * symbol-keyed entries (context vars are stored under symbols), so an object
76
+ * carrying only symbol-keyed vars is still reported as non-empty.
77
+ */
78
+ export function hasContextVars(variables: object): boolean {
79
+ return (
80
+ Object.keys(variables).length > 0 ||
81
+ Object.getOwnPropertySymbols(variables).length > 0
82
+ );
83
+ }
84
+
73
85
  /**
74
86
  * Symbol used as a Set stored on the variables object to track
75
87
  * which keys hold non-cacheable values (from write-level { cache: false }).
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from "react";
2
+ import { isLoaderDataResult } from "./types.js";
3
+
4
+ // Shared by segment-system (server) and LoaderResolver (client) so the
5
+ // legacy/ok/error-fallback/throw decode of resolved loader values lives once.
6
+ // Last failing loader wins errorFallback; an error without a fallback throws.
7
+ export function decodeLoaderResults(
8
+ resolvedData: any[],
9
+ loaderIds: string[],
10
+ ): { loaderData: Record<string, any>; errorFallback: ReactNode } {
11
+ const loaderData: Record<string, any> = {};
12
+ let errorFallback: ReactNode = null;
13
+
14
+ for (let i = 0; i < loaderIds.length; i++) {
15
+ const id = loaderIds[i];
16
+ const result = resolvedData[i];
17
+
18
+ if (!isLoaderDataResult(result)) {
19
+ loaderData[id] = result;
20
+ continue;
21
+ }
22
+
23
+ if (result.ok) {
24
+ loaderData[id] = result.data;
25
+ continue;
26
+ }
27
+
28
+ if (result.fallback) {
29
+ errorFallback = result.fallback;
30
+ } else {
31
+ throw new Error(result.error.message);
32
+ }
33
+ }
34
+
35
+ return { loaderData, errorFallback };
36
+ }
package/src/defer.ts ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Deferred handle values — "decide synchronously, resolve late".
3
+ *
4
+ * A handle is pushed from code that holds `ctx` (a route/layout handler), so the
5
+ * decision to push lands before the handles stream seals. But the value often
6
+ * isn't known there — it may come from a deep async component far from the
7
+ * handler. `ctx.use(Handle).defer()` reserves the handle's slot now (synchronous,
8
+ * so ordering and the pre-seal timing hold) and returns a resolver with the SAME
9
+ * signature as the push: you call it later, anywhere in the render, with the same
10
+ * value you would have passed to the push.
11
+ *
12
+ * const breadcrumb = ctx.use(Breadcrumbs); // (item) => void & .defer()
13
+ * const resolve = breadcrumb.defer({ timeoutMs: 5000, else: null });
14
+ * // deep async component, far from ctx:
15
+ * resolve({ label, href, content }); // identical call, just deferred
16
+ *
17
+ * Under the hood the reserved slot is a Promise the renderer `use()`s; RSC Flight
18
+ * streams it as a late row, so a deferred-aware consumer reading the handle
19
+ * (`useHandle`) sees that entry as a `Promise` until it resolves (see
20
+ * {@link DeferredHandleEntry}). The hazard that guards against bugs: a deferred
21
+ * slot whose resolver is never called would keep the Flight stream — and the HTTP
22
+ * response — open forever. So a deferred auto-resolves to `else` after `timeoutMs`
23
+ * (default {@link DEFAULT_DEFER_TIMEOUT_MS}) if the resolver is never called,
24
+ * degrading gracefully (and warning in dev) instead of hanging the request.
25
+ */
26
+
27
+ /** Default auto-resolve window. Long enough for genuine deep async work, short
28
+ * enough that a forgotten resolve does not hang the response indefinitely. */
29
+ export const DEFAULT_DEFER_TIMEOUT_MS = 10_000;
30
+
31
+ /** Options for `ctx.use(Handle).defer()`. */
32
+ export interface DeferOptions<TData> {
33
+ /**
34
+ * Auto-resolve to `else` after this many ms if the resolver is never called,
35
+ * so a forgotten resolve cannot hold the Flight stream — and thus the HTTP
36
+ * response — open. Defaults to {@link DEFAULT_DEFER_TIMEOUT_MS}. `0` or
37
+ * `Infinity` disable the timeout intentionally (not recommended on a request
38
+ * path). Any other non-finite or negative value is treated as a mistake and
39
+ * falls back to the default rather than silently disabling the safety net.
40
+ * Named `timeoutMs` to match the router's `*Ms` duration convention.
41
+ */
42
+ timeoutMs?: number;
43
+ /**
44
+ * Value the slot resolves to if the timeout fires before the resolver is
45
+ * called. Defaults to `undefined` (the deferred item is skipped/empty). For
46
+ * renderable handle content, `null` is the usual graceful fallback, so the
47
+ * type admits `null` even when `TData` does not.
48
+ */
49
+ else?: TData | null;
50
+ }
51
+
52
+ /**
53
+ * The call signature shared by a handle push and the resolver returned by
54
+ * `.defer()`: a concrete value, a `Promise` of the value (Flight streams it as a
55
+ * late row), or a thunk returning a `Promise` (called immediately).
56
+ */
57
+ export type HandlePushFn<TData> = (
58
+ data: TData | Promise<TData> | (() => Promise<TData>),
59
+ ) => void;
60
+
61
+ /**
62
+ * The push function returned by `ctx.use(Handle)`. Call it to push a value now,
63
+ * or call `.defer()` to reserve the slot now and resolve the value later (e.g.
64
+ * from a deep async component) with a timeout safety net.
65
+ */
66
+ export type HandlePush<TData> = HandlePushFn<TData> & {
67
+ /**
68
+ * Reserve this handle's slot synchronously and return a resolver that is
69
+ * push-equal: it takes the same argument shapes as the push (value, Promise, or
70
+ * thunk) and behaves identically. Two things the resolver adds over a direct
71
+ * push: a timeout (if the resolver is never called, the slot auto-resolves to
72
+ * `options.else` after `options.timeoutMs`; calling the resolver cancels it),
73
+ * and — on the action/revalidation path only — a thunk it runs does NOT
74
+ * re-enter the deadlock-guard push-callback scope a direct push thunk gets,
75
+ * because a deferred resolver fires after the handler phase has closed.
76
+ *
77
+ * The reserved slot appears in the accumulated handle data as a pending
78
+ * `Promise` until it resolves (see {@link DeferredHandleEntry}); a
79
+ * deferred-aware consumer narrows thenable entries (`use()`/`await` + null
80
+ * check) before dereferencing.
81
+ */
82
+ defer(options?: DeferOptions<TData>): HandlePushFn<TData>;
83
+ };
84
+
85
+ /**
86
+ * A handle entry a deferred-aware consumer may read from `useHandle`: either a
87
+ * resolved value, or a pending `Promise` that resolves to the value, to `else`,
88
+ * or (when no `else` was given) `undefined` on timeout. Reading code should treat
89
+ * thenable entries as such and narrow before dereferencing.
90
+ */
91
+ export type DeferredHandleEntry<TData> =
92
+ | TData
93
+ | Promise<TData | null | undefined>;
94
+
95
+ // Internal: a timeout-bounded { promise, resolve }. Not part of the public API
96
+ // (the public surface is `ctx.use(Handle).defer()`); exported for `withDefer`
97
+ // and unit tests only. Resolves to `T`, the `else` fallback, or `undefined`.
98
+ export function createDeferred<T>(options?: {
99
+ timeoutMs?: number;
100
+ fallback?: T | null;
101
+ }): {
102
+ promise: Promise<T | null | undefined>;
103
+ resolve: (value: T | null | undefined) => void;
104
+ } {
105
+ let resolveInner!: (value: T | null | undefined) => void;
106
+ let settled = false;
107
+ let timer: ReturnType<typeof setTimeout> | undefined;
108
+
109
+ const promise = new Promise<T | null | undefined>((resolve) => {
110
+ resolveInner = resolve;
111
+ });
112
+
113
+ const finish = (value: T | null | undefined): void => {
114
+ if (settled) return;
115
+ settled = true;
116
+ if (timer !== undefined) {
117
+ clearTimeout(timer);
118
+ timer = undefined;
119
+ }
120
+ resolveInner(value);
121
+ };
122
+
123
+ // 0 and Infinity are documented intentional disables. Any other non-finite or
124
+ // negative value (NaN, -1, a bad parsed config/env) is a mistake — fall back to
125
+ // the default rather than SILENTLY disabling the safety net, which would let a
126
+ // forgotten resolve hang the Flight stream and the response forever.
127
+ const requested = options?.timeoutMs ?? DEFAULT_DEFER_TIMEOUT_MS;
128
+ let ms: number;
129
+ if (requested === 0 || requested === Infinity) {
130
+ ms = requested;
131
+ } else if (Number.isFinite(requested) && requested > 0) {
132
+ ms = requested;
133
+ } else {
134
+ if (process.env.NODE_ENV !== "production") {
135
+ console.warn(
136
+ `[rango] defer(): invalid timeout ${String(requested)}; using the ` +
137
+ `${DEFAULT_DEFER_TIMEOUT_MS}ms default so the safety net stays on. ` +
138
+ `Use 0 or Infinity to disable the timeout intentionally.`,
139
+ );
140
+ }
141
+ ms = DEFAULT_DEFER_TIMEOUT_MS;
142
+ }
143
+
144
+ if (ms > 0 && ms !== Infinity) {
145
+ timer = setTimeout(() => {
146
+ if (process.env.NODE_ENV !== "production") {
147
+ console.warn(
148
+ `[rango] A deferred handle value was not resolved within ${ms}ms; ` +
149
+ `resolving to the fallback so the response can flush. Call the ` +
150
+ `resolver from the component that produces the value, or raise timeoutMs.`,
151
+ );
152
+ }
153
+ finish(options?.fallback);
154
+ }, ms);
155
+ // Don't let a pending timer alone keep a Node process alive (no-op on workerd).
156
+ (timer as { unref?: () => void }).unref?.();
157
+ }
158
+
159
+ return { promise, resolve: finish };
160
+ }
161
+
162
+ /**
163
+ * Attach `.defer()` to a handle push function. The deferred slot is reserved by
164
+ * pushing the deferred promise through the same push (so ordering, sealing, and
165
+ * Flight streaming all reuse the existing path); the returned resolver settles it.
166
+ */
167
+ export function withDefer<TData>(push: HandlePushFn<TData>): HandlePush<TData> {
168
+ const handlePush = push as HandlePush<TData>;
169
+ // Safe to mutate push in place: each ctx.use(Handle) call (request-context.ts,
170
+ // loader-resolution.ts) builds a fresh closure, so .defer never leaks across
171
+ // handles or requests.
172
+ handlePush.defer = (options) => {
173
+ const deferred = createDeferred<TData>({
174
+ timeoutMs: options?.timeoutMs,
175
+ fallback: options?.else,
176
+ });
177
+ // Reserve the slot now by pushing the pending promise (the renderer use()s it).
178
+ push(deferred.promise as Promise<TData>);
179
+ // The resolver is push-equal: a thunk is invoked immediately (as push does)
180
+ // and a Promise is adopted by the reserved slot. Calling it settles the slot
181
+ // and cancels the timeout — the timeout only fires if it is never called.
182
+ const resolveSlot = deferred.resolve as (
183
+ value: TData | Promise<TData>,
184
+ ) => void;
185
+ return (data) => {
186
+ // The thunk runs without re-entering the push-callback scope a direct push
187
+ // thunk gets on the action/revalidation path (loader-resolution.ts): a
188
+ // deferred resolver fires from a deep component after the handler phase has
189
+ // closed, so there is no live deadlock-guard window to exempt.
190
+ resolveSlot(
191
+ typeof data === "function" ? (data as () => Promise<TData>)() : data,
192
+ );
193
+ };
194
+ };
195
+ return handlePush;
196
+ }
package/src/deps/ssr.ts CHANGED
@@ -1,2 +1 @@
1
- // Re-export @vitejs/plugin-rsc/ssr for internal use by virtual entries
2
1
  export { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
package/src/errors.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Custom error classes for RSC Router
2
+ * Custom error classes for Rango
3
3
  *
4
4
  * All errors include:
5
5
  * - Descriptive names for easy identification
@@ -27,6 +27,17 @@ export class RouteNotFoundError extends Error {
27
27
  }
28
28
  }
29
29
 
30
+ // name fallback covers cross-realm errors (Vite dev dupes, RSC serialization)
31
+ // where instanceof fails.
32
+ export function isRouteNotFoundError(
33
+ error: unknown,
34
+ ): error is RouteNotFoundError {
35
+ return (
36
+ error instanceof RouteNotFoundError ||
37
+ (error instanceof Error && error.name === "RouteNotFoundError")
38
+ );
39
+ }
40
+
30
41
  /**
31
42
  * Thrown when data is not found (e.g., product with ID doesn't exist)
32
43
  * Use this in handlers/loaders to trigger the nearest notFoundBoundary
@@ -109,6 +120,24 @@ export class BuildError extends Error {
109
120
  }
110
121
  }
111
122
 
123
+ /**
124
+ * Thrown when a route-definition DSL helper (route/layout/loader/cache/…) is
125
+ * called outside an active urls()/map() builder, so there is no
126
+ * AsyncLocalStorage build context to attach to. The message names the specific
127
+ * helper and how to fix it; the `cause` records the mechanical reason so the
128
+ * failure mode is identifiable (not conflated with an unrelated throw).
129
+ */
130
+ export class DslContextError extends Error {
131
+ name = "DslContextError" as const;
132
+ cause?: unknown;
133
+
134
+ constructor(message: string, options?: ErrorOptions) {
135
+ super(message);
136
+ Object.setPrototypeOf(this, DslContextError.prototype);
137
+ this.cause = options?.cause;
138
+ }
139
+ }
140
+
112
141
  /**
113
142
  * Thrown when a network request fails (server unreachable, no internet, etc.)
114
143
  * This error triggers the root error boundary with retry capability.
@@ -196,7 +225,6 @@ export function isNetworkError(error: unknown): boolean {
196
225
  export class RouterError extends Error {
197
226
  name = "RouterError" as const;
198
227
  code: string;
199
- type?: string;
200
228
  status: number;
201
229
  cause?: unknown;
202
230
 
@@ -205,7 +233,6 @@ export class RouterError extends Error {
205
233
  message: string,
206
234
  options?: {
207
235
  status?: number;
208
- type?: string;
209
236
  cause?: unknown;
210
237
  },
211
238
  ) {
@@ -213,7 +240,6 @@ export class RouterError extends Error {
213
240
  Object.setPrototypeOf(this, RouterError.prototype);
214
241
  this.code = code;
215
242
  this.status = options?.status ?? 500;
216
- this.type = options?.type;
217
243
  this.cause = options?.cause;
218
244
  }
219
245
  }