@rangojs/router 0.0.0-experimental.002d056c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,253 @@
1
+ ---
2
+ name: caching
3
+ description: Configure segment caching with memory or Cloudflare KV stores in @rangojs/router
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Caching
8
+
9
+ @rangojs/router supports segment-level caching with stale-while-revalidate (SWR) for optimal performance.
10
+
11
+ ## Route-Level Caching with cache()
12
+
13
+ Use the `cache()` DSL function to cache routes:
14
+
15
+ ```typescript
16
+ import { urls } from "@rangojs/router";
17
+
18
+ export const urlpatterns = urls(({ path, cache }) => [
19
+ // Cache these routes for 60 seconds, SWR for 5 minutes
20
+ cache({ ttl: 60, swr: 300 }, () => [
21
+ path("/blog", BlogIndex, { name: "blog" }),
22
+ path("/blog/:slug", BlogPost, { name: "blogPost" }),
23
+ ]),
24
+
25
+ // Uncached routes
26
+ path("/account", AccountPage, { name: "account" }),
27
+ ]);
28
+ ```
29
+
30
+ ## Cache Options
31
+
32
+ ```typescript
33
+ cache(
34
+ {
35
+ ttl: 60, // Time-to-live in seconds (default: 60)
36
+ swr: 300, // Stale-while-revalidate window (default: 300)
37
+ },
38
+ () => [
39
+ // Cached routes
40
+ ],
41
+ );
42
+ ```
43
+
44
+ ## Named Profile Shorthand
45
+
46
+ Use a named cache profile string instead of an options object. The profile must be
47
+ defined in `createRouter({ cacheProfiles })`. Unknown names throw at boot time.
48
+
49
+ ```typescript
50
+ // Define profiles in router
51
+ createRouter({
52
+ cacheProfiles: {
53
+ default: { ttl: 900, swr: 1800 },
54
+ short: { ttl: 60, swr: 120 },
55
+ long: { ttl: 3600, swr: 7200 },
56
+ },
57
+ });
58
+
59
+ // Use by name in urls
60
+ export const urlpatterns = urls(({ path, cache }) => [
61
+ cache("long", () => [path("/blog", BlogIndex, { name: "blog" })]),
62
+
63
+ // Also works without children (orphan cache boundary)
64
+ cache("short"),
65
+ path("/feed", FeedPage, { name: "feed" }),
66
+ ]);
67
+ ```
68
+
69
+ These profile names are shared with the `"use cache: <name>"` directive. See
70
+ `/use-cache` for function-level caching.
71
+
72
+ ## Loader-Level Caching
73
+
74
+ Cache individual loaders:
75
+
76
+ ```typescript
77
+ path("/product/:slug", ProductPage, { name: "product" }, () => [
78
+ // Cache this loader's results
79
+ loader(ProductLoader, () => [cache({ ttl: 300 })]),
80
+
81
+ // This loader is not cached
82
+ loader(CartLoader),
83
+ ]);
84
+ ```
85
+
86
+ ## Global Cache Configuration
87
+
88
+ Configure a cache store in the router:
89
+
90
+ ```typescript
91
+ import { createRouter } from "@rangojs/router";
92
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
93
+
94
+ const store = new MemorySegmentCacheStore({
95
+ defaults: { ttl: 60, swr: 300 },
96
+ });
97
+
98
+ const router = createRouter({
99
+ document: Document,
100
+ urls: urlpatterns,
101
+ cache: {
102
+ store,
103
+ enabled: true,
104
+ },
105
+ });
106
+ ```
107
+
108
+ ## Cache Stores
109
+
110
+ ### Memory Store
111
+
112
+ For single-instance deployments:
113
+
114
+ ```typescript
115
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
116
+
117
+ const store = new MemorySegmentCacheStore({
118
+ defaults: { ttl: 60, swr: 300 },
119
+ maxSize: 1000, // Max entries
120
+ });
121
+ ```
122
+
123
+ ### Cloudflare Edge Cache Store
124
+
125
+ For distributed caching on Cloudflare Workers using the Cache API:
126
+
127
+ ```typescript
128
+ import { CFCacheStore } from "@rangojs/router/cache";
129
+
130
+ const router = createRouter<AppBindings>({
131
+ document: Document,
132
+ urls: urlpatterns,
133
+ cache: (env, ctx) => ({
134
+ store: new CFCacheStore({
135
+ ctx,
136
+ defaults: { ttl: 60, swr: 300 },
137
+ }),
138
+ enabled: true,
139
+ }),
140
+ });
141
+ ```
142
+
143
+ ### With KV L2 Persistence
144
+
145
+ Add a KV namespace for global cross-colo persistence. On Cache API miss, KV is
146
+ checked and hits are promoted back to L1. Writes go to both layers.
147
+
148
+ ```typescript
149
+ import { CFCacheStore } from "@rangojs/router/cache";
150
+
151
+ const router = createRouter<AppBindings>({
152
+ document: Document,
153
+ urls: urlpatterns,
154
+ cache: (env, ctx) => ({
155
+ store: new CFCacheStore({
156
+ ctx,
157
+ kv: env.CACHE_KV, // optional KV namespace binding
158
+ defaults: { ttl: 60, swr: 300 },
159
+ }),
160
+ enabled: true,
161
+ }),
162
+ });
163
+ ```
164
+
165
+ **How the two layers work:**
166
+
167
+ | Scenario | L1 (Cache API) | L2 (KV) | Result |
168
+ | ------------ | -------------- | ------- | ----------------------------- |
169
+ | Hot request | HIT | — | Serve from L1 (fast) |
170
+ | Cold colo | MISS | HIT | Serve from KV, promote to L1 |
171
+ | First render | MISS | MISS | Render, write to both L1 + KV |
172
+
173
+ KV entries require `expirationTtl >= 60s`. Short-lived entries (< 60s total TTL)
174
+ are only cached in L1.
175
+
176
+ ## Nested Cache Boundaries
177
+
178
+ Override cache settings for specific sections:
179
+
180
+ ```typescript
181
+ // Global cache
182
+ cache({ ttl: 300 }, () => [
183
+ path("/blog", BlogIndex, { name: "blog" }),
184
+
185
+ // Override: shorter TTL for dynamic content
186
+ cache({ ttl: 30 }, () => [
187
+ path("/blog/:slug", BlogPost, { name: "blogPost" }),
188
+ ]),
189
+ ]);
190
+ ```
191
+
192
+ ## Custom Cache Store
193
+
194
+ Create a dedicated store for specific routes:
195
+
196
+ ```typescript
197
+ const checkoutCache = new MemorySegmentCacheStore({
198
+ defaults: { ttl: 10 },
199
+ });
200
+
201
+ // In urls
202
+ cache({ store: checkoutCache }, () => [
203
+ path("/checkout", CheckoutPage, { name: "checkout" }),
204
+ ]);
205
+ ```
206
+
207
+ ## Complete Example
208
+
209
+ ```typescript
210
+ import { urls } from "@rangojs/router";
211
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
212
+
213
+ // Custom store for checkout (short TTL)
214
+ const checkoutCache = new MemorySegmentCacheStore({
215
+ defaults: { ttl: 10 },
216
+ });
217
+
218
+ export const urlpatterns = urls(({ path, layout, cache, loader, revalidate }) => [
219
+ // Public routes with aggressive caching
220
+ cache({ ttl: 300, swr: 600 }, () => [
221
+ path("/", HomePage, { name: "home" }),
222
+ path("/about", AboutPage, { name: "about" }),
223
+ ]),
224
+
225
+ // Blog routes with moderate caching
226
+ cache({ ttl: 60, swr: 300 }, () => [
227
+ layout(<BlogLayout />, () => [
228
+ path("/blog", BlogIndex, { name: "blog" }),
229
+ path("/blog/:slug", BlogPost, { name: "blogPost" }, () => [
230
+ loader(BlogPostLoader, () => [cache()]), // Use boundary cache settings
231
+ ]),
232
+ ]),
233
+ ]),
234
+
235
+ // Shop routes with per-loader caching
236
+ layout(<ShopLayout />, () => [
237
+ path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
238
+ loader(ProductLoader, () => [cache({ ttl: 120 })]),
239
+ loader(CartLoader, () => [
240
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
241
+ ]),
242
+ ]),
243
+ ]),
244
+
245
+ // Checkout with custom cache store
246
+ cache({ store: checkoutCache }, () => [
247
+ path("/checkout", CheckoutPage, { name: "checkout" }),
248
+ ]),
249
+
250
+ // No cache for account pages
251
+ path("/account", AccountPage, { name: "account" }),
252
+ ]);
253
+ ```
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: composability
3
+ description: Reusable composition patterns with globally importable route helpers in @rangojs/router
4
+ argument-hint: "pattern-name"
5
+ ---
6
+
7
+ # Composability
8
+
9
+ Route helpers can be imported directly from `@rangojs/router` and used to build reusable composition factories. This enables sharing common route configurations across multiple routes and modules.
10
+
11
+ ## Globally Importable Helpers
12
+
13
+ These helpers can be imported and called outside the `urls()` callback parameter:
14
+
15
+ ```typescript
16
+ import {
17
+ layout,
18
+ cache,
19
+ middleware,
20
+ revalidate,
21
+ loader,
22
+ loading,
23
+ parallel,
24
+ intercept,
25
+ when,
26
+ errorBoundary,
27
+ notFoundBoundary,
28
+ } from "@rangojs/router";
29
+ ```
30
+
31
+ They work because they use AsyncLocalStorage internally and resolve context at call time, not import time.
32
+
33
+ ## Why path() and include() Are Not Global
34
+
35
+ `path()` and `include()` remain exclusive to the `urls()` callback:
36
+
37
+ ```typescript
38
+ urls(({ path, include }) => [
39
+ path("/blog", BlogPage, { name: "blog" }),
40
+ include("/shop", shopPatterns, { name: "shop" }),
41
+ ]);
42
+ ```
43
+
44
+ They define the route structure -- the URL patterns and how modules compose. Keeping them in the `urls()` callback makes the route tree readable at a glance. When scanning a URL file, `path()` and `include()` calls show what renders where. Moving them into factories would hide the routing structure and make it harder to understand which URLs exist and how they nest.
45
+
46
+ The globally importable helpers (`cache`, `middleware`, `loading`, etc.) are configuration -- they modify behavior of routes but don't define routes themselves. Extracting them into factories doesn't obscure the route structure.
47
+
48
+ ## Composition Factories
49
+
50
+ Define reusable factories that return arrays of use items:
51
+
52
+ ```typescript
53
+ import { cache, revalidate, loading, errorBoundary, middleware } from "@rangojs/router";
54
+
55
+ // Shared caching configuration
56
+ const withCaching = () => [
57
+ cache({ ttl: 600_000 }),
58
+ revalidate(({ actionId }) => !!actionId),
59
+ ];
60
+
61
+ // Shared loading and error handling
62
+ const withLoadingAndError = (skeleton: ReactNode) => [
63
+ loading(skeleton),
64
+ errorBoundary(() => <div>Something went wrong</div>),
65
+ ];
66
+
67
+ // Shared auth middleware
68
+ const withAuth = () => [
69
+ middleware(authMiddleware),
70
+ middleware(loggingMiddleware),
71
+ ];
72
+ ```
73
+
74
+ ## Using Factories in Routes
75
+
76
+ Place factory calls inside `path()` or `layout()` use callbacks. The returned arrays are flattened automatically (up to 3 levels):
77
+
78
+ ```typescript
79
+ import { urls } from "@rangojs/router";
80
+ import { withCaching, withLoadingAndError, withAuth } from "./route-config";
81
+
82
+ export const urlpatterns = urls(({ path, layout }) => [
83
+ layout(<AppLayout />, () => [
84
+ withAuth(),
85
+
86
+ path("/blog", BlogIndex, { name: "blog" }, () => [
87
+ withCaching(),
88
+ withLoadingAndError(<BlogSkeleton />),
89
+ ]),
90
+
91
+ path("/shop", ShopIndex, { name: "shop" }, () => [
92
+ withCaching(),
93
+ withLoadingAndError(<ShopSkeleton />),
94
+ ]),
95
+ ]),
96
+ ]);
97
+ ```
98
+
99
+ ## Sharing Across Modules
100
+
101
+ Factories can be defined in shared modules and reused across separate `urls()` definitions:
102
+
103
+ ```typescript
104
+ // src/route-config.ts
105
+ import { cache, revalidate, middleware } from "@rangojs/router";
106
+ import { authMiddleware } from "./middleware/auth";
107
+
108
+ export const withPublicDefaults = () => [
109
+ cache({ ttl: 300 }),
110
+ revalidate(({ actionId }) => !!actionId),
111
+ ];
112
+
113
+ export const withProtectedDefaults = () => [
114
+ middleware(authMiddleware),
115
+ cache({ ttl: 60 }),
116
+ ];
117
+ ```
118
+
119
+ ```typescript
120
+ // src/urls/blog.ts
121
+ import { urls } from "@rangojs/router";
122
+ import { withPublicDefaults } from "../route-config";
123
+
124
+ export const blogPatterns = urls(({ path }) => [
125
+ path("/", BlogIndex, { name: "index" }, () => [withPublicDefaults()]),
126
+ ]);
127
+ ```
128
+
129
+ ```typescript
130
+ // src/urls/admin.ts
131
+ import { urls } from "@rangojs/router";
132
+ import { withProtectedDefaults } from "../route-config";
133
+
134
+ export const adminPatterns = urls(({ path }) => [
135
+ path("/", AdminDashboard, { name: "index" }, () => [withProtectedDefaults()]),
136
+ ]);
137
+ ```
138
+
139
+ ## Composition Types
140
+
141
+ For typed factories, import the composition types:
142
+
143
+ ```typescript
144
+ import type { RouteUseItem, LayoutUseItem, UseItems } from "@rangojs/router";
145
+
146
+ // Factory for path() use callbacks
147
+ const withCaching = (): RouteUseItem[] => [
148
+ cache({ ttl: 600_000 }),
149
+ ];
150
+
151
+ // Factory for layout() use callbacks
152
+ const withAuth = (): LayoutUseItem[] => [
153
+ middleware(authMiddleware),
154
+ ];
155
+
156
+ // Factory that nests other factories (use UseItems for nested arrays)
157
+ const withEverything = (): UseItems<RouteUseItem> => [
158
+ withCaching(),
159
+ loading(<Skeleton />),
160
+ ];
161
+ ```
162
+
163
+ - `RouteUseItem[]` -- flat array for `path()` use callbacks
164
+ - `LayoutUseItem[]` -- flat array for `layout()` use callbacks
165
+ - `UseItems<T>` -- allows nested arrays from composing factories together
166
+
167
+ ## Rules
168
+
169
+ - Helpers execute lazily -- factory functions are defined anywhere, but only called inside a `urls()` context (within `path()` or `layout()` use callbacks)
170
+ - Calling helpers outside a `urls()` context throws an error
171
+ - Nested arrays from factories are flattened automatically via `.flat(3)`
172
+ - `path()` and `include()` cannot be used in factories -- they define route structure and must remain visible in the `urls()` callback
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: debug-manifest
3
+ description: Debug and inspect route manifest structure
4
+ argument-hint:
5
+ ---
6
+
7
+ # Debug Manifest
8
+
9
+ Inspect the route manifest to verify parent relationships, shortCodes, and route structure.
10
+
11
+ ## Quick Access
12
+
13
+ In development, visit:
14
+
15
+ ```
16
+ http://localhost:PORT/__debug_manifest
17
+ ```
18
+
19
+ Returns formatted JSON with all routes and layouts.
20
+
21
+ ## Programmatic Access
22
+
23
+ ```typescript
24
+ import { router } from "./router.js";
25
+
26
+ // Only in development
27
+ if (process.env.NODE_ENV !== "production") {
28
+ const manifest = await router.debugManifest();
29
+ console.log(JSON.stringify(manifest, null, 2));
30
+ }
31
+ ```
32
+
33
+ ## Manifest Structure
34
+
35
+ ```json
36
+ {
37
+ "routes": {
38
+ "home.index": {
39
+ "id": "debug.M0.$root.$route.0.home.index",
40
+ "shortCode": "M0L0R0",
41
+ "type": "route",
42
+ "parentShortCode": "M0L0",
43
+ "pattern": "/",
44
+ "hasLoader": false,
45
+ "hasMiddleware": false,
46
+ "hasErrorBoundary": false,
47
+ "parallelCount": 0,
48
+ "interceptCount": 0
49
+ }
50
+ },
51
+ "layouts": {
52
+ "debug.M0.$root": {
53
+ "id": "debug.M0.$root",
54
+ "shortCode": "M0L0",
55
+ "type": "layout",
56
+ "parentShortCode": null
57
+ }
58
+ },
59
+ "totalRoutes": 45,
60
+ "totalLayouts": 18
61
+ }
62
+ ```
63
+
64
+ ## ShortCode Format
65
+
66
+ | Prefix | Meaning |
67
+ | ------ | ---------------------------------------- |
68
+ | **M** | Mount index (multiple `.routes()` calls) |
69
+ | **L** | Layout |
70
+ | **C** | Cache boundary |
71
+ | **R** | Route |
72
+ | **P** | Parallel slot |
73
+
74
+ Example: `M0L0L1C0R0` = Mount 0 → Root Layout → Nested Layout → Cache → Route
75
+
76
+ ## Debugging Checklist
77
+
78
+ 1. **Routes have parents**: `parentShortCode` should NOT be `null` (except root layout)
79
+ 2. **Correct hierarchy**: ShortCode should reflect nesting (e.g., `M0L0R0` not `M0R0`)
80
+ 3. **Loaders attached**: Check `hasLoader: true` for routes with data requirements
81
+ 4. **Intercepts registered**: `interceptCount > 0` for modal/overlay patterns
82
+
83
+ ## Comparing Manifests
84
+
85
+ ```typescript
86
+ import {
87
+ serializeManifest,
88
+ compareManifests,
89
+ formatManifestDiff,
90
+ } from "@rangojs/router/__internal";
91
+
92
+ const oldManifest = await router.debugManifest();
93
+ // ... make changes ...
94
+ const newManifest = await router.debugManifest();
95
+
96
+ const diff = compareManifests(oldManifest, newManifest);
97
+ console.log(formatManifestDiff(diff));
98
+ ```
99
+
100
+ ## Common Issues
101
+
102
+ ### Routes have `parentShortCode: null`
103
+
104
+ Routes should have a layout parent. Check that `urls()` handler is being wrapped in root layout.
105
+
106
+ ### Missing layouts in hierarchy
107
+
108
+ Verify `layout()` calls wrap child routes correctly.
109
+
110
+ ### Wrong mount index
111
+
112
+ Multiple `.routes()` calls create separate mounts (M0, M1, etc.). Use `include()` to share context.
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: document-cache
3
+ description: Cache full HTTP responses at the edge with Cache-Control headers
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Document Cache
8
+
9
+ Caches complete HTTP responses (HTML/RSC) at the edge based on Cache-Control headers. Routes opt-in by setting `s-maxage`.
10
+
11
+ ## Setup
12
+
13
+ Configure document cache in router:
14
+
15
+ ```typescript
16
+ import { createRouter } from "@rangojs/router";
17
+ import { CFCacheStore } from "@rangojs/router/cache";
18
+ import { urlpatterns } from "./urls";
19
+
20
+ const router = createRouter<AppBindings>({
21
+ document: Document,
22
+ urls: urlpatterns,
23
+ documentCache: (_env, ctx) => ({
24
+ store: new CFCacheStore({ ctx: ctx! }),
25
+ skipPaths: ["/api", "/admin"],
26
+ debug: process.env.NODE_ENV === "development",
27
+ }),
28
+ });
29
+
30
+ export default router;
31
+ ```
32
+
33
+ ## Route Opt-In with cache()
34
+
35
+ Routes opt-in to document caching using the `cache()` DSL with `documentCache` option:
36
+
37
+ ```typescript
38
+ import { urls } from "@rangojs/router";
39
+
40
+ export const urlpatterns = urls(({ path, cache }) => [
41
+ // Cache full page for 5 min, serve stale for 1 hour
42
+ cache({ documentCache: { sMaxAge: 300, swr: 3600 } }, () => [
43
+ path("/blog", BlogIndex, { name: "blog" }),
44
+ ]),
45
+
46
+ // Long cache for individual posts
47
+ cache({ documentCache: { sMaxAge: 3600, swr: 86400 } }, () => [
48
+ path("/blog/:slug", BlogPost, { name: "blogPost" }),
49
+ ]),
50
+
51
+ // No cache for dashboard (no documentCache option)
52
+ path("/dashboard", Dashboard, { name: "dashboard" }),
53
+ ]);
54
+ ```
55
+
56
+ ## Document Cache Options
57
+
58
+ ```typescript
59
+ createRouter({
60
+ // ...
61
+ documentCache: (_env, ctx) => ({
62
+ // Cache store (required)
63
+ store: new CFCacheStore({ ctx: ctx! }),
64
+
65
+ // Skip specific paths
66
+ skipPaths: ["/api", "/admin"],
67
+
68
+ // Custom cache key
69
+ keyGenerator: (url) => url.pathname,
70
+
71
+ // Conditional caching
72
+ isEnabled: (ctx) => !ctx.request.headers.has("x-preview"),
73
+
74
+ // Debug logging
75
+ debug: true,
76
+ }),
77
+ });
78
+ ```
79
+
80
+ ## How It Works
81
+
82
+ ```
83
+ Request → Check Cache
84
+
85
+ ┌──────┴──────┐
86
+ │ │
87
+ HIT MISS
88
+ │ │
89
+ ↓ ↓
90
+ Fresh? Run handler
91
+ │ │
92
+ Yes → Return Has documentCache?
93
+ │ │
94
+ No (stale) Yes → Cache + Return
95
+ │ │
96
+ ↓ No → Return (no cache)
97
+ Return stale,
98
+ revalidate in
99
+ background (SWR)
100
+ ```
101
+
102
+ ## Cache Status Header
103
+
104
+ Response includes `x-document-cache-status`:
105
+
106
+ - `HIT` - Fresh cache hit
107
+ - `STALE` - Served stale, revalidating in background
108
+ - `MISS` - Cache miss, response was generated fresh
109
+
110
+ ## Cache Key Generation
111
+
112
+ Default keys differentiate:
113
+
114
+ - HTML requests: `{pathname}:html`
115
+ - RSC partials: `{pathname}:{segmentHash}:rsc`
116
+
117
+ Segment hash ensures different cached responses for navigations from different source pages (with different layouts).
118
+
119
+ ## What Gets Cached
120
+
121
+ - Full HTML responses (document requests)
122
+ - RSC payloads (client navigation)
123
+ - Only 200 OK responses with documentCache enabled
124
+
125
+ ## What's NOT Cached
126
+
127
+ - Server actions (`_rsc_action`)
128
+ - Loader requests (`_rsc_loader`)
129
+ - Routes without `documentCache` option
130
+ - Non-200 responses
131
+
132
+ ## Complete Example
133
+
134
+ ```typescript
135
+ // router.tsx
136
+ import { createRouter } from "@rangojs/router";
137
+ import { CFCacheStore } from "@rangojs/router/cache";
138
+ import { urlpatterns } from "./urls";
139
+
140
+ const router = createRouter<AppBindings>({
141
+ document: Document,
142
+ urls: urlpatterns,
143
+ documentCache: (_env, ctx) => ({
144
+ store: new CFCacheStore({ ctx: ctx! }),
145
+ skipPaths: ["/api"],
146
+ debug: process.env.NODE_ENV === "development",
147
+ }),
148
+ });
149
+
150
+ export default router;
151
+
152
+ // urls.tsx
153
+ import { urls } from "@rangojs/router";
154
+
155
+ export const urlpatterns = urls(({ path, layout, cache, loader }) => [
156
+ // Blog with document caching
157
+ cache({ documentCache: { sMaxAge: 300, swr: 3600 } }, () => [
158
+ layout(<BlogLayout />, () => [
159
+ path("/blog", BlogIndex, { name: "blog" }),
160
+ path("/blog/:slug", BlogPost, { name: "blogPost" }, () => [
161
+ loader(BlogPostLoader),
162
+ ]),
163
+ ]),
164
+ ]),
165
+
166
+ // Dashboard - no document cache (dynamic content)
167
+ layout(<DashboardLayout />, () => [
168
+ path("/dashboard", Dashboard, { name: "dashboard" }),
169
+ ]),
170
+ ]);
171
+ ```
172
+
173
+ ## Document Cache vs Segment Cache
174
+
175
+ | Feature | Document Cache | Segment Cache |
176
+ | ------------ | -------------------------- | --------------------- |
177
+ | Granularity | Full response | Individual segments |
178
+ | Opt-in | `documentCache` in cache() | `cache({ ttl, swr })` |
179
+ | Use case | Static pages | Dynamic compositions |
180
+ | Key includes | URL + segment hash | Route params |
181
+
182
+ Use document cache for mostly-static pages. Use segment cache when different parts of a page have different cache requirements.