@rangojs/router 0.0.0-experimental.7 → 0.0.0-experimental.71

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 (307) 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 +4951 -930
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  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 +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  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 +92 -31
  18. package/skills/loader/SKILL.md +404 -44
  19. package/skills/middleware/SKILL.md +173 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +204 -1
  22. package/skills/prerender/SKILL.md +685 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +257 -14
  26. package/skills/router-setup/SKILL.md +210 -32
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +328 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  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/app-version.ts +14 -0
  36. package/src/browser/event-controller.ts +92 -64
  37. package/src/browser/history-state.ts +80 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +24 -4
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +20 -12
  42. package/src/browser/navigation-bridge.ts +296 -558
  43. package/src/browser/navigation-client.ts +179 -69
  44. package/src/browser/navigation-store.ts +73 -55
  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 +328 -313
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +150 -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 +160 -0
  53. package/src/browser/prefetch/resource-ready.ts +77 -0
  54. package/src/browser/rango-state.ts +112 -0
  55. package/src/browser/react/Link.tsx +230 -74
  56. package/src/browser/react/NavigationProvider.tsx +87 -11
  57. package/src/browser/react/context.ts +11 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +12 -12
  60. package/src/browser/react/location-state-shared.ts +95 -53
  61. package/src/browser/react/location-state.ts +60 -15
  62. package/src/browser/react/mount-context.ts +6 -1
  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 +29 -51
  66. package/src/browser/react/use-client-cache.ts +5 -3
  67. package/src/browser/react/use-handle.ts +30 -126
  68. package/src/browser/react/use-href.tsx +2 -2
  69. package/src/browser/react/use-link-status.ts +6 -5
  70. package/src/browser/react/use-navigation.ts +22 -63
  71. package/src/browser/react/use-params.ts +65 -0
  72. package/src/browser/react/use-pathname.ts +47 -0
  73. package/src/browser/react/use-router.ts +76 -0
  74. package/src/browser/react/use-search-params.ts +56 -0
  75. package/src/browser/react/use-segments.ts +80 -97
  76. package/src/browser/response-adapter.ts +73 -0
  77. package/src/browser/rsc-router.tsx +214 -58
  78. package/src/browser/scroll-restoration.ts +127 -52
  79. package/src/browser/segment-reconciler.ts +221 -0
  80. package/src/browser/segment-structure-assert.ts +16 -0
  81. package/src/browser/server-action-bridge.ts +510 -603
  82. package/src/browser/shallow.ts +6 -1
  83. package/src/browser/types.ts +141 -48
  84. package/src/browser/validate-redirect-origin.ts +29 -0
  85. package/src/build/generate-manifest.ts +235 -24
  86. package/src/build/generate-route-types.ts +39 -0
  87. package/src/build/index.ts +13 -0
  88. package/src/build/route-trie.ts +265 -0
  89. package/src/build/route-types/ast-helpers.ts +25 -0
  90. package/src/build/route-types/ast-route-extraction.ts +98 -0
  91. package/src/build/route-types/codegen.ts +102 -0
  92. package/src/build/route-types/include-resolution.ts +418 -0
  93. package/src/build/route-types/param-extraction.ts +48 -0
  94. package/src/build/route-types/per-module-writer.ts +128 -0
  95. package/src/build/route-types/router-processing.ts +618 -0
  96. package/src/build/route-types/scan-filter.ts +85 -0
  97. package/src/build/runtime-discovery.ts +231 -0
  98. package/src/cache/background-task.ts +34 -0
  99. package/src/cache/cache-key-utils.ts +44 -0
  100. package/src/cache/cache-policy.ts +125 -0
  101. package/src/cache/cache-runtime.ts +342 -0
  102. package/src/cache/cache-scope.ts +167 -309
  103. package/src/cache/cf/cf-cache-store.ts +571 -17
  104. package/src/cache/cf/index.ts +13 -3
  105. package/src/cache/document-cache.ts +116 -77
  106. package/src/cache/handle-capture.ts +81 -0
  107. package/src/cache/handle-snapshot.ts +41 -0
  108. package/src/cache/index.ts +1 -15
  109. package/src/cache/memory-segment-store.ts +191 -13
  110. package/src/cache/profile-registry.ts +73 -0
  111. package/src/cache/read-through-swr.ts +134 -0
  112. package/src/cache/segment-codec.ts +256 -0
  113. package/src/cache/taint.ts +153 -0
  114. package/src/cache/types.ts +72 -122
  115. package/src/client.rsc.tsx +3 -1
  116. package/src/client.tsx +105 -179
  117. package/src/component-utils.ts +4 -4
  118. package/src/components/DefaultDocument.tsx +5 -1
  119. package/src/context-var.ts +156 -0
  120. package/src/debug.ts +19 -9
  121. package/src/errors.ts +108 -2
  122. package/src/handle.ts +55 -29
  123. package/src/handles/MetaTags.tsx +73 -20
  124. package/src/handles/breadcrumbs.ts +66 -0
  125. package/src/handles/index.ts +1 -0
  126. package/src/handles/meta.ts +30 -13
  127. package/src/host/cookie-handler.ts +21 -15
  128. package/src/host/errors.ts +8 -8
  129. package/src/host/index.ts +4 -7
  130. package/src/host/pattern-matcher.ts +27 -27
  131. package/src/host/router.ts +61 -39
  132. package/src/host/testing.ts +8 -8
  133. package/src/host/types.ts +15 -7
  134. package/src/host/utils.ts +1 -1
  135. package/src/href-client.ts +119 -29
  136. package/src/index.rsc.ts +155 -19
  137. package/src/index.ts +223 -30
  138. package/src/internal-debug.ts +11 -0
  139. package/src/loader.rsc.ts +26 -157
  140. package/src/loader.ts +27 -10
  141. package/src/network-error-thrower.tsx +3 -1
  142. package/src/outlet-provider.tsx +45 -0
  143. package/src/prerender/param-hash.ts +37 -0
  144. package/src/prerender/store.ts +186 -0
  145. package/src/prerender.ts +524 -0
  146. package/src/reverse.ts +351 -0
  147. package/src/root-error-boundary.tsx +41 -29
  148. package/src/route-content-wrapper.tsx +7 -4
  149. package/src/route-definition/dsl-helpers.ts +982 -0
  150. package/src/route-definition/helper-factories.ts +200 -0
  151. package/src/route-definition/helpers-types.ts +434 -0
  152. package/src/route-definition/index.ts +55 -0
  153. package/src/route-definition/redirect.ts +101 -0
  154. package/src/route-definition/resolve-handler-use.ts +149 -0
  155. package/src/route-definition.ts +1 -1428
  156. package/src/route-map-builder.ts +217 -123
  157. package/src/route-name.ts +53 -0
  158. package/src/route-types.ts +70 -8
  159. package/src/router/content-negotiation.ts +215 -0
  160. package/src/router/debug-manifest.ts +72 -0
  161. package/src/router/error-handling.ts +9 -9
  162. package/src/router/find-match.ts +160 -0
  163. package/src/router/handler-context.ts +435 -86
  164. package/src/router/intercept-resolution.ts +402 -0
  165. package/src/router/lazy-includes.ts +237 -0
  166. package/src/router/loader-resolution.ts +356 -128
  167. package/src/router/logging.ts +251 -0
  168. package/src/router/manifest.ts +154 -35
  169. package/src/router/match-api.ts +555 -0
  170. package/src/router/match-context.ts +5 -3
  171. package/src/router/match-handlers.ts +440 -0
  172. package/src/router/match-middleware/background-revalidation.ts +108 -93
  173. package/src/router/match-middleware/cache-lookup.ts +459 -10
  174. package/src/router/match-middleware/cache-store.ts +98 -26
  175. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  176. package/src/router/match-middleware/segment-resolution.ts +80 -6
  177. package/src/router/match-pipelines.ts +10 -45
  178. package/src/router/match-result.ts +135 -35
  179. package/src/router/metrics.ts +240 -15
  180. package/src/router/middleware-cookies.ts +55 -0
  181. package/src/router/middleware-types.ts +220 -0
  182. package/src/router/middleware.ts +324 -369
  183. package/src/router/navigation-snapshot.ts +182 -0
  184. package/src/router/pattern-matching.ts +211 -43
  185. package/src/router/prerender-match.ts +502 -0
  186. package/src/router/preview-match.ts +98 -0
  187. package/src/router/request-classification.ts +310 -0
  188. package/src/router/revalidation.ts +137 -38
  189. package/src/router/route-snapshot.ts +245 -0
  190. package/src/router/router-context.ts +41 -21
  191. package/src/router/router-interfaces.ts +484 -0
  192. package/src/router/router-options.ts +618 -0
  193. package/src/router/router-registry.ts +24 -0
  194. package/src/router/segment-resolution/fresh.ts +748 -0
  195. package/src/router/segment-resolution/helpers.ts +268 -0
  196. package/src/router/segment-resolution/loader-cache.ts +199 -0
  197. package/src/router/segment-resolution/revalidation.ts +1379 -0
  198. package/src/router/segment-resolution/static-store.ts +67 -0
  199. package/src/router/segment-resolution.ts +21 -0
  200. package/src/router/segment-wrappers.ts +291 -0
  201. package/src/router/telemetry-otel.ts +299 -0
  202. package/src/router/telemetry.ts +300 -0
  203. package/src/router/timeout.ts +148 -0
  204. package/src/router/trie-matching.ts +239 -0
  205. package/src/router/types.ts +78 -3
  206. package/src/router.ts +740 -4252
  207. package/src/rsc/handler-context.ts +45 -0
  208. package/src/rsc/handler.ts +907 -797
  209. package/src/rsc/helpers.ts +140 -6
  210. package/src/rsc/index.ts +0 -20
  211. package/src/rsc/loader-fetch.ts +229 -0
  212. package/src/rsc/manifest-init.ts +90 -0
  213. package/src/rsc/nonce.ts +14 -0
  214. package/src/rsc/origin-guard.ts +141 -0
  215. package/src/rsc/progressive-enhancement.ts +391 -0
  216. package/src/rsc/response-error.ts +37 -0
  217. package/src/rsc/response-route-handler.ts +347 -0
  218. package/src/rsc/rsc-rendering.ts +246 -0
  219. package/src/rsc/runtime-warnings.ts +42 -0
  220. package/src/rsc/server-action.ts +356 -0
  221. package/src/rsc/ssr-setup.ts +128 -0
  222. package/src/rsc/types.ts +46 -11
  223. package/src/search-params.ts +230 -0
  224. package/src/segment-system.tsx +165 -17
  225. package/src/server/context.ts +315 -58
  226. package/src/server/cookie-store.ts +190 -0
  227. package/src/server/fetchable-loader-store.ts +37 -0
  228. package/src/server/handle-store.ts +113 -15
  229. package/src/server/loader-registry.ts +24 -64
  230. package/src/server/request-context.ts +607 -81
  231. package/src/server.ts +35 -130
  232. package/src/ssr/index.tsx +103 -30
  233. package/src/static-handler.ts +126 -0
  234. package/src/theme/ThemeProvider.tsx +21 -15
  235. package/src/theme/ThemeScript.tsx +5 -5
  236. package/src/theme/constants.ts +5 -2
  237. package/src/theme/index.ts +4 -14
  238. package/src/theme/theme-context.ts +4 -30
  239. package/src/theme/theme-script.ts +21 -18
  240. package/src/types/boundaries.ts +158 -0
  241. package/src/types/cache-types.ts +198 -0
  242. package/src/types/error-types.ts +192 -0
  243. package/src/types/global-namespace.ts +100 -0
  244. package/src/types/handler-context.ts +791 -0
  245. package/src/types/index.ts +88 -0
  246. package/src/types/loader-types.ts +210 -0
  247. package/src/types/route-config.ts +170 -0
  248. package/src/types/route-entry.ts +109 -0
  249. package/src/types/segments.ts +151 -0
  250. package/src/types.ts +1 -1623
  251. package/src/urls/include-helper.ts +197 -0
  252. package/src/urls/index.ts +53 -0
  253. package/src/urls/path-helper-types.ts +346 -0
  254. package/src/urls/path-helper.ts +364 -0
  255. package/src/urls/pattern-types.ts +107 -0
  256. package/src/urls/response-types.ts +116 -0
  257. package/src/urls/type-extraction.ts +372 -0
  258. package/src/urls/urls-function.ts +98 -0
  259. package/src/urls.ts +1 -802
  260. package/src/use-loader.tsx +161 -81
  261. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  262. package/src/vite/discovery/discover-routers.ts +348 -0
  263. package/src/vite/discovery/prerender-collection.ts +439 -0
  264. package/src/vite/discovery/route-types-writer.ts +258 -0
  265. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  266. package/src/vite/discovery/state.ts +117 -0
  267. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  268. package/src/vite/index.ts +15 -1129
  269. package/src/vite/plugin-types.ts +103 -0
  270. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  271. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  272. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  273. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  274. package/src/vite/plugins/expose-id-utils.ts +299 -0
  275. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  276. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  277. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  278. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  279. package/src/vite/plugins/expose-ids/types.ts +45 -0
  280. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  281. package/src/vite/plugins/performance-tracks.ts +88 -0
  282. package/src/vite/plugins/refresh-cmd.ts +127 -0
  283. package/src/vite/plugins/use-cache-transform.ts +323 -0
  284. package/src/vite/plugins/version-injector.ts +83 -0
  285. package/src/vite/plugins/version-plugin.ts +266 -0
  286. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  287. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  288. package/src/vite/rango.ts +462 -0
  289. package/src/vite/router-discovery.ts +918 -0
  290. package/src/vite/utils/ast-handler-extract.ts +517 -0
  291. package/src/vite/utils/banner.ts +36 -0
  292. package/src/vite/utils/bundle-analysis.ts +137 -0
  293. package/src/vite/utils/manifest-utils.ts +70 -0
  294. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  295. package/src/vite/utils/prerender-utils.ts +207 -0
  296. package/src/vite/utils/shared-utils.ts +170 -0
  297. package/CLAUDE.md +0 -43
  298. package/src/browser/lru-cache.ts +0 -69
  299. package/src/browser/request-controller.ts +0 -164
  300. package/src/cache/memory-store.ts +0 -253
  301. package/src/href-context.ts +0 -33
  302. package/src/href.ts +0 -255
  303. package/src/server/route-manifest-cache.ts +0 -173
  304. package/src/vite/expose-handle-id.ts +0 -209
  305. package/src/vite/expose-loader-id.ts +0 -426
  306. package/src/vite/expose-location-state-id.ts +0 -177
  307. /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/href.ts DELETED
@@ -1,255 +0,0 @@
1
- import type { ExtractParams } from "./types.js";
2
-
3
- /**
4
- * Sanitize prefix string by removing leading slash
5
- * "/shop" -> "shop", "blog" -> "blog", "" -> ""
6
- */
7
- export type SanitizePrefix<T extends string> = T extends `/${infer P}` ? P : T;
8
-
9
- /**
10
- * Helper type to merge multiple route definitions into a single accumulated type.
11
- * Note: When using createRouter, types accumulate automatically through the
12
- * builder chain, so this type is typically not needed.
13
- *
14
- * @example
15
- * ```typescript
16
- * // Manual type merging (rarely needed):
17
- * type AppRoutes = MergeRoutes<[
18
- * typeof homeRoutes,
19
- * PrefixRoutePatterns<typeof blogRoutes, "/blog">,
20
- * ]>;
21
- *
22
- * // Preferred: Let router accumulate types automatically
23
- * const router = createRouter<AppEnv>()
24
- * .routes(homeRoutes).map(...)
25
- * .routes("/blog", blogRoutes).map(...);
26
- * type AppRoutes = typeof router.routeMap;
27
- * ```
28
- */
29
- export type MergeRoutes<T extends unknown[]> = T extends [
30
- infer First,
31
- ...infer Rest
32
- ]
33
- ? First & MergeRoutes<Rest>
34
- : {};
35
-
36
- /**
37
- * Add key prefix to all entries in a route map
38
- * { "cart": "/cart" } with prefix "shop" -> { "shop.cart": "/shop/cart" }
39
- */
40
- export type PrefixRouteKeys<
41
- T,
42
- Prefix extends string
43
- > = Prefix extends ""
44
- ? T
45
- : { [K in keyof T as `${Prefix}.${K & string}`]: T[K] };
46
-
47
- /**
48
- * Add path prefix to all patterns in a route map
49
- * { "cart": "/cart" } with prefix "/shop" -> { "cart": "/shop/cart" }
50
- */
51
- export type PrefixRoutePatterns<
52
- T,
53
- PathPrefix extends string
54
- > = {
55
- [K in keyof T]: PathPrefix extends "" | "/"
56
- ? T[K]
57
- : T[K] extends "/"
58
- ? PathPrefix
59
- : T[K] extends string
60
- ? `${PathPrefix}${T[K]}`
61
- : T[K];
62
- };
63
-
64
- /**
65
- * Combined: prefix both keys and patterns
66
- * Used for module augmentation registration
67
- *
68
- * @example
69
- * ```typescript
70
- * // Given shopRoutes = { "index": "/", "cart": "/cart", "products.detail": "/product/:slug" }
71
- * // PrefixedRoutes<typeof shopRoutes, "shop"> produces:
72
- * // { "shop.index": "/shop", "shop.cart": "/shop/cart", "shop.products.detail": "/shop/product/:slug" }
73
- * ```
74
- */
75
- export type PrefixedRoutes<
76
- T,
77
- KeyPrefix extends string,
78
- PathPrefix extends string = KeyPrefix extends "" ? "" : `/${KeyPrefix}`
79
- > = PrefixRouteKeys<PrefixRoutePatterns<T, PathPrefix>, KeyPrefix>;
80
-
81
- /**
82
- * Helper to safely extract route patterns from a routes object
83
- * Handles both Record<string, string> and interface types (like RegisteredRoutes)
84
- */
85
- type RoutePatternFor<TRoutes, TName extends keyof TRoutes> =
86
- TRoutes[TName] extends string ? TRoutes[TName] : string;
87
-
88
- /**
89
- * Extract params type for a route
90
- */
91
- export type ParamsFor<
92
- TRoutes,
93
- TName extends keyof TRoutes
94
- > = ExtractParams<RoutePatternFor<TRoutes, TName>>;
95
-
96
- /**
97
- * Check if an object type has any keys
98
- */
99
- type IsEmptyObject<T> = keyof T extends never ? true : false;
100
-
101
- /**
102
- * Type-safe href function signature
103
- *
104
- * Validates route names and params at compile time.
105
- * Use route names instead of raw paths for full type safety.
106
- *
107
- * @example
108
- * ```typescript
109
- * href("cart") // ✓ Validates route exists
110
- * href("product.detail", { id: "123" }) // ✓ Validates route + params
111
- * ```
112
- */
113
- export type HrefFunction<TRoutes> = {
114
- /**
115
- * Route without params - validates route name exists
116
- */
117
- <TName extends keyof TRoutes & string>(
118
- name: IsEmptyObject<ExtractParams<RoutePatternFor<TRoutes, TName>>> extends true ? TName : never
119
- ): string;
120
-
121
- /**
122
- * Route with params - validates both route name and params
123
- */
124
- <TName extends keyof TRoutes & string>(
125
- name: TName,
126
- params: ExtractParams<RoutePatternFor<TRoutes, TName>>
127
- ): string;
128
- };
129
-
130
- /**
131
- * Type-safe scoped href function signature for use with scopedHref<typeof patterns>()
132
- *
133
- * **Recommended: Use route names for type safety.**
134
- * Route names validate both the route exists and params are correct.
135
- * Path-based URLs (`/...`) are an escape hatch with no validation.
136
- *
137
- * @example
138
- * ```typescript
139
- * // RECOMMENDED: Use route names for type safety
140
- * href("blog.post", { slug: "hello" }) // ✓ Validates route + params
141
- * href("shop.cart") // ✓ Validates route exists
142
- *
143
- * // ESCAPE HATCH: Path-based URLs (no validation)
144
- * href("/about") // ⚠ No type checking
145
- * href("/typo/in/path") // ⚠ Won't catch errors
146
- * ```
147
- */
148
- export type ScopedHrefFunction<TLocalRoutes> = {
149
- /**
150
- * Route without params - validates route name exists
151
- * @recommended Use this for type-safe URL generation
152
- */
153
- <TName extends keyof TLocalRoutes & string>(
154
- name: IsEmptyObject<ExtractParams<RoutePatternFor<TLocalRoutes, TName>>> extends true ? TName : never
155
- ): string;
156
-
157
- /**
158
- * Route with params - validates both route name and params
159
- * @recommended Use this for type-safe URL generation with parameters
160
- */
161
- <TName extends keyof TLocalRoutes & string>(
162
- name: TName,
163
- params: ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
164
- ): string;
165
-
166
- /**
167
- * Absolute route name (contains dot) - global lookup
168
- * Use for cross-module navigation: "shop.cart", "blog.post"
169
- */
170
- (name: `${string}.${string}`, params?: Record<string, string>): string;
171
-
172
- /**
173
- * Path-based URL - ESCAPE HATCH, no type validation
174
- * Prefer route names for type safety. Only use paths when necessary.
175
- */
176
- (name: `/${string}`, params?: Record<string, string>): string;
177
- };
178
-
179
- /**
180
- * Extract local routes type from UrlPatterns
181
- * Used with scopedHref() to get the routes type from patterns
182
- */
183
- export type ExtractLocalRoutes<TPatterns> =
184
- TPatterns extends { readonly _routes?: infer TRoutes } ? TRoutes : Record<string, string>;
185
-
186
- /**
187
- * Get a locally-typed href function from ctx.href for composable modules.
188
- *
189
- * This is a type-only cast - ctx.href already resolves local names at runtime
190
- * based on the current route prefix. This helper just provides type safety
191
- * for local route names within a url module.
192
- *
193
- * @param href - The ctx.href function from HandlerContext
194
- * @returns The same href function, but typed for local routes
195
- *
196
- * @example
197
- * ```typescript
198
- * // urls/blog.tsx
199
- * export const blogPatterns = urls(({ path }) => [
200
- * path("/", (ctx) => {
201
- * // Get locally-typed href for this module's routes
202
- * const href = scopedHref<typeof blogPatterns>(ctx.href);
203
- *
204
- * href("index"); // ✓ Type-safe local route
205
- * href("post", { slug: "x" }); // ✓ Type-safe with params
206
- * href("shop.cart"); // ✓ Cross-module (absolute name)
207
- *
208
- * return <BlogIndex />;
209
- * }, { name: "index" }),
210
- *
211
- * path("/:slug", BlogPost, { name: "post" }),
212
- * ]);
213
- * ```
214
- */
215
- export function scopedHref<TPatterns>(
216
- href: (name: string, params?: Record<string, string>) => string
217
- ): ScopedHrefFunction<ExtractLocalRoutes<TPatterns>> {
218
- return href as ScopedHrefFunction<ExtractLocalRoutes<TPatterns>>;
219
- }
220
-
221
- /**
222
- * Create a type-safe href function for URL generation
223
- *
224
- * @param routeMap - Flattened route map with all registered routes
225
- * @returns Type-safe href function
226
- *
227
- * @example
228
- * ```typescript
229
- * // Given routes: { cart: "/shop/cart", detail: "/shop/product/:slug" }
230
- * const href = createHref(routeMap);
231
- * href("cart"); // "/shop/cart"
232
- * href("detail", { slug: "my-product" }); // "/shop/product/my-product"
233
- * ```
234
- */
235
- export function createHref<TRoutes extends Record<string, string>>(
236
- routeMap: TRoutes
237
- ): HrefFunction<TRoutes & Record<string, string>> {
238
- return ((name: string, params?: Record<string, string>) => {
239
- const pattern = routeMap[name];
240
- if (!pattern) {
241
- throw new Error(`Unknown route: ${name}`);
242
- }
243
-
244
- if (!params) return pattern;
245
-
246
- // Replace :param placeholders with actual values
247
- return pattern.replace(/:([^/]+)/g, (_, key) => {
248
- const value = params[key];
249
- if (value === undefined) {
250
- throw new Error(`Missing param "${key}" for route "${name}"`);
251
- }
252
- return encodeURIComponent(value);
253
- });
254
- }) as HrefFunction<TRoutes>;
255
- }