@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126

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 (235) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/rango.js +3 -4
  3. package/dist/vite/index.js +315 -68
  4. package/package.json +19 -18
  5. package/skills/breadcrumbs/SKILL.md +60 -0
  6. package/skills/hooks/SKILL.md +2 -2
  7. package/skills/route/SKILL.md +6 -0
  8. package/skills/server-actions/SKILL.md +25 -1
  9. package/skills/testing/SKILL.md +17 -17
  10. package/skills/testing/cache-prerender.md +29 -3
  11. package/skills/testing/flight.md +13 -10
  12. package/skills/testing/render-handler.md +3 -0
  13. package/skills/testing/server-tree.md +1 -1
  14. package/skills/testing/setup.md +1 -1
  15. package/src/__internal.ts +0 -65
  16. package/src/browser/action-coordinator.ts +1 -1
  17. package/src/browser/action-fence.ts +10 -0
  18. package/src/browser/event-controller.ts +1 -83
  19. package/src/browser/navigation-store-handle.ts +3 -4
  20. package/src/browser/navigation-store.ts +0 -39
  21. package/src/browser/navigation-transaction.ts +0 -32
  22. package/src/browser/partial-update.ts +23 -84
  23. package/src/browser/prefetch/cache.ts +6 -45
  24. package/src/browser/prefetch/queue.ts +6 -3
  25. package/src/browser/rango-state.ts +2 -23
  26. package/src/browser/react/Link.tsx +0 -2
  27. package/src/browser/react/NavigationProvider.tsx +2 -1
  28. package/src/browser/react/ScrollRestoration.tsx +10 -6
  29. package/src/browser/react/filter-segment-order.ts +0 -2
  30. package/src/browser/react/index.ts +0 -45
  31. package/src/browser/react/location-state-shared.ts +0 -13
  32. package/src/browser/react/location-state.ts +0 -1
  33. package/src/browser/react/use-action.ts +6 -15
  34. package/src/browser/react/use-handle.ts +0 -5
  35. package/src/browser/react/use-link-status.ts +0 -4
  36. package/src/browser/react/use-navigation.ts +0 -3
  37. package/src/browser/react/use-params.ts +0 -2
  38. package/src/browser/react/use-router.ts +2 -1
  39. package/src/browser/react/use-search-params.ts +0 -5
  40. package/src/browser/react/use-segments.ts +0 -13
  41. package/src/browser/rsc-router.tsx +10 -3
  42. package/src/browser/server-action-bridge.ts +51 -3
  43. package/src/browser/types.ts +23 -5
  44. package/src/browser/validate-redirect-origin.ts +43 -16
  45. package/src/build/index.ts +8 -9
  46. package/src/build/route-trie.ts +46 -11
  47. package/src/build/route-types/param-extraction.ts +6 -3
  48. package/src/build/route-types/router-processing.ts +0 -8
  49. package/src/cache/cache-policy.ts +0 -54
  50. package/src/cache/cache-runtime.ts +48 -24
  51. package/src/cache/cache-scope.ts +0 -27
  52. package/src/cache/cache-tag.ts +0 -37
  53. package/src/cache/cf/cf-cache-store.ts +72 -45
  54. package/src/cache/cf/index.ts +0 -24
  55. package/src/cache/document-cache.ts +10 -36
  56. package/src/cache/handle-snapshot.ts +0 -40
  57. package/src/cache/index.ts +0 -27
  58. package/src/cache/memory-segment-store.ts +0 -52
  59. package/src/cache/profile-registry.ts +6 -30
  60. package/src/cache/read-through-swr.ts +41 -11
  61. package/src/cache/segment-codec.ts +0 -16
  62. package/src/cache/types.ts +0 -98
  63. package/src/client.rsc.tsx +4 -22
  64. package/src/client.tsx +19 -32
  65. package/src/context-var.ts +12 -0
  66. package/src/defer.ts +196 -0
  67. package/src/deps/ssr.ts +0 -1
  68. package/src/handle.ts +2 -12
  69. package/src/handles/MetaTags.tsx +0 -14
  70. package/src/handles/breadcrumbs.ts +16 -5
  71. package/src/handles/meta.ts +0 -39
  72. package/src/host/cookie-handler.ts +0 -36
  73. package/src/host/errors.ts +0 -24
  74. package/src/host/index.ts +6 -0
  75. package/src/host/pattern-matcher.ts +7 -50
  76. package/src/host/router.ts +1 -65
  77. package/src/host/testing.ts +0 -16
  78. package/src/host/types.ts +6 -2
  79. package/src/href-client.ts +0 -4
  80. package/src/index.rsc.ts +27 -2
  81. package/src/index.ts +7 -0
  82. package/src/internal-debug.ts +2 -4
  83. package/src/loader.rsc.ts +4 -15
  84. package/src/loader.ts +3 -9
  85. package/src/network-error-thrower.tsx +1 -6
  86. package/src/outlet-provider.tsx +1 -5
  87. package/src/prerender/param-hash.ts +10 -11
  88. package/src/prerender/store.ts +23 -30
  89. package/src/prerender.ts +34 -0
  90. package/src/redirect-origin.ts +100 -0
  91. package/src/root-error-boundary.tsx +1 -19
  92. package/src/route-content-wrapper.tsx +1 -44
  93. package/src/route-definition/dsl-helpers.ts +7 -19
  94. package/src/route-definition/helpers-types.ts +3 -3
  95. package/src/route-definition/redirect.ts +43 -9
  96. package/src/route-definition/resolve-handler-use.ts +6 -0
  97. package/src/route-map-builder.ts +0 -16
  98. package/src/router/content-negotiation.ts +0 -13
  99. package/src/router/error-handling.ts +12 -16
  100. package/src/router/find-match.ts +4 -31
  101. package/src/router/intercept-resolution.ts +10 -1
  102. package/src/router/lazy-includes.ts +1 -57
  103. package/src/router/loader-resolution.ts +25 -23
  104. package/src/router/logging.ts +0 -6
  105. package/src/router/manifest.ts +1 -25
  106. package/src/router/match-api.ts +0 -20
  107. package/src/router/match-context.ts +0 -22
  108. package/src/router/match-handlers.ts +0 -43
  109. package/src/router/match-middleware/background-revalidation.ts +0 -7
  110. package/src/router/match-middleware/cache-lookup.ts +96 -179
  111. package/src/router/match-middleware/cache-store.ts +0 -31
  112. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  113. package/src/router/match-middleware/segment-resolution.ts +0 -22
  114. package/src/router/match-pipelines.ts +1 -42
  115. package/src/router/match-result.ts +1 -52
  116. package/src/router/metrics.ts +0 -34
  117. package/src/router/middleware-types.ts +0 -116
  118. package/src/router/middleware.ts +77 -60
  119. package/src/router/navigation-snapshot.ts +0 -51
  120. package/src/router/params-util.ts +23 -0
  121. package/src/router/pattern-matching.ts +5 -56
  122. package/src/router/prerender-match.ts +56 -51
  123. package/src/router/request-classification.ts +1 -38
  124. package/src/router/revalidation.ts +14 -62
  125. package/src/router/route-snapshot.ts +0 -1
  126. package/src/router/router-context.ts +0 -27
  127. package/src/router/router-interfaces.ts +10 -0
  128. package/src/router/segment-resolution/fresh.ts +25 -57
  129. package/src/router/segment-resolution/helpers.ts +34 -0
  130. package/src/router/segment-resolution/loader-cache.ts +35 -23
  131. package/src/router/segment-resolution/revalidation.ts +188 -283
  132. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  133. package/src/router/segment-resolution.ts +4 -1
  134. package/src/router/segment-wrappers.ts +0 -3
  135. package/src/router/telemetry-otel.ts +0 -20
  136. package/src/router/telemetry.ts +0 -22
  137. package/src/router/timeout.ts +0 -20
  138. package/src/router/trie-matching.ts +66 -45
  139. package/src/router/types.ts +1 -63
  140. package/src/router/url-params.ts +0 -5
  141. package/src/router.ts +8 -11
  142. package/src/rsc/handler-context.ts +1 -0
  143. package/src/rsc/handler.ts +20 -4
  144. package/src/rsc/helpers.ts +71 -3
  145. package/src/rsc/json-route-result.ts +38 -0
  146. package/src/rsc/origin-guard.ts +9 -15
  147. package/src/rsc/progressive-enhancement.ts +10 -1
  148. package/src/rsc/redirect-guard.ts +99 -0
  149. package/src/rsc/response-route-handler.ts +23 -18
  150. package/src/rsc/rsc-rendering.ts +2 -7
  151. package/src/rsc/runtime-warnings.ts +14 -0
  152. package/src/rsc/server-action.ts +34 -29
  153. package/src/rsc/types.ts +6 -3
  154. package/src/search-params.ts +0 -16
  155. package/src/segment-loader-promise.ts +14 -2
  156. package/src/segment-system.tsx +79 -88
  157. package/src/server/handle-store.ts +7 -24
  158. package/src/server/loader-registry.ts +5 -24
  159. package/src/server/request-context.ts +29 -92
  160. package/src/ssr/index.tsx +14 -14
  161. package/src/static-handler.ts +2 -27
  162. package/src/testing/cache-status.ts +44 -48
  163. package/src/testing/collect-handle.ts +1 -24
  164. package/src/testing/dispatch.ts +43 -6
  165. package/src/testing/e2e/index.ts +1 -22
  166. package/src/testing/e2e/matchers.ts +0 -16
  167. package/src/testing/flight-matchers.ts +0 -13
  168. package/src/testing/flight-normalize.ts +3 -30
  169. package/src/testing/flight.ts +46 -48
  170. package/src/testing/generated-routes.ts +1 -41
  171. package/src/testing/index.ts +1 -21
  172. package/src/testing/internal/context.ts +3 -45
  173. package/src/testing/internal/seed-vars.ts +0 -26
  174. package/src/testing/render-handler.ts +31 -61
  175. package/src/testing/render-route.tsx +75 -103
  176. package/src/testing/run-loader.ts +0 -96
  177. package/src/testing/run-middleware.ts +0 -26
  178. package/src/theme/ThemeProvider.tsx +0 -52
  179. package/src/theme/ThemeScript.tsx +0 -6
  180. package/src/theme/constants.ts +0 -12
  181. package/src/theme/index.ts +0 -7
  182. package/src/theme/theme-context.ts +1 -5
  183. package/src/theme/theme-script.ts +0 -14
  184. package/src/theme/use-theme.ts +0 -3
  185. package/src/types/boundaries.ts +0 -35
  186. package/src/types/error-types.ts +25 -89
  187. package/src/types/global-namespace.ts +4 -14
  188. package/src/types/handler-context.ts +28 -9
  189. package/src/types/index.ts +0 -10
  190. package/src/types/request-scope.ts +0 -19
  191. package/src/types/route-config.ts +6 -50
  192. package/src/types/route-entry.ts +0 -6
  193. package/src/types/segments.ts +0 -13
  194. package/src/urls/include-helper.ts +0 -4
  195. package/src/urls/index.ts +0 -6
  196. package/src/urls/path-helper-types.ts +2 -2
  197. package/src/urls/path-helper.ts +0 -54
  198. package/src/urls/urls-function.ts +0 -13
  199. package/src/use-loader.tsx +0 -186
  200. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  201. package/src/vite/discovery/discover-routers.ts +28 -18
  202. package/src/vite/discovery/prerender-collection.ts +2 -4
  203. package/src/vite/discovery/state.ts +5 -0
  204. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  205. package/src/vite/plugin-types.ts +35 -9
  206. package/src/vite/plugins/cjs-to-esm.ts +0 -11
  207. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  208. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  209. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  210. package/src/vite/plugins/expose-action-id.ts +2 -73
  211. package/src/vite/plugins/expose-id-utils.ts +0 -55
  212. package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
  213. package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
  214. package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
  215. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  216. package/src/vite/plugins/expose-internal-ids.ts +10 -0
  217. package/src/vite/plugins/performance-tracks.ts +0 -3
  218. package/src/vite/plugins/refresh-cmd.ts +1 -1
  219. package/src/vite/plugins/use-cache-transform.ts +21 -46
  220. package/src/vite/plugins/version-injector.ts +0 -20
  221. package/src/vite/plugins/version-plugin.ts +1 -49
  222. package/src/vite/plugins/virtual-entries.ts +0 -15
  223. package/src/vite/rango.ts +2 -108
  224. package/src/vite/router-discovery.ts +9 -1
  225. package/src/vite/utils/ast-handler-extract.ts +0 -16
  226. package/src/vite/utils/bundle-analysis.ts +6 -13
  227. package/src/vite/utils/client-chunks.ts +0 -6
  228. package/src/vite/utils/forward-user-plugins.ts +0 -22
  229. package/src/vite/utils/manifest-utils.ts +0 -4
  230. package/src/vite/utils/package-resolution.ts +1 -73
  231. package/src/vite/utils/prerender-utils.ts +0 -35
  232. package/src/vite/utils/shared-utils.ts +3 -35
  233. package/src/browser/shallow.ts +0 -40
  234. package/src/handles/index.ts +0 -7
  235. package/src/router/middleware-cookies.ts +0 -55
@@ -125,17 +125,14 @@ export function withInterceptResolution<TEnv>(
125
125
  ): AsyncGenerator<ResolvedSegment> {
126
126
  const ms = ctx.metricsStore;
127
127
 
128
- // First, yield all segments from the source (main segment resolution or cache)
129
128
  const segments: ResolvedSegment[] = [];
130
129
  for await (const segment of source) {
131
130
  segments.push(segment);
132
131
  yield segment;
133
132
  }
134
133
 
135
- // Measure own work only (after source iteration completes)
136
134
  const ownStart = performance.now();
137
135
 
138
- // Skip intercept resolution for full match (document requests don't have intercepts)
139
136
  if (ctx.isFullMatch) {
140
137
  if (ms) {
141
138
  ms.metrics.push({
@@ -147,18 +144,12 @@ export function withInterceptResolution<TEnv>(
147
144
  return;
148
145
  }
149
146
 
150
- // Skip intercept resolution if:
151
- // 1. No intercept result
152
- // 2. Already have intercept segments (from cache hit with intercept key)
153
- // 3. Cache hit with intercept key
154
147
  const skipInterceptResolution =
155
148
  !ctx.interceptResult ||
156
149
  state.interceptSegments.length > 0 ||
157
150
  (state.cacheHit && ctx.isIntercept);
158
151
 
159
152
  if (skipInterceptResolution) {
160
- // For cache hit with intercept, extract intercept segments from cached data for slots
161
- // and re-resolve loaders for fresh data
162
153
  if (ctx.interceptResult && state.cacheHit && ctx.isIntercept) {
163
154
  await handleCacheHitIntercept(ctx, state, segments);
164
155
  }
@@ -172,7 +163,6 @@ export function withInterceptResolution<TEnv>(
172
163
  return;
173
164
  }
174
165
 
175
- // Resolve intercept segments
176
166
  const { resolveInterceptEntry } = getRouterContext<TEnv>();
177
167
 
178
168
  const slotName = ctx.interceptResult!.intercept.slotName;
@@ -181,7 +171,6 @@ export function withInterceptResolution<TEnv>(
181
171
  slotName,
182
172
  });
183
173
 
184
- // Resolve intercept entry (middleware, loaders, handler)
185
174
  const Store = ctx.Store;
186
175
  const interceptSegments = await Store.run(() =>
187
176
  resolveInterceptEntry(
@@ -203,14 +192,12 @@ export function withInterceptResolution<TEnv>(
203
192
  ),
204
193
  );
205
194
 
206
- // Update state
207
195
  state.interceptSegments = interceptSegments;
208
196
  state.slots[slotName] = {
209
197
  active: true,
210
198
  segments: interceptSegments,
211
199
  };
212
200
 
213
- // Yield intercept segments
214
201
  for (const segment of interceptSegments) {
215
202
  yield segment;
216
203
  }
@@ -225,11 +212,6 @@ export function withInterceptResolution<TEnv>(
225
212
  };
226
213
  }
227
214
 
228
- /**
229
- * Handle cache hit with intercept scenario
230
- *
231
- * Extract intercept segments from cached data and re-resolve loaders for fresh data.
232
- */
233
215
  async function handleCacheHitIntercept<TEnv>(
234
216
  ctx: MatchContext<TEnv>,
235
217
  state: MatchPipelineState,
@@ -241,14 +223,11 @@ async function handleCacheHitIntercept<TEnv>(
241
223
 
242
224
  const slotName = ctx.interceptResult.intercept.slotName;
243
225
 
244
- // Find intercept segments from cached segments (namespace starts with "intercept:")
245
226
  const interceptSegments = segments.filter((s) =>
246
227
  s.namespace?.startsWith("intercept:"),
247
228
  );
248
229
  state.interceptSegments = interceptSegments;
249
230
 
250
- // Re-resolve intercept loaders for fresh data on cache hit
251
- // This keeps cached component/layout but fetches fresh loader data
252
231
  if (resolveInterceptLoadersOnly) {
253
232
  const Store = ctx.Store;
254
233
  const freshLoaderResult = await Store.run(() =>
@@ -271,7 +250,6 @@ async function handleCacheHitIntercept<TEnv>(
271
250
  ),
272
251
  );
273
252
 
274
- // Update intercept segment's loaderDataPromise with fresh data
275
253
  if (freshLoaderResult) {
276
254
  const interceptMainSegment = interceptSegments.find(
277
255
  (s) => s.type === "parallel" && s.slot,
@@ -93,12 +93,6 @@ import type { MatchContext, MatchPipelineState } from "../match-context.js";
93
93
  import { getRouterContext } from "../router-context.js";
94
94
  import type { GeneratorMiddleware } from "./cache-lookup.js";
95
95
 
96
- /**
97
- * Check whether any entry in the tree uses loading() (streaming).
98
- * Matches the router's streaming semantics in fresh.ts: streaming is
99
- * enabled when `loading` is defined AND not `false`. `loading: false`
100
- * explicitly disables streaming; `undefined` means no loading at all.
101
- */
102
96
  export function treeHasStreaming(entries: EntryData[]): boolean {
103
97
  for (const entry of entries) {
104
98
  if (
@@ -130,12 +124,6 @@ export function treeHasStreaming(entries: EntryData[]): boolean {
130
124
  return false;
131
125
  }
132
126
 
133
- /**
134
- * Creates segment resolution middleware
135
- *
136
- * Only runs on cache miss (state.cacheHit === false).
137
- * Uses resolveAllSegmentsWithRevalidation from RouterContext to resolve segments.
138
- */
139
127
  export function withSegmentResolution<TEnv>(
140
128
  ctx: MatchContext<TEnv>,
141
129
  state: MatchPipelineState,
@@ -145,17 +133,12 @@ export function withSegmentResolution<TEnv>(
145
133
  ): AsyncGenerator<ResolvedSegment> {
146
134
  const ms = ctx.metricsStore;
147
135
 
148
- // IMPORTANT: Always iterate source first to give cache-lookup a chance
149
- // to run and set state.cacheHit. Without this, cache-lookup never executes!
150
136
  for await (const segment of source) {
151
137
  yield segment;
152
138
  }
153
139
 
154
- // Measure own work only (after source iteration completes)
155
140
  const ownStart = performance.now();
156
141
 
157
- // If cache hit, segments were already yielded by cache lookup
158
- // (render barrier is resolved on the cache-hit path)
159
142
  if (state.cacheHit) {
160
143
  if (ms) {
161
144
  ms.metrics.push({
@@ -178,7 +161,6 @@ export function withSegmentResolution<TEnv>(
178
161
  const Store = ctx.Store;
179
162
 
180
163
  if (ctx.isFullMatch) {
181
- // Full match (document request) - simple resolution without revalidation
182
164
  const segments = await Store.run(() =>
183
165
  resolveAllSegments(
184
166
  ctx.entries,
@@ -189,15 +171,12 @@ export function withSegmentResolution<TEnv>(
189
171
  ),
190
172
  );
191
173
 
192
- // Update state with resolved segments
193
174
  state.segments = segments;
194
175
  state.matchedIds = segments.map((s: { id: string }) => s.id);
195
176
 
196
177
  if (reqCtx) {
197
178
  reqCtx._resolveRenderBarrier(segments);
198
179
  }
199
-
200
- // Yield all resolved segments
201
180
  for (const segment of segments) {
202
181
  yield segment;
203
182
  }
@@ -214,7 +193,6 @@ export function withSegmentResolution<TEnv>(
214
193
  ctx.request,
215
194
  ctx.prevUrl,
216
195
  ctx.url,
217
- ctx.loaderPromises,
218
196
  ctx.actionContext,
219
197
  ctx.interceptResult,
220
198
  ctx.localRouteName,
@@ -106,16 +106,6 @@ import {
106
106
  withSegmentResolution,
107
107
  } from "./match-middleware/index.js";
108
108
 
109
- /**
110
- * Compose multiple async generator middleware into a single middleware
111
- *
112
- * Middleware are applied in reverse order (rightmost runs first, innermost).
113
- * For the pipeline:
114
- * compose(A, B, C)(source)
115
- *
116
- * The flow is: source -> C -> B -> A -> output
117
- * Where C is the innermost (runs first on input) and A is outermost (runs last).
118
- */
119
109
  export function compose<T>(
120
110
  ...middleware: GeneratorMiddleware<T>[]
121
111
  ): GeneratorMiddleware<T> {
@@ -126,54 +116,23 @@ export function compose<T>(
126
116
  return middleware[0];
127
117
  }
128
118
  return (source) => {
129
- // Apply middleware in reverse order (rightmost first)
130
119
  return middleware.reduceRight((prev, fn) => fn(prev), source);
131
120
  };
132
121
  }
133
122
 
134
- /**
135
- * Create an empty async generator (source for pipeline)
136
- */
137
- export async function* empty<T>(): AsyncGenerator<T> {
138
- // Yields nothing - used as the initial source for the pipeline
139
- }
123
+ export async function* empty<T>(): AsyncGenerator<T> {}
140
124
 
141
- /**
142
- * Create the match partial pipeline
143
- *
144
- * Pipeline order (innermost to outermost):
145
- * 1. cache-lookup - Check cache first, yield cached segments if hit
146
- * 2. segment-resolution - Resolve segments if cache miss
147
- * 3. intercept-resolution - Resolve intercept segments
148
- * 4. cache-store - Store segments in cache
149
- * 5. background-revalidation - Trigger SWR if cache was stale
150
- *
151
- * Data flow:
152
- * - empty() produces no segments
153
- * - cache-lookup either yields cached segments OR passes through to segment-resolution
154
- * - segment-resolution resolves fresh segments on cache miss
155
- * - intercept-resolution adds intercept segments
156
- * - cache-store observes and caches segments
157
- * - background-revalidation triggers SWR revalidation if needed
158
- */
159
125
  export function createMatchPartialPipeline<TEnv>(
160
126
  ctx: MatchContext<TEnv>,
161
127
  state: MatchPipelineState,
162
128
  ): AsyncGenerator<ResolvedSegment> {
163
- // Build the middleware chain
164
129
  const pipeline = compose<ResolvedSegment>(
165
- // Outermost - observes segments and triggers background revalidation
166
130
  withBackgroundRevalidation(ctx, state),
167
- // Observes and stores segments in cache
168
131
  withCacheStore(ctx, state),
169
- // Adds intercept segments after main segments
170
132
  withInterceptResolution(ctx, state),
171
- // Resolves segments on cache miss
172
133
  withSegmentResolution(ctx, state),
173
- // Innermost - checks cache first
174
134
  withCacheLookup(ctx, state),
175
135
  );
176
136
 
177
- // Start with empty source - cache lookup or segment resolution will produce segments
178
137
  return pipeline(empty());
179
138
  }
@@ -112,9 +112,6 @@ import type { MatchContext, MatchPipelineState } from "./match-context.js";
112
112
  import { debugLog } from "./logging.js";
113
113
  import { appendMetric } from "./metrics.js";
114
114
 
115
- /**
116
- * Collect all segments from an async generator
117
- */
118
115
  export async function collectSegments(
119
116
  generator: AsyncGenerator<ResolvedSegment>,
120
117
  ): Promise<ResolvedSegment[]> {
@@ -159,8 +156,6 @@ function deduplicateLoaderSegments(
159
156
  }
160
157
  }
161
158
 
162
- // An inherited loader is needed when it shares a namespace with a
163
- // loading-bearing segment (its data sits behind that LoaderBoundary).
164
159
  const loadersWithLoading = new Set<string>();
165
160
  for (const ns of namespacesWithLoading) {
166
161
  for (const id of loaderIdsByNamespace.get(ns) ?? []) {
@@ -195,9 +190,6 @@ function deduplicateLoaderSegments(
195
190
  return { segments: result, removedIds };
196
191
  }
197
192
 
198
- /**
199
- * Build the final MatchResult from collected segments and context
200
- */
201
193
  export function buildMatchResult<TEnv>(
202
194
  allSegments: ResolvedSegment[],
203
195
  ctx: MatchContext<TEnv>,
@@ -211,11 +203,6 @@ export function buildMatchResult<TEnv>(
211
203
  let segmentsToRender: ResolvedSegment[];
212
204
 
213
205
  if (ctx.isFullMatch) {
214
- // Full match (document request) - all segments are rendered
215
- // Deduplicate by segment ID (defense-in-depth). The primary dedup is in
216
- // resolveAllSegments, but this guards against any path that bypasses it.
217
- // include() scopes can produce entries that resolve the same shared layout,
218
- // and duplicate IDs change the client's React tree depth causing remounts.
219
206
  const seen = new Set<string>();
220
207
  segmentsToRender = [];
221
208
  for (const s of allSegments) {
@@ -226,24 +213,14 @@ export function buildMatchResult<TEnv>(
226
213
  }
227
214
  allIds = segmentsToRender.map((s) => s.id);
228
215
  } else {
229
- // Partial match (navigation) - filter and handle intercepts
230
- // When intercepting, tell browser to keep its current segments + add modal
231
- // This prevents the browser from discarding the current page content
232
- // If client sent empty segments (HMR recovery), use segment IDs from allSegments
233
216
  allIds = ctx.interceptResult
234
217
  ? ctx.clientSegmentIds.length > 0
235
218
  ? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
236
- : allSegments.map((s) => s.id) // Use actual segments, not matchedIds
219
+ : allSegments.map((s) => s.id)
237
220
  : [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
238
221
 
239
- // Deduplicate allIds (defense-in-depth for partial match path)
240
222
  allIds = [...new Set(allIds)];
241
223
 
242
- // Filter out null-component segments only when the client already has
243
- // them cached (revalidation skip). If the client doesn't have the segment,
244
- // it must be included even with null component — it's structurally required
245
- // as a parent node for child layouts/parallels to reconcile against.
246
- // Loader segments are always included as they carry data.
247
224
  const clientIdSet = new Set(ctx.clientSegmentIds);
248
225
  segmentsToRender = allSegments.filter(
249
226
  (s) =>
@@ -256,34 +233,13 @@ export function buildMatchResult<TEnv>(
256
233
  logPrefix,
257
234
  );
258
235
 
259
- debugLog(logPrefix, "all segments", {
260
- segments: allSegments.map((s) => ({
261
- id: s.id,
262
- type: s.type,
263
- hasComponent: s.component !== null,
264
- })),
265
- });
266
- debugLog(logPrefix, "segments to render", {
267
- segmentIds: dedupedSegments.map((s) => s.id),
268
- });
269
-
270
- // Remove deduped loader IDs from matched so the client doesn't treat
271
- // them as missing segments and trigger a fallback refetch.
272
236
  const matchedIds =
273
237
  removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
274
238
 
275
- // resolvedIds: every segment whose handler actually ran this request.
276
- // For full-match every segment is fresh; for partial-match we filter by
277
- // the internal `_handlerRan` flag set in revalidation.ts. Drives the
278
- // client's handle-bucket cleanup — a slot that re-resolved and pushed
279
- // nothing must have its previous handle data cleared, but `diff` won't
280
- // carry it because the segment payload skips null-component cached
281
- // segments to save bytes.
282
239
  const resolvedIds = ctx.isFullMatch
283
240
  ? allSegments.map((s) => s.id)
284
241
  : allSegments.filter((s) => s._handlerRan).map((s) => s.id);
285
242
 
286
- // Strip internal-only fields from the segments going on the wire.
287
243
  const cleanedSegments = dedupedSegments.map((s) => {
288
244
  if (s._handlerRan === undefined) return s;
289
245
  const { _handlerRan: _drop, ...rest } = s;
@@ -303,12 +259,6 @@ export function buildMatchResult<TEnv>(
303
259
  };
304
260
  }
305
261
 
306
- /**
307
- * Collect segments from pipeline and build MatchResult
308
- *
309
- * This is the main entry point for building the final result after
310
- * the pipeline has processed all segments.
311
- */
312
262
  export async function collectMatchResult<TEnv>(
313
263
  pipeline: AsyncGenerator<ResolvedSegment>,
314
264
  ctx: MatchContext<TEnv>,
@@ -318,7 +268,6 @@ export async function collectMatchResult<TEnv>(
318
268
 
319
269
  const buildStart = performance.now();
320
270
 
321
- // Update state with collected segments if not already set
322
271
  if (state.segments.length === 0) {
323
272
  state.segments = allSegments;
324
273
  }
@@ -1,9 +1,3 @@
1
- /**
2
- * Router Metrics Utilities
3
- *
4
- * Performance metrics collection and reporting for Rango.
5
- */
6
-
7
1
  import type { MetricsStore, PerformanceMetric } from "../server/context";
8
2
 
9
3
  const BASE_INDENT = 2;
@@ -16,7 +10,6 @@ function formatMs(value: number): string {
16
10
 
17
11
  function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
18
12
  return [...metrics].sort((a, b) => {
19
- // handler:total always goes last (it wraps everything)
20
13
  if (a.label === "handler:total") return 1;
21
14
  if (b.label === "handler:total") return -1;
22
15
  return a.startTime - b.startTime;
@@ -68,11 +61,6 @@ function createTimelineAxis(total: number): string {
68
61
  )}${totalLabel}`;
69
62
  }
70
63
 
71
- /**
72
- * Create a metrics store for the request if debugPerformance is enabled.
73
- * An optional `requestStart` timestamp can anchor the store to an earlier
74
- * point (e.g. handler start) so that handler:total has startTime=0.
75
- */
76
64
  export function createMetricsStore(
77
65
  debugPerformance: boolean,
78
66
  requestStart?: number,
@@ -85,9 +73,6 @@ export function createMetricsStore(
85
73
  };
86
74
  }
87
75
 
88
- /**
89
- * Append a metric to the request store using an absolute start timestamp.
90
- */
91
76
  export function appendMetric(
92
77
  metricsStore: MetricsStore | undefined,
93
78
  label: string,
@@ -104,9 +89,6 @@ export function appendMetric(
104
89
  });
105
90
  }
106
91
 
107
- /**
108
- * Log the current request metrics and return the corresponding Server-Timing value.
109
- */
110
92
  export function buildMetricsTiming(
111
93
  method: string,
112
94
  pathname: string,
@@ -117,7 +99,6 @@ export function buildMetricsTiming(
117
99
  return generateServerTiming(metricsStore) || undefined;
118
100
  }
119
101
 
120
- /** Display row produced by merging :pre/:post metric pairs. */
121
102
  interface DisplayRow {
122
103
  label: string;
123
104
  startTime: number;
@@ -126,12 +107,7 @@ interface DisplayRow {
126
107
  spans: Span[];
127
108
  }
128
109
 
129
- /**
130
- * Build display rows from sorted metrics, merging :pre/:post pairs into
131
- * a single row with disjoint timeline segments.
132
- */
133
110
  function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
134
- // Index :pre and :post metrics by their base label
135
111
  const preMap = new Map<string, PerformanceMetric>();
136
112
  const postMap = new Map<string, PerformanceMetric>();
137
113
  const consumed = new Set<PerformanceMetric>();
@@ -211,11 +187,6 @@ function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
211
187
  return rows;
212
188
  }
213
189
 
214
- /**
215
- * Log metrics to console in a formatted way.
216
- * Uses a shared-axis timeline so overlapping work stays visible.
217
- * Merges :pre/:post pairs onto one row with disjoint timeline segments.
218
- */
219
190
  export function logMetrics(
220
191
  method: string,
221
192
  pathname: string,
@@ -267,11 +238,6 @@ export function logMetrics(
267
238
  }
268
239
  }
269
240
 
270
- /**
271
- * Generate Server-Timing header value from metrics
272
- * Format: metric-name;dur=X.XX
273
- * Depth is encoded as a "d{N}-" prefix for nested metrics.
274
- */
275
241
  export function generateServerTiming(metricsStore: MetricsStore): string {
276
242
  return metricsStore.metrics
277
243
  .map((m) => {
@@ -1,10 +1,3 @@
1
- /**
2
- * Middleware Types
3
- *
4
- * Type definitions and interfaces for the middleware system.
5
- * Separated from execution logic for cleaner imports.
6
- */
7
-
8
1
  import type { ContextVar } from "../context-var.js";
9
2
  import type {
10
3
  DefaultReverseRouteMap,
@@ -16,17 +9,11 @@ import type { Theme } from "../theme/types.js";
16
9
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
17
10
  import type { RequestScope } from "../types/request-scope.js";
18
11
 
19
- /**
20
- * Get variable function type
21
- */
22
12
  type GetVariableFn = {
23
13
  <T>(contextVar: ContextVar<T>): T | undefined;
24
14
  <K extends keyof DefaultVars>(key: K): DefaultVars[K];
25
15
  };
26
16
 
27
- /**
28
- * Set variable function type
29
- */
30
17
  type SetVariableFn = {
31
18
  <T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
32
19
  <K extends keyof DefaultVars>(
@@ -36,9 +23,6 @@ type SetVariableFn = {
36
23
  ): void;
37
24
  };
38
25
 
39
- /**
40
- * Cookie options for setting cookies
41
- */
42
26
  export interface CookieOptions {
43
27
  domain?: string;
44
28
  path?: string;
@@ -49,109 +33,36 @@ export interface CookieOptions {
49
33
  sameSite?: "strict" | "lax" | "none";
50
34
  }
51
35
 
52
- /**
53
- * Context passed to middleware
54
- *
55
- * @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
56
- * @template TParams - URL params type (typed for route middleware,
57
- * `Record<string, string | undefined>` for global middleware — absent
58
- * optional segments are omitted from the params record at runtime, so
59
- * the index signature must include `undefined`)
60
- */
61
36
  export interface MiddlewareContext<
62
37
  TEnv = any,
63
38
  TParams = Record<string, string | undefined>,
64
39
  > extends RequestScope<TEnv> {
65
- /** URL params extracted from route/middleware pattern */
66
40
  params: TParams;
67
41
 
68
- /**
69
- * Response headers.
70
- * Before `next()`, returns headers from the shared response stub.
71
- * After `next()`, returns headers from the downstream response.
72
- */
73
42
  readonly headers: Headers;
74
43
 
75
- /** Get a context variable (shared with route handlers) */
76
44
  get: GetVariableFn;
77
45
 
78
- /** Set a context variable (shared with route handlers) */
79
46
  set: SetVariableFn;
80
47
 
81
- /**
82
- * Set a response header - can be called before or after `next()`.
83
- *
84
- * When called before `next()`, headers are queued and merged into the final response.
85
- * When called after `next()`, headers are set directly on the response.
86
- */
87
48
  header(name: string, value: string): void;
88
49
 
89
- /**
90
- * The matched route name, if available and the route has an explicit name.
91
- * Undefined for global middleware (runs before route matching) or unnamed routes.
92
- */
93
50
  routeName?: DefaultRouteName;
94
51
 
95
- /**
96
- * Enable performance metrics for this request.
97
- * When called, granular timing breakdown is logged to console and
98
- * included in the Server-Timing response header, regardless of the
99
- * router-level `debugPerformance` option.
100
- *
101
- * Call **before** `await next()` so the metrics store exists when
102
- * downstream phases (route matching, rendering, SSR) record their
103
- * spans. Calling after `next()` returns still emits `handler:total`
104
- * but misses all upstream metrics.
105
- */
106
52
  debugPerformance(): void;
107
53
 
108
- /**
109
- * Current theme (from cookie or default).
110
- * Only available when theme is enabled in router config.
111
- */
112
54
  theme?: Theme;
113
55
 
114
- /**
115
- * Set the theme (only available when theme is enabled in router config).
116
- * Sets a cookie with the new theme value.
117
- */
118
56
  setTheme?: (theme: Theme) => void;
119
57
 
120
- /**
121
- * Attach location state entries to this response.
122
- * State is delivered to the client via history.pushState and accessible
123
- * through the useLocationState() hook.
124
- */
125
58
  setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
126
59
 
127
- /**
128
- * Generate URLs from route names.
129
- * - `name` — global route, from the named-routes definition
130
- */
131
60
  reverse: ScopedReverseFunction<
132
61
  Record<string, string>,
133
62
  DefaultReverseRouteMap
134
63
  >;
135
64
  }
136
65
 
137
- /**
138
- * Middleware function signature
139
- *
140
- * @template TEnv - Environment type - defaults to any for internal flexibility
141
- * @template TParams - URL params type (typed for route middleware)
142
- *
143
- * When using middleware with global augmentation (Rango.Env), explicitly
144
- * annotate your middleware functions, or the types will be inferred from context:
145
- *
146
- * @example
147
- * ```typescript
148
- * // With explicit annotation (recommended for reusable middleware)
149
- * const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
150
- *
151
- * // Types inferred from router.use() call
152
- * router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
153
- * ```
154
- */
155
66
  export type MiddlewareFn<
156
67
  TEnv = any,
157
68
  TParams = Record<string, string | undefined>,
@@ -160,50 +71,23 @@ export type MiddlewareFn<
160
71
  next: () => Promise<Response>,
161
72
  ) => Response | void | Promise<Response | void>;
162
73
 
163
- /**
164
- * Stored middleware entry with pattern matching info
165
- * @internal - uses any for internal flexibility
166
- */
167
74
  export interface MiddlewareEntry<TEnv = any> {
168
- /** Original pattern string */
169
75
  pattern: string | null;
170
-
171
- /** Compiled regex for matching */
172
76
  regex: RegExp | null;
173
-
174
- /** Param names extracted from pattern */
175
77
  paramNames: string[];
176
-
177
- /** The middleware function */
178
78
  handler: MiddlewareFn<TEnv>;
179
-
180
- /** Mount prefix this middleware is scoped to (null = global) */
181
- mountPrefix: string | null;
182
79
  }
183
80
 
184
- /**
185
- * Mutable response holder - tracks the current response through the middleware chain.
186
- */
187
81
  export interface ResponseHolder {
188
82
  response: Response | null;
189
83
  }
190
84
 
191
- /**
192
- * Entry type for middleware collection
193
- * Matches the shape of EntryData used in router.ts
194
- */
195
85
  export interface MiddlewareCollectableEntry {
196
86
  middleware?: MiddlewareFn<any, any>[];
197
87
  layout?: MiddlewareCollectableEntry[];
198
88
  }
199
89
 
200
- /**
201
- * Collected route middleware with params
202
- */
203
90
  export interface CollectedMiddleware {
204
91
  handler: MiddlewareFn<any, any>;
205
- // Internal shape only. The user-facing `MiddlewareContext.params` is
206
- // typed `Record<string, string | undefined>` to reflect that absent
207
- // optional segments are omitted from the params record at runtime.
208
92
  params: Record<string, string>;
209
93
  }