@rangojs/router 0.0.0-experimental.9 → 0.0.0-experimental.a5f27bd5

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 (299) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -155
  4. package/dist/vite/index.js +4440 -2170
  5. package/package.json +60 -54
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +6 -4
  13. package/skills/hooks/SKILL.md +333 -71
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +74 -15
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +15 -11
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +405 -45
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +144 -91
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +316 -87
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +102 -4
  31. package/src/bin/rango.ts +312 -15
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +24 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +285 -553
  41. package/src/browser/navigation-client.ts +123 -73
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +261 -309
  46. package/src/browser/prefetch/cache.ts +154 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +48 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +182 -70
  53. package/src/browser/react/NavigationProvider.tsx +51 -11
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +6 -1
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +29 -70
  65. package/src/browser/react/use-link-status.ts +6 -5
  66. package/src/browser/react/use-navigation.ts +22 -63
  67. package/src/browser/react/use-params.ts +65 -0
  68. package/src/browser/react/use-pathname.ts +47 -0
  69. package/src/browser/react/use-router.ts +63 -0
  70. package/src/browser/react/use-search-params.ts +56 -0
  71. package/src/browser/react/use-segments.ts +80 -97
  72. package/src/browser/response-adapter.ts +73 -0
  73. package/src/browser/rsc-router.tsx +106 -27
  74. package/src/browser/scroll-restoration.ts +92 -16
  75. package/src/browser/segment-reconciler.ts +216 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +504 -599
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +107 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +82 -21
  82. package/src/build/generate-route-types.ts +36 -752
  83. package/src/build/index.ts +6 -5
  84. package/src/build/route-trie.ts +39 -13
  85. package/src/build/route-types/ast-helpers.ts +25 -0
  86. package/src/build/route-types/ast-route-extraction.ts +98 -0
  87. package/src/build/route-types/codegen.ts +102 -0
  88. package/src/build/route-types/include-resolution.ts +411 -0
  89. package/src/build/route-types/param-extraction.ts +48 -0
  90. package/src/build/route-types/per-module-writer.ts +128 -0
  91. package/src/build/route-types/router-processing.ts +469 -0
  92. package/src/build/route-types/scan-filter.ts +78 -0
  93. package/src/build/runtime-discovery.ts +231 -0
  94. package/src/cache/background-task.ts +34 -0
  95. package/src/cache/cache-key-utils.ts +44 -0
  96. package/src/cache/cache-policy.ts +125 -0
  97. package/src/cache/cache-runtime.ts +338 -0
  98. package/src/cache/cache-scope.ts +120 -301
  99. package/src/cache/cf/cf-cache-store.ts +119 -7
  100. package/src/cache/cf/index.ts +8 -2
  101. package/src/cache/document-cache.ts +101 -72
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +0 -15
  105. package/src/cache/memory-segment-store.ts +191 -13
  106. package/src/cache/profile-registry.ts +73 -0
  107. package/src/cache/read-through-swr.ts +134 -0
  108. package/src/cache/segment-codec.ts +256 -0
  109. package/src/cache/taint.ts +98 -0
  110. package/src/cache/types.ts +72 -122
  111. package/src/client.rsc.tsx +3 -1
  112. package/src/client.tsx +84 -126
  113. package/src/component-utils.ts +4 -4
  114. package/src/components/DefaultDocument.tsx +5 -1
  115. package/src/context-var.ts +86 -0
  116. package/src/debug.ts +17 -7
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +15 -10
  119. package/src/handles/MetaTags.tsx +73 -20
  120. package/src/handles/breadcrumbs.ts +66 -0
  121. package/src/handles/index.ts +1 -0
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +21 -15
  124. package/src/host/errors.ts +8 -8
  125. package/src/host/index.ts +4 -7
  126. package/src/host/pattern-matcher.ts +27 -27
  127. package/src/host/router.ts +61 -39
  128. package/src/host/testing.ts +8 -8
  129. package/src/host/types.ts +15 -7
  130. package/src/host/utils.ts +1 -1
  131. package/src/href-client.ts +65 -45
  132. package/src/index.rsc.ts +133 -21
  133. package/src/index.ts +164 -52
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +25 -143
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +4 -2
  140. package/src/prerender/store.ts +158 -13
  141. package/src/prerender.ts +333 -26
  142. package/src/reverse.ts +184 -121
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +7 -4
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1431
  151. package/src/route-map-builder.ts +156 -123
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +48 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +374 -81
  159. package/src/router/intercept-resolution.ts +24 -16
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +83 -32
  164. package/src/router/match-api.ts +118 -119
  165. package/src/router/match-context.ts +4 -2
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +336 -84
  169. package/src/router/match-middleware/cache-store.ts +43 -24
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -20
  171. package/src/router/match-middleware/segment-resolution.ts +16 -8
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -28
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +197 -41
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1239 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1315
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +96 -29
  197. package/src/router/types.ts +16 -9
  198. package/src/router.ts +590 -1983
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +661 -1015
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +0 -20
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +237 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +38 -11
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +25 -13
  217. package/src/server/context.ts +173 -48
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +430 -70
  223. package/src/server.ts +35 -155
  224. package/src/ssr/index.tsx +100 -31
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1757
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -1282
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -1963
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -43
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/router.gen.ts +0 -6
  294. package/src/urls.gen.ts +0 -8
  295. package/src/vite/expose-handle-id.ts +0 -209
  296. package/src/vite/expose-loader-id.ts +0 -426
  297. package/src/vite/expose-location-state-id.ts +0 -177
  298. package/src/vite/expose-prerender-handler-id.ts +0 -429
  299. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -1,164 +0,0 @@
1
- import type { RequestController, DisposableAbortController } from "./types.js";
2
-
3
- // Polyfill Symbol.dispose for Safari and older browsers
4
- if (typeof Symbol.dispose === "undefined") {
5
- (Symbol as any).dispose = Symbol("Symbol.dispose");
6
- }
7
-
8
- /**
9
- * Create a request controller for managing concurrent abort controllers
10
- *
11
- * This utility helps manage concurrent navigation requests by providing
12
- * a way to abort all pending requests when a new navigation starts.
13
- *
14
- * @returns RequestController instance
15
- *
16
- * @example
17
- * ```typescript
18
- * const controller = createRequestController();
19
- *
20
- * // Start a new request
21
- * const abortController = controller.create();
22
- * fetch(url, { signal: abortController.signal });
23
- *
24
- * // Abort all pending requests (e.g., when starting new navigation)
25
- * controller.abortAll();
26
- *
27
- * // Clean up completed request
28
- * controller.remove(abortController);
29
- * ```
30
- */
31
- export function createRequestController(): RequestController {
32
- // Navigation controllers - aborted on new navigation
33
- // Using WeakRef to allow GC if controller is no longer referenced elsewhere
34
- const controllers: WeakRef<AbortController>[] = [];
35
- // Action controllers - NOT aborted by navigation, only by errors
36
- const actionControllers: WeakRef<AbortController>[] = [];
37
-
38
- /**
39
- * Remove stale (garbage collected) refs from an array
40
- */
41
- function pruneStaleRefs(refs: WeakRef<AbortController>[]): void {
42
- for (let i = refs.length - 1; i >= 0; i--) {
43
- if (!refs[i].deref()) {
44
- refs.splice(i, 1);
45
- }
46
- }
47
- }
48
-
49
- return {
50
- /**
51
- * Create a new abort controller and track it for navigation
52
- *
53
- * @returns A new AbortController
54
- */
55
- create(): AbortController {
56
- const controller = new AbortController();
57
- controllers.push(new WeakRef(controller));
58
- console.log(
59
- `[Browser] Created abort controller, total: ${controllers.length}`,
60
- );
61
- return controller;
62
- },
63
-
64
- /**
65
- * Create a disposable abort controller for navigation use with `using` keyword
66
- *
67
- * The controller will be automatically removed from tracking when
68
- * it goes out of scope, regardless of how the scope is exited.
69
- *
70
- * @returns A DisposableAbortController
71
- *
72
- * @example
73
- * ```typescript
74
- * async function handleNavigation() {
75
- * requestController.abortAll();
76
- * using { controller } = requestController.createDisposable();
77
- * // ... use controller.signal ...
78
- * // controller is automatically removed on scope exit
79
- * }
80
- * ```
81
- */
82
- createDisposable(): DisposableAbortController {
83
- const controller = this.create();
84
- return {
85
- controller,
86
- [Symbol.dispose]: () => {
87
- this.remove(controller);
88
- },
89
- };
90
- },
91
-
92
- /**
93
- * Create a disposable abort controller for actions
94
- *
95
- * Action controllers are NOT aborted by navigation - they complete
96
- * independently. Only aborted by abortAllActions() on error.
97
- *
98
- * @returns A DisposableAbortController
99
- */
100
- createActionDisposable(): DisposableAbortController {
101
- const controller = new AbortController();
102
- const ref = new WeakRef(controller);
103
- actionControllers.push(ref);
104
- console.log(
105
- `[Browser] Created action controller, total: ${actionControllers.length}`,
106
- );
107
- return {
108
- controller,
109
- [Symbol.dispose]: () => {
110
- const index = actionControllers.indexOf(ref);
111
- if (index !== -1) {
112
- actionControllers.splice(index, 1);
113
- console.log(
114
- `[Browser] Removed action controller, remaining: ${actionControllers.length}`,
115
- );
116
- }
117
- },
118
- };
119
- },
120
-
121
- /**
122
- * Abort all navigation controllers (NOT actions)
123
- *
124
- * Called when starting new navigation. Actions continue
125
- * to complete in the background.
126
- */
127
- abortAll(): void {
128
- controllers.forEach((ref) => ref.deref()?.abort());
129
- controllers.length = 0;
130
- console.log(`[Browser] Aborted all navigation controllers`);
131
- },
132
-
133
- /**
134
- * Abort all action controllers
135
- *
136
- * Called when an action error occurs - prevents other actions
137
- * from completing and overwriting the error UI.
138
- */
139
- abortAllActions(): void {
140
- actionControllers.forEach((ref) => ref.deref()?.abort());
141
- actionControllers.length = 0;
142
- console.log(`[Browser] Aborted all action controllers`);
143
- },
144
-
145
- /**
146
- * Remove a specific controller from tracking
147
- *
148
- * Call this when a request completes successfully.
149
- *
150
- * @param controller - The controller to remove
151
- */
152
- remove(controller: AbortController): void {
153
- // Prune any stale refs while searching
154
- pruneStaleRefs(controllers);
155
- const index = controllers.findIndex((ref) => ref.deref() === controller);
156
- if (index !== -1) {
157
- controllers.splice(index, 1);
158
- console.log(
159
- `[Browser] Removed abort controller, remaining: ${controllers.length}`,
160
- );
161
- }
162
- },
163
- };
164
- }
@@ -1,253 +0,0 @@
1
- /**
2
- * In-Memory Cache Store
3
- *
4
- * Simple implementation for development and testing.
5
- * Not suitable for production (no persistence, single-instance only).
6
- *
7
- * @internal This is reserved for future extensibility.
8
- * For segment caching, use MemorySegmentCacheStore instead.
9
- */
10
-
11
- import type {
12
- CacheStore,
13
- CacheEntry,
14
- CacheValue,
15
- CachePutOptions,
16
- CacheMetadata,
17
- CacheValueType,
18
- } from "./types.js";
19
-
20
- // ============================================================================
21
- // Constants
22
- // ============================================================================
23
-
24
- /** Default TTL when no explicit value is provided */
25
- const DEFAULT_TTL_SECONDS = 60;
26
-
27
- // ============================================================================
28
- // Types
29
- // ============================================================================
30
-
31
- interface StoredEntry {
32
- /** Stored value (streams/responses converted to ArrayBuffer) */
33
- value: ArrayBuffer | string | object;
34
- metadata: CacheMetadata;
35
- }
36
-
37
- /**
38
- * In-memory cache store implementation
39
- */
40
- export class MemoryCacheStore implements CacheStore {
41
- private cache = new Map<string, StoredEntry>();
42
-
43
- async match<T = CacheValue>(key: string): Promise<CacheEntry<T> | undefined> {
44
- const entry = this.cache.get(key);
45
-
46
- if (!entry) {
47
- return undefined;
48
- }
49
-
50
- // Check expiration
51
- if (entry.metadata.expiresAt && Date.now() > entry.metadata.expiresAt) {
52
- this.cache.delete(key);
53
- return undefined;
54
- }
55
-
56
- // Reconstruct value based on original type
57
- const value = this.reconstructValue(entry);
58
-
59
- return {
60
- value: value as T,
61
- metadata: entry.metadata,
62
- };
63
- }
64
-
65
- async put<T extends CacheValue>(
66
- key: string,
67
- value: T,
68
- options?: CachePutOptions
69
- ): Promise<void> {
70
- const ttl = options?.ttl ?? DEFAULT_TTL_SECONDS;
71
- const expiresAt = Date.now() + ttl * 1000;
72
-
73
- // Detect value type and convert for storage
74
- const { storedValue, valueType, responseHeaders, responseStatus } =
75
- await this.prepareForStorage(value);
76
-
77
- const metadata: CacheMetadata = {
78
- ...options?.metadata,
79
- expiresAt,
80
- valueType,
81
- responseHeaders,
82
- responseStatus,
83
- };
84
-
85
- this.cache.set(key, {
86
- value: storedValue,
87
- metadata,
88
- });
89
- }
90
-
91
- async delete(key: string): Promise<boolean> {
92
- return this.cache.delete(key);
93
- }
94
-
95
- /**
96
- * Clear all entries (useful for testing)
97
- */
98
- clear(): void {
99
- this.cache.clear();
100
- }
101
-
102
- /**
103
- * Get current cache size (useful for testing/debugging)
104
- */
105
- get size(): number {
106
- return this.cache.size;
107
- }
108
-
109
- /**
110
- * Manually purge expired entries
111
- */
112
- purgeExpired(): number {
113
- const now = Date.now();
114
- let purged = 0;
115
-
116
- for (const [key, entry] of this.cache) {
117
- if (entry.metadata.expiresAt && now > entry.metadata.expiresAt) {
118
- this.cache.delete(key);
119
- purged++;
120
- }
121
- }
122
-
123
- return purged;
124
- }
125
-
126
- /**
127
- * Prepare a value for storage
128
- * Converts streams and responses to ArrayBuffer, detects type
129
- */
130
- private async prepareForStorage(value: CacheValue): Promise<{
131
- storedValue: ArrayBuffer | string | object;
132
- valueType: CacheValueType;
133
- responseHeaders?: Record<string, string>;
134
- responseStatus?: number;
135
- }> {
136
- // ReadableStream -> ArrayBuffer
137
- if (value instanceof ReadableStream) {
138
- return {
139
- storedValue: await streamToArrayBuffer(value),
140
- valueType: "stream",
141
- };
142
- }
143
-
144
- // Response -> ArrayBuffer + headers/status
145
- if (value instanceof Response) {
146
- const headers: Record<string, string> = {};
147
- value.headers.forEach((v, k) => {
148
- headers[k] = v;
149
- });
150
-
151
- return {
152
- storedValue: await value.clone().arrayBuffer(),
153
- valueType: "response",
154
- responseHeaders: headers,
155
- responseStatus: value.status,
156
- };
157
- }
158
-
159
- // ArrayBuffer -> store as-is
160
- if (value instanceof ArrayBuffer) {
161
- return {
162
- storedValue: value,
163
- valueType: "arraybuffer",
164
- };
165
- }
166
-
167
- // String -> store as-is
168
- if (typeof value === "string") {
169
- return {
170
- storedValue: value,
171
- valueType: "string",
172
- };
173
- }
174
-
175
- // Object -> store as-is (JSON-serializable)
176
- return {
177
- storedValue: value,
178
- valueType: "object",
179
- };
180
- }
181
-
182
- /**
183
- * Reconstruct original value type from stored entry
184
- */
185
- private reconstructValue(entry: StoredEntry): CacheValue {
186
- const { value, metadata } = entry;
187
-
188
- switch (metadata.valueType) {
189
- case "stream":
190
- return arrayBufferToStream(value as ArrayBuffer);
191
-
192
- case "response": {
193
- const status = metadata.responseStatus ?? 200;
194
- // Status codes 204 (No Content) and 304 (Not Modified) cannot have a body
195
- const isNullBodyStatus = status === 204 || status === 304;
196
- return new Response(isNullBodyStatus ? null : (value as ArrayBuffer), {
197
- status,
198
- headers: metadata.responseHeaders,
199
- });
200
- }
201
-
202
- case "arraybuffer":
203
- case "string":
204
- case "object":
205
- default:
206
- return value as CacheValue;
207
- }
208
- }
209
- }
210
-
211
- /**
212
- * Convert a ReadableStream to ArrayBuffer.
213
- * @internal
214
- */
215
- async function streamToArrayBuffer(
216
- stream: ReadableStream<Uint8Array>
217
- ): Promise<ArrayBuffer> {
218
- const chunks: Uint8Array[] = [];
219
- const reader = stream.getReader();
220
-
221
- while (true) {
222
- const { done, value } = await reader.read();
223
- if (done) break;
224
- chunks.push(value);
225
- }
226
-
227
- // Concatenate chunks
228
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
229
- const result = new Uint8Array(totalLength);
230
- let offset = 0;
231
-
232
- for (const chunk of chunks) {
233
- result.set(chunk, offset);
234
- offset += chunk.length;
235
- }
236
-
237
- return result.buffer;
238
- }
239
-
240
- /**
241
- * Convert an ArrayBuffer to a ReadableStream.
242
- * @internal
243
- */
244
- function arrayBufferToStream(buffer: ArrayBuffer): ReadableStream<Uint8Array> {
245
- const uint8 = new Uint8Array(buffer);
246
-
247
- return new ReadableStream({
248
- start(controller) {
249
- controller.enqueue(uint8);
250
- controller.close();
251
- },
252
- });
253
- }
@@ -1,33 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * Href Context for route name resolution
5
- *
6
- * This module is marked "use client" so it can be imported by both:
7
- * - Server (segment-system): Gets a client reference for createElement
8
- * - Client (useHref): Uses the actual context for useContext
9
- *
10
- * The context stores:
11
- * - routeMap: Map of route names to URL patterns
12
- * - routeName: Current matched route name (for local name resolution)
13
- */
14
- import { createContext, type Context } from "react";
15
-
16
- /**
17
- * Context value for href resolution
18
- */
19
- export interface HrefContextValue {
20
- /** Route map: route name -> URL pattern */
21
- routeMap: Record<string, string>;
22
- /** Current matched route name (includes name prefix from include()) */
23
- routeName?: string;
24
- }
25
-
26
- /**
27
- * Context for href resolution (route map and current route name)
28
- *
29
- * On the server: Populated by renderSegments() via HrefContext.Provider
30
- * On the client: Populated by NavigationProvider from RSC metadata
31
- */
32
- export const HrefContext: Context<HrefContextValue | null> =
33
- createContext<HrefContextValue | null>(null);
package/src/router.gen.ts DELETED
@@ -1,6 +0,0 @@
1
- // Auto-generated by @rangojs/router - do not edit
2
- export const routes = {
3
- about: "/about",
4
- home: "/",
5
- } as const;
6
- export type routes = typeof routes;
package/src/urls.gen.ts DELETED
@@ -1,8 +0,0 @@
1
- // Auto-generated by @rangojs/router - do not edit
2
- export const routes = {
3
- health: "/health",
4
- home: "/",
5
- index: "/",
6
- post: "/:slug",
7
- } as const;
8
- export type routes = typeof routes;
@@ -1,209 +0,0 @@
1
- import type { Plugin, ResolvedConfig } from "vite";
2
- import MagicString from "magic-string";
3
- import path from "node:path";
4
- import crypto from "node:crypto";
5
-
6
- /**
7
- * Normalize path to forward slashes
8
- */
9
- function normalizePath(p: string): string {
10
- return p.split(path.sep).join("/");
11
- }
12
-
13
- /**
14
- * Generate a short hash for a handle ID
15
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short
16
- * Appends export name for easier debugging: "abc123#Breadcrumbs"
17
- */
18
- function hashHandleId(filePath: string, exportName: string): string {
19
- const input = `${filePath}#${exportName}`;
20
- const hash = crypto.createHash("sha256").update(input).digest("hex");
21
- return `${hash.slice(0, 8)}#${exportName}`;
22
- }
23
-
24
- /**
25
- * Check if file imports createHandle from rsc-router
26
- */
27
- function hasCreateHandleImport(code: string): boolean {
28
- // Match: import { createHandle } from "@rangojs/router" or "@rangojs/router/..."
29
- const pattern =
30
- /import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/[^"']+)?["']/;
31
- return pattern.test(code);
32
- }
33
-
34
- /**
35
- * Analyze createHandle arguments to determine injection strategy
36
- * Returns: { hasArgs: boolean, firstArgIsString: boolean, firstArgIsFunction: boolean }
37
- */
38
- function analyzeCreateHandleArgs(
39
- code: string,
40
- startPos: number,
41
- endPos: number
42
- ): { hasArgs: boolean; firstArgIsString: boolean; firstArgIsFunction: boolean } {
43
- // Extract the content between parentheses
44
- const content = code.slice(startPos, endPos).trim();
45
-
46
- if (!content) {
47
- return { hasArgs: false, firstArgIsString: false, firstArgIsFunction: false };
48
- }
49
-
50
- // Check if first arg starts with a quote (string literal)
51
- const firstArgIsString = /^["']/.test(content);
52
-
53
- // Check if first arg starts with ( for arrow function or function keyword
54
- const firstArgIsFunction =
55
- content.startsWith("(") ||
56
- content.startsWith("function") ||
57
- // Check for identifier that could be a collect function reference
58
- /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*(?:,|$)/.test(content);
59
-
60
- return { hasArgs: true, firstArgIsString, firstArgIsFunction };
61
- }
62
-
63
- /**
64
- * Transform export const X = createHandle(...) patterns to inject $$id
65
- *
66
- * Handles these cases:
67
- * 1. createHandle() - no args -> inject (undefined, "id")
68
- * 2. createHandle("name") - string name -> inject (, "id") after existing arg
69
- * 3. createHandle(collectFn) - collect function -> inject (collectFn, "id")
70
- * 4. createHandle("name", collectFn) - both -> inject (, "id") after existing args
71
- */
72
- function transformHandleExports(
73
- code: string,
74
- filePath: string,
75
- sourceId?: string,
76
- isBuild: boolean = false
77
- ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
78
- // Quick bail-out
79
- if (!code.includes("createHandle")) {
80
- return null;
81
- }
82
-
83
- // Must have direct import from rsc-router
84
- if (!hasCreateHandleImport(code)) {
85
- return null;
86
- }
87
-
88
- // Match: export const X = createHandle<...>(
89
- // Captures the export name (X)
90
- const pattern = /export\s+const\s+(\w+)\s*=\s*createHandle\s*(?:<[^>]*>)?\s*\(/g;
91
-
92
- const s = new MagicString(code);
93
- let hasChanges = false;
94
- let match: RegExpExecArray | null;
95
-
96
- while ((match = pattern.exec(code)) !== null) {
97
- const exportName = match[1];
98
- const matchEnd = match.index + match[0].length;
99
-
100
- // Find the end of the createHandle(...) call
101
- let parenDepth = 1;
102
- let i = matchEnd;
103
- while (i < code.length && parenDepth > 0) {
104
- if (code[i] === "(") parenDepth++;
105
- if (code[i] === ")") parenDepth--;
106
- i++;
107
- }
108
-
109
- // i now points just after the closing )
110
- const closeParenPos = i - 1;
111
-
112
- // Analyze what arguments exist
113
- const args = analyzeCreateHandleArgs(code, matchEnd, closeParenPos);
114
-
115
- // Find the semicolon or end of statement
116
- let statementEnd = i;
117
- while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
118
- statementEnd++;
119
- }
120
- if (code[statementEnd] === ";") {
121
- statementEnd++;
122
- }
123
-
124
- // Generate ID: hashed in production, readable in dev
125
- const handleId = isBuild
126
- ? hashHandleId(filePath, exportName)
127
- : `${filePath}#${exportName}`;
128
-
129
- // Inject $$id as the last parameter
130
- let paramInjection: string;
131
- if (!args.hasArgs) {
132
- // No args: createHandle() -> createHandle(undefined, "id")
133
- paramInjection = `undefined, "${handleId}"`;
134
- } else {
135
- // Has args: createHandle(x) -> createHandle(x, "id")
136
- paramInjection = `, "${handleId}"`;
137
- }
138
- s.appendLeft(closeParenPos, paramInjection);
139
-
140
- // Also set $$id property for external access
141
- const propInjection = `\n${exportName}.$$id = "${handleId}";`;
142
- s.appendRight(statementEnd, propInjection);
143
- hasChanges = true;
144
- }
145
-
146
- if (!hasChanges) {
147
- return null;
148
- }
149
-
150
- return {
151
- code: s.toString(),
152
- map: s.generateMap({ source: sourceId, includeContent: true }),
153
- };
154
- }
155
-
156
- /**
157
- * Vite plugin that exposes $$id on createHandle calls.
158
- *
159
- * When users create handles with createHandle(), this plugin:
160
- * 1. Injects a $$id as the last parameter (used as the handle name)
161
- * 2. Sets $$id property on the exported constant for external access
162
- *
163
- * This allows handles to be created without explicit names:
164
- * - Before: export const Breadcrumbs = createHandle<Item>("breadcrumbs")
165
- * - After: export const Breadcrumbs = createHandle<Item>()
166
- *
167
- * The name is auto-generated from file path + export name.
168
- *
169
- * Requirements:
170
- * - Must use direct import: import { createHandle } from "@rangojs/router"
171
- * - Must use named export: export const MyHandle = createHandle(...)
172
- */
173
- export function exposeHandleId(): Plugin {
174
- let config: ResolvedConfig;
175
- let isBuild = false;
176
-
177
- return {
178
- name: "@rangojs/router:expose-handle-id",
179
- enforce: "post",
180
-
181
- configResolved(resolvedConfig) {
182
- config = resolvedConfig;
183
- isBuild = config.command === "build";
184
- },
185
-
186
- transform(code, id) {
187
- // Skip node_modules
188
- if (id.includes("/node_modules/")) {
189
- return;
190
- }
191
-
192
- // Quick bail-out
193
- if (!code.includes("createHandle")) {
194
- return;
195
- }
196
-
197
- // Must have direct import from rsc-router
198
- if (!hasCreateHandleImport(code)) {
199
- return;
200
- }
201
-
202
- // Get relative path for the ID
203
- const relativePath = normalizePath(path.relative(config.root, id));
204
-
205
- // Transform: inject $$id
206
- return transformHandleExports(code, relativePath, id, isBuild);
207
- },
208
- };
209
- }