@rangojs/router 0.0.0-experimental.34 → 0.0.0-experimental.36

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.
package/AGENTS.md CHANGED
@@ -3,3 +3,7 @@
3
3
  A file-system based React Server Components router.
4
4
 
5
5
  Run `/rango` to understand the API. Detailed guides for each feature are in the `skills/` directory (e.g. `node_modules/@rangojs/router/skills/loader`, `skills/caching`, `skills/middleware`, etc.).
6
+
7
+ ## Development rules
8
+
9
+ - Always commit generated files (e.g. `*.gen.ts`) alongside the source changes that produced them.
@@ -1745,7 +1745,7 @@ import { resolve } from "node:path";
1745
1745
  // package.json
1746
1746
  var package_default = {
1747
1747
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.34",
1748
+ version: "0.0.0-experimental.36",
1749
1749
  description: "Django-inspired RSC router with composable URL patterns",
1750
1750
  keywords: [
1751
1751
  "react",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.34",
3
+ "version": "0.0.0-experimental.36",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -40,11 +40,6 @@ if (typeof Symbol.dispose === "undefined") {
40
40
  (Symbol as any).dispose = Symbol("Symbol.dispose");
41
41
  }
42
42
 
43
- /** Get IDs of non-loader segments (layouts, routes, parallels). */
44
- function getNonLoaderSegmentIds(segments: ResolvedSegment[]): string[] {
45
- return segments.filter((s) => s.type !== "loader").map((s) => s.id);
46
- }
47
-
48
43
  export { createNavigationTransaction };
49
44
 
50
45
  /**
@@ -284,7 +279,7 @@ export function createNavigationBridge(
284
279
  await fetchPartialUpdate(
285
280
  url,
286
281
  hasUsableCache
287
- ? getNonLoaderSegmentIds(cachedSegments!)
282
+ ? cachedSegments!.map((s) => s.id)
288
283
  : options?._skipCache
289
284
  ? [] // Action redirect: send no segments so server renders everything fresh
290
285
  : undefined,
@@ -497,7 +492,7 @@ export function createNavigationBridge(
497
492
  if (isStale) {
498
493
  debugLog("[Browser] Cache is stale, background revalidating...");
499
494
  // Background revalidation - don't await, just fire and forget
500
- const segmentIds = getNonLoaderSegmentIds(cachedSegments);
495
+ const segmentIds = cachedSegments.map((s) => s.id);
501
496
 
502
497
  const tx = createNavigationTransaction(
503
498
  store,
@@ -411,8 +411,10 @@ export function createPartialUpdater(
411
411
  }
412
412
  }
413
413
 
414
- // Commit navigation - transaction handles all store mutations atomically
415
- const allSegmentIds = reconciled.segments.map((s) => s.id);
414
+ // Commit navigation - use server's matched as the authoritative segment ID list.
415
+ // reconciled.segments may be missing IDs (e.g., loader segments not in diff or cache)
416
+ // but the server's matched always includes all expected segment IDs.
417
+ const allSegmentIds = matchedIds;
416
418
  const serverLocationState = payload.metadata?.locationState;
417
419
  const overrides: CommitOverrides | undefined = isInterceptResponse
418
420
  ? {
@@ -21,11 +21,19 @@ let cacheTTL = 300_000;
21
21
  /**
22
22
  * Initialize the prefetch cache with the configured TTL.
23
23
  * Called once at app startup with the value from server metadata.
24
- * A TTL of 0 disables the in-memory cache.
24
+ * A TTL of 0 disables the in-memory cache and all prefetching.
25
25
  */
26
26
  export function initPrefetchCache(ttlMs: number): void {
27
27
  cacheTTL = ttlMs;
28
28
  }
29
+
30
+ /**
31
+ * Check if the prefetch cache is disabled (TTL <= 0).
32
+ * When disabled, no prefetch requests should be issued.
33
+ */
34
+ export function isPrefetchCacheDisabled(): boolean {
35
+ return cacheTTL <= 0;
36
+ }
29
37
  const MAX_PREFETCH_CACHE_SIZE = 50;
30
38
 
31
39
  interface PrefetchCacheEntry {
@@ -5,6 +5,8 @@
5
5
  * Honors browser reduced-data preferences when available.
6
6
  */
7
7
 
8
+ import { isPrefetchCacheDisabled } from "./cache.js";
9
+
8
10
  type NavigatorWithConnection = Navigator & {
9
11
  connection?: {
10
12
  saveData?: boolean;
@@ -18,6 +20,10 @@ type NavigatorWithConnection = Navigator & {
18
20
  export function shouldPrefetch(): boolean {
19
21
  if (typeof window === "undefined") return false;
20
22
 
23
+ // When prefetchCacheTTL is false/0, prefetching is fully disabled —
24
+ // no point issuing requests whose responses will be discarded.
25
+ if (isPrefetchCacheDisabled()) return false;
26
+
21
27
  const nav =
22
28
  typeof navigator !== "undefined"
23
29
  ? (navigator as NavigatorWithConnection)
@@ -1213,9 +1213,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1213
1213
  ),
1214
1214
  (seg) => ({ segments: [seg], matchedIds: [seg.id] }),
1215
1215
  deps,
1216
- telemetry
1217
- ? { request, url: context.url, routeKey, isPartial: true, telemetry }
1218
- : undefined,
1216
+ { request, url: context.url, routeKey, isPartial: true, telemetry },
1219
1217
  pathname,
1220
1218
  );
1221
1219
  doneEntry();
@@ -83,6 +83,7 @@ export async function handleRscRendering<TEnv>(
83
83
  slots: result.slots,
84
84
  handles: handleStore.stream(),
85
85
  version: ctx.version,
86
+ prefetchCacheTTL: ctx.router.prefetchCacheTTL,
86
87
  },
87
88
  };
88
89
  }
@@ -143,6 +144,7 @@ export async function handleRscRendering<TEnv>(
143
144
  rootLayout: ctx.router.rootLayout,
144
145
  handles: handleStore.stream(),
145
146
  version: ctx.version,
147
+ prefetchCacheTTL: ctx.router.prefetchCacheTTL,
146
148
  themeConfig: ctx.router.themeConfig,
147
149
  initialTheme: reqCtx.theme,
148
150
  },