@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -1,10 +1,3 @@
1
- /**
2
- * Package Resolution Utilities
3
- *
4
- * Handles detection of workspace vs npm install context and generates
5
- * appropriate aliases and exclude lists for Vite configuration.
6
- */
7
-
8
1
  import { existsSync } from "node:fs";
9
2
  import { createRequire } from "node:module";
10
3
  import { resolve } from "node:path";
@@ -17,44 +10,20 @@ const require = createRequire(import.meta.url);
17
10
  */
18
11
  const VIRTUAL_PACKAGE_NAME = "@rangojs/router";
19
12
 
20
- /**
21
- * Get the published package name (e.g., "@rangojs/router")
22
- */
23
13
  export function getPublishedPackageName(): string {
24
14
  return packageJson.name;
25
15
  }
26
16
 
27
- /**
28
- * Check if the package is installed from npm (scoped) vs workspace (unscoped)
29
- *
30
- * In workspace development:
31
- * - Package is installed as "@rangojs/router" via pnpm workspace alias
32
- * - The scoped name (@rangojs/router) doesn't exist in node_modules
33
- *
34
- * When installed from npm:
35
- * - Package is installed as "@rangojs/router"
36
- * - We need aliases to map "@rangojs/router/*" to "@rangojs/router/*"
37
- */
38
17
  export function isInstalledFromNpm(): boolean {
39
18
  const packageName = getPublishedPackageName();
40
19
  // Check if the scoped package exists in node_modules
41
20
  return existsSync(resolve(process.cwd(), "node_modules", packageName));
42
21
  }
43
22
 
44
- /**
45
- * Check if we're in a monorepo/workspace development context
46
- */
47
23
  export function isWorkspaceDevelopment(): boolean {
48
24
  return !isInstalledFromNpm();
49
25
  }
50
26
 
51
- /**
52
- * Subpaths derived from package.json exports that use TypeScript source.
53
- * These must be excluded from Vite's dependency optimization (they ship
54
- * as .ts/.tsx, not compiled JS) and aliased when installed from npm.
55
- *
56
- * Derived automatically from the exports field to prevent drift.
57
- */
58
27
  const SOURCE_EXPORT_SUBPATHS = Object.keys(packageJson.exports)
59
28
  .filter((key) => {
60
29
  const entry = (
@@ -70,12 +39,6 @@ const SOURCE_EXPORT_SUBPATHS = Object.keys(packageJson.exports)
70
39
  })
71
40
  .map((key) => key.replace(/^\./, ""));
72
41
 
73
- /**
74
- * Generate the list of modules to exclude from Vite's dependency optimization.
75
- *
76
- * We include both the published name and the virtual name because
77
- * Vite's optimizer runs before alias resolution.
78
- */
79
42
  export function getExcludeDeps(): string[] {
80
43
  const packageName = getPublishedPackageName();
81
44
  const excludes: string[] = [];
@@ -92,24 +55,10 @@ export function getExcludeDeps(): string[] {
92
55
  return excludes;
93
56
  }
94
57
 
95
- /**
96
- * Subpaths that need aliasing — same as SOURCE_EXPORT_SUBPATHS.
97
- * When installed from npm, virtual entries may use a different package name
98
- * than the published one; aliases bridge them.
99
- */
100
58
  const ALIAS_SUBPATHS = SOURCE_EXPORT_SUBPATHS;
101
59
 
102
- /**
103
- * Generate aliases to map virtual package paths to the actual published package.
104
- *
105
- * Only needed when installed from npm, where the package is under @rangojs/router
106
- * but virtual entries import from rsc-router/*.
107
- *
108
- * Returns empty object in workspace development where rsc-router resolves directly.
109
- */
110
60
  export function getPackageAliases(): Record<string, string> {
111
61
  if (isWorkspaceDevelopment()) {
112
- // No aliases needed - rsc-router resolves directly
113
62
  return {};
114
63
  }
115
64
 
@@ -123,28 +72,7 @@ export function getPackageAliases(): Record<string, string> {
123
72
  return aliases;
124
73
  }
125
74
 
126
- /**
127
- * Plugin-rsc pushes bare specs like
128
- * `@vitejs/plugin-rsc/vendor/react-server-dom/client.edge` into
129
- * `optimizeDeps.include` for the ssr and rsc environments. In strict pnpm
130
- * consumer apps, `@vitejs/plugin-rsc` is only reachable from @rangojs/router's
131
- * node_modules, so Vite's optimizer — which resolves from the project root —
132
- * can't find them and emits "Failed to resolve dependency" warnings.
133
- *
134
- * We resolve those specs from this plugin's location (where plugin-rsc is
135
- * guaranteed to be installed as our dep) and expose them as `resolve.alias`
136
- * entries. The optimizer's resolver honors aliases, so the bare specs map to
137
- * absolute paths and resolve cleanly.
138
- */
139
75
  export function getVendorAliases(): Record<string, string> {
140
- // client.browser is intentionally NOT aliased. plugin-rsc injects it into
141
- // the client env's optimizeDeps.include; Vite's manual-include path resolves
142
- // and pre-bundles regardless of optimizeDeps.exclude, so aliasing would
143
- // trigger esbuild pre-bundling of the CJS vendor file and bypass the
144
- // cjs-to-esm transform that patches `require('react'|'react-dom')` into
145
- // real ESM imports. The consumer may still see a single "Failed to resolve"
146
- // warning for client.browser; runtime resolution from plugin-rsc's own
147
- // importer works because Vite resolves relative to the importer (not root).
148
76
  const specs = [
149
77
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
150
78
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
@@ -154,7 +82,7 @@ export function getVendorAliases(): Record<string, string> {
154
82
  try {
155
83
  aliases[spec] = require.resolve(spec);
156
84
  } catch {
157
- // Spec unresolvable (unexpected but non-fatal Vite will warn as before).
85
+ // Non-fatal; Vite will warn if it cannot resolve the spec
158
86
  }
159
87
  }
160
88
  return aliases;
@@ -9,30 +9,13 @@ import {
9
9
  } from "node:fs";
10
10
  import { resolve } from "node:path";
11
11
 
12
- /**
13
- * Escape special RegExp characters in a string for safe interpolation
14
- * into new RegExp() patterns.
15
- */
16
- export function escapeRegExp(str: string): string {
17
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
- }
12
+ import { escapeRegExp } from "../../regex-escape.js";
13
+ import { encodePathRemainder } from "../../router/url-params.js";
19
14
 
20
- /**
21
- * Encode route param values for path interpolation while preserving path
22
- * separators for wildcard params (splat-style values can include `/`).
23
- */
24
15
  export function encodePathParam(value: unknown): string {
25
- return String(value)
26
- .split("/")
27
- .map((segment) => encodeURIComponent(segment))
28
- .join("/");
16
+ return encodePathRemainder(String(value), encodeURIComponent);
29
17
  }
30
18
 
31
- /**
32
- * Substitute route params into a pattern, stripping constraint and optional
33
- * syntax (:param(a|b)? -> value). Also handles wildcard params (*key).
34
- * Optional params not present in `params` are removed from the output.
35
- */
36
19
  export function substituteRouteParams(
37
20
  pattern: string,
38
21
  params: Record<string, string>,
@@ -41,17 +24,24 @@ export function substituteRouteParams(
41
24
  let result = pattern;
42
25
  let hadOmittedOptional = false;
43
26
 
44
- // First pass: substitute provided params.
45
- // Empty string on an optional placeholder is treated as omitted —
46
- // caller-supplied params or `getParams()` shapes may pass `""` for an
47
- // absent optional, so letting the second pass strip them keeps slash
48
- // cleanup consistent. Empty string on required `:key` or wildcard
49
- // `*key` still substitutes, matching prior behaviour.
50
27
  for (const [key, value] of Object.entries(params)) {
51
28
  const escaped = escapeRegExp(key);
29
+ // Named catch-all `:key+` / `:key*` (issue #634). Like the runtime reverse,
30
+ // the value is multi-segment: encode each segment and keep the `/` separators
31
+ // (a whole-value encode would turn `a/b` into `a%2Fb`), and consume the
32
+ // trailing modifier so it does not leak into the URL. `*` (zero-or-more)
33
+ // absent collapses via the trailing cleanup below.
34
+ const catchAllRe = new RegExp(`:${escaped}[+*]`);
35
+ if (catchAllRe.test(result)) {
36
+ if (value === "") {
37
+ result = result.replace(catchAllRe, "");
38
+ hadOmittedOptional = true;
39
+ } else {
40
+ result = result.replace(catchAllRe, encodePathRemainder(value, encode));
41
+ }
42
+ continue;
43
+ }
52
44
  if (value === "") {
53
- // Only replace required placeholders (negative lookahead for `?`);
54
- // leave `:key?` for the second pass.
55
45
  result = result.replace(
56
46
  new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
57
47
  "",
@@ -66,13 +56,11 @@ export function substituteRouteParams(
66
56
  }
67
57
  }
68
58
 
69
- // Second pass: strip remaining optional param placeholders not in params
70
59
  result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
71
60
  hadOmittedOptional = true;
72
61
  return "";
73
62
  });
74
63
 
75
- // Clean up slashes from omitted optional segments
76
64
  if (hadOmittedOptional) {
77
65
  const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
78
66
  result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
@@ -82,10 +70,6 @@ export function substituteRouteParams(
82
70
  return result;
83
71
  }
84
72
 
85
- /**
86
- * Run an async function over items with bounded concurrency.
87
- * Errors propagate immediately and abort remaining work.
88
- */
89
73
  export async function runWithConcurrency<T>(
90
74
  items: T[],
91
75
  concurrency: number,
@@ -97,19 +81,30 @@ export async function runWithConcurrency<T>(
97
81
  return;
98
82
  }
99
83
  let nextIndex = 0;
84
+ // On first failure (the default prerender.onError: "fail" re-throws), stop
85
+ // scheduling new work so the remaining workers don't keep running full RSC
86
+ // renders before the build aborts. The first error is surfaced after all
87
+ // in-flight workers settle, preserving the throw-to-fail-build behavior.
88
+ let firstError: unknown;
89
+ let failed = false;
100
90
  async function worker() {
101
- while (nextIndex < items.length) {
91
+ while (nextIndex < items.length && !failed) {
102
92
  const idx = nextIndex++;
103
- await fn(items[idx]);
93
+ try {
94
+ await fn(items[idx]);
95
+ } catch (err) {
96
+ if (!failed) {
97
+ failed = true;
98
+ firstError = err;
99
+ }
100
+ return;
101
+ }
104
102
  }
105
103
  }
106
104
  await Promise.all(Array.from({ length: limit }, () => worker()));
105
+ if (failed) throw firstError;
107
106
  }
108
107
 
109
- /**
110
- * Group prerender entries by their concurrency setting so each group
111
- * can be rendered with the appropriate parallelism.
112
- */
113
108
  export function groupByConcurrency<T extends { concurrency: number }>(
114
109
  entries: T[],
115
110
  ): { concurrency: number; entries: T[] }[] {
@@ -129,10 +124,6 @@ export function groupByConcurrency<T extends { concurrency: number }>(
129
124
  }));
130
125
  }
131
126
 
132
- /**
133
- * Notify all routers' onError callbacks about a build-time error.
134
- * Uses a synthetic request since there is no real request during build.
135
- */
136
127
  export function notifyOnError(
137
128
  registry: Map<string, any>,
138
129
  error: unknown,
@@ -172,6 +163,42 @@ export function notifyOnError(
172
163
  }
173
164
  }
174
165
 
166
+ /**
167
+ * Resolve a thrown build-time render error into the prerender build's policy and
168
+ * log a per-entry line. A `Skip` (or any render error under `prerender.onError:
169
+ * "warn"`) logs and returns so the caller skips the entry; a render error under
170
+ * the default "fail" logs FAIL, notifies `onError`, and re-throws to fail the
171
+ * build. Shared by `expandPrerenderRoutes` (prerender) and `renderStaticHandlers`
172
+ * (static) so the Skip/warn/fail policy lives in one place. `label` is the padded
173
+ * URL / handler name for the log line; `elapsed` is the per-entry duration string.
174
+ */
175
+ export function resolvePrerenderError(
176
+ registry: Map<string, any>,
177
+ error: any,
178
+ onError: "fail" | "warn",
179
+ label: string,
180
+ elapsed: string,
181
+ phase: "prerender" | "static",
182
+ routeKey?: string,
183
+ pathname?: string,
184
+ ): void {
185
+ const isSkip = error?.name === "Skip";
186
+ if (isSkip || onError === "warn") {
187
+ if (isSkip) {
188
+ console.log(`[rango] SKIP ${label} (${elapsed}ms) - ${error.message}`);
189
+ } else {
190
+ console.warn(
191
+ `[rango] WARN ${label} (${elapsed}ms) - render error, not pre-rendered (prerender.onError: "warn"): ${error.message}`,
192
+ );
193
+ }
194
+ notifyOnError(registry, error, phase, routeKey, pathname, true);
195
+ return;
196
+ }
197
+ console.error(`[rango] FAIL ${label} (${elapsed}ms) - ${error.message}`);
198
+ notifyOnError(registry, error, phase, routeKey, pathname);
199
+ throw error;
200
+ }
201
+
175
202
  function getStagedAssetDir(projectRoot: string): string {
176
203
  return resolve(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
177
204
  }
@@ -1,11 +1,13 @@
1
1
  import type { Plugin, ResolvedConfig } from "vite";
2
2
  import * as Vite from "vite";
3
+ import { isAbsolute, resolve } from "node:path";
3
4
  import { getPublishedPackageName } from "./package-resolution.js";
4
5
  import { performanceTracksOptimizeDepsPlugin } from "../plugins/performance-tracks.js";
5
6
  import {
6
7
  VIRTUAL_ENTRY_BROWSER,
7
8
  VIRTUAL_ENTRY_SSR,
8
9
  getVirtualEntryRSC,
10
+ getVirtualEntryRSCHost,
9
11
  getVirtualVersionContent,
10
12
  VIRTUAL_IDS,
11
13
  } from "../plugins/virtual-entries.js";
@@ -54,13 +56,59 @@ export const sharedRolldownOptions: {
54
56
  plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()],
55
57
  };
56
58
 
59
+ /**
60
+ * Normalize an explicit `hostRouter` option into the specifier emitted as the
61
+ * host entry's `import ... from "<path>"`, and — for the forms that resolve
62
+ * unambiguously against the project root — verify the file exists (failing with
63
+ * a rango message instead of a downstream bundler "failed to resolve").
64
+ *
65
+ * Only a BARE specifier ("src/worker.rsc.tsx") that exists under the root is
66
+ * rewritten (to "./src/...") so it is not read as a package. A bare specifier
67
+ * that does NOT exist under the root is passed through VERBATIM: it may be a
68
+ * Vite alias ("@/worker.rsc.tsx"), an imports-map entry ("#app/worker"), or a
69
+ * workspace package the bundler resolves — rewriting or rejecting those was a
70
+ * regression (aliases worked before normalization existed). "./"/"../", a
71
+ * leading-slash (Vite root-relative), and a filesystem-absolute path are
72
+ * emitted unchanged — Vite resolves each. A leading-slash path and a
73
+ * filesystem-absolute path are indistinguishable on POSIX, so both are treated
74
+ * as "rooted": passed through WITHOUT an existence check (guessing between the
75
+ * two would wrongly reject a valid file — this was a regression the first time
76
+ * the check was added). Only the explicitly-relative forms, which resolve
77
+ * unambiguously against the root, fail fast with a rango message.
78
+ *
79
+ * `exists` is injected so the logic is unit-testable without a real filesystem.
80
+ */
81
+ export function normalizeHostRouterEntry(
82
+ rawInput: string,
83
+ root: string,
84
+ exists: (absPath: string) => boolean,
85
+ ): string {
86
+ const raw = rawInput.replaceAll("\\", "/");
87
+ const isRelative = raw.startsWith("./") || raw.startsWith("../");
88
+ const isRooted = raw.startsWith("/") || isAbsolute(rawInput);
89
+ if (isRooted) return raw;
90
+ if (isRelative) {
91
+ if (!exists(resolve(root, raw))) {
92
+ throw new Error(
93
+ `[rango] hostRouter entry not found: "${rawInput}" (resolved under ` +
94
+ `${root}). Point it at your createHostRouter() module, e.g. ` +
95
+ `rango({ hostRouter: "./src/worker.rsc.tsx" }).`,
96
+ );
97
+ }
98
+ return raw;
99
+ }
100
+ // Bare specifier: an existing root-relative file gets the explicit "./";
101
+ // anything else (alias, imports-map, package) is the bundler's to resolve.
102
+ return exists(resolve(root, raw)) ? "./" + raw : raw;
103
+ }
104
+
57
105
  /**
58
106
  * Create a virtual modules plugin for default entry files.
59
107
  * Provides virtual module content when entries use VIRTUAL_IDS (no custom entry configured).
60
108
  */
61
109
  export function createVirtualEntriesPlugin(
62
110
  entries: { client: string; ssr: string; rsc?: string },
63
- routerPathRef?: { path?: string },
111
+ routerPathRef?: { path?: string; kind?: "router" | "host" },
64
112
  ): Plugin {
65
113
  // Build virtual modules map based on which entries use virtual IDs
66
114
  const virtualModules: Record<string, string> = {};
@@ -108,7 +156,9 @@ export function createVirtualEntriesPlugin(
108
156
  : routerPathRef.path;
109
157
  // Normalize backslashes for Windows (path.join/slice preserve native separators)
110
158
  const absoluteRouterPath = raw.replaceAll("\\", "/");
111
- return getVirtualEntryRSC(absoluteRouterPath);
159
+ return routerPathRef.kind === "host"
160
+ ? getVirtualEntryRSCHost(absoluteRouterPath)
161
+ : getVirtualEntryRSC(absoluteRouterPath);
112
162
  }
113
163
  }
114
164
  return null;
@@ -161,23 +211,9 @@ function isContentHashedAssetConflict(message: string | undefined): boolean {
161
211
  }
162
212
 
163
213
  /**
164
- * Rollup onwarn handler that suppresses known harmless warnings:
165
- * - "use client" directives: handled by the RSC plugin, not relevant to Rollup
166
- * - sourcemap errors: caused by "use client" directive at line 1:0 confusing sourcemap resolution
167
- * - sourcemap incomplete: plugins that transform without generating sourcemaps (router + RSC plugin)
168
- * - dynamic/static mixed imports: expected for router internals (e.g. request-context, cache-scope).
169
- * Under Rolldown (Vite 8) this surfaces as the INEFFECTIVE_DYNAMIC_IMPORT code emitted directly
170
- * by the bundler, rather than the vite:reporter message handled below (Rollup/Vite 7 shape).
171
- * - empty bundle: @vitejs/plugin-rsc scan build (step 1/5) produces an empty "index" chunk
172
- * because the RSC entry is fully externalized during client-reference analysis
173
- * - file name conflicts on content-hashed assets: @vitejs/plugin-rsc copies the rsc
174
- * environment's imported CSS/assets into the client bundle (its assets-manifest
175
- * generateBundle re-emits each via emitFile with an explicit content-hashed
176
- * fileName). When the client bundle already produced that identical asset,
177
- * rollup raises FILE_NAME_CONFLICT even though the bytes are identical (a
178
- * content hash collision IS a content match). Only these are suppressed; a
179
- * collision on a stable name still surfaces. No upstream fix as of
180
- * @vitejs/plugin-rsc@0.5.27; remove when it skips the redundant emit.
214
+ * Suppress known harmless warnings: "use client" directives, sourcemap errors,
215
+ * mixed imports (expected for router internals), empty bundles, and content-hashed
216
+ * asset collisions from @vitejs/plugin-rsc copying RSC-env assets to the client bundle.
181
217
  */
182
218
  export function onwarn(
183
219
  warning: Vite.Rollup.RollupLog,
@@ -197,10 +233,6 @@ export function onwarn(
197
233
  ) {
198
234
  return;
199
235
  }
200
- // @vitejs/plugin-rsc@0.5.14: rsc:virtual:vite-rsc/assets-manifest renderChunk
201
- // returns { code } without map, causing Rollup to warn about incorrect sourcemaps.
202
- // This is harmless (simple string replacement). Remove this suppression if a
203
- // future version of @vitejs/plugin-rsc fixes the missing sourcemap.
204
236
  if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
205
237
  return;
206
238
  }
@@ -215,10 +247,6 @@ export function onwarn(
215
247
  defaultHandler(warning);
216
248
  }
217
249
 
218
- /**
219
- * Manual chunks configuration for client build.
220
- * Splits React and router packages into separate chunks for better caching.
221
- */
222
250
  export function getManualChunks(id: string): string | undefined {
223
251
  const normalized = Vite.normalizePath(id);
224
252
 
@@ -230,16 +258,6 @@ export function getManualChunks(id: string): string | undefined {
230
258
  ) {
231
259
  return "react";
232
260
  }
233
- // Use dynamic package name from package.json
234
- // Check both npm install path and workspace symlink resolved path.
235
- //
236
- // The workspace patterns are anchored to the package's own `src`/`dist` so
237
- // they match the router runtime but NOT consumer apps that merely live under a
238
- // `packages/rangojs-router/` ancestor (the in-repo e2e apps at
239
- // `packages/rangojs-router/e2e/<app>/src/...`). Without the anchor those apps'
240
- // own client components were force-merged into the shared "router" chunk,
241
- // which both misrepresented real-consumer bundles and blocked `clientChunks`
242
- // splitting from relocating them.
243
261
  const packageName = getPublishedPackageName();
244
262
  if (
245
263
  normalized.includes(`node_modules/${packageName}/`) ||
@@ -1,58 +0,0 @@
1
- "use client";
2
-
3
- import { useContext, useCallback } from "react";
4
- import { NavigationStoreContext } from "./context.js";
5
-
6
- /**
7
- * Return type for useClientCache hook
8
- */
9
- export interface ClientCacheControls {
10
- /**
11
- * Clear the client-side navigation cache.
12
- * Call this after data changes that happen outside of server actions
13
- * (e.g., REST API calls, WebSocket updates, etc.)
14
- *
15
- * This will also broadcast to other tabs to clear their caches.
16
- */
17
- clear: () => void;
18
- }
19
-
20
- /**
21
- * Hook to access client-side cache controls
22
- *
23
- * Use this when you need to manually invalidate the navigation cache
24
- * after data changes that happen outside of server actions.
25
- *
26
- * Server actions automatically clear the cache, so you only need this for:
27
- * - REST API mutations
28
- * - WebSocket-driven updates
29
- * - Other non-RSC data changes
30
- *
31
- * @example
32
- * ```tsx
33
- * function DataEditor() {
34
- * const { clear } = useClientCache();
35
- *
36
- * async function handleSave() {
37
- * await fetch('/api/data', { method: 'POST', body: JSON.stringify(data) });
38
- * // Clear cache so back/forward navigation shows fresh data
39
- * clear();
40
- * }
41
- *
42
- * return <button onClick={handleSave}>Save</button>;
43
- * }
44
- * ```
45
- */
46
- export function useClientCache(): ClientCacheControls {
47
- const ctx = useContext(NavigationStoreContext);
48
-
49
- if (!ctx) {
50
- throw new Error("useClientCache must be used within NavigationProvider");
51
- }
52
-
53
- const clear = useCallback(() => {
54
- ctx.store.clearHistoryCache();
55
- }, [ctx]);
56
-
57
- return { clear };
58
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * Shallow comparison utility for selector optimization
3
- *
4
- * Used by useNavigation hook to prevent unnecessary re-renders
5
- * when the selected value hasn't changed.
6
- *
7
- * @param a - First value
8
- * @param b - Second value
9
- * @returns true if values are shallowly equal
10
- */
11
- export function shallow<T>(a: T, b: T): boolean {
12
- // Same reference or primitive equality
13
- if (Object.is(a, b)) return true;
14
-
15
- // Different types or non-objects
16
- if (typeof a !== "object" || typeof b !== "object") return false;
17
-
18
- // Null checks
19
- if (a === null || b === null) return false;
20
-
21
- // Compare object keys
22
- const keysA = Object.keys(a);
23
- const keysB = Object.keys(b);
24
-
25
- if (keysA.length !== keysB.length) return false;
26
-
27
- // Check each key's value with Object.is
28
- for (const key of keysA) {
29
- if (
30
- !Object.is(
31
- (a as Record<string, unknown>)[key],
32
- (b as Record<string, unknown>)[key],
33
- )
34
- ) {
35
- return false;
36
- }
37
- }
38
-
39
- return true;
40
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Built-in handles for rsc-router.
3
- */
4
-
5
- export { Meta } from "./meta.ts";
6
- export { MetaTags } from "./MetaTags.tsx";
7
- export { Breadcrumbs, type BreadcrumbItem } from "./breadcrumbs.ts";
@@ -1,23 +0,0 @@
1
- "use client";
2
-
3
- import type { ReactNode } from "react";
4
- import type { NetworkError } from "./errors.js";
5
-
6
- interface NetworkErrorThrowerProps {
7
- error: NetworkError;
8
- }
9
-
10
- /**
11
- * Client component that throws a NetworkError during render.
12
- * Used to trigger the root error boundary when a network error occurs
13
- * during navigation or server actions.
14
- *
15
- * This must be a separate component because:
16
- * 1. Errors must be thrown during React's render phase to be caught by error boundaries
17
- * 2. The error occurs in async code (fetch), so we need to propagate it to React's render
18
- */
19
- export function NetworkErrorThrower({
20
- error,
21
- }: NetworkErrorThrowerProps): ReactNode {
22
- throw error;
23
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * Cookie Utilities
3
- *
4
- * Parsing and serialization for HTTP cookies used by middleware context.
5
- */
6
-
7
- import type { CookieOptions } from "./middleware-types.js";
8
-
9
- /**
10
- * Parse cookies from Cookie header
11
- */
12
- export function parseCookies(
13
- cookieHeader: string | null,
14
- ): Record<string, string> {
15
- if (!cookieHeader) return {};
16
-
17
- const cookies: Record<string, string> = {};
18
- const pairs = cookieHeader.split(";");
19
-
20
- for (const pair of pairs) {
21
- const [name, ...rest] = pair.trim().split("=");
22
- if (name) {
23
- const raw = rest.join("=");
24
- try {
25
- cookies[name] = decodeURIComponent(raw);
26
- } catch {
27
- // Malformed percent-encoded value (e.g. %zz) - fall back to raw value
28
- cookies[name] = raw;
29
- }
30
- }
31
- }
32
-
33
- return cookies;
34
- }
35
-
36
- /**
37
- * Serialize a cookie for Set-Cookie header
38
- */
39
- export function serializeCookie(
40
- name: string,
41
- value: string,
42
- options: CookieOptions = {},
43
- ): string {
44
- let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
45
-
46
- if (options.domain) cookie += `; Domain=${options.domain}`;
47
- if (options.path) cookie += `; Path=${options.path}`;
48
- if (options.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`;
49
- if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
50
- if (options.httpOnly) cookie += "; HttpOnly";
51
- if (options.secure) cookie += "; Secure";
52
- if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
53
-
54
- return cookie;
55
- }