@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
@@ -292,10 +292,7 @@ export function createRouterDiscoveryPlugin(
292
292
  serverMod.setManifestReadyPromise(discoveryPromise);
293
293
  }
294
294
 
295
- const serverModAfterDiscovery = await discoverRouters(s, rscEnv);
296
-
297
- // Save registry for the /__rsc_prerender endpoint (avoids creating a temp server)
298
- mainRegistry = serverModAfterDiscovery?.RouterRegistry ?? null;
295
+ await discoverRouters(s, rscEnv);
299
296
 
300
297
  // Store server origin for dev prerender endpoint (virtual module injection)
301
298
  s.devServerOrigin = getDevServerOrigin();
@@ -307,37 +304,8 @@ export function createRouterDiscoveryPlugin(
307
304
  // won't cause unnecessary HMR triggers.
308
305
  writeRouteTypesFiles(s);
309
306
 
310
- // Populate the route map in the RSC env
311
- if (s.mergedRouteManifest && serverMod?.setCachedManifest) {
312
- serverMod.setCachedManifest(s.mergedRouteManifest);
313
- }
314
- if (
315
- s.mergedPrecomputedEntries &&
316
- s.mergedPrecomputedEntries.length > 0 &&
317
- serverMod?.setPrecomputedEntries
318
- ) {
319
- serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
320
- }
321
- if (s.mergedRouteTrie && serverMod?.setRouteTrie) {
322
- serverMod.setRouteTrie(s.mergedRouteTrie);
323
- }
324
- // Populate per-router isolated data eagerly in dev (HMR).
325
- // In production builds, per-router data is loaded lazily via import().
326
- if (serverMod?.setRouterManifest) {
327
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
328
- serverMod.setRouterManifest(routerId, manifest);
329
- }
330
- }
331
- if (serverMod?.setRouterTrie) {
332
- for (const [routerId, trie] of s.perRouterTrieMap) {
333
- serverMod.setRouterTrie(routerId, trie);
334
- }
335
- }
336
- if (serverMod?.setRouterPrecomputedEntries) {
337
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
338
- serverMod.setRouterPrecomputedEntries(routerId, entries);
339
- }
340
- }
307
+ // Populate the route map and per-router data in the RSC env
308
+ await propagateDiscoveryState(rscEnv);
341
309
  } catch (err: any) {
342
310
  console.warn(
343
311
  `[rsc-router] Router discovery failed: ${err.message}\n${err.stack}`,
@@ -365,6 +333,49 @@ export function createRouterDiscoveryPlugin(
365
333
  // Registry from the main server's RSC environment (populated by discoverRouters)
366
334
  let mainRegistry: Map<string, any> | null = null;
367
335
 
336
+ // Push discovery state (manifest, trie, precomputed entries) to the
337
+ // server module so runtime request handling uses the current routes.
338
+ // Shared by initial discovery and HMR-triggered re-discovery.
339
+ const propagateDiscoveryState = async (rscEnv: any) => {
340
+ const serverMod = await rscEnv.runner.import("@rangojs/router/server");
341
+ if (!serverMod) return;
342
+ // Clear stale per-router and global route data before repopulating.
343
+ // Without this, removed routers/routes survive in the per-router maps
344
+ // and shrunk precomputed entries or tries are never purged.
345
+ if (serverMod.clearAllRouterData) {
346
+ serverMod.clearAllRouterData();
347
+ }
348
+ mainRegistry = serverMod.RouterRegistry ?? null;
349
+ if (s.mergedRouteManifest && serverMod.setCachedManifest) {
350
+ serverMod.setCachedManifest(s.mergedRouteManifest);
351
+ }
352
+ if (
353
+ s.mergedPrecomputedEntries &&
354
+ s.mergedPrecomputedEntries.length > 0 &&
355
+ serverMod.setPrecomputedEntries
356
+ ) {
357
+ serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
358
+ }
359
+ if (s.mergedRouteTrie && serverMod.setRouteTrie) {
360
+ serverMod.setRouteTrie(s.mergedRouteTrie);
361
+ }
362
+ if (serverMod.setRouterManifest) {
363
+ for (const [routerId, manifest] of s.perRouterManifestDataMap) {
364
+ serverMod.setRouterManifest(routerId, manifest);
365
+ }
366
+ }
367
+ if (serverMod.setRouterTrie) {
368
+ for (const [routerId, trie] of s.perRouterTrieMap) {
369
+ serverMod.setRouterTrie(routerId, trie);
370
+ }
371
+ }
372
+ if (serverMod.setRouterPrecomputedEntries) {
373
+ for (const [routerId, entries] of s.perRouterPrecomputedMap) {
374
+ serverMod.setRouterPrecomputedEntries(routerId, entries);
375
+ }
376
+ }
377
+ };
378
+
368
379
  server.middlewares.use("/__rsc_prerender", async (req: any, res: any) => {
369
380
  if (s.discoveryDone) await s.discoveryDone;
370
381
 
@@ -396,12 +407,24 @@ export function createRouterDiscoveryPlugin(
396
407
  }
397
408
 
398
409
  const wantIntercept = url.searchParams.get("intercept") === "1";
410
+ const wantRouteName = url.searchParams.get("routeName");
411
+ const wantPassthrough = url.searchParams.get("passthrough") === "1";
399
412
 
400
413
  for (const [, routerInstance] of registry) {
401
414
  if (!routerInstance.matchForPrerender) continue;
402
415
  try {
403
- const result = await routerInstance.matchForPrerender(pathname, {});
416
+ const result = await routerInstance.matchForPrerender(
417
+ pathname,
418
+ {},
419
+ undefined,
420
+ wantPassthrough,
421
+ );
404
422
  if (!result) continue;
423
+ if (result.passthrough) continue;
424
+ // When routeName is specified, only accept a match for that route.
425
+ // This prevents returning the wrong entry when multiple routers
426
+ // have prerenderable routes sharing the same pathname.
427
+ if (wantRouteName && result.routeName !== wantRouteName) continue;
405
428
  res.setHeader("content-type", "application/json");
406
429
  let payload: Record<string, unknown>;
407
430
  if (wantIntercept && result.interceptSegments?.length) {
@@ -449,6 +472,13 @@ export function createRouterDiscoveryPlugin(
449
472
  ): boolean => {
450
473
  if (!isGeneratedRouteFile(filePath)) return false;
451
474
  if (consumeSelfGenWrite(s, filePath)) return true;
475
+ // In Cloudflare dev (no module runner), perRouterManifests is never
476
+ // refreshed after HMR so regenerateGeneratedRouteFiles() would use
477
+ // stale data and revert user edits. Source files own route state;
478
+ // gen files are derived output. Skip regeneration and let the next
479
+ // source-file change rebuild them from the static parser.
480
+ const hasRunner = !!(server.environments as any)?.rsc?.runner;
481
+ if (!hasRunner) return true;
452
482
  regenerateGeneratedRouteFiles();
453
483
  return true;
454
484
  };
@@ -459,6 +489,26 @@ export function createRouterDiscoveryPlugin(
459
489
  // only the expensive regeneration is debounced.
460
490
  let routeChangeTimer: ReturnType<typeof setTimeout> | undefined;
461
491
 
492
+ // Re-run runtime discovery so factory-generated routes that the
493
+ // static parser cannot see are refreshed after source changes.
494
+ let runtimeRediscoveryInProgress = false;
495
+ const refreshRuntimeDiscovery = async () => {
496
+ const rscEnv = (server.environments as any)?.rsc;
497
+ if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
498
+ runtimeRediscoveryInProgress = true;
499
+ try {
500
+ await discoverRouters(s, rscEnv);
501
+ writeRouteTypesFiles(s);
502
+ await propagateDiscoveryState(rscEnv);
503
+ } catch (err: any) {
504
+ console.warn(
505
+ `[rsc-router] Runtime re-discovery failed: ${err.message}`,
506
+ );
507
+ } finally {
508
+ runtimeRediscoveryInProgress = false;
509
+ }
510
+ };
511
+
462
512
  const scheduleRouteRegeneration = () => {
463
513
  clearTimeout(routeChangeTimer);
464
514
  routeChangeTimer = setTimeout(() => {
@@ -473,6 +523,15 @@ export function createRouterDiscoveryPlugin(
473
523
  `[rsc-router] Route regeneration error: ${err.message}`,
474
524
  );
475
525
  }
526
+ // Async: re-run runtime discovery to refresh factory-generated
527
+ // routes that the static parser cannot resolve.
528
+ if (s.perRouterManifests.length > 0) {
529
+ refreshRuntimeDiscovery().catch((err: any) => {
530
+ console.warn(
531
+ `[rsc-router] Runtime re-discovery error: ${err.message}`,
532
+ );
533
+ });
534
+ }
476
535
  }, 100);
477
536
  };
478
537
 
@@ -515,8 +574,12 @@ export function createRouterDiscoveryPlugin(
515
574
  server.watcher.on("change", handleRouteFileChange);
516
575
 
517
576
  // Regenerate gen files when they are deleted (e.g. manual cleanup).
577
+ // Same no-runner guard as change/add: stale perRouterManifests would
578
+ // reintroduce reverted content.
518
579
  server.watcher.on("unlink", (filePath) => {
519
580
  if (!isGeneratedRouteFile(filePath)) return;
581
+ const hasRunner = !!(server.environments as any)?.rsc?.runner;
582
+ if (!hasRunner) return;
520
583
  regenerateGeneratedRouteFiles();
521
584
  });
522
585
  }
@@ -17,6 +17,27 @@ export function encodePathParam(value: unknown): string {
17
17
  .join("/");
18
18
  }
19
19
 
20
+ /**
21
+ * Substitute route params into a pattern, stripping constraint and optional
22
+ * syntax (:param(a|b)? -> value). Also handles wildcard params (*key).
23
+ */
24
+ export function substituteRouteParams(
25
+ pattern: string,
26
+ params: Record<string, string>,
27
+ encode: (value: string) => string = encodeURIComponent,
28
+ ): string {
29
+ let result = pattern;
30
+ for (const [key, value] of Object.entries(params)) {
31
+ const escaped = escapeRegExp(key);
32
+ result = result.replace(
33
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
34
+ encode(value),
35
+ );
36
+ result = result.replace(`*${key}`, encode(value));
37
+ }
38
+ return result;
39
+ }
40
+
20
41
  /**
21
42
  * Run an async function over items with bounded concurrency.
22
43
  * Errors propagate immediately and abort remaining work.
@@ -89,9 +89,11 @@ export function createVirtualEntriesPlugin(
89
89
  }
90
90
  // Lazy RSC entry: routerPath may have been set by a config() hook
91
91
  if (virtualId === VIRTUAL_IDS.rsc && routerPathRef?.path) {
92
- const absoluteRouterPath = routerPathRef.path.startsWith(".")
92
+ const raw = routerPathRef.path.startsWith(".")
93
93
  ? "/" + routerPathRef.path.slice(2) // ./src/router.tsx -> /src/router.tsx
94
94
  : routerPathRef.path;
95
+ // Normalize backslashes for Windows (path.join/slice preserve native separators)
96
+ const absoluteRouterPath = raw.replaceAll("\\", "/");
95
97
  return getVirtualEntryRSC(absoluteRouterPath);
96
98
  }
97
99
  }
@@ -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,33 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * Href Context for route name resolution
5
- *
6
- * This module is marked "use client" so it can be imported by both:
7
- * - Server (segment-system): Gets a client reference for createElement
8
- * - Client (useHref): Uses the actual context for useContext
9
- *
10
- * The context stores:
11
- * - routeMap: Map of route names to URL patterns
12
- * - routeName: Current matched route name (for local name resolution)
13
- */
14
- import { createContext, type Context } from "react";
15
-
16
- /**
17
- * Context value for href resolution
18
- */
19
- export interface HrefContextValue {
20
- /** Route map: route name -> URL pattern */
21
- routeMap: Record<string, string>;
22
- /** Current matched route name (includes name prefix from include()) */
23
- routeName?: string;
24
- }
25
-
26
- /**
27
- * Context for href resolution (route map and current route name)
28
- *
29
- * On the server: Populated by renderSegments() via HrefContext.Provider
30
- * On the client: Populated by NavigationProvider from RSC metadata
31
- */
32
- export const HrefContext: Context<HrefContextValue | null> =
33
- createContext<HrefContextValue | null>(null);
package/src/router.gen.ts DELETED
@@ -1,6 +0,0 @@
1
- // Auto-generated by @rangojs/router - do not edit
2
- export const routes = {
3
- about: "/about",
4
- home: "/",
5
- } as const;
6
- export type routes = typeof routes;
@@ -1,5 +0,0 @@
1
- // Auto-generated by @rangojs/router - do not edit
2
- export const routes = {
3
- "doc.gs": "/getting-started",
4
- } as const;
5
- export type routes = typeof routes;
package/src/urls.gen.ts DELETED
@@ -1,8 +0,0 @@
1
- // Auto-generated by @rangojs/router - do not edit
2
- export const routes = {
3
- health: "/health",
4
- home: "/",
5
- index: "/",
6
- post: "/:slug",
7
- } as const;
8
- export type routes = typeof routes;