@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19

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 (177) hide show
  1. package/README.md +46 -8
  2. package/dist/bin/rango.js +105 -18
  3. package/dist/vite/index.js +227 -93
  4. package/package.json +15 -14
  5. package/skills/hooks/SKILL.md +1 -1
  6. package/skills/intercept/SKILL.md +79 -0
  7. package/skills/layout/SKILL.md +62 -2
  8. package/skills/loader/SKILL.md +94 -1
  9. package/skills/middleware/SKILL.md +81 -0
  10. package/skills/parallel/SKILL.md +57 -2
  11. package/skills/prerender/SKILL.md +187 -17
  12. package/skills/route/SKILL.md +42 -1
  13. package/skills/router-setup/SKILL.md +77 -0
  14. package/src/__internal.ts +1 -1
  15. package/src/bin/rango.ts +38 -19
  16. package/src/browser/action-coordinator.ts +97 -0
  17. package/src/browser/event-controller.ts +25 -27
  18. package/src/browser/history-state.ts +80 -0
  19. package/src/browser/intercept-utils.ts +1 -1
  20. package/src/browser/link-interceptor.ts +0 -3
  21. package/src/browser/merge-segment-loaders.ts +9 -2
  22. package/src/browser/navigation-bridge.ts +46 -13
  23. package/src/browser/navigation-client.ts +32 -61
  24. package/src/browser/navigation-store.ts +1 -31
  25. package/src/browser/navigation-transaction.ts +46 -207
  26. package/src/browser/partial-update.ts +102 -150
  27. package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
  28. package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
  29. package/src/browser/prefetch/policy.ts +42 -0
  30. package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
  31. package/src/browser/react/Link.tsx +28 -23
  32. package/src/browser/react/NavigationProvider.tsx +9 -1
  33. package/src/browser/react/index.ts +2 -6
  34. package/src/browser/react/location-state-shared.ts +1 -1
  35. package/src/browser/react/location-state.ts +2 -0
  36. package/src/browser/react/nonce-context.ts +23 -0
  37. package/src/browser/react/use-action.ts +9 -1
  38. package/src/browser/react/use-handle.ts +3 -25
  39. package/src/browser/react/use-params.ts +2 -4
  40. package/src/browser/react/use-pathname.ts +2 -3
  41. package/src/browser/react/use-router.ts +1 -1
  42. package/src/browser/react/use-search-params.ts +2 -1
  43. package/src/browser/react/use-segments.ts +7 -60
  44. package/src/browser/response-adapter.ts +73 -0
  45. package/src/browser/rsc-router.tsx +29 -23
  46. package/src/browser/scroll-restoration.ts +10 -7
  47. package/src/browser/server-action-bridge.ts +115 -96
  48. package/src/browser/types.ts +1 -31
  49. package/src/browser/validate-redirect-origin.ts +29 -0
  50. package/src/build/generate-manifest.ts +5 -0
  51. package/src/build/generate-route-types.ts +2 -0
  52. package/src/build/route-types/codegen.ts +13 -4
  53. package/src/build/route-types/include-resolution.ts +13 -0
  54. package/src/build/route-types/per-module-writer.ts +15 -3
  55. package/src/build/route-types/router-processing.ts +45 -3
  56. package/src/build/runtime-discovery.ts +13 -1
  57. package/src/cache/background-task.ts +34 -0
  58. package/src/cache/cache-key-utils.ts +44 -0
  59. package/src/cache/cache-policy.ts +125 -0
  60. package/src/cache/cache-runtime.ts +132 -96
  61. package/src/cache/cache-scope.ts +71 -73
  62. package/src/cache/cf/cf-cache-store.ts +9 -4
  63. package/src/cache/document-cache.ts +72 -47
  64. package/src/cache/handle-capture.ts +81 -0
  65. package/src/cache/memory-segment-store.ts +18 -7
  66. package/src/cache/profile-registry.ts +43 -8
  67. package/src/cache/read-through-swr.ts +134 -0
  68. package/src/cache/segment-codec.ts +101 -112
  69. package/src/cache/taint.ts +26 -0
  70. package/src/client.tsx +53 -30
  71. package/src/errors.ts +6 -1
  72. package/src/handle.ts +1 -1
  73. package/src/handles/MetaTags.tsx +5 -2
  74. package/src/host/cookie-handler.ts +8 -3
  75. package/src/host/router.ts +14 -1
  76. package/src/href-client.ts +3 -1
  77. package/src/index.rsc.ts +33 -1
  78. package/src/index.ts +27 -0
  79. package/src/loader.rsc.ts +12 -4
  80. package/src/loader.ts +8 -0
  81. package/src/prerender/store.ts +4 -3
  82. package/src/prerender.ts +76 -18
  83. package/src/reverse.ts +11 -7
  84. package/src/root-error-boundary.tsx +30 -26
  85. package/src/route-definition/dsl-helpers.ts +9 -6
  86. package/src/route-definition/redirect.ts +15 -3
  87. package/src/route-map-builder.ts +38 -2
  88. package/src/route-name.ts +53 -0
  89. package/src/route-types.ts +7 -0
  90. package/src/router/content-negotiation.ts +1 -1
  91. package/src/router/debug-manifest.ts +16 -3
  92. package/src/router/handler-context.ts +94 -15
  93. package/src/router/intercept-resolution.ts +6 -4
  94. package/src/router/lazy-includes.ts +4 -0
  95. package/src/router/loader-resolution.ts +1 -0
  96. package/src/router/logging.ts +100 -3
  97. package/src/router/manifest.ts +32 -3
  98. package/src/router/match-api.ts +61 -7
  99. package/src/router/match-context.ts +3 -0
  100. package/src/router/match-handlers.ts +185 -11
  101. package/src/router/match-middleware/background-revalidation.ts +65 -85
  102. package/src/router/match-middleware/cache-lookup.ts +69 -4
  103. package/src/router/match-middleware/cache-store.ts +2 -0
  104. package/src/router/match-pipelines.ts +8 -43
  105. package/src/router/middleware-types.ts +7 -0
  106. package/src/router/middleware.ts +93 -8
  107. package/src/router/pattern-matching.ts +41 -5
  108. package/src/router/prerender-match.ts +34 -6
  109. package/src/router/preview-match.ts +7 -1
  110. package/src/router/revalidation.ts +61 -2
  111. package/src/router/router-context.ts +15 -0
  112. package/src/router/router-interfaces.ts +34 -0
  113. package/src/router/router-options.ts +200 -0
  114. package/src/router/segment-resolution/fresh.ts +123 -30
  115. package/src/router/segment-resolution/helpers.ts +19 -0
  116. package/src/router/segment-resolution/loader-cache.ts +37 -146
  117. package/src/router/segment-resolution/revalidation.ts +358 -94
  118. package/src/router/segment-wrappers.ts +3 -0
  119. package/src/router/telemetry-otel.ts +299 -0
  120. package/src/router/telemetry.ts +300 -0
  121. package/src/router/timeout.ts +148 -0
  122. package/src/router/types.ts +7 -1
  123. package/src/router.ts +155 -11
  124. package/src/rsc/handler-context.ts +11 -0
  125. package/src/rsc/handler.ts +380 -88
  126. package/src/rsc/helpers.ts +25 -16
  127. package/src/rsc/loader-fetch.ts +84 -42
  128. package/src/rsc/origin-guard.ts +141 -0
  129. package/src/rsc/progressive-enhancement.ts +232 -19
  130. package/src/rsc/response-route-handler.ts +37 -26
  131. package/src/rsc/rsc-rendering.ts +12 -5
  132. package/src/rsc/runtime-warnings.ts +42 -0
  133. package/src/rsc/server-action.ts +134 -58
  134. package/src/rsc/types.ts +8 -0
  135. package/src/search-params.ts +22 -10
  136. package/src/server/context.ts +53 -5
  137. package/src/server/fetchable-loader-store.ts +11 -6
  138. package/src/server/handle-store.ts +66 -9
  139. package/src/server/loader-registry.ts +11 -46
  140. package/src/server/request-context.ts +90 -9
  141. package/src/ssr/index.tsx +63 -27
  142. package/src/static-handler.ts +7 -0
  143. package/src/theme/ThemeProvider.tsx +6 -1
  144. package/src/theme/index.ts +1 -6
  145. package/src/theme/theme-context.ts +1 -28
  146. package/src/theme/theme-script.ts +2 -1
  147. package/src/types/cache-types.ts +5 -0
  148. package/src/types/error-types.ts +3 -0
  149. package/src/types/global-namespace.ts +9 -0
  150. package/src/types/handler-context.ts +35 -13
  151. package/src/types/loader-types.ts +7 -0
  152. package/src/types/route-entry.ts +28 -0
  153. package/src/urls/include-helper.ts +49 -8
  154. package/src/urls/index.ts +1 -0
  155. package/src/urls/path-helper-types.ts +30 -12
  156. package/src/urls/path-helper.ts +17 -2
  157. package/src/urls/pattern-types.ts +21 -1
  158. package/src/urls/response-types.ts +27 -2
  159. package/src/urls/type-extraction.ts +23 -15
  160. package/src/use-loader.tsx +12 -4
  161. package/src/vite/discovery/bundle-postprocess.ts +12 -7
  162. package/src/vite/discovery/discover-routers.ts +30 -18
  163. package/src/vite/discovery/prerender-collection.ts +24 -27
  164. package/src/vite/discovery/route-types-writer.ts +7 -7
  165. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  166. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  167. package/src/vite/plugins/use-cache-transform.ts +91 -3
  168. package/src/vite/rango.ts +3 -3
  169. package/src/vite/router-discovery.ts +99 -36
  170. package/src/vite/utils/prerender-utils.ts +21 -0
  171. package/src/vite/utils/shared-utils.ts +3 -1
  172. package/src/browser/request-controller.ts +0 -164
  173. package/src/href-context.ts +0 -33
  174. package/src/router.gen.ts +0 -6
  175. package/src/static-handler.gen.ts +0 -5
  176. package/src/urls.gen.ts +0 -8
  177. /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
@@ -20,12 +20,17 @@
20
20
 
21
21
  import type { LoaderEntry } from "../../server/context.js";
22
22
  import type { HandlerContext } from "../../types.js";
23
- import type { SegmentCacheStore } from "../../cache/types.js";
24
23
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
24
+ import { getRequestContext } from "../../server/request-context.js";
25
+ import { sortedRouteParams } from "../../cache/cache-key-utils.js";
25
26
  import {
26
- getRequestContext,
27
- _getRequestContext,
28
- } from "../../server/request-context.js";
27
+ resolveTtl,
28
+ resolveSwrWindow,
29
+ resolveCacheKey,
30
+ resolveCacheStore,
31
+ DEFAULT_ROUTE_TTL,
32
+ } from "../../cache/cache-policy.js";
33
+ import { readThroughItem } from "../../cache/read-through-swr.js";
29
34
  // Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
30
35
  // import segment-resolution but never use loader caching.
31
36
  let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
@@ -42,8 +47,6 @@ async function getCodec() {
42
47
  };
43
48
  }
44
49
 
45
- const DEFAULT_TTL_SECONDS = 60;
46
-
47
50
  function debugLoaderCacheLog(message: string): void {
48
51
  if (INTERNAL_RANGO_DEBUG) {
49
52
  console.log(message);
@@ -55,69 +58,32 @@ function getDefaultLoaderCacheKey(
55
58
  pathname: string,
56
59
  params: Record<string, string>,
57
60
  ): string {
58
- const paramStr = Object.entries(params)
59
- .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
60
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
61
- .join("&");
62
-
61
+ const paramStr = sortedRouteParams(params);
63
62
  const base = paramStr ? `${pathname}:${paramStr}` : pathname;
64
63
  return `loader:${loaderId}:${base}`;
65
64
  }
66
65
 
67
66
  /**
68
- * Resolve cache key using the same 3-tier priority as CacheScope.resolveKey():
69
- * 1. options.key (full override)
70
- * 2. store.keyGenerator (modifies default)
71
- * 3. Default loader key
67
+ * Resolve cache key using the shared 3-tier priority.
72
68
  */
73
69
  async function resolveLoaderKey(
74
70
  loaderEntry: LoaderEntry,
75
- store: SegmentCacheStore,
71
+ store: import("../../cache/types.js").SegmentCacheStore,
76
72
  loaderId: string,
77
73
  pathname: string,
78
74
  params: Record<string, string>,
79
75
  ): Promise<string> {
80
76
  const options = loaderEntry.cache!.options;
81
- if (options === false) {
82
- return getDefaultLoaderCacheKey(loaderId, pathname, params);
83
- }
84
-
85
- const requestCtx = getRequestContext();
86
-
87
- // Priority 1: Route-level key function (full override)
88
- if (options.key && requestCtx) {
89
- try {
90
- return await options.key(requestCtx);
91
- } catch (error) {
92
- console.error(
93
- `[LoaderCache] Custom key function failed, using default:`,
94
- error,
95
- );
96
- }
97
- }
98
-
99
77
  const defaultKey = getDefaultLoaderCacheKey(loaderId, pathname, params);
100
-
101
- // Priority 2: Store-level keyGenerator
102
- if (store.keyGenerator && requestCtx) {
103
- try {
104
- return await store.keyGenerator(requestCtx, defaultKey);
105
- } catch (error) {
106
- console.error(
107
- `[LoaderCache] Store keyGenerator failed, using default:`,
108
- error,
109
- );
110
- }
111
- }
112
-
113
- // Priority 3: Default key
114
- return defaultKey;
78
+ if (options === false) return defaultKey;
79
+ return resolveCacheKey(options.key, store, defaultKey, "LoaderCache");
115
80
  }
116
81
 
117
82
  /**
118
83
  * Resolve tags from cache options (static array or function).
119
84
  * Fails open: a thrown tag callback falls back to no tags rather than
120
- * aborting the request, consistent with resolveLoaderKey().
85
+ * aborting the request. Tags are additive metadata (not identity), so
86
+ * a missing tag does not cause cache collisions.
121
87
  */
122
88
  function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
123
89
  const options = loaderEntry.cache?.options;
@@ -140,41 +106,12 @@ function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
140
106
  return options.tags;
141
107
  }
142
108
 
143
- function getLoaderStore(loaderEntry: LoaderEntry): SegmentCacheStore | null {
144
- const cacheConfig = loaderEntry.cache;
145
- if (!cacheConfig || cacheConfig.options === false) return null;
146
- const options = cacheConfig.options;
147
-
148
- // Explicit store from cache() options
149
- if (options.store) return options.store;
150
-
151
- // App-level store from request context
152
- return _getRequestContext()?._cacheStore ?? null;
153
- }
154
-
155
- function getLoaderTtl(
156
- loaderEntry: LoaderEntry,
157
- store: SegmentCacheStore,
158
- ): number {
159
- const cacheConfig = loaderEntry.cache;
160
- if (!cacheConfig || cacheConfig.options === false) return DEFAULT_TTL_SECONDS;
161
- const options = cacheConfig.options;
162
-
163
- if (options.ttl !== undefined) return options.ttl;
164
- if (store.defaults?.ttl !== undefined) return store.defaults.ttl;
165
- return DEFAULT_TTL_SECONDS;
166
- }
167
-
168
- function getLoaderSwr(
109
+ function getLoaderStore(
169
110
  loaderEntry: LoaderEntry,
170
- store: SegmentCacheStore,
171
- ): number | undefined {
111
+ ): import("../../cache/types.js").SegmentCacheStore | null {
172
112
  const cacheConfig = loaderEntry.cache;
173
- if (!cacheConfig || cacheConfig.options === false) return undefined;
174
- const options = cacheConfig.options;
175
-
176
- if (options.swr !== undefined) return options.swr;
177
- return store.defaults?.swr;
113
+ if (!cacheConfig || cacheConfig.options === false) return null;
114
+ return resolveCacheStore(cacheConfig.options.store);
178
115
  }
179
116
 
180
117
  /**
@@ -210,8 +147,9 @@ export function resolveLoaderData<TEnv>(
210
147
  }
211
148
 
212
149
  const loaderId = loaderEntry.loader.$$id;
213
- const ttl = getLoaderTtl(loaderEntry, store);
214
- const swr = getLoaderSwr(loaderEntry, store);
150
+ const ttl = resolveTtl(options.ttl, store.defaults, DEFAULT_ROUTE_TTL);
151
+ const swrWindow = resolveSwrWindow(options.swr, store.defaults);
152
+ const swr = swrWindow || undefined;
215
153
  const tags = resolveTags(loaderEntry);
216
154
 
217
155
  // Wrap ctx.use() so cache HIT primes the handler's memoization map.
@@ -228,67 +166,20 @@ export function resolveLoaderData<TEnv>(
228
166
  ctx.params,
229
167
  );
230
168
 
231
- // Cache lookup
232
- try {
233
- const cached = await store.getItem!(key);
234
-
235
- if (cached) {
236
- const data = await codec.deserializeResult(cached.value);
237
-
238
- if (!cached.shouldRevalidate) {
239
- debugLoaderCacheLog(`[LoaderCache] HIT: ${key}`);
240
- return data;
241
- }
242
-
243
- // Stale hit — return stale data, revalidate in background
244
- debugLoaderCacheLog(`[LoaderCache] STALE: ${key}`);
245
- const requestCtx = getRequestContext();
246
- const revalidate = async () => {
247
- try {
248
- const fresh = await originalUse(loaderEntry.loader);
249
- const serialized = await codec.serializeResult(fresh);
250
- if (serialized !== null) {
251
- await store.setItem!(key, serialized, { ttl, swr, tags });
252
- }
253
- } catch {
254
- // Background revalidation failed silently
255
- }
256
- };
257
- if (requestCtx?.waitUntil) {
258
- requestCtx.waitUntil(revalidate);
259
- } else {
260
- revalidate();
261
- }
262
- return data;
263
- }
264
- } catch {
265
- // Cache lookup failed, fall through to fresh execution
266
- }
267
-
268
- // Cache miss — execute loader via ctx.use() (which memoizes it)
269
- debugLoaderCacheLog(`[LoaderCache] MISS: ${key}`);
270
- const data = await originalUse(loaderEntry.loader);
271
-
272
- // Non-blocking cache write
273
- const requestCtx = getRequestContext();
274
- const cacheWrite = async () => {
275
- try {
276
- const serialized = await codec.serializeResult(data);
277
- if (serialized !== null) {
278
- await store.setItem!(key, serialized, { ttl, swr, tags });
279
- debugLoaderCacheLog(`[LoaderCache] Cached: ${key}`);
280
- }
281
- } catch {
282
- // Cache write failed silently
283
- }
284
- };
285
- if (requestCtx?.waitUntil) {
286
- requestCtx.waitUntil(cacheWrite);
287
- } else {
288
- await cacheWrite();
289
- }
290
-
291
- return data;
169
+ return readThroughItem({
170
+ getItem: (k) => store.getItem!(k),
171
+ setItem: (k, v, o) => store.setItem!(k, v, o),
172
+ key,
173
+ execute: () => originalUse(loaderEntry.loader),
174
+ serialize: (d) => codec.serializeResult(d),
175
+ deserialize: (v) => codec.deserializeResult(v),
176
+ storeOptions: { ttl, swr, tags },
177
+ onHit: () => debugLoaderCacheLog(`[LoaderCache] HIT: ${key}`),
178
+ onStale: () => debugLoaderCacheLog(`[LoaderCache] STALE: ${key}`),
179
+ onMiss: () => debugLoaderCacheLog(`[LoaderCache] MISS: ${key}`),
180
+ onCached: () => debugLoaderCacheLog(`[LoaderCache] Cached: ${key}`),
181
+ host: getRequestContext(),
182
+ });
292
183
  })();
293
184
 
294
185
  // Temporarily replace ctx.use() so the handler's call returns cached data.