@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81

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 (316) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +5091 -941
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +61 -52
  7. package/skills/breadcrumbs/SKILL.md +250 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +167 -0
  14. package/skills/handler-use/SKILL.md +362 -0
  15. package/skills/hooks/SKILL.md +340 -72
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/intercept/SKILL.md +151 -8
  18. package/skills/layout/SKILL.md +122 -3
  19. package/skills/links/SKILL.md +92 -31
  20. package/skills/loader/SKILL.md +404 -44
  21. package/skills/middleware/SKILL.md +205 -37
  22. package/skills/migrate-nextjs/SKILL.md +560 -0
  23. package/skills/migrate-react-router/SKILL.md +765 -0
  24. package/skills/mime-routes/SKILL.md +128 -0
  25. package/skills/parallel/SKILL.md +263 -1
  26. package/skills/prerender/SKILL.md +685 -0
  27. package/skills/rango/SKILL.md +87 -16
  28. package/skills/response-routes/SKILL.md +411 -0
  29. package/skills/route/SKILL.md +281 -14
  30. package/skills/router-setup/SKILL.md +210 -32
  31. package/skills/tailwind/SKILL.md +129 -0
  32. package/skills/theme/SKILL.md +9 -8
  33. package/skills/typesafety/SKILL.md +328 -89
  34. package/skills/use-cache/SKILL.md +324 -0
  35. package/src/__internal.ts +102 -4
  36. package/src/bin/rango.ts +321 -0
  37. package/src/browser/action-coordinator.ts +97 -0
  38. package/src/browser/action-response-classifier.ts +99 -0
  39. package/src/browser/app-version.ts +14 -0
  40. package/src/browser/event-controller.ts +92 -64
  41. package/src/browser/history-state.ts +80 -0
  42. package/src/browser/intercept-utils.ts +52 -0
  43. package/src/browser/link-interceptor.ts +24 -4
  44. package/src/browser/logging.ts +55 -0
  45. package/src/browser/merge-segment-loaders.ts +20 -12
  46. package/src/browser/navigation-bridge.ts +317 -560
  47. package/src/browser/navigation-client.ts +206 -68
  48. package/src/browser/navigation-store.ts +73 -55
  49. package/src/browser/navigation-transaction.ts +297 -0
  50. package/src/browser/network-error-handler.ts +61 -0
  51. package/src/browser/partial-update.ts +343 -316
  52. package/src/browser/prefetch/cache.ts +216 -0
  53. package/src/browser/prefetch/fetch.ts +206 -0
  54. package/src/browser/prefetch/observer.ts +65 -0
  55. package/src/browser/prefetch/policy.ts +48 -0
  56. package/src/browser/prefetch/queue.ts +160 -0
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +112 -0
  59. package/src/browser/react/Link.tsx +253 -74
  60. package/src/browser/react/NavigationProvider.tsx +91 -11
  61. package/src/browser/react/context.ts +11 -0
  62. package/src/browser/react/filter-segment-order.ts +11 -0
  63. package/src/browser/react/index.ts +12 -12
  64. package/src/browser/react/location-state-shared.ts +95 -53
  65. package/src/browser/react/location-state.ts +60 -15
  66. package/src/browser/react/mount-context.ts +6 -1
  67. package/src/browser/react/nonce-context.ts +23 -0
  68. package/src/browser/react/shallow-equal.ts +27 -0
  69. package/src/browser/react/use-action.ts +29 -51
  70. package/src/browser/react/use-client-cache.ts +5 -3
  71. package/src/browser/react/use-handle.ts +30 -126
  72. package/src/browser/react/use-href.tsx +2 -2
  73. package/src/browser/react/use-link-status.ts +6 -5
  74. package/src/browser/react/use-navigation.ts +44 -65
  75. package/src/browser/react/use-params.ts +75 -0
  76. package/src/browser/react/use-pathname.ts +47 -0
  77. package/src/browser/react/use-router.ts +76 -0
  78. package/src/browser/react/use-search-params.ts +56 -0
  79. package/src/browser/react/use-segments.ts +80 -97
  80. package/src/browser/response-adapter.ts +73 -0
  81. package/src/browser/rsc-router.tsx +214 -58
  82. package/src/browser/scroll-restoration.ts +127 -52
  83. package/src/browser/segment-reconciler.ts +243 -0
  84. package/src/browser/segment-structure-assert.ts +16 -0
  85. package/src/browser/server-action-bridge.ts +510 -603
  86. package/src/browser/shallow.ts +6 -1
  87. package/src/browser/types.ts +141 -48
  88. package/src/browser/validate-redirect-origin.ts +29 -0
  89. package/src/build/generate-manifest.ts +235 -24
  90. package/src/build/generate-route-types.ts +39 -0
  91. package/src/build/index.ts +13 -0
  92. package/src/build/route-trie.ts +291 -0
  93. package/src/build/route-types/ast-helpers.ts +25 -0
  94. package/src/build/route-types/ast-route-extraction.ts +98 -0
  95. package/src/build/route-types/codegen.ts +102 -0
  96. package/src/build/route-types/include-resolution.ts +418 -0
  97. package/src/build/route-types/param-extraction.ts +48 -0
  98. package/src/build/route-types/per-module-writer.ts +128 -0
  99. package/src/build/route-types/router-processing.ts +618 -0
  100. package/src/build/route-types/scan-filter.ts +85 -0
  101. package/src/build/runtime-discovery.ts +231 -0
  102. package/src/cache/background-task.ts +34 -0
  103. package/src/cache/cache-key-utils.ts +44 -0
  104. package/src/cache/cache-policy.ts +125 -0
  105. package/src/cache/cache-runtime.ts +342 -0
  106. package/src/cache/cache-scope.ts +167 -309
  107. package/src/cache/cf/cf-cache-store.ts +571 -17
  108. package/src/cache/cf/index.ts +13 -3
  109. package/src/cache/document-cache.ts +116 -77
  110. package/src/cache/handle-capture.ts +81 -0
  111. package/src/cache/handle-snapshot.ts +41 -0
  112. package/src/cache/index.ts +1 -15
  113. package/src/cache/memory-segment-store.ts +191 -13
  114. package/src/cache/profile-registry.ts +73 -0
  115. package/src/cache/read-through-swr.ts +134 -0
  116. package/src/cache/segment-codec.ts +256 -0
  117. package/src/cache/taint.ts +153 -0
  118. package/src/cache/types.ts +72 -122
  119. package/src/client.rsc.tsx +3 -1
  120. package/src/client.tsx +135 -301
  121. package/src/component-utils.ts +4 -4
  122. package/src/components/DefaultDocument.tsx +5 -1
  123. package/src/context-var.ts +156 -0
  124. package/src/debug.ts +19 -9
  125. package/src/errors.ts +108 -2
  126. package/src/handle.ts +55 -29
  127. package/src/handles/MetaTags.tsx +73 -20
  128. package/src/handles/breadcrumbs.ts +66 -0
  129. package/src/handles/index.ts +1 -0
  130. package/src/handles/meta.ts +30 -13
  131. package/src/host/cookie-handler.ts +21 -15
  132. package/src/host/errors.ts +8 -8
  133. package/src/host/index.ts +4 -7
  134. package/src/host/pattern-matcher.ts +27 -27
  135. package/src/host/router.ts +61 -39
  136. package/src/host/testing.ts +8 -8
  137. package/src/host/types.ts +15 -7
  138. package/src/host/utils.ts +1 -1
  139. package/src/href-client.ts +119 -29
  140. package/src/index.rsc.ts +155 -19
  141. package/src/index.ts +251 -30
  142. package/src/internal-debug.ts +11 -0
  143. package/src/loader.rsc.ts +26 -157
  144. package/src/loader.ts +27 -10
  145. package/src/network-error-thrower.tsx +3 -1
  146. package/src/outlet-provider.tsx +45 -0
  147. package/src/prerender/param-hash.ts +37 -0
  148. package/src/prerender/store.ts +186 -0
  149. package/src/prerender.ts +524 -0
  150. package/src/reverse.ts +354 -0
  151. package/src/root-error-boundary.tsx +41 -29
  152. package/src/route-content-wrapper.tsx +7 -4
  153. package/src/route-definition/dsl-helpers.ts +1121 -0
  154. package/src/route-definition/helper-factories.ts +200 -0
  155. package/src/route-definition/helpers-types.ts +478 -0
  156. package/src/route-definition/index.ts +55 -0
  157. package/src/route-definition/redirect.ts +101 -0
  158. package/src/route-definition/resolve-handler-use.ts +149 -0
  159. package/src/route-definition.ts +1 -1428
  160. package/src/route-map-builder.ts +217 -123
  161. package/src/route-name.ts +53 -0
  162. package/src/route-types.ts +77 -8
  163. package/src/router/content-negotiation.ts +215 -0
  164. package/src/router/debug-manifest.ts +72 -0
  165. package/src/router/error-handling.ts +9 -9
  166. package/src/router/find-match.ts +160 -0
  167. package/src/router/handler-context.ts +438 -86
  168. package/src/router/intercept-resolution.ts +402 -0
  169. package/src/router/lazy-includes.ts +237 -0
  170. package/src/router/loader-resolution.ts +356 -128
  171. package/src/router/logging.ts +251 -0
  172. package/src/router/manifest.ts +163 -35
  173. package/src/router/match-api.ts +555 -0
  174. package/src/router/match-context.ts +5 -3
  175. package/src/router/match-handlers.ts +440 -0
  176. package/src/router/match-middleware/background-revalidation.ts +108 -93
  177. package/src/router/match-middleware/cache-lookup.ts +460 -10
  178. package/src/router/match-middleware/cache-store.ts +98 -26
  179. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  180. package/src/router/match-middleware/segment-resolution.ts +80 -6
  181. package/src/router/match-pipelines.ts +10 -45
  182. package/src/router/match-result.ts +135 -35
  183. package/src/router/metrics.ts +240 -15
  184. package/src/router/middleware-cookies.ts +55 -0
  185. package/src/router/middleware-types.ts +220 -0
  186. package/src/router/middleware.ts +324 -369
  187. package/src/router/navigation-snapshot.ts +182 -0
  188. package/src/router/pattern-matching.ts +211 -43
  189. package/src/router/prerender-match.ts +502 -0
  190. package/src/router/preview-match.ts +98 -0
  191. package/src/router/request-classification.ts +310 -0
  192. package/src/router/revalidation.ts +137 -38
  193. package/src/router/route-snapshot.ts +245 -0
  194. package/src/router/router-context.ts +41 -21
  195. package/src/router/router-interfaces.ts +484 -0
  196. package/src/router/router-options.ts +618 -0
  197. package/src/router/router-registry.ts +24 -0
  198. package/src/router/segment-resolution/fresh.ts +748 -0
  199. package/src/router/segment-resolution/helpers.ts +268 -0
  200. package/src/router/segment-resolution/loader-cache.ts +199 -0
  201. package/src/router/segment-resolution/revalidation.ts +1379 -0
  202. package/src/router/segment-resolution/static-store.ts +67 -0
  203. package/src/router/segment-resolution.ts +21 -0
  204. package/src/router/segment-wrappers.ts +291 -0
  205. package/src/router/telemetry-otel.ts +299 -0
  206. package/src/router/telemetry.ts +300 -0
  207. package/src/router/timeout.ts +148 -0
  208. package/src/router/trie-matching.ts +239 -0
  209. package/src/router/types.ts +78 -3
  210. package/src/router.ts +740 -4252
  211. package/src/rsc/handler-context.ts +45 -0
  212. package/src/rsc/handler.ts +907 -797
  213. package/src/rsc/helpers.ts +140 -6
  214. package/src/rsc/index.ts +0 -20
  215. package/src/rsc/loader-fetch.ts +229 -0
  216. package/src/rsc/manifest-init.ts +90 -0
  217. package/src/rsc/nonce.ts +14 -0
  218. package/src/rsc/origin-guard.ts +141 -0
  219. package/src/rsc/progressive-enhancement.ts +393 -0
  220. package/src/rsc/response-error.ts +37 -0
  221. package/src/rsc/response-route-handler.ts +347 -0
  222. package/src/rsc/rsc-rendering.ts +246 -0
  223. package/src/rsc/runtime-warnings.ts +42 -0
  224. package/src/rsc/server-action.ts +358 -0
  225. package/src/rsc/ssr-setup.ts +128 -0
  226. package/src/rsc/types.ts +46 -11
  227. package/src/search-params.ts +230 -0
  228. package/src/segment-content-promise.ts +67 -0
  229. package/src/segment-loader-promise.ts +122 -0
  230. package/src/segment-system.tsx +134 -36
  231. package/src/server/context.ts +341 -61
  232. package/src/server/cookie-store.ts +190 -0
  233. package/src/server/fetchable-loader-store.ts +37 -0
  234. package/src/server/handle-store.ts +113 -15
  235. package/src/server/loader-registry.ts +24 -64
  236. package/src/server/request-context.ts +607 -81
  237. package/src/server.ts +35 -130
  238. package/src/ssr/index.tsx +103 -30
  239. package/src/static-handler.ts +126 -0
  240. package/src/theme/ThemeProvider.tsx +21 -15
  241. package/src/theme/ThemeScript.tsx +5 -5
  242. package/src/theme/constants.ts +5 -2
  243. package/src/theme/index.ts +4 -14
  244. package/src/theme/theme-context.ts +4 -30
  245. package/src/theme/theme-script.ts +21 -18
  246. package/src/types/boundaries.ts +158 -0
  247. package/src/types/cache-types.ts +198 -0
  248. package/src/types/error-types.ts +192 -0
  249. package/src/types/global-namespace.ts +100 -0
  250. package/src/types/handler-context.ts +791 -0
  251. package/src/types/index.ts +88 -0
  252. package/src/types/loader-types.ts +210 -0
  253. package/src/types/route-config.ts +170 -0
  254. package/src/types/route-entry.ts +120 -0
  255. package/src/types/segments.ts +150 -0
  256. package/src/types.ts +1 -1623
  257. package/src/urls/include-helper.ts +207 -0
  258. package/src/urls/index.ts +53 -0
  259. package/src/urls/path-helper-types.ts +372 -0
  260. package/src/urls/path-helper.ts +364 -0
  261. package/src/urls/pattern-types.ts +107 -0
  262. package/src/urls/response-types.ts +116 -0
  263. package/src/urls/type-extraction.ts +372 -0
  264. package/src/urls/urls-function.ts +98 -0
  265. package/src/urls.ts +1 -802
  266. package/src/use-loader.tsx +161 -81
  267. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  268. package/src/vite/discovery/discover-routers.ts +348 -0
  269. package/src/vite/discovery/prerender-collection.ts +439 -0
  270. package/src/vite/discovery/route-types-writer.ts +258 -0
  271. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  272. package/src/vite/discovery/state.ts +117 -0
  273. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  274. package/src/vite/index.ts +15 -1133
  275. package/src/vite/plugin-types.ts +103 -0
  276. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  277. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  278. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  279. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  280. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  281. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  282. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  283. package/src/vite/plugins/expose-id-utils.ts +299 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  290. package/src/vite/plugins/performance-tracks.ts +88 -0
  291. package/src/vite/plugins/refresh-cmd.ts +127 -0
  292. package/src/vite/plugins/use-cache-transform.ts +323 -0
  293. package/src/vite/plugins/version-injector.ts +83 -0
  294. package/src/vite/plugins/version-plugin.ts +266 -0
  295. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +462 -0
  298. package/src/vite/router-discovery.ts +977 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  304. package/src/vite/utils/prerender-utils.ts +221 -0
  305. package/src/vite/utils/shared-utils.ts +170 -0
  306. package/CLAUDE.md +0 -43
  307. package/src/browser/lru-cache.ts +0 -69
  308. package/src/browser/request-controller.ts +0 -164
  309. package/src/cache/memory-store.ts +0 -253
  310. package/src/href-context.ts +0 -33
  311. package/src/href.ts +0 -255
  312. package/src/server/route-manifest-cache.ts +0 -173
  313. package/src/vite/expose-handle-id.ts +0 -209
  314. package/src/vite/expose-loader-id.ts +0 -426
  315. package/src/vite/expose-location-state-id.ts +0 -177
  316. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -10,21 +10,30 @@ Django-inspired RSC router with composable URL patterns, type-safe href, and ser
10
10
 
11
11
  ## Skills
12
12
 
13
- | Skill | Description |
14
- |-------|-------------|
15
- | `/router-setup` | Create and configure the RSC router |
16
- | `/route` | Define routes with `urls()` and `path()` |
17
- | `/layout` | Layouts that wrap child routes |
18
- | `/loader` | Data loaders with `createLoader()` |
19
- | `/middleware` | Request processing and authentication |
20
- | `/intercept` | Modal/slide-over patterns for soft navigation |
21
- | `/parallel` | Multi-column layouts and sidebars |
22
- | `/caching` | Segment caching with memory or KV stores |
23
- | `/document-cache` | Edge caching with Cache-Control headers |
24
- | `/theme` | Light/dark mode with FOUC prevention |
25
- | `/links` | URL generation: ctx.href, href, useHref, useMount, scopedHref |
26
- | `/hooks` | Client-side React hooks |
27
- | `/typesafety` | Type-safe routes, params, href, and environment |
13
+ | Skill | Description |
14
+ | ----------------------- | -------------------------------------------------------------------------- |
15
+ | `/router-setup` | Create and configure the RSC router |
16
+ | `/route` | Define routes with `urls()` and `path()` |
17
+ | `/layout` | Layouts that wrap child routes |
18
+ | `/loader` | Data loaders with `createLoader()` |
19
+ | `/middleware` | Request processing and authentication |
20
+ | `/intercept` | Modal/slide-over patterns for soft navigation |
21
+ | `/parallel` | Multi-column layouts and sidebars |
22
+ | `/caching` | Segment caching with memory or KV stores |
23
+ | `/use-cache` | Function-level caching with `"use cache"` directive |
24
+ | `/cache-guide` | When to use `cache()` vs `"use cache"` — differences and decision guide |
25
+ | `/document-cache` | Edge caching with Cache-Control headers |
26
+ | `/theme` | Light/dark mode with FOUC prevention |
27
+ | `/links` | URL generation: ctx.reverse, href, useHref, useMount, scopedReverse |
28
+ | `/hooks` | Client-side React hooks |
29
+ | `/typesafety` | Type-safe routes, params, href, and environment |
30
+ | `/host-router` | Multi-app host routing with domain/subdomain patterns |
31
+ | `/tailwind` | Set up Tailwind CSS v4 with `?url` imports |
32
+ | `/response-routes` | JSON/text/HTML/XML/stream endpoints with `path.json()`, `path.text()` |
33
+ | `/mime-routes` | Content negotiation — same URL, different response types via Accept header |
34
+ | `/fonts` | Load web fonts with preload hints |
35
+ | `/migrate-nextjs` | Migrate a Next.js App Router project to Rango |
36
+ | `/migrate-react-router` | Migrate a React Router / Remix project to Rango |
28
37
 
29
38
  ## Quick Start
30
39
 
@@ -43,7 +52,69 @@ export const urlpatterns = urls(({ path, layout }) => [
43
52
  import { createRouter } from "@rangojs/router";
44
53
  import { urlpatterns } from "./urls";
45
54
 
46
- export default createRouter({ document: Document }).urls(urlpatterns);
55
+ export default createRouter({ document: Document }).routes(urlpatterns);
47
56
  ```
48
57
 
49
58
  Use `/typesafety` for type-safe href and environment setup.
59
+
60
+ ## CLI: `npx rango generate`
61
+
62
+ Single command to generate `.gen.ts` route type files. Auto-detects file type and
63
+ generates the appropriate output.
64
+
65
+ ```bash
66
+ # Single file
67
+ npx rango generate src/urls.tsx
68
+
69
+ # Multiple files
70
+ npx rango generate src/router.tsx src/urls.tsx
71
+
72
+ # Directory (recursive scan)
73
+ npx rango generate src/
74
+
75
+ # Mix of files and directories
76
+ npx rango generate src/urls.tsx src/api/
77
+ ```
78
+
79
+ ### Auto-detection
80
+
81
+ Each file is classified by its contents:
82
+
83
+ | Contains | Generated output |
84
+ | -------------- | ---------------------------------------------------------------- |
85
+ | `urls(` | Per-module `*.gen.ts` with route names, patterns, params, search |
86
+ | `createRouter` | Per-router `*.named-routes.gen.ts` with global route map |
87
+ | Both | Both files |
88
+
89
+ Directories are scanned recursively for `.ts`/`.tsx` files, skipping `node_modules`,
90
+ dotfiles, and existing `.gen.` files.
91
+
92
+ ### Recursive includes
93
+
94
+ The generator follows `include()` calls across files, resolving imports to build
95
+ the full route tree. Circular includes are detected and warned about.
96
+
97
+ ### First-wins deduplication
98
+
99
+ When a route name appears more than once, the first definition wins and duplicates
100
+ are dropped with a warning. This applies only to the generated `.gen.ts` type files.
101
+ Define the primary route before any fallback variant that reuses the same name.
102
+
103
+ Content negotiation (see `/mime-routes`) is unaffected — negotiated routes use
104
+ distinct names (e.g. `"product"` and `"productJson"`) and the Accept header
105
+ dispatching happens at runtime in the trie, not in the type generator.
106
+
107
+ ### Limitations
108
+
109
+ The CLI uses static source analysis (AST walking), not runtime execution. It cannot
110
+ extract routes defined dynamically:
111
+
112
+ - `Array.from()` or `.map()` generating path() calls
113
+ - Conditional routes behind `import.meta.env` or feature flags
114
+ - Routes computed from external data (databases, config files)
115
+ - Template literal patterns with interpolated variables
116
+
117
+ These routes are only discovered by the Vite plugin's runtime discovery during
118
+ `pnpm dev` or `pnpm build`. The CLI-generated `.gen.ts` may have fewer routes
119
+ than the runtime-generated version. During dev, the `preserveIfLarger` guard
120
+ prevents the static parser from overwriting a larger runtime-discovered file.
@@ -0,0 +1,411 @@
1
+ ---
2
+ name: response-routes
3
+ description: Response routes (path.json, path.text, etc.) for non-RSC endpoints with typed responses
4
+ argument-hint: [json|text|html|xml|md|image|stream]
5
+ ---
6
+
7
+ # Response Routes
8
+
9
+ Response routes skip the RSC pipeline entirely. Use them for JSON APIs, plain text endpoints,
10
+ XML feeds, image proxies, and any route that returns a `Response` instead of React components.
11
+
12
+ ## Route-Level Tags: path.json(), path.text(), etc.
13
+
14
+ Inside any `urls()` callback, use `path.json()`, `path.text()`, or other tags alongside regular RSC routes:
15
+
16
+ ```typescript
17
+ import { urls, RouterError } from "@rangojs/router";
18
+
19
+ export const urlpatterns = urls(({ path, layout, include }) => [
20
+ // RSC routes (normal)
21
+ path("/", HomePage, { name: "home" }),
22
+ path("/about", AboutPage, { name: "about" }),
23
+
24
+ // JSON API route (inline, alongside RSC routes)
25
+ path.json(
26
+ "/api/status",
27
+ (ctx) => ({
28
+ status: "ok",
29
+ timestamp: Date.now(),
30
+ }),
31
+ { name: "status" },
32
+ ),
33
+
34
+ // Text route
35
+ path.text(
36
+ "/robots.txt",
37
+ (ctx) => {
38
+ return "User-agent: *\nAllow: /\nDisallow: /api/\n";
39
+ },
40
+ { name: "robots" },
41
+ ),
42
+
43
+ // Markdown route
44
+ path.md(
45
+ "/docs/:slug.md",
46
+ (ctx) => {
47
+ return `# ${ctx.params.slug}\n\nDocumentation content here.`;
48
+ },
49
+ { name: "docs" },
50
+ ),
51
+
52
+ // Response route (full control, returns Response directly)
53
+ path.image(
54
+ "/og/:slug.png",
55
+ async (ctx) => {
56
+ const image = await generateOgImage(ctx.params.slug);
57
+ return new Response(image, {
58
+ headers: {
59
+ "Content-Type": "image/png",
60
+ "Cache-Control": "public, max-age=86400",
61
+ },
62
+ });
63
+ },
64
+ { name: "ogImage" },
65
+ ),
66
+ ]);
67
+ ```
68
+
69
+ ## Available Tags
70
+
71
+ | Tag | Usage | Handler returns | Auto-wrap |
72
+ | -------- | --------------- | ------------------ | ------------------------ |
73
+ | `json` | `path.json()` | plain object/array | `{ data: T }` envelope |
74
+ | `text` | `path.text()` | string | text/plain Response |
75
+ | `html` | `path.html()` | string | text/html Response |
76
+ | `xml` | `path.xml()` | string | application/xml Response |
77
+ | `md` | `path.md()` | string | text/markdown Response |
78
+ | `image` | `path.image()` | Response | pass-through |
79
+ | `stream` | `path.stream()` | Response | pass-through |
80
+ | `any` | `path.any()` | Response | pass-through |
81
+
82
+ ## ResponseHandlerContext
83
+
84
+ Response route handlers receive a lighter context (no `ctx.use()`, no `ctx.res`):
85
+
86
+ ```typescript
87
+ interface ResponseHandlerContext<TParams, TEnv> {
88
+ request: Request;
89
+ params: TParams; // Typed from URL pattern
90
+ env: TEnv; // Plain bindings (DB, KV, etc.)
91
+ searchParams: URLSearchParams;
92
+ url: URL;
93
+ pathname: string;
94
+ reverse: (name: string, params?: Record<string, string>) => string;
95
+ get: GetVariableFn; // Read middleware variables
96
+ header: (name: string, value: string) => void;
97
+ // Use cookies().set(name, value, opts) for cookie mutations (standalone API)
98
+ }
99
+ ```
100
+
101
+ ### Setting Headers and Cookies
102
+
103
+ String-returning handlers (json, text, html, xml, md) can set custom headers and cookies
104
+ without constructing a full Response:
105
+
106
+ ```typescript
107
+ path.md(
108
+ "/docs/:slug.md",
109
+ (ctx) => {
110
+ ctx.header("Cache-Control", "public, max-age=3600");
111
+ cookies().set("last-doc", ctx.params.slug, { path: "/" });
112
+ return `# ${ctx.params.slug}\n\nContent here.`;
113
+ },
114
+ { name: "docs" },
115
+ );
116
+ ```
117
+
118
+ Headers set via `ctx.header()` and cookies set via `cookies().set()` are merged into the
119
+ auto-wrapped Response. If the handler returns a `Response` directly, these are ignored
120
+ (use the Response headers instead).
121
+
122
+ ### Environment Access
123
+
124
+ `ctx.env` is always the plain bindings passed as TEnv to `createRouter<TEnv>()`:
125
+
126
+ ```typescript
127
+ // createRouter<{ DB: D1Database; KV: KVNamespace }>({ ... })
128
+
129
+ // In a response handler:
130
+ path.json(
131
+ "/api/data",
132
+ (ctx) => {
133
+ ctx.env.DB; // D1Database (plain bindings)
134
+ ctx.env.KV; // KVNamespace
135
+ // Variables are accessed via ctx.get("key") or ctx.get(ContextVar)
136
+ return { data: "ok" };
137
+ },
138
+ { name: "data" },
139
+ );
140
+ ```
141
+
142
+ ## JSON Envelope
143
+
144
+ `path.json()` handlers return plain data. The framework auto-wraps it
145
+ in a `ResponseEnvelope<T>` discriminated union:
146
+
147
+ ```typescript
148
+ // Success: HTTP 200
149
+ { "data": { "status": "ok", "timestamp": 1700000000 } }
150
+
151
+ // Error: HTTP 404 (or whatever status RouterError specifies)
152
+ { "error": { "message": "Product 999 not found", "code": "NOT_FOUND" } }
153
+ ```
154
+
155
+ ### Error Handling with RouterError
156
+
157
+ Throw `RouterError` to return structured error envelopes:
158
+
159
+ ```typescript
160
+ import { RouterError } from "@rangojs/router";
161
+
162
+ path.json(
163
+ "/api/users/:id",
164
+ (ctx) => {
165
+ const user = users.get(ctx.params.id);
166
+ if (!user) {
167
+ throw new RouterError("NOT_FOUND", `User ${ctx.params.id} not found`, {
168
+ status: 404,
169
+ });
170
+ }
171
+ if (!hasPermission(ctx)) {
172
+ throw new RouterError("FORBIDDEN", "Access denied", { status: 403 });
173
+ }
174
+ return user;
175
+ },
176
+ { name: "user" },
177
+ );
178
+ ```
179
+
180
+ ### Returning Response Directly
181
+
182
+ JSON handlers can return `Response` to bypass auto-wrap (custom status, headers, streaming):
183
+
184
+ ```typescript
185
+ path.json(
186
+ "/api/export",
187
+ (ctx) => {
188
+ const csv = generateCsv();
189
+ return new Response(csv, {
190
+ headers: {
191
+ "Content-Type": "text/csv",
192
+ "Content-Disposition": "attachment; filename=export.csv",
193
+ },
194
+ });
195
+ },
196
+ { name: "export" },
197
+ );
198
+ ```
199
+
200
+ ## Client-Side Type Safety
201
+
202
+ ### ResponseEnvelope and isResponseError
203
+
204
+ ```typescript
205
+ "use client";
206
+ import type { ResponseEnvelope, ResponseError } from "@rangojs/router/client";
207
+ import { isResponseError } from "@rangojs/router/client";
208
+
209
+ // Fetch a typed response
210
+ const res = await fetch("/api/products/1");
211
+ const result: ResponseEnvelope<Product> = await res.json();
212
+
213
+ if (isResponseError(result)) {
214
+ // result.error: ResponseError (message, code?, type?)
215
+ // result.data: undefined
216
+ console.error(result.error.message);
217
+ } else {
218
+ // result.data: Product
219
+ // result.error: undefined
220
+ console.log(result.data.name);
221
+ }
222
+ ```
223
+
224
+ ### RouteResponse (scoped lookup by route name)
225
+
226
+ Look up response type from a `path.json()` or `path.text()` module by route name:
227
+
228
+ ```typescript
229
+ import type { RouteResponse } from "@rangojs/router";
230
+
231
+ // From the apiPatterns module (before include)
232
+ type HealthData = RouteResponse<typeof apiPatterns, "health">;
233
+ // = ResponseEnvelope<{ status: string; timestamp: number }>
234
+
235
+ type ProductsData = RouteResponse<typeof apiPatterns, "products">;
236
+ // = ResponseEnvelope<{ id: string; name: string; price: number }[]>
237
+ ```
238
+
239
+ ### PathResponse (global lookup by URL pattern)
240
+
241
+ Look up response type from the merged route map by URL pattern:
242
+
243
+ ```typescript
244
+ import type { PathResponse } from "@rangojs/router/client";
245
+
246
+ // After include("/api", apiPatterns) in main urls
247
+ type Health = PathResponse<"/api/health">;
248
+ // = ResponseEnvelope<{ status: string; timestamp: number }>
249
+
250
+ // RSC routes return ResponseEnvelope<never>
251
+ type Home = PathResponse<"/">;
252
+ // = ResponseEnvelope<never>
253
+ ```
254
+
255
+ ### ParamsFor with Response Routes
256
+
257
+ ```typescript
258
+ import type { ParamsFor } from "@rangojs/router/client";
259
+
260
+ // Works for both RSC and response routes
261
+ type ProductParams = ParamsFor<"api.productDetail">;
262
+ // = { id: string }
263
+ ```
264
+
265
+ ## Links to Response Routes
266
+
267
+ ### Client: href.json(), href.text(), etc.
268
+
269
+ Response route links need `data-external` to trigger hard navigation (skip RSC fetch).
270
+ Use `href.json()` which returns props to spread on `<Link>`:
271
+
272
+ ```typescript
273
+ "use client";
274
+ import { href, Link } from "@rangojs/router/client";
275
+
276
+ function Nav() {
277
+ return (
278
+ <>
279
+ {/* RSC link (client-side navigation) */}
280
+ <Link to={href("/about")}>About</Link>
281
+
282
+ {/* Response route link (hard navigation via data-external) */}
283
+ <Link {...href.json("/api/health")}>API Health</Link>
284
+ <Link {...href.text("/robots.txt")}>Robots</Link>
285
+ </>
286
+ );
287
+ }
288
+
289
+ // href.json("/api/health") returns:
290
+ // { to: "/api/health", "data-external": "" }
291
+ ```
292
+
293
+ ## Use Items
294
+
295
+ Response routes support only `middleware()` and `cache()` as use items.
296
+ No `loader`, `loading`, `layout`, or `parallel`.
297
+
298
+ ```typescript
299
+ path.json("/api/users", handler, { name: "users" }, () => [
300
+ cache({ ttl: 60, swr: 300 }),
301
+ ]);
302
+ ```
303
+
304
+ ## Mountable Module Pattern
305
+
306
+ A self-contained module with RSC pages + JSON APIs, mountable via `include()`:
307
+
308
+ ```typescript
309
+ // blog/api/urls.tsx
310
+ import { urls, RouterError } from "@rangojs/router";
311
+
312
+ export const blogApiPatterns = urls(({ path }) => [
313
+ path.json(
314
+ "/stats",
315
+ (ctx) => ({
316
+ views: 1200,
317
+ visitors: 450,
318
+ }),
319
+ { name: "stats" },
320
+ ),
321
+
322
+ path.json(
323
+ "/:slug/likes",
324
+ (ctx) => ({
325
+ slug: ctx.params.slug,
326
+ count: 42,
327
+ }),
328
+ { name: "likes" },
329
+ ),
330
+
331
+ path.json(
332
+ "/:slug/comments",
333
+ (ctx) => [{ id: "c1", body: "Great post", author: "alice" }],
334
+ { name: "comments" },
335
+ ),
336
+ ]);
337
+
338
+ // blog/urls.tsx
339
+ import { urls } from "@rangojs/router";
340
+ import { blogApiPatterns } from "./api/urls";
341
+
342
+ export const blogPatterns = urls(({ path, include }) => [
343
+ path("/", BlogIndex, { name: "index" }),
344
+ path("/:slug", BlogPost, { name: "post" }),
345
+ path("/category/:catId", BlogCategory, { name: "category" }),
346
+
347
+ include("/api", blogApiPatterns, { name: "api" }),
348
+ ]);
349
+
350
+ // app/urls.tsx
351
+ import { urls } from "@rangojs/router";
352
+ import { blogPatterns } from "./blog/urls";
353
+
354
+ export const urlpatterns = urls(({ path, include }) => [
355
+ path("/", HomePage, { name: "home" }),
356
+ include("/blog", blogPatterns, { name: "blog" }),
357
+ ]);
358
+ ```
359
+
360
+ ### Type safety after mounting
361
+
362
+ ```typescript
363
+ import type { RouteResponse } from "@rangojs/router";
364
+ import type { PathResponse, ParamsFor } from "@rangojs/router/client";
365
+
366
+ // Scoped (before mount) -- use the module directly
367
+ type Stats = RouteResponse<typeof blogApiPatterns, "stats">;
368
+ // = ResponseEnvelope<{ views: number; visitors: number }>
369
+
370
+ // After mounting -- names get prefixed
371
+ type BlogStats = PathResponse<"/blog/api/stats">;
372
+ // = ResponseEnvelope<{ views: number; visitors: number }>
373
+
374
+ // Params work through nested includes
375
+ type LikesParams = ParamsFor<"blog.api.likes">;
376
+ // = { slug: string }
377
+ ```
378
+
379
+ ### ctx.reverse inside mounted modules
380
+
381
+ Response route handlers inside a mounted module can reference local names:
382
+
383
+ ```typescript
384
+ // Inside blogApiPatterns handler
385
+ path(
386
+ "/:slug/likes",
387
+ (ctx) => {
388
+ // ctx.reverse resolves names relative to the mount point
389
+ const commentsUrl = ctx.reverse("comments", { slug: ctx.params.slug });
390
+ // -> "/blog/api/my-post/comments"
391
+
392
+ return { slug: ctx.params.slug, count: 42, commentsUrl };
393
+ },
394
+ { name: "likes" },
395
+ );
396
+ ```
397
+
398
+ ## Content Negotiation
399
+
400
+ Multiple response types can share the same URL pattern. See `/mime-routes` for the
401
+ full content negotiation API (Accept header matching, Vary: Accept, multi-variant routes).
402
+
403
+ ## How It Works
404
+
405
+ 1. `path.json()` tags the route at the trie level with a MIME type
406
+ 2. `coreRequestHandler()` checks the tag before the RSC pipeline
407
+ 3. Tagged routes short-circuit: handler runs, Response is returned directly
408
+ 4. JSON routes auto-wrap return values in `{ data }` / `{ error }` envelope
409
+ 5. Client-side navigation to response routes gets `X-RSC-Reload` header, triggering hard navigation
410
+ 6. Response types flow through `_responses` phantom type on `UrlPatterns`, propagated by `include()`
411
+ 7. When multiple routes share a URL pattern, the trie merges them for content negotiation (see `/mime-routes`)