@rangojs/router 0.5.2 → 0.6.0

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 (173) hide show
  1. package/dist/bin/rango.js +343 -125
  2. package/dist/types/browser/react/use-router.d.ts +10 -3
  3. package/dist/types/browser/react/use-search-params.d.ts +57 -10
  4. package/dist/types/browser/types.d.ts +22 -0
  5. package/dist/types/build/merge-full-manifests.d.ts +3 -0
  6. package/dist/types/build/route-trie.d.ts +4 -73
  7. package/dist/types/build/route-types/per-module-writer.d.ts +6 -4
  8. package/dist/types/build/route-types/router-processing.d.ts +2 -3
  9. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  10. package/dist/types/cache/taint.d.ts +12 -6
  11. package/dist/types/client-urls/client-root.d.ts +38 -0
  12. package/dist/types/client-urls/client-urls.d.ts +5 -0
  13. package/dist/types/client-urls/navigation.d.ts +38 -0
  14. package/dist/types/client-urls/revalidation-protocol.d.ts +25 -0
  15. package/dist/types/client-urls/server-projection.d.ts +62 -0
  16. package/dist/types/client-urls/types.d.ts +144 -0
  17. package/dist/types/client.d.ts +12 -4
  18. package/dist/types/client.rsc.d.ts +4 -1
  19. package/dist/types/decode-loader-results.d.ts +37 -0
  20. package/dist/types/errors.d.ts +1 -0
  21. package/dist/types/index.d.ts +1 -1
  22. package/dist/types/loader-redirect.d.ts +27 -0
  23. package/dist/types/outlet-context.d.ts +12 -0
  24. package/dist/types/outlet-provider.d.ts +3 -1
  25. package/dist/types/redirect-origin.d.ts +4 -0
  26. package/dist/types/route-content-wrapper.d.ts +42 -1
  27. package/dist/types/route-definition/helpers-types.d.ts +13 -2
  28. package/dist/types/router/error-handling.d.ts +35 -1
  29. package/dist/types/router/intercept-resolution.d.ts +12 -0
  30. package/dist/types/router/loader-resolution.d.ts +24 -2
  31. package/dist/types/router/revalidation.d.ts +7 -0
  32. package/dist/types/router/route-trie-builder.d.ts +77 -0
  33. package/dist/types/router/router-interfaces.d.ts +20 -0
  34. package/dist/types/router/segment-resolution/helpers.d.ts +1 -1
  35. package/dist/types/router/trie-matching.d.ts +1 -1
  36. package/dist/types/rsc/manifest-init.d.ts +5 -5
  37. package/dist/types/rsc/shell-capture.d.ts +9 -0
  38. package/dist/types/rsc/shell-serve.d.ts +11 -0
  39. package/dist/types/rsc/types.d.ts +30 -0
  40. package/dist/types/segment-system.d.ts +2 -0
  41. package/dist/types/server/context.d.ts +10 -0
  42. package/dist/types/server/handle-store.d.ts +34 -3
  43. package/dist/types/server/request-context.d.ts +11 -1
  44. package/dist/types/server.d.ts +1 -0
  45. package/dist/types/ssr/index.d.ts +22 -0
  46. package/dist/types/ssr/ssr-root.d.ts +10 -0
  47. package/dist/types/testing/dom.entry.d.ts +1 -1
  48. package/dist/types/testing/render-route.d.ts +16 -6
  49. package/dist/types/testing/run-loader.d.ts +9 -0
  50. package/dist/types/types/boundaries.d.ts +22 -0
  51. package/dist/types/types/index.d.ts +1 -1
  52. package/dist/types/types/loader-types.d.ts +57 -5
  53. package/dist/types/types/segments.d.ts +7 -0
  54. package/dist/types/urls/path-helper-types.d.ts +10 -4
  55. package/dist/types/vite/discovery/client-urls-projection.d.ts +53 -0
  56. package/dist/types/vite/discovery/discover-routers.d.ts +1 -1
  57. package/dist/types/vite/discovery/state.d.ts +8 -1
  58. package/dist/vite/index.js +5313 -2365
  59. package/package.json +1 -1
  60. package/skills/breadcrumbs/SKILL.md +39 -9
  61. package/skills/catalog.json +7 -1
  62. package/skills/client-urls/SKILL.md +338 -0
  63. package/skills/comparison/references/framework-comparison.md +23 -9
  64. package/skills/hooks/SKILL.md +2 -2
  65. package/skills/hooks/data.md +11 -2
  66. package/skills/hooks/handle-and-actions.md +7 -0
  67. package/skills/hooks/outlets.md +26 -5
  68. package/skills/hooks/urls.md +40 -3
  69. package/skills/loader/SKILL.md +132 -20
  70. package/skills/migrate-nextjs/SKILL.md +70 -10
  71. package/skills/migrate-react-router/SKILL.md +49 -13
  72. package/skills/migrate-react-router/component-migration.md +18 -13
  73. package/skills/migrate-react-router/data-and-actions.md +14 -3
  74. package/skills/migrate-react-router/route-mapping.md +15 -2
  75. package/skills/parallel/SKILL.md +32 -1
  76. package/skills/ppr/SKILL.md +16 -6
  77. package/skills/prerender/SKILL.md +8 -4
  78. package/skills/rango/SKILL.md +21 -17
  79. package/skills/react-compiler/SKILL.md +3 -3
  80. package/skills/route/SKILL.md +5 -2
  81. package/skills/router-setup/SKILL.md +16 -2
  82. package/skills/scripts/SKILL.md +16 -6
  83. package/skills/shell-manifest/SKILL.md +16 -7
  84. package/skills/testing/SKILL.md +2 -2
  85. package/skills/testing/client-components.md +6 -0
  86. package/skills/testing/handles.md +30 -8
  87. package/skills/testing/loader.md +51 -49
  88. package/skills/testing/middleware.md +1 -1
  89. package/skills/theme/SKILL.md +8 -5
  90. package/src/bin/rango.ts +7 -3
  91. package/src/browser/navigation-bridge.ts +6 -0
  92. package/src/browser/navigation-client.ts +5 -0
  93. package/src/browser/partial-update.ts +65 -13
  94. package/src/browser/react/use-router.ts +40 -11
  95. package/src/browser/react/use-search-params.ts +140 -17
  96. package/src/browser/rsc-router.tsx +59 -0
  97. package/src/browser/server-action-bridge.ts +26 -0
  98. package/src/browser/types.ts +22 -0
  99. package/src/build/merge-full-manifests.ts +161 -0
  100. package/src/build/route-trie.ts +9 -332
  101. package/src/build/route-types/include-resolution.ts +66 -11
  102. package/src/build/route-types/per-module-writer.ts +11 -6
  103. package/src/build/route-types/router-processing.ts +184 -153
  104. package/src/build/runtime-discovery.ts +23 -12
  105. package/src/cache/cache-exec-scope.ts +47 -0
  106. package/src/cache/cache-runtime.ts +24 -25
  107. package/src/cache/taint.ts +28 -9
  108. package/src/client-urls/client-root.tsx +168 -0
  109. package/src/client-urls/client-urls.ts +698 -0
  110. package/src/client-urls/navigation.ts +237 -0
  111. package/src/client-urls/revalidation-protocol.ts +56 -0
  112. package/src/client-urls/server-projection.ts +579 -0
  113. package/src/client-urls/types.ts +195 -0
  114. package/src/client.rsc.tsx +12 -0
  115. package/src/client.tsx +49 -6
  116. package/src/decode-loader-results.ts +113 -0
  117. package/src/errors.ts +14 -0
  118. package/src/handles/deferred-resolution.ts +14 -7
  119. package/src/index.ts +1 -0
  120. package/src/loader-redirect.tsx +64 -0
  121. package/src/outlet-context.ts +12 -0
  122. package/src/outlet-provider.tsx +15 -1
  123. package/src/redirect-origin.ts +29 -0
  124. package/src/route-content-wrapper.tsx +96 -3
  125. package/src/route-definition/dsl-helpers.ts +28 -3
  126. package/src/route-definition/helpers-types.ts +13 -0
  127. package/src/route-definition/redirect.ts +17 -18
  128. package/src/router/error-handling.ts +65 -11
  129. package/src/router/intercept-resolution.ts +29 -0
  130. package/src/router/loader-resolution.ts +261 -28
  131. package/src/router/match-result.ts +7 -0
  132. package/src/router/revalidation.ts +24 -11
  133. package/src/router/route-trie-builder.ts +334 -0
  134. package/src/router/router-interfaces.ts +38 -0
  135. package/src/router/segment-resolution/fresh.ts +47 -0
  136. package/src/router/segment-resolution/helpers.ts +9 -11
  137. package/src/router/segment-resolution/loader-cache.ts +14 -24
  138. package/src/router/segment-resolution/revalidation.ts +20 -1
  139. package/src/router/trie-matching.ts +3 -3
  140. package/src/router.ts +46 -1
  141. package/src/rsc/full-payload.ts +6 -0
  142. package/src/rsc/handler.ts +10 -7
  143. package/src/rsc/loader-fetch.ts +2 -2
  144. package/src/rsc/manifest-init.ts +28 -9
  145. package/src/rsc/rsc-rendering.ts +15 -1
  146. package/src/rsc/shell-capture.ts +12 -0
  147. package/src/rsc/shell-serve.ts +15 -2
  148. package/src/rsc/ssr-setup.ts +10 -1
  149. package/src/rsc/types.ts +31 -2
  150. package/src/segment-system.tsx +83 -26
  151. package/src/server/context.ts +10 -0
  152. package/src/server/cookie-store.ts +19 -19
  153. package/src/server/handle-store.ts +185 -48
  154. package/src/server/request-context.ts +30 -6
  155. package/src/server.ts +7 -0
  156. package/src/ssr/index.tsx +37 -2
  157. package/src/ssr/ssr-root.tsx +29 -2
  158. package/src/testing/dom.entry.ts +1 -1
  159. package/src/testing/render-route.tsx +22 -8
  160. package/src/testing/run-loader.ts +51 -13
  161. package/src/types/boundaries.ts +19 -0
  162. package/src/types/index.ts +1 -0
  163. package/src/types/loader-types.ts +60 -5
  164. package/src/types/segments.ts +7 -0
  165. package/src/urls/include-helper.ts +22 -4
  166. package/src/urls/path-helper-types.ts +14 -1
  167. package/src/use-loader.tsx +67 -6
  168. package/src/vite/discovery/client-urls-projection.ts +322 -0
  169. package/src/vite/discovery/discover-routers.ts +43 -17
  170. package/src/vite/discovery/state.ts +11 -1
  171. package/src/vite/discovery/virtual-module-codegen.ts +20 -0
  172. package/src/vite/plugins/virtual-entries.ts +12 -3
  173. package/src/vite/router-discovery.ts +163 -12
@@ -1,19 +1,66 @@
1
1
  import type { ReadonlyURLSearchParams } from "../types.js";
2
2
  /**
3
- * Hook to access the current URL search params.
3
+ * Accepted shapes for the setter: a full replacement for the search string.
4
+ * Record values are stringified; array values append one entry per element;
5
+ * null/undefined values are skipped (ergonomic conditional spreads).
6
+ */
7
+ export type SearchParamsInit = string | URLSearchParams | ReadonlyURLSearchParams | Record<string, string | number | boolean | readonly (string | number | boolean)[] | null | undefined>;
8
+ export interface SetSearchParamsOptions {
9
+ /** Replace the current history entry instead of pushing (default false). */
10
+ replace?: boolean;
11
+ /** Scroll behavior for the navigation (default: router default, scroll). */
12
+ scroll?: boolean;
13
+ /**
14
+ * Set false to skip the server fetch and only update the URL (default
15
+ * true). Purely client-derived search state (open accordions, view modes)
16
+ * needs no loader re-run; all location-aware hooks still update. Same
17
+ * contract as NavigateOptions.revalidate — it only applies because the
18
+ * setter never changes the pathname.
19
+ */
20
+ revalidate?: boolean;
21
+ }
22
+ export type SetSearchParams = (init: SearchParamsInit | ((prev: URLSearchParams) => SearchParamsInit), options?: SetSearchParamsOptions) => Promise<void>;
23
+ /**
24
+ * Hook to read and write the current URL search params
25
+ * (React Router-style tuple).
4
26
  *
5
- * Returns a read-only URLSearchParams object from the committed location.
6
- * Updates when navigation completes, not during pending navigation.
27
+ * The first element is a read-only URLSearchParams from the COMMITTED
28
+ * location — it updates when navigation completes, not during a pending
29
+ * navigation. During document SSR it carries the LIVE request's search (the
30
+ * SSR store is seeded from SSRRenderOptions.search), and the browser's
31
+ * first render seeds from its own store location (window.location) — the
32
+ * same URL, so hydration matches. Ppr capture/resume renders seed the SHELL
33
+ * KEY's search (sorted, cache.searchParams filter applied — search is part
34
+ * of shell identity like every other ppr key), so static-part reads bake
35
+ * markup consistent with the shell's own key. Two edges: a param EXCLUDED
36
+ * by cache.searchParams is absent in shell renders but present in the
37
+ * browser (exclusion declares "does not affect markup" — reading one
38
+ * anyway hydration-mismatches), and toString() renders sorted order while
39
+ * the browser holds the raw URL order.
7
40
  *
8
- * Note: During SSR the search params are not available (the server only sends
9
- * the pathname). The hook returns empty params during SSR and syncs from
10
- * the browser URL on mount.
41
+ * The setter REPLACES the whole search string (React Router semantics) and
42
+ * navigates to the current pathname with the new params a same-route
43
+ * navigation, so route loaders re-evaluate per their revalidate() contract
44
+ * and the commit holds previous content (the same-structure transition
45
+ * lane). Pass a function to merge with the current params: it receives a
46
+ * MUTABLE copy read at call time. The hash is dropped, like React Router.
11
47
  *
12
48
  * @example
13
49
  * ```tsx
14
- * const searchParams = useSearchParams();
15
- * const query = searchParams.get("q"); // "react"
16
- * const page = searchParams.get("page"); // "2"
50
+ * const [searchParams, setSearchParams] = useSearchParams();
51
+ * const category = searchParams.get("category");
52
+ *
53
+ * // Replace the whole search string
54
+ * setSearchParams({ category: "electronics" });
55
+ *
56
+ * // Merge with what's there now
57
+ * setSearchParams((prev) => {
58
+ * prev.set("page", "2");
59
+ * return prev;
60
+ * });
61
+ *
62
+ * // Filter UIs usually want replace + preserved scroll
63
+ * setSearchParams({ category: "home" }, { replace: true, scroll: false });
17
64
  * ```
18
65
  */
19
- export declare function useSearchParams(): ReadonlyURLSearchParams;
66
+ export declare function useSearchParams(): [ReadonlyURLSearchParams, SetSearchParams];
@@ -47,12 +47,27 @@ export interface RscMetadata {
47
47
  * Slots are used for intercepting routes during soft navigation
48
48
  */
49
49
  slots?: Record<string, SlotState>;
50
+ /**
51
+ * Intercept TARGET route names reachable from this location as a
52
+ * navigation origin. The browser-local clientUrls matcher declines its
53
+ * optimistic presentation for these targets (the canonical response would
54
+ * commit the intercept over the ORIGIN page, so destination loading would
55
+ * flash and revert). Missing/empty means no targets.
56
+ */
57
+ interceptTargets?: string[];
50
58
  /** Root layout component for browser-side re-renders */
51
59
  rootLayout?: ComponentType<{
52
60
  children: ReactNode;
53
61
  }>;
54
62
  /** Handle data accumulated across route segments (async generator that yields on each push) */
55
63
  handles?: AsyncGenerator<HandleData, void, unknown>;
64
+ /**
65
+ * Document-lane late handle channel: pushes landing after the handler
66
+ * barrier (streaming loader ctx.use(Handle) writes). Consumed non-blocking
67
+ * post-hydration (rsc-router.tsx); `handles` above is drained in blocking
68
+ * positions and must complete at the handler barrier.
69
+ */
70
+ handlesLate?: AsyncGenerator<HandleData, void, unknown>;
56
71
  /** Cached handle data (for back/forward navigation from cache) */
57
72
  cachedHandleData?: HandleData;
58
73
  /**
@@ -438,6 +453,13 @@ export interface FetchPartialOptions {
438
453
  signal?: AbortSignal;
439
454
  /** If true, this is a stale cache revalidation request - server should force revalidators */
440
455
  staleRevalidation?: boolean;
456
+ /**
457
+ * Encoded client-run per-loader revalidation decisions
458
+ * (clientUrls revalidate() predicates executed in the browser); sent as
459
+ * X-Rango-Client-Reval and honored only by materialized client-urls loader
460
+ * stubs. Null/absent = locked server defaults.
461
+ */
462
+ clientRevalidation?: string | null;
441
463
  interceptSourceUrl?: string;
442
464
  /** RSC version for cache invalidation detection */
443
465
  version?: string;
@@ -0,0 +1,3 @@
1
+ import type { FullManifest } from "./generate-manifest.js";
2
+ /** Merge ordered per-mount manifests without mutating any input manifest. */
3
+ export declare function mergeFullManifests(manifests: readonly FullManifest[]): FullManifest;
@@ -1,81 +1,12 @@
1
1
  /**
2
2
  * Build-time Route Trie Construction
3
3
  *
4
- * Builds a serializable trie from the route manifest for O(path_length)
5
- * route matching at runtime.
4
+ * Adapts generated manifests to the runtime-owned route trie builder.
6
5
  */
6
+ import { buildRouteTrie, type TrieNode } from "../router/route-trie-builder.js";
7
7
  import type { FullManifest } from "./generate-manifest.js";
8
- /**
9
- * A response-type variant folded into a primary leaf's negotiate list. `pa` is
10
- * the variant's own positional param-name array, carried so the runtime can
11
- * re-key the matched params under the variant's names when it wins negotiation
12
- * (the trie match extracts params under the PRIMARY leaf's pa). Omitted when the
13
- * variant has no params; absent/identical pa means no re-key is needed.
14
- */
15
- export interface NegotiateVariant {
16
- routeKey: string;
17
- responseType: string;
18
- pa?: string[];
19
- }
20
- export interface TrieLeaf {
21
- /** Route name (e.g., "site.l1_500") */
22
- n: string;
23
- /** Static prefix of the entry (e.g., "/site") */
24
- sp: string;
25
- /** Constraint validation: paramName -> allowed values */
26
- cv?: Record<string, string[]>;
27
- /** Ordered param names for this route (positional) */
28
- pa?: string[];
29
- /** Trailing slash mode */
30
- ts?: string;
31
- /** Route has pre-rendered data available */
32
- pr?: true;
33
- /** Passthrough: handler kept in bundle for live fallback on unknown params */
34
- pt?: true;
35
- /** Response type for non-RSC routes (json, text, image, any) */
36
- rt?: string;
37
- /** Negotiate variants: response-type routes sharing this path */
38
- nv?: NegotiateVariant[];
39
- /** RSC-first: RSC route was defined before response-type variants */
40
- rf?: true;
41
- }
42
- export interface TrieNode {
43
- /** Route terminal at this node */
44
- r?: TrieLeaf;
45
- /** Static segment children */
46
- s?: Record<string, TrieNode>;
47
- /** Param child: { n: paramName, c: child node } */
48
- p?: {
49
- n: string;
50
- c: TrieNode;
51
- };
52
- /** Suffix-param children keyed by suffix (e.g., ".html" → { n: "productId", c: ... }) */
53
- xp?: Record<string, {
54
- n: string;
55
- c: TrieNode;
56
- }>;
57
- /**
58
- * Wildcard terminal: leaf + paramName (`pn`). `pn` is "*" for the bare `/*`
59
- * form and the param name for a named catch-all (`:name+`/`:name*`). `w1`
60
- * marks a one-or-more catch-all (`:name+`): the runtime walker then rejects
61
- * the zero-segment/empty-remainder case. Absent `w1` is zero-or-more.
62
- */
63
- w?: TrieLeaf & {
64
- pn: string;
65
- w1?: true;
66
- };
67
- }
68
- /**
69
- * Build a route trie from build-time manifest data.
70
- *
71
- * @param routeManifest - Map of route name to full URL pattern
72
- * @param routeToStaticPrefix - Map of route name to its entry's staticPrefix
73
- * @param routeTrailingSlash - Optional map of route name to trailing slash mode
74
- * @param prerenderRouteNames - Optional set of prerendered route names (sets leaf.pr)
75
- * @param passthroughRouteNames - Optional set of passthrough route names (sets leaf.pt)
76
- * @param responseTypeRoutes - Optional map of route name to response type (sets leaf.rt)
77
- */
78
- export declare function buildRouteTrie(routeManifest: Record<string, string>, routeToStaticPrefix: Record<string, string>, routeTrailingSlash?: Record<string, string>, prerenderRouteNames?: Set<string>, passthroughRouteNames?: Set<string>, responseTypeRoutes?: Record<string, string>): TrieNode;
8
+ export { buildRouteTrie };
9
+ export type { NegotiateVariant, TrieLeaf, TrieNode, } from "../router/route-trie-builder.js";
79
10
  /**
80
11
  * Build a per-router trie from a generated manifest. This is the single
81
12
  * construction path shared by build/discovery (discover-routers.ts, serialized
@@ -1,18 +1,20 @@
1
1
  import type { ScanFilter } from "./scan-filter.js";
2
2
  /**
3
3
  * Generate per-module route type files by statically parsing url module source.
4
- * Scans for files containing `urls(` and writes a sibling `.gen.ts` with the
5
- * extracted route name/pattern pairs. Only writes when content has changed.
4
+ * Scans for files containing `urls(` or `clientUrls(` and writes a sibling
5
+ * `.gen.ts` with the extracted route name/pattern pairs. Only writes when
6
+ * content has changed.
6
7
  */
7
8
  export declare function writePerModuleRouteTypes(root: string, filter?: ScanFilter): void;
8
9
  /**
9
- * Find all variable names assigned to urls() calls in source code.
10
+ * Find all variable names assigned to urls() or clientUrls() calls in source.
10
11
  * e.g. `export const patterns = urls(...)` -> ["patterns"]
11
12
  */
12
13
  export declare function findUrlsVariableNames(code: string): string[];
13
14
  /**
14
15
  * Generate per-module route types for a single url module file.
15
16
  * Follows include() calls recursively to produce the full route tree.
16
- * No-ops if the file doesn't contain `urls(` or has no named routes.
17
+ * No-ops if the file doesn't contain `urls(`/`clientUrls(` or has no named
18
+ * routes.
17
19
  */
18
20
  export declare function writePerModuleRouteTypesForFile(filePath: string): void;
@@ -38,9 +38,8 @@ export declare function extractBasenameFromRouter(code: string): string | undefi
38
38
  export declare function genFileTsPath(sourceFile: string): string;
39
39
  export declare function resolveSearchSchemas(publicRouteNames: string[], runtimeSchemas: Record<string, Record<string, string>> | undefined, sourceFile: string): Record<string, Record<string, string>> | undefined;
40
40
  /**
41
- * Resolve routes and search schemas from a router source file by following the
42
- * variable passed to `.routes(...)` or `urls: ...` in createRouter options,
43
- * or by parsing an inline builder function directly.
41
+ * Resolve routes and search schemas from every direct mount in a createRouter
42
+ * chain. Registrations are merged in runtime order, with later mounts winning.
44
43
  */
45
44
  export declare function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
46
45
  routes: Record<string, string>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Execution-chain scope for "use cache" function bodies.
3
+ *
4
+ * The ambient guards (cookies(), headers(), ctx.set() reached via
5
+ * getRequestContext()) must fire for code running INSIDE a cached body and
6
+ * stay silent for everything else on the request. A property stamped on the
7
+ * shared RequestContext cannot express that: while a slow "use cache" body is
8
+ * in flight, every parallel read on the same request sees the stamp. Scar: a
9
+ * 2s cached product fetch running beside a sibling loader made the loader's
10
+ * cookies() read throw `cannot be called inside a "use cache" function` —
11
+ * same shared-object hazard as issue #684 plan 010, which fixed only the
12
+ * background-revalidation path. AsyncLocalStorage follows the cached body's
13
+ * own async chain, so parallel work is invisible to the guard while genuine
14
+ * in-body reads still throw.
15
+ *
16
+ * Deliberately NOT entered on the store-less bypass paths in cache-runtime.ts:
17
+ * there the body executes for real on every call, side effects take effect,
18
+ * and request-scoped reads are safe (see the bypass comments there).
19
+ *
20
+ * Kept separate from taint.ts so `node:async_hooks` stays out of that module —
21
+ * taint.ts is reachable from browser-bundled DSL helpers. The probe
22
+ * registration below wires assertNotInsideCacheExec to this scope without
23
+ * taint.ts importing it.
24
+ */
25
+ /**
26
+ * Run fn with the "use cache" execution scope active. Continuations spawned
27
+ * from fn's synchronous kickoff inherit the scope; parallel chains do not.
28
+ */
29
+ export declare function runWithCacheExecScope<T>(fn: () => T): T;
30
+ /** True when the calling async chain is inside a "use cache" body. */
31
+ export declare function isInsideCacheExecScope(): boolean;
@@ -11,14 +11,19 @@ export declare const NOCACHE_SYMBOL: unique symbol;
11
11
  */
12
12
  export declare function isTainted(value: unknown): boolean;
13
13
  /**
14
- * Symbol stamped on tainted ctx during "use cache" function execution.
15
- * cookies(), headers(), ctx.set(), ctx.header(), etc. check this flag and
16
- * throw if present — reads would cache per-request data under a shared key,
17
- * and side effects would be lost on cache hit.
14
+ * Symbol stamped on tainted ctx objects PASSED AS ARGUMENTS to a "use cache"
15
+ * function during its execution. ctx.set(), ctx.header(), etc. check this
16
+ * flag and throw if present — those side effects are lost on cache hit.
17
+ *
18
+ * Argument objects only. Ambient access (cookies()/headers() and ctx methods
19
+ * reached via getRequestContext()) is guarded by the AsyncLocalStorage scope
20
+ * in cache-exec-scope.ts instead: stamping the SHARED RequestContext made
21
+ * every parallel read on the request throw for the cached body's whole
22
+ * execution window (a 2s cached fetch poisoned a sibling loader's cookies()).
18
23
  *
19
24
  * The value is a numeric reference count, not a boolean. Multiple concurrent
20
- * cached functions sharing the same ctx/requestCtx each increment on entry
21
- * and decrement on exit. Guards fire when count > 0.
25
+ * cached functions sharing the same ctx each increment on entry and decrement
26
+ * on exit. Guards fire when count > 0.
22
27
  */
23
28
  export declare const INSIDE_CACHE_EXEC: unique symbol;
24
29
  /**
@@ -31,6 +36,7 @@ export declare function stampCacheExec(obj: object): void;
31
36
  * used by guards no longer fires.
32
37
  */
33
38
  export declare function unstampCacheExec(obj: object): void;
39
+ export declare function _setCacheExecScopeProbe(probe: () => boolean): void;
34
40
  /**
35
41
  * Throw if ctx is inside a "use cache" execution.
36
42
  * Call from side-effecting ctx methods (set, header, etc.) and cookie mutations.
@@ -0,0 +1,38 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ClientUrlPatterns } from "./types.js";
3
+ /**
4
+ * Server-materialized wrapper layout for a clientUrls() group that declares
5
+ * intercepts. Attachment scar: intercepts emitted at the TOP LEVEL of a lazily
6
+ * included module attach to the isolated parent clone
7
+ * (getIsolatedLazyParent in server/context.ts) and are silently discarded —
8
+ * only intercepts attached to a layout entry created WITHIN the expansion
9
+ * survive in origin chains. Materialization therefore wraps the group's routes
10
+ * and intercept entries in this layout; it renders the child outlet plus one
11
+ * named outlet per declared slot, so the modal presents inside the group's own
12
+ * subtree (module-local declaration, module-local presentation).
13
+ */
14
+ export declare function ClientUrlsGroupLayout({ slotNames, }: {
15
+ slotNames: readonly `@${string}`[];
16
+ }): ReactNode;
17
+ /** Slot content for a client-declared intercept: renders the definition's
18
+ * modal component; its useLoader() calls read the slot segment's loader data
19
+ * from the surrounding outlet context. */
20
+ export declare function ClientUrlsInterceptSlot({ definition, interceptIndex, }: {
21
+ definition: ClientUrlPatterns;
22
+ interceptIndex: number;
23
+ }): ReactNode;
24
+ export declare function ClientUrlsInterceptLoading({ definition, interceptIndex, }: {
25
+ definition: ClientUrlPatterns;
26
+ interceptIndex: number;
27
+ }): ReactNode;
28
+ export declare function ClientUrlsRoot({ definition, routeId, namePrefix, }: {
29
+ definition: ClientUrlPatterns;
30
+ routeId: string;
31
+ /** include() route-name prefix, injected at materialization for canonical
32
+ * name composition (intercept-target coordination). */
33
+ namePrefix?: string;
34
+ }): ReactNode;
35
+ export declare function ClientUrlsLoading({ definition, routeId, }: {
36
+ definition: ClientUrlPatterns;
37
+ routeId: string;
38
+ }): ReactNode;
@@ -0,0 +1,5 @@
1
+ import type { ClientUrlBuilder, ClientUrlPatterns } from "./types.js";
2
+ import type { ExtractRoutes } from "../urls/type-extraction.js";
3
+ import type { ClientUrlItems } from "./types.js";
4
+ export declare function clientUrls<const TItems extends ClientUrlItems>(builder: ClientUrlBuilder<TItems>): ClientUrlPatterns<ExtractRoutes<TItems>>;
5
+ export type { ClientRevalidateArgs, ClientRevalidateFn, ClientTransitionConfig, ClientUrlBuilder, ClientUrlHelpers, ClientUrlInterceptRecord, ClientUrlItem, ClientUrlItemInput, ClientUrlItems, ClientLayoutFn, ClientPathOptions, ClientUrlLoaderRecord, ClientPathFn, ClientUrlPatterns, ClientUrlRouteRecord, ClientUrlUse, } from "./types.js";
@@ -0,0 +1,38 @@
1
+ import type { ClientUrlPatterns } from "./types.js";
2
+ export interface ClientUrlNavigationIntent {
3
+ readonly routeId: string;
4
+ }
5
+ export declare function setActiveInterceptTargets(targets: readonly string[] | undefined): void;
6
+ export interface ClientUrlNavigationPresentation {
7
+ readonly routeId: string;
8
+ clear(): void;
9
+ }
10
+ export declare function registerClientUrlGroup(definition: ClientUrlPatterns, mount: string, namePrefix: string, setIntent: (intent: ClientUrlNavigationIntent | null) => void): () => void;
11
+ export declare function beginClientUrlNavigation(targetUrl: URL, signal: AbortSignal): ClientUrlNavigationPresentation | null;
12
+ /**
13
+ * Run the held clientUrls route's per-loader revalidate() predicates and
14
+ * encode their decisions for the request header. Predicates are CLIENT code
15
+ * (declared in the "use client" definition module); only decisions cross the
16
+ * wire — the synthesized per-loader revalidate() on every materialized stub
17
+ * (server-projection.ts) reads them back by loader $$id.
18
+ *
19
+ * Decisions exist only for the loaders the client currently HOLDS: the local
20
+ * match of currentUrl. They matter when the server would re-evaluate those
21
+ * held loader segments — same-route param/search navs, actions, and stale
22
+ * restores. Cross-route targets render new segments unconditionally, so a
23
+ * decision would be ignored; we still compute against the current route since
24
+ * only its held segments are addressable.
25
+ *
26
+ * Returns null when no group is active, the current location is not a client
27
+ * route, or no predicate produced a decision that differs from the locked
28
+ * default — the server then applies defaults, which is also the behavior for
29
+ * requests that cannot carry decisions (no-JS, PE, prefetch, document loads).
30
+ */
31
+ export declare function collectClientRevalidationDecisions(options: {
32
+ currentUrl: URL;
33
+ nextUrl: URL;
34
+ isAction: boolean;
35
+ actionId?: string;
36
+ stale: boolean;
37
+ }): string | null;
38
+ export declare function clearClientUrlNavigationRegistry(): void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Wire protocol for CLIENT-RUN per-loader revalidation decisions.
3
+ *
4
+ * clientUrls() revalidate() predicates are declared in a "use client" module,
5
+ * so they cannot cross the projection boundary as functions — instead they
6
+ * EXECUTE in the browser and only their DECISIONS cross: the browser attaches
7
+ * this header to partial-navigation and action requests, and the synthesized
8
+ * per-loader revalidate() on every materialized loader stub reads it back
9
+ * (see materializeRouteItems in server-projection.ts).
10
+ *
11
+ * Trust model: same class as `_rsc_segments` — a decision can only make the
12
+ * CLIENT's own view staler (skip) or fresher (force), and the synthesized
13
+ * predicates exist only on client-urls loader stubs, so the header can never
14
+ * influence server-tree loaders. Requests without the header (no-JS, PE,
15
+ * prefetch, document loads) fall back to the locked server defaults.
16
+ */
17
+ export declare const CLIENT_REVALIDATION_HEADER = "X-Rango-Client-Reval";
18
+ export interface ClientRevalidationDecisions {
19
+ /** Loader $$ids whose client predicate said false (keep held data). */
20
+ readonly skip: readonly string[];
21
+ /** Loader $$ids whose client predicate said true against a false default. */
22
+ readonly force: readonly string[];
23
+ }
24
+ export declare function encodeClientRevalidationDecisions(decisions: ClientRevalidationDecisions): string | null;
25
+ export declare function decodeClientRevalidationDecisions(headerValue: string | null): ClientRevalidationDecisions | null;
@@ -0,0 +1,62 @@
1
+ import type { SearchSchemaValue } from "../search-params.js";
2
+ import type { TrailingSlashMode } from "../types.js";
3
+ import type { UrlPatterns } from "../urls/pattern-types.js";
4
+ import type { ClientTransitionConfig, ClientUrlPatterns } from "./types.js";
5
+ export interface ClientUrlReference {
6
+ readonly $$typeof: symbol;
7
+ readonly $$id: string;
8
+ }
9
+ export type ClientUrlDefinitionSource = ClientUrlReference | ClientUrlPatterns;
10
+ export interface ClientUrlProjectionOptions {
11
+ readonly search?: Readonly<Record<string, SearchSchemaValue>>;
12
+ readonly trailingSlash?: TrailingSlashMode;
13
+ }
14
+ export interface ClientUrlProjectionRoute {
15
+ readonly id: string;
16
+ readonly pattern: string;
17
+ readonly name: string | null;
18
+ readonly options: ClientUrlProjectionOptions;
19
+ readonly loaderIds: readonly string[];
20
+ readonly hasLoading: boolean;
21
+ /** Indices into loaderIds of loaders declared loader(Def, { stream:
22
+ * "navigation" }); materialization passes the option through to the server
23
+ * loader() so document renders await them before first flush. Absent (=
24
+ * none) in projections serialized before stream support. */
25
+ readonly awaitedLoaderIndices?: readonly number[];
26
+ /** Data-only transition config (no `when` — server-tree only); absent in
27
+ * projections serialized before transition support. */
28
+ readonly transition?: Readonly<ClientTransitionConfig>;
29
+ }
30
+ export interface ClientUrlProjectionIntercept {
31
+ readonly slotName: `@${string}`;
32
+ /** Bare local target name; materialization re-dots it so the server
33
+ * intercept() helper composes it against the include's name prefix. */
34
+ readonly targetName: string;
35
+ readonly loaderIds: readonly string[];
36
+ readonly hasLoading: boolean;
37
+ }
38
+ export interface ClientUrlProjection {
39
+ readonly version: 1;
40
+ readonly routes: readonly ClientUrlProjectionRoute[];
41
+ /** Absent in projections serialized before intercept support; consumers
42
+ * must treat missing as empty. */
43
+ readonly intercepts?: readonly ClientUrlProjectionIntercept[];
44
+ }
45
+ export declare function serializeClientUrlPatterns(patterns: ClientUrlPatterns): ClientUrlProjection;
46
+ export declare function isClientUrlPatterns(value: unknown): value is ClientUrlPatterns;
47
+ export declare function isClientUrlReference(value: unknown): value is ClientUrlReference;
48
+ export declare function setClientUrlProjection(reference: string | ClientUrlReference, projection: ClientUrlProjection): void;
49
+ export declare function getClientUrlProjection(reference: string | ClientUrlReference): ClientUrlProjection | undefined;
50
+ export declare function clearClientUrlProjections(): void;
51
+ export declare function materializeClientUrlPatterns(reference: ClientUrlDefinitionSource, projection: ClientUrlProjection): UrlPatterns;
52
+ /**
53
+ * Adapt a clientUrls() source for `include()` mounting inside the canonical
54
+ * urls() tree. Materialization is DEFERRED into the returned handler: it
55
+ * resolves the discovery-installed projection at evaluation time (lazy include
56
+ * expansion, build manifest generation, dev per-request trie rebuild) — by
57
+ * then the routes-manifest bootstrap has installed projections, so registration
58
+ * order never matters. The include machinery applies the URL and route-name
59
+ * prefixes exactly as it does for any urls() module; surrounding RSC layouts,
60
+ * middleware scoping, and boundaries derive from the canonical server tree.
61
+ */
62
+ export declare function clientUrlIncludePatterns(source: ClientUrlDefinitionSource): UrlPatterns;
@@ -0,0 +1,144 @@
1
+ import type { ComponentType, ReactNode } from "react";
2
+ import type { LoaderDefinition, LoaderOptions, TransitionConfig } from "../types.js";
3
+ import type { TrieMatchResult } from "../router/trie-matching.js";
4
+ import type { PathOptions } from "../urls/pattern-types.js";
5
+ import type { SearchSchema } from "../search-params.js";
6
+ import type { TypedLayoutItem, TypedRouteItem } from "../route-types.js";
7
+ import type { ExtractRoutes } from "../urls/type-extraction.js";
8
+ import type { UnnamedRoute } from "../urls/pattern-types.js";
9
+ declare const CLIENT_URL_ITEM_BRAND: unique symbol;
10
+ declare const CLIENT_URL_PATTERNS_BRAND: unique symbol;
11
+ /** Opaque value returned by a clientUrls() helper. */
12
+ export interface ClientUrlItem {
13
+ readonly [CLIENT_URL_ITEM_BRAND]: void;
14
+ }
15
+ export type ClientUrlItemInput = ClientUrlItem | readonly ClientUrlItemInput[];
16
+ export type ClientUrlItems = readonly ClientUrlItemInput[];
17
+ export type ClientUrlUse = () => ClientUrlItems;
18
+ export type ClientPathOptions<TName extends string = string, TSearch extends SearchSchema = SearchSchema> = Pick<PathOptions<TName, TSearch>, "name" | "search" | "trailingSlash">;
19
+ export type ClientPathFn = <const TPattern extends string, const TName extends string = UnnamedRoute, const TSearch extends SearchSchema = {}>(pattern: TPattern, component: ComponentType, optionsOrUse?: ClientPathOptions<TName, TSearch> | ClientUrlUse, use?: ClientUrlUse) => ClientUrlItem & TypedRouteItem<TName, TPattern, unknown, TSearch>;
20
+ export type ClientLayoutFn = <const TItems extends ClientUrlItems>(component: ComponentType, children: () => TItems) => ClientUrlItem & TypedLayoutItem<ExtractRoutes<TItems>>;
21
+ /**
22
+ * Arguments a clientUrls() revalidate() predicate receives. A client-computable
23
+ * subset of the server ShouldRevalidateFn args — the predicate RUNS IN THE
24
+ * BROWSER (it is declared in a "use client" module and never crosses the
25
+ * projection boundary); only its decision is sent to the server. There is no
26
+ * `context` — no server handler context exists where this executes.
27
+ */
28
+ export interface ClientRevalidateArgs {
29
+ /** Full URL of the page being navigated away from (current location). */
30
+ readonly currentUrl: URL;
31
+ /** Full URL of the navigation target (equals currentUrl for actions). */
32
+ readonly nextUrl: URL;
33
+ /** Route params of the held client route (definition-local match). */
34
+ readonly currentParams: Record<string, string>;
35
+ /** Route params for the navigation target (definition-local match). */
36
+ readonly nextParams: Record<string, string>;
37
+ /**
38
+ * The locked default decision for this loader, computed client-side with
39
+ * the same rules the server would apply: `true` on actions and when
40
+ * params/search changed, `false` otherwise. Return it for default behavior
41
+ * plus your own conditions.
42
+ */
43
+ readonly defaultShouldRevalidate: boolean;
44
+ /** True when this is a stale history-entry background revalidation. */
45
+ readonly stale: boolean;
46
+ /** True when revalidation is triggered by a server action. */
47
+ readonly isAction: boolean;
48
+ /** The triggering server action's id, when isAction. */
49
+ readonly actionId?: string;
50
+ }
51
+ export type ClientRevalidateFn = (args: ClientRevalidateArgs) => boolean;
52
+ export interface ClientUrlLoaderRecord {
53
+ readonly loader: LoaderDefinition<any, any>;
54
+ /** Client-run per-loader revalidation predicates; empty = locked defaults. */
55
+ readonly revalidate: readonly ClientRevalidateFn[];
56
+ /**
57
+ * loader(Def, { stream: "navigation" }): document renders await this loader
58
+ * before first flush (see {@link LoaderOptions}). Projected into the server
59
+ * tree, where the per-isSSR entry stamping applies — client navigations
60
+ * stream regardless.
61
+ */
62
+ readonly stream?: "navigation";
63
+ }
64
+ /**
65
+ * The data-only subset of TransitionConfig a clientUrls() route may declare:
66
+ * ViewTransition classes/name plus the boundary opt-out. The `when` gate is a
67
+ * server-executed predicate and stays in the server tree — it cannot cross the
68
+ * "use client" projection boundary.
69
+ */
70
+ export type ClientTransitionConfig = Pick<TransitionConfig, "enter" | "exit" | "update" | "share" | "default" | "name" | "viewTransition">;
71
+ export interface ClientUrlRouteRecord {
72
+ readonly id: string;
73
+ readonly pattern: string;
74
+ readonly name: string | undefined;
75
+ readonly options: Readonly<ClientPathOptions> | undefined;
76
+ readonly component: ComponentType;
77
+ readonly layouts: readonly ComponentType[];
78
+ readonly loaders: readonly ClientUrlLoaderRecord[];
79
+ readonly loading: ReactNode | undefined;
80
+ readonly transition: Readonly<ClientTransitionConfig> | undefined;
81
+ }
82
+ /**
83
+ * A restricted intercept declared inside clientUrls(). Compared to the server
84
+ * intercept() there is no `when` selector, no middleware, and the target must
85
+ * be a dot-local NAMED route in the same definition — every field is
86
+ * JSON-projectable, which is what makes the declaration legal in a
87
+ * "use client" module. `targetName` is stored bare (no leading dot).
88
+ */
89
+ export interface ClientUrlInterceptRecord {
90
+ readonly slotName: `@${string}`;
91
+ readonly targetName: string;
92
+ readonly component: ComponentType;
93
+ readonly loaders: readonly ClientUrlLoaderRecord[];
94
+ readonly loading: ReactNode | undefined;
95
+ }
96
+ export interface ClientUrlHelpers {
97
+ readonly path: ClientPathFn;
98
+ readonly layout: ClientLayoutFn;
99
+ /**
100
+ * Attach a projected loader. The optional use callback may contain
101
+ * revalidate() only — a CLIENT-RUN per-loader predicate; its decision (not
102
+ * the function) is sent with the revalidation request.
103
+ *
104
+ * Pass `{ stream: "navigation" }` to await this loader before first flush
105
+ * on DOCUMENT requests (see {@link LoaderOptions}) — the opt-in for loaders
106
+ * whose data, handle pushes, or thrown notFound()/redirect() must be in the
107
+ * SSR'd HTML. Per-loader: a dynamic sibling keeps streaming.
108
+ */
109
+ readonly loader: <TData>(definition: LoaderDefinition<TData>, optionsOrUse?: LoaderOptions | ClientUrlUse, use?: ClientUrlUse) => ClientUrlItem;
110
+ readonly loading: (component: ReactNode) => ClientUrlItem;
111
+ /**
112
+ * Per-loader revalidation predicate, valid inside a loader() use callback
113
+ * only. Runs IN THE BROWSER with client-computable args; return true to
114
+ * re-run the loader, false to keep held data. Absent predicates (and
115
+ * requests that carry no decisions: no-JS, PE, prefetch, document loads)
116
+ * follow the locked server defaults.
117
+ */
118
+ readonly revalidate: (fn: ClientRevalidateFn) => ClientUrlItem;
119
+ /**
120
+ * Declare an intercept for a named route in THIS definition. The target is
121
+ * dot-local (`.detail`); scoping is module-local — only navigations whose
122
+ * origin is inside this clientUrls() group render the intercept. `use` may
123
+ * contain loader() and loading() only.
124
+ */
125
+ readonly intercept: (slotName: `@${string}`, targetName: `.${string}`, component: ComponentType, use?: ClientUrlUse) => ClientUrlItem;
126
+ /**
127
+ * Opt THIS route into transition-driven navigation: the canonical commit
128
+ * holds previous content instead of re-streaming the loading() fallback,
129
+ * and on experimental React the config's ViewTransition classes apply.
130
+ * Data-only — no `when` gate (server-executed; declare it in the server
131
+ * tree). Valid inside a path() use callback only.
132
+ */
133
+ readonly transition: (config: ClientTransitionConfig) => ClientUrlItem;
134
+ }
135
+ export type ClientUrlBuilder<TItems extends ClientUrlItems = ClientUrlItems> = (helpers: ClientUrlHelpers) => TItems;
136
+ export interface ClientUrlPatterns<TRoutes extends Record<string, any> = Record<string, any>> {
137
+ readonly __brand: "client-urls";
138
+ readonly routes: readonly ClientUrlRouteRecord[];
139
+ readonly intercepts: readonly ClientUrlInterceptRecord[];
140
+ readonly [CLIENT_URL_PATTERNS_BRAND]: void;
141
+ readonly _routes?: TRoutes;
142
+ match(pathname: string): TrieMatchResult | null;
143
+ }
144
+ export {};