@rangojs/router 0.0.0-experimental.122 → 0.0.0-experimental.125

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 (260) hide show
  1. package/dist/bin/rango.js +10 -6
  2. package/dist/testing/vitest.js +82 -0
  3. package/dist/vite/index.js +55 -48
  4. package/package.json +61 -21
  5. package/skills/caching/SKILL.md +2 -1
  6. package/skills/hooks/SKILL.md +40 -29
  7. package/skills/host-router/SKILL.md +16 -2
  8. package/skills/intercept/SKILL.md +4 -2
  9. package/skills/layout/SKILL.md +11 -6
  10. package/skills/loader/SKILL.md +6 -2
  11. package/skills/middleware/SKILL.md +4 -2
  12. package/skills/migrate-nextjs/SKILL.md +3 -1
  13. package/skills/parallel/SKILL.md +9 -4
  14. package/skills/rango/SKILL.md +12 -0
  15. package/skills/route/SKILL.md +10 -2
  16. package/skills/testing/SKILL.md +129 -0
  17. package/skills/testing/bindings.md +89 -0
  18. package/skills/testing/cache-prerender.md +98 -0
  19. package/skills/testing/client-components.md +122 -0
  20. package/skills/testing/e2e-parity.md +125 -0
  21. package/skills/testing/flight.md +89 -0
  22. package/skills/testing/handles.md +129 -0
  23. package/skills/testing/loader.md +128 -0
  24. package/skills/testing/middleware.md +99 -0
  25. package/skills/testing/render-handler.md +118 -0
  26. package/skills/testing/response-routes.md +95 -0
  27. package/skills/testing/reverse-and-types.md +84 -0
  28. package/skills/testing/server-actions.md +107 -0
  29. package/skills/testing/server-tree.md +128 -0
  30. package/skills/testing/setup.md +120 -0
  31. package/src/__internal.ts +0 -65
  32. package/src/browser/action-coordinator.ts +1 -1
  33. package/src/browser/action-fence.ts +47 -0
  34. package/src/browser/cookie-name.ts +140 -0
  35. package/src/browser/event-controller.ts +1 -83
  36. package/src/browser/invalidate-client-cache.ts +52 -0
  37. package/src/browser/navigation-bridge.ts +14 -1
  38. package/src/browser/navigation-client.ts +14 -1
  39. package/src/browser/navigation-store-handle.ts +38 -0
  40. package/src/browser/navigation-store.ts +26 -51
  41. package/src/browser/navigation-transaction.ts +0 -32
  42. package/src/browser/partial-update.ts +1 -83
  43. package/src/browser/prefetch/cache.ts +6 -45
  44. package/src/browser/prefetch/fetch.ts +7 -0
  45. package/src/browser/prefetch/queue.ts +6 -3
  46. package/src/browser/rango-state.ts +157 -99
  47. package/src/browser/react/Link.tsx +0 -2
  48. package/src/browser/react/NavigationProvider.tsx +2 -1
  49. package/src/browser/react/ScrollRestoration.tsx +10 -6
  50. package/src/browser/react/filter-segment-order.ts +0 -2
  51. package/src/browser/react/index.ts +0 -51
  52. package/src/browser/react/location-state-shared.ts +0 -13
  53. package/src/browser/react/location-state.ts +0 -1
  54. package/src/browser/react/use-action.ts +6 -15
  55. package/src/browser/react/use-handle.ts +0 -5
  56. package/src/browser/react/use-link-status.ts +0 -4
  57. package/src/browser/react/use-navigation.ts +0 -3
  58. package/src/browser/react/use-params.ts +0 -2
  59. package/src/browser/react/use-search-params.ts +0 -5
  60. package/src/browser/react/use-segments.ts +0 -13
  61. package/src/browser/rsc-router.tsx +12 -4
  62. package/src/browser/server-action-bridge.ts +77 -15
  63. package/src/browser/types.ts +7 -2
  64. package/src/browser/validate-redirect-origin.ts +4 -5
  65. package/src/build/route-trie.ts +3 -0
  66. package/src/build/route-types/param-extraction.ts +6 -3
  67. package/src/build/route-types/router-processing.ts +0 -8
  68. package/src/cache/cache-policy.ts +0 -54
  69. package/src/cache/cache-runtime.ts +27 -24
  70. package/src/cache/cache-scope.ts +0 -27
  71. package/src/cache/cache-tag.ts +0 -37
  72. package/src/cache/cf/cf-cache-store.ts +94 -46
  73. package/src/cache/cf/index.ts +0 -24
  74. package/src/cache/document-cache.ts +11 -36
  75. package/src/cache/handle-snapshot.ts +0 -40
  76. package/src/cache/index.ts +0 -27
  77. package/src/cache/memory-segment-store.ts +2 -48
  78. package/src/cache/profile-registry.ts +7 -3
  79. package/src/cache/read-through-swr.ts +41 -11
  80. package/src/cache/segment-codec.ts +0 -16
  81. package/src/cache/types.ts +0 -98
  82. package/src/client.rsc.tsx +1 -22
  83. package/src/client.tsx +14 -38
  84. package/src/component-utils.ts +19 -0
  85. package/src/deps/ssr.ts +0 -1
  86. package/src/handle.ts +28 -18
  87. package/src/handles/MetaTags.tsx +0 -14
  88. package/src/handles/meta.ts +0 -39
  89. package/src/host/cookie-handler.ts +0 -36
  90. package/src/host/errors.ts +0 -24
  91. package/src/host/index.ts +6 -0
  92. package/src/host/pattern-matcher.ts +7 -50
  93. package/src/host/router.ts +1 -65
  94. package/src/host/testing.ts +40 -27
  95. package/src/host/types.ts +6 -2
  96. package/src/href-client.ts +0 -4
  97. package/src/index.rsc.ts +42 -3
  98. package/src/index.ts +31 -1
  99. package/src/internal-debug.ts +2 -4
  100. package/src/loader.rsc.ts +19 -9
  101. package/src/loader.ts +12 -4
  102. package/src/network-error-thrower.tsx +1 -6
  103. package/src/outlet-provider.tsx +1 -5
  104. package/src/prerender/param-hash.ts +10 -11
  105. package/src/prerender/store.ts +23 -30
  106. package/src/prerender.ts +58 -3
  107. package/src/root-error-boundary.tsx +1 -19
  108. package/src/route-content-wrapper.tsx +1 -44
  109. package/src/route-definition/dsl-helpers.ts +7 -19
  110. package/src/route-definition/helpers-types.ts +3 -3
  111. package/src/route-definition/redirect.ts +11 -1
  112. package/src/route-map-builder.ts +0 -16
  113. package/src/router/basename.ts +14 -0
  114. package/src/router/content-negotiation.ts +0 -13
  115. package/src/router/error-handling.ts +12 -16
  116. package/src/router/find-match.ts +4 -30
  117. package/src/router/intercept-resolution.ts +10 -1
  118. package/src/router/lazy-includes.ts +1 -57
  119. package/src/router/loader-resolution.ts +3 -2
  120. package/src/router/logging.ts +0 -6
  121. package/src/router/manifest.ts +1 -25
  122. package/src/router/match-api.ts +0 -20
  123. package/src/router/match-context.ts +0 -22
  124. package/src/router/match-handlers.ts +57 -58
  125. package/src/router/match-middleware/background-revalidation.ts +0 -7
  126. package/src/router/match-middleware/cache-lookup.ts +1 -54
  127. package/src/router/match-middleware/cache-store.ts +0 -31
  128. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  129. package/src/router/match-middleware/segment-resolution.ts +0 -21
  130. package/src/router/match-pipelines.ts +1 -42
  131. package/src/router/match-result.ts +1 -52
  132. package/src/router/metrics.ts +0 -34
  133. package/src/router/middleware-cookies.ts +0 -13
  134. package/src/router/middleware-types.ts +0 -115
  135. package/src/router/middleware.ts +7 -30
  136. package/src/router/navigation-snapshot.ts +0 -51
  137. package/src/router/params-util.ts +23 -0
  138. package/src/router/pattern-matching.ts +1 -33
  139. package/src/router/prerender-match.ts +33 -45
  140. package/src/router/request-classification.ts +1 -38
  141. package/src/router/revalidation.ts +5 -58
  142. package/src/router/router-context.ts +0 -26
  143. package/src/router/router-interfaces.ts +7 -0
  144. package/src/router/router-options.ts +30 -0
  145. package/src/router/segment-resolution/fresh.ts +25 -57
  146. package/src/router/segment-resolution/helpers.ts +34 -0
  147. package/src/router/segment-resolution/loader-cache.ts +10 -13
  148. package/src/router/segment-resolution/revalidation.ts +5 -42
  149. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  150. package/src/router/segment-resolution.ts +4 -1
  151. package/src/router/state-cookie-name.ts +33 -0
  152. package/src/router/telemetry-otel.ts +0 -20
  153. package/src/router/telemetry.ts +96 -19
  154. package/src/router/timeout.ts +0 -20
  155. package/src/router/trie-matching.ts +63 -40
  156. package/src/router/types.ts +1 -63
  157. package/src/router/url-params.ts +0 -5
  158. package/src/router.ts +40 -9
  159. package/src/rsc/handler.ts +14 -2
  160. package/src/rsc/helpers.ts +34 -0
  161. package/src/rsc/origin-guard.ts +0 -12
  162. package/src/rsc/progressive-enhancement.ts +4 -1
  163. package/src/rsc/rsc-rendering.ts +4 -7
  164. package/src/rsc/runtime-warnings.ts +14 -0
  165. package/src/rsc/server-action.ts +30 -28
  166. package/src/rsc/types.ts +2 -1
  167. package/src/runtime-env.ts +18 -0
  168. package/src/search-params.ts +0 -16
  169. package/src/segment-loader-promise.ts +14 -2
  170. package/src/segment-system.tsx +79 -88
  171. package/src/server/cookie-store.ts +52 -1
  172. package/src/server/handle-store.ts +7 -24
  173. package/src/server/loader-registry.ts +5 -24
  174. package/src/server/request-context.ts +74 -77
  175. package/src/ssr/index.tsx +14 -14
  176. package/src/static-handler.ts +10 -13
  177. package/src/testing/cache-status.ts +119 -0
  178. package/src/testing/collect-handle.ts +40 -0
  179. package/src/testing/dispatch.ts +581 -0
  180. package/src/testing/dom.entry.ts +22 -0
  181. package/src/testing/e2e/fixture.ts +188 -0
  182. package/src/testing/e2e/index.ts +127 -0
  183. package/src/testing/e2e/matchers.ts +35 -0
  184. package/src/testing/e2e/page-helpers.ts +272 -0
  185. package/src/testing/e2e/parity.ts +387 -0
  186. package/src/testing/e2e/server.ts +195 -0
  187. package/src/testing/flight-matchers.ts +97 -0
  188. package/src/testing/flight-normalize.ts +11 -0
  189. package/src/testing/flight-runtime.d.ts +57 -0
  190. package/src/testing/flight-tree.ts +682 -0
  191. package/src/testing/flight.entry.ts +52 -0
  192. package/src/testing/flight.ts +186 -0
  193. package/src/testing/generated-routes.ts +183 -0
  194. package/src/testing/index.ts +98 -0
  195. package/src/testing/internal/context.ts +348 -0
  196. package/src/testing/internal/flight-client-globals.ts +30 -0
  197. package/src/testing/internal/seed-vars.ts +54 -0
  198. package/src/testing/render-handler.ts +311 -0
  199. package/src/testing/render-route.tsx +504 -0
  200. package/src/testing/run-loader.ts +378 -0
  201. package/src/testing/run-middleware.ts +205 -0
  202. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  203. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  204. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  205. package/src/testing/vitest-stubs/version.ts +5 -0
  206. package/src/testing/vitest.ts +305 -0
  207. package/src/theme/ThemeProvider.tsx +0 -52
  208. package/src/theme/ThemeScript.tsx +0 -6
  209. package/src/theme/constants.ts +0 -12
  210. package/src/theme/index.ts +0 -7
  211. package/src/theme/theme-context.ts +1 -5
  212. package/src/theme/theme-script.ts +0 -14
  213. package/src/theme/use-theme.ts +0 -3
  214. package/src/types/boundaries.ts +0 -35
  215. package/src/types/error-types.ts +25 -89
  216. package/src/types/global-namespace.ts +15 -15
  217. package/src/types/handler-context.ts +16 -13
  218. package/src/types/index.ts +0 -10
  219. package/src/types/request-scope.ts +0 -19
  220. package/src/types/route-config.ts +6 -50
  221. package/src/types/route-entry.ts +0 -6
  222. package/src/types/segments.ts +0 -13
  223. package/src/urls/include-helper.ts +0 -4
  224. package/src/urls/index.ts +0 -6
  225. package/src/urls/path-helper-types.ts +2 -2
  226. package/src/urls/path-helper.ts +0 -54
  227. package/src/urls/urls-function.ts +0 -13
  228. package/src/use-loader.tsx +0 -186
  229. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  230. package/src/vite/discovery/discover-routers.ts +6 -7
  231. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  232. package/src/vite/plugin-types.ts +3 -1
  233. package/src/vite/plugins/cjs-to-esm.ts +0 -11
  234. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  235. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  236. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  237. package/src/vite/plugins/expose-action-id.ts +2 -73
  238. package/src/vite/plugins/expose-id-utils.ts +0 -55
  239. package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
  240. package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
  241. package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
  242. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  243. package/src/vite/plugins/expose-internal-ids.ts +10 -0
  244. package/src/vite/plugins/performance-tracks.ts +0 -3
  245. package/src/vite/plugins/use-cache-transform.ts +0 -36
  246. package/src/vite/plugins/version-injector.ts +0 -20
  247. package/src/vite/plugins/version-plugin.ts +1 -49
  248. package/src/vite/plugins/virtual-entries.ts +0 -15
  249. package/src/vite/rango.ts +1 -108
  250. package/src/vite/router-discovery.ts +2 -1
  251. package/src/vite/utils/ast-handler-extract.ts +0 -16
  252. package/src/vite/utils/bundle-analysis.ts +6 -13
  253. package/src/vite/utils/client-chunks.ts +0 -6
  254. package/src/vite/utils/forward-user-plugins.ts +0 -22
  255. package/src/vite/utils/manifest-utils.ts +0 -4
  256. package/src/vite/utils/package-resolution.ts +1 -73
  257. package/src/vite/utils/prerender-utils.ts +0 -35
  258. package/src/vite/utils/shared-utils.ts +3 -35
  259. package/src/browser/react/use-client-cache.ts +0 -58
  260. package/src/browser/shallow.ts +0 -40
package/dist/bin/rango.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ var __esm = (fn, res, err) => function __init() {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
6
11
  };
7
12
  var __export = (target, all) => {
8
13
  for (var name in all)
@@ -22,10 +27,10 @@ function extractParamsFromPattern(pattern) {
22
27
  function formatRouteEntry(key, pattern, _params, search) {
23
28
  const hasSearch = search && Object.keys(search).length > 0;
24
29
  if (!hasSearch) {
25
- return ` ${key}: "${pattern}",`;
30
+ return ` ${key}: ${JSON.stringify(pattern)},`;
26
31
  }
27
- const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
28
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
32
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
33
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
29
34
  }
30
35
  var init_param_extraction = __esm({
31
36
  "src/build/route-types/param-extraction.ts"() {
@@ -1287,7 +1292,6 @@ function createVersionPlugin() {
1287
1292
  }
1288
1293
  return null;
1289
1294
  },
1290
- // Track RSC module changes and update version
1291
1295
  async hotUpdate(ctx) {
1292
1296
  if (!isDev) return;
1293
1297
  const isRscModule = this.environment?.name === "rsc";
@@ -0,0 +1,82 @@
1
+ // src/testing/vitest.ts
2
+ import { fileURLToPath } from "node:url";
3
+ function here(relativeFromRoot) {
4
+ return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
5
+ }
6
+ function rangoTestAliases(opts = {}) {
7
+ const aliases = [
8
+ // Real impls (index.rsc.ts) for the bare specifier ONLY — exact regex so
9
+ // subpaths (/testing, /client, /cache, ...) are untouched. React stays the
10
+ // client build, so createContext and "use client" modules work.
11
+ { find: /^@rangojs\/router$/, replacement: here("src/index.rsc.ts") },
12
+ {
13
+ find: "@rangojs/router:version",
14
+ replacement: here("src/testing/vitest-stubs/version.ts")
15
+ },
16
+ {
17
+ find: /^@vitejs\/plugin-rsc\/rsc$/,
18
+ replacement: here("src/testing/vitest-stubs/plugin-rsc.ts")
19
+ }
20
+ ];
21
+ if (opts.preset === "cloudflare") {
22
+ aliases.push(
23
+ {
24
+ find: "cloudflare:workers",
25
+ replacement: here("src/testing/vitest-stubs/cloudflare-workers.ts")
26
+ },
27
+ {
28
+ find: "cloudflare:email",
29
+ replacement: here("src/testing/vitest-stubs/cloudflare-email.ts")
30
+ }
31
+ );
32
+ }
33
+ return aliases;
34
+ }
35
+ var rangoInlineDeps = [/@rangojs[/\\]router/];
36
+ function rangoTestConfig(opts = {}) {
37
+ return {
38
+ alias: rangoTestAliases(opts),
39
+ // fresh copy so the shared rangoInlineDeps const is never aliased into (or
40
+ // mutated through) a consumer's resolved config
41
+ server: { deps: { inline: [...rangoInlineDeps] } }
42
+ };
43
+ }
44
+ function rangoUseClientTransform() {
45
+ return {
46
+ name: "rango:testing-use-client",
47
+ async transform(code, id) {
48
+ if (id.includes("/node_modules/")) return void 0;
49
+ if (!code.includes("use client")) return void 0;
50
+ const { parseAstAsync } = await import("vite");
51
+ const { hasDirective, transformDirectiveProxyExport } = await import("@vitejs/plugin-rsc/transforms");
52
+ let ast;
53
+ try {
54
+ ast = await parseAstAsync(code);
55
+ } catch {
56
+ return void 0;
57
+ }
58
+ if (!hasDirective(ast.body, "use client")) return void 0;
59
+ const result = transformDirectiveProxyExport(ast, {
60
+ directive: "use client",
61
+ code,
62
+ runtime: (name) => `$$RangoRSD.registerClientReference(() => { throw new Error("client reference " + ${JSON.stringify(name)} + " is not callable on the server"); }, ${JSON.stringify(id)}, ${JSON.stringify(name)})`
63
+ });
64
+ if (!result) return void 0;
65
+ const { output } = result;
66
+ output.prepend(
67
+ `import * as $$RangoRSD from "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";
68
+ `
69
+ );
70
+ return {
71
+ code: output.toString(),
72
+ map: output.generateMap({ hires: true })
73
+ };
74
+ }
75
+ };
76
+ }
77
+ export {
78
+ rangoInlineDeps,
79
+ rangoTestAliases,
80
+ rangoTestConfig,
81
+ rangoUseClientTransform
82
+ };
@@ -431,7 +431,6 @@ function exposeActionId() {
431
431
  counterTransform?.record(id, performance.now() - start);
432
432
  }
433
433
  },
434
- // Build mode: renderChunk runs after all transforms and bundling complete
435
434
  renderChunk(code, chunk) {
436
435
  const start = counterRender ? performance.now() : 0;
437
436
  try {
@@ -1345,6 +1344,7 @@ ${lazyImports.join(",\n")}
1345
1344
  // --------------- Loader pre-scan (build mode) ---------------
1346
1345
  async buildStart() {
1347
1346
  if (!isBuild) return;
1347
+ if (this.environment && this.environment.name !== "rsc") return;
1348
1348
  const fs2 = await import("node:fs/promises");
1349
1349
  const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage"]);
1350
1350
  async function scanDir(dir) {
@@ -1997,7 +1997,6 @@ function clientRefDedup() {
1997
1997
  if (this.environment?.name !== "client") return;
1998
1998
  if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
1999
1999
  if (!source.includes("/node_modules/")) return;
2000
- if (!importer) return;
2001
2000
  const packageName = extractPackageName(source);
2002
2001
  if (!packageName) return;
2003
2002
  if (clientExclude.includes(packageName)) return;
@@ -2130,7 +2129,7 @@ import { resolve } from "node:path";
2130
2129
  // package.json
2131
2130
  var package_default = {
2132
2131
  name: "@rangojs/router",
2133
- version: "0.0.0-experimental.122",
2132
+ version: "0.0.0-experimental.125",
2134
2133
  description: "Django-inspired RSC router with composable URL patterns",
2135
2134
  keywords: [
2136
2135
  "react",
@@ -2256,6 +2255,31 @@ var package_default = {
2256
2255
  "./host/testing": {
2257
2256
  types: "./src/host/testing.ts",
2258
2257
  default: "./src/host/testing.ts"
2258
+ },
2259
+ "./testing": {
2260
+ types: "./src/testing/index.ts",
2261
+ default: "./src/testing/index.ts"
2262
+ },
2263
+ "./testing/vitest": {
2264
+ types: "./src/testing/vitest.ts",
2265
+ default: "./dist/testing/vitest.js"
2266
+ },
2267
+ "./testing/dom": {
2268
+ types: "./src/testing/dom.entry.ts",
2269
+ default: "./src/testing/dom.entry.ts"
2270
+ },
2271
+ "./testing/e2e": {
2272
+ types: "./src/testing/e2e/index.ts",
2273
+ default: "./src/testing/e2e/index.ts"
2274
+ },
2275
+ "./testing/flight": {
2276
+ types: "./src/testing/flight.entry.ts",
2277
+ "react-server": "./src/testing/flight.entry.ts",
2278
+ default: "./src/testing/flight.entry.ts"
2279
+ },
2280
+ "./testing/flight-matchers": {
2281
+ types: "./src/testing/flight-matchers.ts",
2282
+ default: "./src/testing/flight-matchers.ts"
2259
2283
  }
2260
2284
  },
2261
2285
  publishConfig: {
@@ -2263,14 +2287,15 @@ var package_default = {
2263
2287
  tag: "experimental"
2264
2288
  },
2265
2289
  scripts: {
2266
- build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2290
+ build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2267
2291
  prepublishOnly: "pnpm build",
2268
2292
  typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
2269
2293
  test: "playwright test",
2270
2294
  "test:ui": "playwright test --ui",
2271
2295
  "test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
2272
2296
  "test:unit": "vitest run",
2273
- "test:unit:watch": "vitest"
2297
+ "test:unit:watch": "vitest",
2298
+ "test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
2274
2299
  },
2275
2300
  dependencies: {
2276
2301
  "@types/debug": "^4.1.12",
@@ -2278,35 +2303,50 @@ var package_default = {
2278
2303
  debug: "^4.4.1",
2279
2304
  "magic-string": "^0.30.17",
2280
2305
  picomatch: "^4.0.3",
2281
- "rsc-html-stream": "^0.0.7"
2306
+ "rsc-html-stream": "^0.0.7",
2307
+ tinyexec: "^0.3.2"
2282
2308
  },
2283
2309
  devDependencies: {
2284
2310
  "@playwright/test": "^1.49.1",
2285
2311
  "@shared/e2e": "workspace:*",
2312
+ "@testing-library/dom": "^10.4.1",
2313
+ "@testing-library/react": "^16.3.2",
2286
2314
  "@types/node": "^24.10.1",
2287
2315
  "@types/react": "catalog:",
2288
2316
  "@types/react-dom": "catalog:",
2289
2317
  esbuild: "^0.27.0",
2318
+ "happy-dom": "^20.10.1",
2290
2319
  jiti: "^2.6.1",
2291
2320
  react: "catalog:",
2292
2321
  "react-dom": "catalog:",
2293
- tinyexec: "^0.3.2",
2294
2322
  typescript: "^5.3.0",
2295
2323
  vitest: "^4.0.0"
2296
2324
  },
2297
2325
  peerDependencies: {
2298
2326
  "@cloudflare/vite-plugin": "^1.38.0",
2327
+ "@playwright/test": "^1.49.1",
2328
+ "@testing-library/react": ">=16",
2299
2329
  "@vitejs/plugin-rsc": "^0.5.26",
2300
2330
  react: ">=19.2.6 <20",
2301
2331
  "react-dom": ">=19.2.6 <20",
2302
- vite: "^8.0.0"
2332
+ vite: "^8.0.0",
2333
+ vitest: ">=3"
2303
2334
  },
2304
2335
  peerDependenciesMeta: {
2305
2336
  "@cloudflare/vite-plugin": {
2306
2337
  optional: true
2307
2338
  },
2339
+ "@playwright/test": {
2340
+ optional: true
2341
+ },
2342
+ "@testing-library/react": {
2343
+ optional: true
2344
+ },
2308
2345
  vite: {
2309
2346
  optional: true
2347
+ },
2348
+ vitest: {
2349
+ optional: true
2310
2350
  }
2311
2351
  }
2312
2352
  };
@@ -2381,10 +2421,10 @@ function extractParamsFromPattern(pattern) {
2381
2421
  function formatRouteEntry(key, pattern, _params, search) {
2382
2422
  const hasSearch = search && Object.keys(search).length > 0;
2383
2423
  if (!hasSearch) {
2384
- return ` ${key}: "${pattern}",`;
2424
+ return ` ${key}: ${JSON.stringify(pattern)},`;
2385
2425
  }
2386
- const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
2387
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
2426
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
2427
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
2388
2428
  }
2389
2429
 
2390
2430
  // src/build/route-types/ast-route-extraction.ts
@@ -3241,7 +3281,6 @@ function createVersionPlugin() {
3241
3281
  }
3242
3282
  return null;
3243
3283
  },
3244
- // Track RSC module changes and update version
3245
3284
  async hotUpdate(ctx) {
3246
3285
  if (!isDev) return;
3247
3286
  const isRscModule = this.environment?.name === "rsc";
@@ -3318,9 +3357,6 @@ function performanceTracksOptimizeDepsPlugin() {
3318
3357
  const RSDW_CLIENT_RE = /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
3319
3358
  return {
3320
3359
  name: "@rangojs/router:performance-tracks-optimize-deps",
3321
- // Vite 8 optimizes deps with Rolldown (Rollup-style plugin pipeline), so the
3322
- // pre-bundled RSDW client is patched via load() rather than esbuild's onLoad.
3323
- // Returning code overrides Rolldown's default filesystem read for the module.
3324
3360
  async load(id) {
3325
3361
  const cleanId = id.split("?")[0] ?? id;
3326
3362
  if (!RSDW_CLIENT_RE.test(cleanId)) return null;
@@ -3510,7 +3546,6 @@ function hashClientRefs(projectRoot) {
3510
3546
  const counter = createCounter(debug7, "hash-client-refs");
3511
3547
  return {
3512
3548
  name: "@rangojs/router:hash-client-refs",
3513
- // Run after the RSC plugin's transform (default enforce is normal)
3514
3549
  enforce: "post",
3515
3550
  applyToEnvironment(env) {
3516
3551
  return env.name === "rsc";
@@ -4823,18 +4858,14 @@ async function discoverRouters(state, rscEnv) {
4823
4858
  if (manifest.routeTrailingSlash) {
4824
4859
  Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
4825
4860
  }
4826
- flattenLeafEntries(
4827
- manifest.prefixTree,
4828
- manifest.routeManifest,
4829
- newMergedPrecomputedEntries
4830
- );
4831
- newPerRouterManifestDataMap.set(id, manifest.routeManifest);
4832
4861
  const routerPrecomputed = [];
4833
4862
  flattenLeafEntries(
4834
4863
  manifest.prefixTree,
4835
4864
  manifest.routeManifest,
4836
4865
  routerPrecomputed
4837
4866
  );
4867
+ newMergedPrecomputedEntries.push(...routerPrecomputed);
4868
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
4838
4869
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
4839
4870
  console.log(
4840
4871
  `[rango] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
@@ -5159,7 +5190,7 @@ function generatePerRouterModule(state, routerId) {
5159
5190
  `export const precomputedEntries = ${jsonParseExpression(entries)};`
5160
5191
  );
5161
5192
  }
5162
- return lines.join("\n") || "// empty router manifest";
5193
+ return lines.join("\n") || "";
5163
5194
  }
5164
5195
 
5165
5196
  // src/vite/discovery/bundle-postprocess.ts
@@ -5230,7 +5261,7 @@ function postprocessBundle(state) {
5230
5261
  manifestMap[key] = `./assets/${assetFileName}`;
5231
5262
  }
5232
5263
  const manifestCode = [
5233
- `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
5264
+ `const m=${jsonParseExpression(manifestMap)};`,
5234
5265
  `export function loadPrerenderAsset(s){return import(s)}`,
5235
5266
  `export default m;`,
5236
5267
  ""
@@ -6494,8 +6525,6 @@ async function rango(options) {
6494
6525
  enforce: "pre",
6495
6526
  config() {
6496
6527
  return {
6497
- // Exclude rsc-router modules from optimization to prevent module duplication
6498
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
6499
6528
  optimizeDeps: {
6500
6529
  exclude: excludeDeps,
6501
6530
  rolldownOptions: sharedRolldownOptions
@@ -6518,21 +6547,12 @@ async function rango(options) {
6518
6547
  client: {
6519
6548
  build: {
6520
6549
  rollupOptions: {
6521
- // FILE_NAME_CONFLICT (and any other client-build warning) is
6522
- // emitted by the CLIENT environment build, which consults THIS
6523
- // env's onwarn -- Vite 8's environment builds do NOT propagate
6524
- // the top-level build.rollupOptions.onwarn into the client env.
6525
- // Wire it here so the suppression runs where the conflicts
6526
- // originate (the top-level handler is invoked 0x for these; the
6527
- // client-env handler is invoked for all of them).
6528
6550
  onwarn,
6529
6551
  output: {
6530
6552
  manualChunks: getManualChunks
6531
6553
  }
6532
6554
  }
6533
6555
  },
6534
- // Pre-bundle rsc-html-stream to prevent discovery during first request
6535
- // Exclude rsc-router modules to ensure same Context instance
6536
6556
  optimizeDeps: {
6537
6557
  include: [nested("rsc-html-stream/client")],
6538
6558
  exclude: excludeDeps,
@@ -6540,12 +6560,9 @@ async function rango(options) {
6540
6560
  }
6541
6561
  },
6542
6562
  ssr: {
6543
- // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
6544
6563
  build: {
6545
6564
  outDir: "./dist/rsc/ssr"
6546
6565
  },
6547
- // Pre-bundle SSR entry and React for proper module linking with childEnvironments
6548
- // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6549
6566
  optimizeDeps: {
6550
6567
  entries: [finalEntries.ssr],
6551
6568
  include: [
@@ -6565,10 +6582,7 @@ async function rango(options) {
6565
6582
  }
6566
6583
  },
6567
6584
  rsc: {
6568
- // RSC environment needs exclude list and esbuild options
6569
- // Exclude rsc-router modules to prevent createContext in RSC environment
6570
6585
  optimizeDeps: {
6571
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6572
6586
  include: [
6573
6587
  "react",
6574
6588
  "react/jsx-runtime",
@@ -6654,13 +6668,6 @@ ${list}`);
6654
6668
  client: {
6655
6669
  build: {
6656
6670
  rollupOptions: {
6657
- // FILE_NAME_CONFLICT (and any other client-build warning) is
6658
- // emitted by the CLIENT environment build, which consults THIS
6659
- // env's onwarn -- Vite 8's environment builds do NOT propagate
6660
- // the top-level build.rollupOptions.onwarn into the client env.
6661
- // Wire it here so the suppression runs where the conflicts
6662
- // originate (the top-level handler is invoked 0x for these; the
6663
- // client-env handler is invoked for all of them).
6664
6671
  onwarn,
6665
6672
  output: {
6666
6673
  manualChunks: getManualChunks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.122",
3
+ "version": "0.0.0-experimental.125",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -126,57 +126,97 @@
126
126
  "./host/testing": {
127
127
  "types": "./src/host/testing.ts",
128
128
  "default": "./src/host/testing.ts"
129
+ },
130
+ "./testing": {
131
+ "types": "./src/testing/index.ts",
132
+ "default": "./src/testing/index.ts"
133
+ },
134
+ "./testing/vitest": {
135
+ "types": "./src/testing/vitest.ts",
136
+ "default": "./dist/testing/vitest.js"
137
+ },
138
+ "./testing/dom": {
139
+ "types": "./src/testing/dom.entry.ts",
140
+ "default": "./src/testing/dom.entry.ts"
141
+ },
142
+ "./testing/e2e": {
143
+ "types": "./src/testing/e2e/index.ts",
144
+ "default": "./src/testing/e2e/index.ts"
145
+ },
146
+ "./testing/flight": {
147
+ "types": "./src/testing/flight.entry.ts",
148
+ "react-server": "./src/testing/flight.entry.ts",
149
+ "default": "./src/testing/flight.entry.ts"
150
+ },
151
+ "./testing/flight-matchers": {
152
+ "types": "./src/testing/flight-matchers.ts",
153
+ "default": "./src/testing/flight-matchers.ts"
129
154
  }
130
155
  },
131
156
  "publishConfig": {
132
157
  "access": "public",
133
158
  "tag": "experimental"
134
159
  },
135
- "scripts": {
136
- "build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
137
- "prepublishOnly": "pnpm build",
138
- "typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
139
- "test": "playwright test",
140
- "test:ui": "playwright test --ui",
141
- "test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
142
- "test:unit": "vitest run",
143
- "test:unit:watch": "vitest"
144
- },
145
160
  "dependencies": {
146
161
  "@types/debug": "^4.1.12",
147
162
  "@vitejs/plugin-rsc": "^0.5.26",
148
163
  "debug": "^4.4.1",
149
164
  "magic-string": "^0.30.17",
150
165
  "picomatch": "^4.0.3",
151
- "rsc-html-stream": "^0.0.7"
166
+ "rsc-html-stream": "^0.0.7",
167
+ "tinyexec": "^0.3.2"
152
168
  },
153
169
  "devDependencies": {
154
170
  "@playwright/test": "^1.49.1",
155
- "@shared/e2e": "workspace:*",
171
+ "@testing-library/dom": "^10.4.1",
172
+ "@testing-library/react": "^16.3.2",
156
173
  "@types/node": "^24.10.1",
157
- "@types/react": "catalog:",
158
- "@types/react-dom": "catalog:",
174
+ "@types/react": "^19.2.7",
175
+ "@types/react-dom": "^19.2.3",
159
176
  "esbuild": "^0.27.0",
177
+ "happy-dom": "^20.10.1",
160
178
  "jiti": "^2.6.1",
161
- "react": "catalog:",
162
- "react-dom": "catalog:",
163
- "tinyexec": "^0.3.2",
179
+ "react": "^19.2.6",
180
+ "react-dom": "^19.2.6",
164
181
  "typescript": "^5.3.0",
165
- "vitest": "^4.0.0"
182
+ "vitest": "^4.0.0",
183
+ "@shared/e2e": "0.0.1"
166
184
  },
167
185
  "peerDependencies": {
168
186
  "@cloudflare/vite-plugin": "^1.38.0",
187
+ "@playwright/test": "^1.49.1",
188
+ "@testing-library/react": ">=16",
169
189
  "@vitejs/plugin-rsc": "^0.5.26",
170
190
  "react": ">=19.2.6 <20",
171
191
  "react-dom": ">=19.2.6 <20",
172
- "vite": "^8.0.0"
192
+ "vite": "^8.0.0",
193
+ "vitest": ">=3"
173
194
  },
174
195
  "peerDependenciesMeta": {
175
196
  "@cloudflare/vite-plugin": {
176
197
  "optional": true
177
198
  },
199
+ "@playwright/test": {
200
+ "optional": true
201
+ },
202
+ "@testing-library/react": {
203
+ "optional": true
204
+ },
178
205
  "vite": {
179
206
  "optional": true
207
+ },
208
+ "vitest": {
209
+ "optional": true
180
210
  }
211
+ },
212
+ "scripts": {
213
+ "build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
214
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
215
+ "test": "playwright test",
216
+ "test:ui": "playwright test --ui",
217
+ "test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
218
+ "test:unit": "vitest run",
219
+ "test:unit:watch": "vitest",
220
+ "test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
181
221
  }
182
- }
222
+ }
@@ -471,6 +471,7 @@ cache({ store: checkoutCache }, () => [
471
471
  ```typescript
472
472
  import { urls } from "@rangojs/router";
473
473
  import { MemorySegmentCacheStore } from "@rangojs/router/cache";
474
+ import * as CartActions from "./actions/cart";
474
475
 
475
476
  // Custom store for checkout (short TTL)
476
477
  const checkoutCache = new MemorySegmentCacheStore({
@@ -499,7 +500,7 @@ export const urlpatterns = urls(({ path, layout, cache, loader, revalidate }) =>
499
500
  path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
500
501
  loader(ProductLoader, () => [cache({ ttl: 120 })]),
501
502
  loader(CartLoader, () => [
502
- revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
503
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
503
504
  ]),
504
505
  ]),
505
506
  ]),
@@ -89,8 +89,8 @@ import { useSegments } from "@rangojs/router/client";
89
89
  function Breadcrumbs() {
90
90
  const { path, segmentIds, location } = useSegments();
91
91
 
92
- // path: ["/shop", "products", "123"]
93
- // segmentIds: ["shop-layout", "products-route"]
92
+ // path: ["shop", "products", "123"] (split on "/", no leading slash on any element)
93
+ // segmentIds: ["L0", "L0L1", "L0L1R0"] (opaque internal short-codes, not route names)
94
94
  // location: URL object
95
95
 
96
96
  return <nav>{path.join(" > ")}</nav>;
@@ -683,33 +683,44 @@ ProductState.delete();
683
683
  | `.write()` | yes (replace this slot) | no | throws |
684
684
  | `.delete()` | yes (remove this slot) | no | throws |
685
685
 
686
- ## Cache Hooks
686
+ ## Cache Control
687
687
 
688
- ### useClientCache()
688
+ ### invalidateClientCache()
689
689
 
690
- Manually control client-side navigation cache:
690
+ Force the client's caches to miss after a mutation the router can't see (a REST
691
+ call, a WebSocket push, a login). It is a plain function, not a hook, so it works
692
+ from module-level callbacks too. Imported from the root entry `@rangojs/router`,
693
+ it is selected by export conditions: in a client component it marks the caches
694
+ stale immediately; from a handler/server component it writes a rotated
695
+ `Set-Cookie` for the responding client.
691
696
 
692
697
  ```tsx
693
698
  "use client";
694
- import { useClientCache } from "@rangojs/router/client";
699
+ import { invalidateClientCache } from "@rangojs/router";
695
700
 
696
701
  function SaveButton() {
697
- const { clear } = useClientCache();
698
-
699
702
  const handleSave = async () => {
700
703
  await fetch("/api/data", {
701
704
  method: "POST",
702
705
  body: JSON.stringify(data),
703
706
  });
704
707
 
705
- // Invalidate cache after mutation
706
- clear();
708
+ // Invalidate the client's caches after the mutation
709
+ invalidateClientCache();
707
710
  };
708
711
 
709
712
  return <button onClick={handleSave}>Save</button>;
710
713
  }
711
714
  ```
712
715
 
716
+ A module-level subscription works the same way (no component needed):
717
+
718
+ ```ts
719
+ import { invalidateClientCache } from "@rangojs/router";
720
+
721
+ socket.on("catalog-updated", () => invalidateClientCache());
722
+ ```
723
+
713
724
  **Use cases**: REST API mutations, WebSocket updates, non-RSC data changes.
714
725
 
715
726
  ## Outlet Components
@@ -892,22 +903,22 @@ See `/links` for the full URL generation guide. `ctx.reverse()` is server-only;
892
903
 
893
904
  ## Hook Summary
894
905
 
895
- | Hook | Purpose | Returns |
896
- | --------------------- | --------------------------------- | ------------------------------------------------------------------ |
897
- | `useParams()` | Route params | `Readonly<T>` (default `Record<string, string>`) or selected value |
898
- | `usePathname()` | Current pathname | `string` |
899
- | `useSearchParams()` | URL search params | `ReadonlyURLSearchParams` |
900
- | `useHref()` | Mount-aware href | `(path) => string` |
901
- | `useMount()` | Current include() mount path | `string` |
902
- | `useReverse()` | Local reverse for imported routes | `(name, params?, search?) => string` |
903
- | `useNavigation()` | Reactive navigation state | state, location, isStreaming |
904
- | `useRouter()` | Stable router actions | push, replace, refresh, prefetch, back, forward |
905
- | `useSegments()` | URL path & segment IDs | path, segmentIds, location |
906
- | `useLinkStatus()` | Link pending state | { pending } |
907
- | `useLoader()` | Loader data (strict) | data, isLoading, error |
908
- | `useFetchLoader()` | Loader with on-demand fetch | data, load, isLoading |
909
- | `useRefreshLoaders()` | Refresh cross-loader group(s) | `() => (groups: string \| string[]) => Promise<void>` |
910
- | `useHandle()` | Accumulated handle data | T (handle type) |
911
- | `useAction()` | Server action state | state, error, result |
912
- | `useLocationState()` | History state (persists or flash) | T \| undefined |
913
- | `useClientCache()` | Cache control | { clear } |
906
+ | Hook | Purpose | Returns |
907
+ | ------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------ |
908
+ | `useParams()` | Route params | `Readonly<T>` (default `Record<string, string>`) or selected value |
909
+ | `usePathname()` | Current pathname | `string` |
910
+ | `useSearchParams()` | URL search params | `ReadonlyURLSearchParams` |
911
+ | `useHref()` | Mount-aware href | `(path) => string` |
912
+ | `useMount()` | Current include() mount path | `string` |
913
+ | `useReverse()` | Local reverse for imported routes | `(name, params?, search?) => string` |
914
+ | `useNavigation()` | Reactive navigation state | state, location, isStreaming |
915
+ | `useRouter()` | Stable router actions | push, replace, refresh, prefetch, back, forward |
916
+ | `useSegments()` | URL path & segment IDs | path, segmentIds, location |
917
+ | `useLinkStatus()` | Link pending state | { pending } |
918
+ | `useLoader()` | Loader data (strict) | data, isLoading, error |
919
+ | `useFetchLoader()` | Loader with on-demand fetch | data, load, isLoading |
920
+ | `useRefreshLoaders()` | Refresh cross-loader group(s) | `() => (groups: string \| string[]) => Promise<void>` |
921
+ | `useHandle()` | Accumulated handle data | T (handle type) |
922
+ | `useAction()` | Server action state | state, error, result |
923
+ | `useLocationState()` | History state (persists or flash) | T \| undefined |
924
+ | `invalidateClientCache()` | Force client caches to miss (function, not a hook; root entry) | `void` |