@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.9 → 3.5.0-ultramodern.91

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 (270) hide show
  1. package/dist/cjs/cli/artifacts.js +157 -0
  2. package/dist/cjs/cli/index.js +8 -79
  3. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +113 -0
  4. package/dist/cjs/cli/tanstackTypes/shared.js +123 -0
  5. package/dist/cjs/cli/tanstackTypes/source.js +255 -0
  6. package/dist/cjs/cli/tanstackTypes.js +4 -350
  7. package/dist/cjs/runtime/blockingSubscribe.js +57 -0
  8. package/dist/cjs/runtime/{pluginCore.js → clientBasepath.js} +12 -19
  9. package/dist/cjs/runtime/clientHydration.js +118 -0
  10. package/dist/cjs/runtime/dataMutation.js +11 -176
  11. package/dist/cjs/runtime/formData.js +116 -0
  12. package/dist/cjs/runtime/loaderBridge.js +7 -11
  13. package/dist/cjs/runtime/plugin.js +21 -125
  14. package/dist/cjs/runtime/plugin.node.js +45 -125
  15. package/dist/cjs/runtime/pluginShared.js +87 -0
  16. package/dist/cjs/runtime/prefetchLink.js +15 -2
  17. package/dist/cjs/runtime/register.js +3 -16
  18. package/dist/cjs/runtime/routeTree/components.js +102 -0
  19. package/dist/cjs/runtime/routeTree/construction.js +152 -0
  20. package/dist/cjs/runtime/routeTree/index.js +47 -0
  21. package/dist/cjs/runtime/routeTree/loaders.js +158 -0
  22. package/dist/cjs/runtime/routeTree/paths.js +57 -0
  23. package/dist/cjs/runtime/routeTree/staticData.js +63 -0
  24. package/dist/cjs/runtime/routeTree/tanstackRoutes.js +57 -0
  25. package/dist/cjs/runtime/routeTree/types.js +18 -0
  26. package/dist/cjs/runtime/routeTree.js +7 -320
  27. package/dist/cjs/runtime/router.js +15 -2
  28. package/dist/cjs/runtime/rsc/CompositeComponent.js +18 -13
  29. package/dist/cjs/runtime/rsc/ReplayableStream.js +0 -4
  30. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +5 -16
  31. package/dist/cjs/runtime/rsc/client.js +8 -25
  32. package/dist/cjs/runtime/rsc/flightSerialization.js +132 -0
  33. package/dist/cjs/runtime/rsc/payloadErrors.js +99 -0
  34. package/dist/cjs/runtime/rsc/payloadFetch.js +105 -0
  35. package/dist/cjs/runtime/rsc/payloadRouter.js +21 -142
  36. package/dist/cjs/runtime/rsc/payloadRoutes.js +94 -0
  37. package/dist/cjs/runtime/rsc/payloadStorage.js +58 -0
  38. package/dist/cjs/runtime/rsc/server.js +14 -18
  39. package/dist/cjs/runtime/rsc/shared.js +68 -0
  40. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +1 -0
  41. package/dist/cjs/runtime/rsc/treeGetter.js +72 -0
  42. package/dist/cjs/runtime/shared/isAbsoluteUrl.js +47 -0
  43. package/dist/cjs/runtime/ssrManagedTags.js +103 -0
  44. package/dist/cjs/runtime/ssrPreload.js +89 -0
  45. package/dist/cjs/runtime/ssrTypes.js +18 -0
  46. package/dist/cjs/runtime/state.js +1 -1
  47. package/dist/cjs/runtime/submitAction.js +161 -0
  48. package/dist/cjs/runtime/utils.js +49 -86
  49. package/dist/esm/cli/artifacts.mjs +103 -0
  50. package/dist/esm/cli/index.mjs +8 -82
  51. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +75 -0
  52. package/dist/esm/cli/tanstackTypes/shared.mjs +61 -0
  53. package/dist/esm/cli/tanstackTypes/source.mjs +207 -0
  54. package/dist/esm/cli/tanstackTypes.mjs +2 -339
  55. package/dist/esm/runtime/blockingSubscribe.mjs +19 -0
  56. package/dist/esm/runtime/clientBasepath.mjs +10 -0
  57. package/dist/esm/runtime/clientHydration.mjs +71 -0
  58. package/dist/esm/runtime/dataMutation.mjs +2 -167
  59. package/dist/esm/runtime/formData.mjs +63 -0
  60. package/dist/esm/runtime/loaderBridge.mjs +5 -9
  61. package/dist/esm/runtime/plugin.mjs +14 -118
  62. package/dist/esm/runtime/plugin.node.mjs +41 -121
  63. package/dist/esm/runtime/pluginShared.mjs +40 -0
  64. package/dist/esm/runtime/prefetchLink.mjs +18 -5
  65. package/dist/esm/runtime/register.mjs +1 -14
  66. package/dist/esm/runtime/routeTree/components.mjs +55 -0
  67. package/dist/esm/runtime/routeTree/construction.mjs +111 -0
  68. package/dist/esm/runtime/routeTree/index.mjs +2 -0
  69. package/dist/esm/runtime/routeTree/loaders.mjs +117 -0
  70. package/dist/esm/runtime/routeTree/paths.mjs +16 -0
  71. package/dist/esm/runtime/routeTree/staticData.mjs +22 -0
  72. package/dist/esm/runtime/routeTree/tanstackRoutes.mjs +13 -0
  73. package/dist/esm/runtime/routeTree/types.mjs +0 -0
  74. package/dist/esm/runtime/routeTree.mjs +1 -318
  75. package/dist/esm/runtime/router.mjs +16 -2
  76. package/dist/esm/runtime/rsc/CompositeComponent.mjs +18 -13
  77. package/dist/esm/runtime/rsc/ReplayableStream.mjs +1 -1
  78. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +4 -15
  79. package/dist/esm/runtime/rsc/client.mjs +6 -26
  80. package/dist/esm/runtime/rsc/flightSerialization.mjs +88 -0
  81. package/dist/esm/runtime/rsc/payloadErrors.mjs +49 -0
  82. package/dist/esm/runtime/rsc/payloadFetch.mjs +55 -0
  83. package/dist/esm/runtime/rsc/payloadRouter.mjs +12 -133
  84. package/dist/esm/runtime/rsc/payloadRoutes.mjs +50 -0
  85. package/dist/esm/runtime/rsc/payloadStorage.mjs +14 -0
  86. package/dist/esm/runtime/rsc/server.mjs +17 -21
  87. package/dist/esm/runtime/rsc/shared.mjs +21 -0
  88. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  89. package/dist/esm/runtime/rsc/treeGetter.mjs +34 -0
  90. package/dist/esm/runtime/shared/isAbsoluteUrl.mjs +9 -0
  91. package/dist/esm/runtime/ssrManagedTags.mjs +53 -0
  92. package/dist/esm/runtime/ssrPreload.mjs +51 -0
  93. package/dist/esm/runtime/ssrTypes.mjs +0 -0
  94. package/dist/esm/runtime/state.mjs +1 -1
  95. package/dist/esm/runtime/submitAction.mjs +120 -0
  96. package/dist/esm/runtime/utils.mjs +47 -75
  97. package/dist/esm-node/cli/artifacts.mjs +104 -0
  98. package/dist/esm-node/cli/index.mjs +8 -82
  99. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +76 -0
  100. package/dist/esm-node/cli/tanstackTypes/shared.mjs +62 -0
  101. package/dist/esm-node/cli/tanstackTypes/source.mjs +208 -0
  102. package/dist/esm-node/cli/tanstackTypes.mjs +2 -339
  103. package/dist/esm-node/runtime/blockingSubscribe.mjs +20 -0
  104. package/dist/esm-node/runtime/clientBasepath.mjs +11 -0
  105. package/dist/esm-node/runtime/clientHydration.mjs +72 -0
  106. package/dist/esm-node/runtime/dataMutation.mjs +2 -167
  107. package/dist/esm-node/runtime/formData.mjs +64 -0
  108. package/dist/esm-node/runtime/loaderBridge.mjs +5 -9
  109. package/dist/esm-node/runtime/plugin.mjs +14 -118
  110. package/dist/esm-node/runtime/plugin.node.mjs +41 -121
  111. package/dist/esm-node/runtime/pluginShared.mjs +41 -0
  112. package/dist/esm-node/runtime/prefetchLink.mjs +18 -5
  113. package/dist/esm-node/runtime/register.mjs +1 -14
  114. package/dist/esm-node/runtime/routeTree/components.mjs +56 -0
  115. package/dist/esm-node/runtime/routeTree/construction.mjs +112 -0
  116. package/dist/esm-node/runtime/routeTree/index.mjs +3 -0
  117. package/dist/esm-node/runtime/routeTree/loaders.mjs +118 -0
  118. package/dist/esm-node/runtime/routeTree/paths.mjs +17 -0
  119. package/dist/esm-node/runtime/routeTree/staticData.mjs +23 -0
  120. package/dist/esm-node/runtime/routeTree/tanstackRoutes.mjs +14 -0
  121. package/dist/esm-node/runtime/routeTree/types.mjs +1 -0
  122. package/dist/esm-node/runtime/routeTree.mjs +1 -318
  123. package/dist/esm-node/runtime/router.mjs +16 -2
  124. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +18 -13
  125. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +1 -1
  126. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +4 -15
  127. package/dist/esm-node/runtime/rsc/client.mjs +6 -26
  128. package/dist/esm-node/runtime/rsc/flightSerialization.mjs +89 -0
  129. package/dist/esm-node/runtime/rsc/payloadErrors.mjs +50 -0
  130. package/dist/esm-node/runtime/rsc/payloadFetch.mjs +56 -0
  131. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +12 -133
  132. package/dist/esm-node/runtime/rsc/payloadRoutes.mjs +51 -0
  133. package/dist/esm-node/runtime/rsc/payloadStorage.mjs +15 -0
  134. package/dist/esm-node/runtime/rsc/server.mjs +17 -21
  135. package/dist/esm-node/runtime/rsc/shared.mjs +22 -0
  136. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  137. package/dist/esm-node/runtime/rsc/treeGetter.mjs +35 -0
  138. package/dist/esm-node/runtime/shared/isAbsoluteUrl.mjs +10 -0
  139. package/dist/esm-node/runtime/ssrManagedTags.mjs +54 -0
  140. package/dist/esm-node/runtime/ssrPreload.mjs +52 -0
  141. package/dist/esm-node/runtime/ssrTypes.mjs +1 -0
  142. package/dist/esm-node/runtime/state.mjs +1 -1
  143. package/dist/esm-node/runtime/submitAction.mjs +121 -0
  144. package/dist/esm-node/runtime/utils.mjs +47 -75
  145. package/dist/types/cli/artifacts.d.ts +33 -0
  146. package/dist/types/cli/index.d.ts +2 -22
  147. package/dist/types/cli/tanstackTypes/canonicalRoutes.d.ts +22 -0
  148. package/dist/types/cli/tanstackTypes/shared.d.ts +20 -0
  149. package/dist/types/cli/tanstackTypes/source.d.ts +12 -0
  150. package/dist/types/cli/tanstackTypes.d.ts +3 -31
  151. package/dist/types/runtime/blockingSubscribe.d.ts +1 -0
  152. package/dist/types/runtime/clientBasepath.d.ts +1 -0
  153. package/dist/types/runtime/clientHydration.d.ts +10 -0
  154. package/dist/types/runtime/dataMutation.d.ts +3 -12
  155. package/dist/types/runtime/formData.d.ts +24 -0
  156. package/dist/types/runtime/loaderBridge.d.ts +2 -1
  157. package/dist/types/runtime/plugin.d.ts +1 -1
  158. package/dist/types/runtime/plugin.node.d.ts +1 -1
  159. package/dist/types/runtime/pluginShared.d.ts +38 -0
  160. package/dist/types/runtime/register.d.ts +10 -9
  161. package/dist/types/runtime/routeTree/components.d.ts +7 -0
  162. package/dist/types/runtime/routeTree/construction.d.ts +5 -0
  163. package/dist/types/runtime/routeTree/index.d.ts +2 -0
  164. package/dist/types/runtime/routeTree/loaders.d.ts +4 -0
  165. package/dist/types/runtime/routeTree/paths.d.ts +3 -0
  166. package/dist/types/runtime/routeTree/staticData.d.ts +19 -0
  167. package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +4 -0
  168. package/dist/types/runtime/routeTree/types.d.ts +76 -0
  169. package/dist/types/runtime/routeTree.d.ts +1 -9
  170. package/dist/types/runtime/router.d.ts +6 -7
  171. package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -2
  172. package/dist/types/runtime/rsc/ReplayableStream.d.ts +3 -2
  173. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +2 -3
  174. package/dist/types/runtime/rsc/SlotContext.d.ts +2 -1
  175. package/dist/types/runtime/rsc/client.d.ts +2 -0
  176. package/dist/types/runtime/rsc/flightSerialization.d.ts +3 -0
  177. package/dist/types/runtime/rsc/payloadErrors.d.ts +6 -0
  178. package/dist/types/runtime/rsc/payloadFetch.d.ts +8 -0
  179. package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -10
  180. package/dist/types/runtime/rsc/payloadRoutes.d.ts +40 -0
  181. package/dist/types/runtime/rsc/payloadStorage.d.ts +5 -0
  182. package/dist/types/runtime/rsc/shared.d.ts +18 -0
  183. package/dist/types/runtime/rsc/treeGetter.d.ts +1 -0
  184. package/dist/types/runtime/shared/isAbsoluteUrl.d.ts +1 -0
  185. package/dist/types/runtime/ssrManagedTags.d.ts +7 -0
  186. package/dist/types/runtime/ssrPreload.d.ts +2 -0
  187. package/dist/types/runtime/ssrTypes.d.ts +44 -0
  188. package/dist/types/runtime/state.d.ts +1 -1
  189. package/dist/types/runtime/submitAction.d.ts +14 -0
  190. package/dist/types/runtime/types.d.ts +4 -10
  191. package/dist/types/runtime/utils.d.ts +4 -24
  192. package/package.json +37 -17
  193. package/rstest.config.mts +23 -0
  194. package/src/cli/artifacts.ts +255 -0
  195. package/src/cli/index.ts +7 -185
  196. package/src/cli/tanstackTypes/canonicalRoutes.ts +157 -0
  197. package/src/cli/tanstackTypes/shared.ts +113 -0
  198. package/src/cli/tanstackTypes/source.ts +414 -0
  199. package/src/cli/tanstackTypes.ts +4 -647
  200. package/src/runtime/blockingSubscribe.ts +48 -0
  201. package/src/runtime/clientBasepath.ts +12 -0
  202. package/src/runtime/clientHydration.ts +149 -0
  203. package/src/runtime/dataMutation.tsx +9 -306
  204. package/src/runtime/formData.ts +153 -0
  205. package/src/runtime/loaderBridge.ts +11 -12
  206. package/src/runtime/plugin.node.tsx +76 -291
  207. package/src/runtime/plugin.tsx +21 -257
  208. package/src/runtime/pluginShared.ts +112 -0
  209. package/src/runtime/prefetchLink.tsx +78 -15
  210. package/src/runtime/register.ts +10 -42
  211. package/src/runtime/routeTree/components.ts +130 -0
  212. package/src/runtime/routeTree/construction.ts +238 -0
  213. package/src/runtime/routeTree/index.ts +7 -0
  214. package/src/runtime/routeTree/loaders.ts +232 -0
  215. package/src/runtime/routeTree/paths.ts +36 -0
  216. package/src/runtime/routeTree/staticData.ts +66 -0
  217. package/src/runtime/routeTree/tanstackRoutes.ts +36 -0
  218. package/src/runtime/routeTree/types.ts +87 -0
  219. package/src/runtime/routeTree.ts +5 -750
  220. package/src/runtime/router.ts +35 -7
  221. package/src/runtime/rsc/CompositeComponent.tsx +29 -20
  222. package/src/runtime/rsc/ReplayableStream.ts +2 -2
  223. package/src/runtime/rsc/RscNodeRenderer.tsx +9 -27
  224. package/src/runtime/rsc/SlotContext.tsx +1 -1
  225. package/src/runtime/rsc/client.tsx +12 -40
  226. package/src/runtime/rsc/flightSerialization.ts +158 -0
  227. package/src/runtime/rsc/payloadErrors.ts +73 -0
  228. package/src/runtime/rsc/payloadFetch.ts +92 -0
  229. package/src/runtime/rsc/payloadRouter.ts +29 -234
  230. package/src/runtime/rsc/payloadRoutes.ts +122 -0
  231. package/src/runtime/rsc/payloadStorage.ts +35 -0
  232. package/src/runtime/rsc/server.tsx +20 -29
  233. package/src/runtime/rsc/shared.ts +70 -0
  234. package/src/runtime/rsc/slotUsageSanitizer.ts +4 -0
  235. package/src/runtime/rsc/treeGetter.tsx +52 -0
  236. package/src/runtime/shared/isAbsoluteUrl.ts +8 -0
  237. package/src/runtime/ssrManagedTags.ts +107 -0
  238. package/src/runtime/ssrPreload.ts +125 -0
  239. package/src/runtime/ssrTypes.ts +51 -0
  240. package/src/runtime/state.ts +9 -3
  241. package/src/runtime/submitAction.ts +201 -0
  242. package/src/runtime/types.ts +4 -18
  243. package/src/runtime/utils.tsx +76 -110
  244. package/src/types/react-server-dom-rspack.d.ts +56 -0
  245. package/tests/castRatchet.test.ts +85 -0
  246. package/tests/realm-isolation.test.ts +114 -0
  247. package/tests/router/cli.test.ts +7 -0
  248. package/tests/router/clientHydration.test.tsx +61 -0
  249. package/tests/router/dataMutation.test.tsx +59 -0
  250. package/tests/router/fixtures/legacyRuntimeRouter.ts +10 -0
  251. package/tests/router/fixtures/router-gen.golden.txt +71 -0
  252. package/tests/router/fixtures/router-gen.oxlint.json +5 -0
  253. package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
  254. package/tests/router/generateRouteArtifacts.test.ts +106 -0
  255. package/tests/router/hooks.test.ts +14 -0
  256. package/tests/router/loaderBridge.test.ts +21 -0
  257. package/tests/router/prefetchLink.test.tsx +106 -59
  258. package/tests/router/prefetchLinkPreload.test.tsx +85 -0
  259. package/tests/router/register.test.ts +1 -39
  260. package/tests/router/routeTree.test.ts +51 -11
  261. package/tests/router/routerCompatibility.test.ts +245 -0
  262. package/tests/router/rsc.test.tsx +189 -0
  263. package/tests/router/rscPayloadRouterMatrix.test.ts +197 -0
  264. package/tests/router/slotUsageSanitizer.test.ts +23 -0
  265. package/tests/router/ssrPreload.test.ts +30 -0
  266. package/tests/router/tanstackTypes.test.ts +298 -1
  267. package/dist/esm/runtime/pluginCore.mjs +0 -14
  268. package/dist/esm-node/runtime/pluginCore.mjs +0 -15
  269. package/dist/types/runtime/pluginCore.d.ts +0 -21
  270. package/src/runtime/pluginCore.ts +0 -48
@@ -0,0 +1,197 @@
1
+ import {
2
+ __setTanstackRscPayloadDecoderForTests,
3
+ createTanstackRscServerPayload,
4
+ handleTanstackRscRedirect,
5
+ loadTanstackRscRouteData,
6
+ } from '../../src/runtime/rsc/payloadRouter';
7
+
8
+ function createPayload(
9
+ routeId: string,
10
+ loaderData: unknown,
11
+ options: { errors?: Record<string, unknown> | null } = {},
12
+ ) {
13
+ return {
14
+ type: 'render',
15
+ actionData: null,
16
+ errors: options.errors ?? null,
17
+ loaderData: loaderData === undefined ? {} : { [routeId]: loaderData },
18
+ location: { href: routeId },
19
+ routes: [{ id: routeId, hasLoader: true }],
20
+ };
21
+ }
22
+
23
+ async function decodeJsonPayload(stream: ReadableStream<Uint8Array>) {
24
+ return JSON.parse(await new Response(stream).text());
25
+ }
26
+
27
+ describe('tanstack rsc payload router matrix', () => {
28
+ afterEach(() => {
29
+ __setTanstackRscPayloadDecoderForTests();
30
+ rstest.restoreAllMocks();
31
+ });
32
+
33
+ test('normalizes redirect basenames while preserving status and non-location headers', () => {
34
+ const scenarios = [
35
+ {
36
+ location: '/base',
37
+ name: 'exact basename',
38
+ redirect: '/',
39
+ },
40
+ {
41
+ location: '/base/login?from=%2Fbase',
42
+ name: 'leading basename',
43
+ redirect: '/login?from=%2Fbase',
44
+ },
45
+ {
46
+ location: '/shop/base/login',
47
+ name: 'mid-path basename',
48
+ redirect: '/shop/base/login',
49
+ },
50
+ ] as const;
51
+
52
+ for (const scenario of scenarios) {
53
+ const response = handleTanstackRscRedirect(
54
+ new Headers({
55
+ Location: scenario.location,
56
+ 'X-Trace': scenario.name,
57
+ }),
58
+ '/base',
59
+ 307,
60
+ );
61
+
62
+ expect(response.status).toBe(307);
63
+ expect(response.headers.get('Location')).toBeNull();
64
+ expect(response.headers.get('X-Modernjs-BaseUrl')).toBe('/base');
65
+ expect(response.headers.get('X-Modernjs-Redirect')).toBe(
66
+ scenario.redirect,
67
+ );
68
+ expect(response.headers.get('X-Trace')).toBe(scenario.name);
69
+ }
70
+ });
71
+
72
+ test('separates payload fetch cache entries by URL and request method', async () => {
73
+ const payloads = [
74
+ createPayload('/resource', { method: 'GET' }),
75
+ createPayload('/resource', { method: 'POST' }),
76
+ ];
77
+ let payloadIndex = 0;
78
+ const fetchMock = rstest.fn(() =>
79
+ Promise.resolve(new Response(JSON.stringify(payloads[payloadIndex++]))),
80
+ );
81
+ rstest.stubGlobal('fetch', fetchMock);
82
+ __setTanstackRscPayloadDecoderForTests(decodeJsonPayload);
83
+
84
+ const [getData, postData] = await Promise.all([
85
+ loadTanstackRscRouteData({
86
+ loadClientData: async () => ({ fallback: 'get' }),
87
+ request: new Request('http://localhost/resource', { method: 'GET' }),
88
+ routeId: '/resource',
89
+ }),
90
+ loadTanstackRscRouteData({
91
+ loadClientData: async () => ({ fallback: 'post' }),
92
+ request: new Request('http://localhost/resource', { method: 'POST' }),
93
+ routeId: '/resource',
94
+ }),
95
+ ]);
96
+
97
+ expect(getData).toEqual({ method: 'GET' });
98
+ expect(postData).toEqual({ method: 'POST' });
99
+ expect(fetchMock).toHaveBeenCalledTimes(2);
100
+ expect(fetchMock.mock.calls.map(call => call[0])).toEqual([
101
+ 'http://localhost/resource',
102
+ 'http://localhost/resource',
103
+ ]);
104
+ });
105
+
106
+ test('rethrows matched route errors from the decoded payload', async () => {
107
+ const routeError = new Error('route loader failed');
108
+ const payload = createPayload('/broken', undefined, {
109
+ errors: { '/broken': routeError },
110
+ });
111
+ rstest.stubGlobal(
112
+ 'fetch',
113
+ rstest.fn(() => Promise.resolve(new Response('payload'))),
114
+ );
115
+ __setTanstackRscPayloadDecoderForTests(async () => payload);
116
+
117
+ await expect(
118
+ loadTanstackRscRouteData({
119
+ loadClientData: async () => ({ fallback: true }),
120
+ request: new Request('http://localhost/broken'),
121
+ routeId: '/broken',
122
+ }),
123
+ ).rejects.toBe(routeError);
124
+ });
125
+
126
+ test('returns undefined for route ids missing from the decoded payload', async () => {
127
+ const loadClientData = rstest.fn(async () => ({ fallback: true }));
128
+ const payload = createPayload('/known', { known: true });
129
+ rstest.stubGlobal(
130
+ 'fetch',
131
+ rstest.fn(() => Promise.resolve(new Response('payload'))),
132
+ );
133
+ __setTanstackRscPayloadDecoderForTests(async () => payload);
134
+
135
+ await expect(
136
+ loadTanstackRscRouteData({
137
+ loadClientData,
138
+ request: new Request('http://localhost/missing'),
139
+ routeId: '/missing',
140
+ }),
141
+ ).resolves.toBeUndefined();
142
+ expect(loadClientData).not.toHaveBeenCalled();
143
+ });
144
+
145
+ test('omits server payload matches with no route identity', () => {
146
+ const payload = createTanstackRscServerPayload({
147
+ state: {
148
+ location: { href: '/kept' },
149
+ matches: [
150
+ {
151
+ loaderData: { omitted: true },
152
+ params: {},
153
+ pathname: '/missing-id',
154
+ pathnameBase: '/missing-id',
155
+ route: {
156
+ options: {
157
+ path: 'missing-id',
158
+ staticData: { modernRouteHasLoader: true },
159
+ },
160
+ },
161
+ },
162
+ {
163
+ loaderData: { kept: true },
164
+ params: {},
165
+ pathname: '/kept',
166
+ pathnameBase: '/kept',
167
+ route: {
168
+ id: '/kept',
169
+ options: {
170
+ path: 'kept',
171
+ staticData: { modernRouteHasLoader: true },
172
+ },
173
+ },
174
+ },
175
+ ],
176
+ },
177
+ });
178
+
179
+ expect(payload.loaderData).toEqual({ '/kept': { kept: true } });
180
+ expect(payload.routes).toEqual([
181
+ {
182
+ handle: undefined,
183
+ hasAction: false,
184
+ hasClientLoader: false,
185
+ hasErrorBoundary: false,
186
+ hasLoader: true,
187
+ id: '/kept',
188
+ index: undefined,
189
+ params: {},
190
+ parentId: undefined,
191
+ path: 'kept',
192
+ pathname: '/kept',
193
+ pathnameBase: '/kept',
194
+ },
195
+ ]);
196
+ });
197
+ });
@@ -0,0 +1,23 @@
1
+ import { sanitizeSlotArgs } from '../../src/runtime/rsc/slotUsageSanitizer';
2
+
3
+ describe('RSC slot usage sanitizer', () => {
4
+ test('redacts callable slot args without serializing function source', () => {
5
+ function tokenFactory() {
6
+ return 'token-secret';
7
+ }
8
+
9
+ const args = sanitizeSlotArgs([
10
+ tokenFactory,
11
+ { nested: tokenFactory },
12
+ [tokenFactory],
13
+ ]);
14
+
15
+ expect(args).toEqual([
16
+ '[Function]',
17
+ { nested: '[Function]' },
18
+ ['[Function]'],
19
+ ]);
20
+ expect(JSON.stringify(args)).not.toContain('token-secret');
21
+ expect(JSON.stringify(args)).not.toContain('tokenFactory');
22
+ });
23
+ });
@@ -0,0 +1,30 @@
1
+ import { preloadMatchedRouteComponents } from '../../src/runtime/ssrPreload';
2
+
3
+ describe('preloadMatchedRouteComponents', () => {
4
+ test('preloads each component identity once per SSR pass', async () => {
5
+ const sharedComponent = Object.assign(() => null, {
6
+ preload: rstest.fn(),
7
+ });
8
+
9
+ await preloadMatchedRouteComponents({
10
+ routesById: {
11
+ child: {
12
+ options: {
13
+ component: sharedComponent,
14
+ },
15
+ },
16
+ root: {
17
+ options: {
18
+ component: sharedComponent,
19
+ pendingComponent: sharedComponent,
20
+ },
21
+ },
22
+ },
23
+ state: {
24
+ matches: [{ routeId: 'root' }, { routeId: 'child' }],
25
+ },
26
+ } as never);
27
+
28
+ expect(sharedComponent.preload).toHaveBeenCalledTimes(1);
29
+ });
30
+ });
@@ -1,19 +1,172 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { promisify } from 'node:util';
6
+ import { isRedirect } from '@tanstack/react-router';
7
+
8
+ const resolverDelays = new Map<string, number>();
9
+
10
+ rstest.mock('../../src/cli/tanstackTypes/shared', () => {
11
+ const actual = rstest.requireActual(
12
+ '../../src/cli/tanstackTypes/shared',
13
+ ) as typeof import('../../src/cli/tanstackTypes/shared');
14
+
15
+ return {
16
+ ...actual,
17
+ resolveFileNoExt: async (inputNoExtPath: string) => {
18
+ const normalized = inputNoExtPath.replaceAll('\\', '/');
19
+ const delay =
20
+ [...resolverDelays.entries()].find(([suffix]) =>
21
+ normalized.endsWith(suffix),
22
+ )?.[1] ?? 0;
23
+
24
+ if (delay > 0) {
25
+ await new Promise(resolve => setTimeout(resolve, delay));
26
+ }
27
+
28
+ return actual.resolveFileNoExt(inputNoExtPath);
29
+ },
30
+ };
31
+ });
32
+
6
33
  import {
7
34
  collectCanonicalRoutesForEntry,
8
35
  generateTanstackRouterTypesSourceForEntry,
9
36
  } from '../../src/cli/tanstackTypes';
37
+ import { throwTanstackRedirect } from '../../src/runtime/loaderBridge';
10
38
 
11
39
  const execFileAsync = promisify(execFile);
40
+ const routerGenGoldenPath = path.join(
41
+ __dirname,
42
+ 'fixtures',
43
+ 'router-gen.golden.txt',
44
+ );
45
+ const routerGenOxlintConfigPath = path.join(
46
+ __dirname,
47
+ 'fixtures',
48
+ 'router-gen.oxlint.json',
49
+ );
50
+ const oxlintCliPath = path.resolve(
51
+ __dirname,
52
+ '../../../../toolkit/code-tools/node_modules/oxlint/bin/oxlint',
53
+ );
54
+
55
+ type RedirectLike = {
56
+ options?: {
57
+ href?: string;
58
+ to?: string;
59
+ };
60
+ };
61
+
62
+ function catchThrown(fn: () => unknown): unknown {
63
+ try {
64
+ fn();
65
+ } catch (err) {
66
+ return err;
67
+ }
68
+ throw new Error('expected function to throw');
69
+ }
70
+
71
+ function extractRouteGenerationOrder(source: string) {
72
+ return source.split('\n').filter(line => {
73
+ return (
74
+ line.startsWith('import component_') ||
75
+ line.startsWith('const route_') ||
76
+ line.includes('component: component_') ||
77
+ line.startsWith('export const routeTree')
78
+ );
79
+ });
80
+ }
81
+
82
+ async function writeGoldenRouterFixture(srcDirectory: string) {
83
+ const files = new Map([
84
+ [
85
+ 'routes/(app)/layout.tsx',
86
+ 'export default function AppLayout() { return null; }',
87
+ ],
88
+ [
89
+ 'routes/(app)/users/(userId)/page.tsx',
90
+ 'export default function UserPage() { return null; }',
91
+ ],
92
+ [
93
+ 'routes/(app)/users/(userId)/page.data.ts',
94
+ [
95
+ 'export const loader = () => ({ userId: "42" });',
96
+ 'export const action = () => Response.json({ ok: true });',
97
+ ].join('\n'),
98
+ ],
99
+ [
100
+ 'routes/(app)/docs/splat.tsx',
101
+ 'export default function DocsSplatPage() { return null; }',
102
+ ],
103
+ [
104
+ 'routes/search.contract.ts',
105
+ [
106
+ 'export const validateSearch = (search: { tab?: string }) => ({ tab: search.tab ?? "overview" });',
107
+ 'export const loaderDeps = ({ search }: { search: { tab: string } }) => ({ tab: search.tab });',
108
+ ].join('\n'),
109
+ ],
110
+ ]);
111
+
112
+ for (const [relativePath, contents] of files) {
113
+ const filePath = path.join(srcDirectory, relativePath);
114
+ await mkdir(path.dirname(filePath), { recursive: true });
115
+ await writeFile(filePath, contents);
116
+ }
117
+ }
118
+
119
+ async function generateGoldenRouterGen(srcDirectory: string) {
120
+ await writeGoldenRouterFixture(srcDirectory);
121
+
122
+ const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
123
+ appContext: {
124
+ srcDirectory,
125
+ internalSrcAlias: '@/_',
126
+ } as any,
127
+ entryName: 'golden',
128
+ routes: [
129
+ {
130
+ type: 'nested',
131
+ id: 'layout',
132
+ isRoot: true,
133
+ children: [
134
+ {
135
+ type: 'nested',
136
+ id: '(app)/layout',
137
+ _component: '@/_/routes/(app)/layout',
138
+ children: [
139
+ {
140
+ type: 'nested',
141
+ id: '(app)/users/(userId)/page',
142
+ path: 'users/:userId',
143
+ _component: '@/_/routes/(app)/users/(userId)/page',
144
+ data: '@/_/routes/(app)/users/(userId)/page.data',
145
+ action: '@/_/routes/(app)/users/(userId)/page.data',
146
+ validateSearch: '@/_/routes/search.contract',
147
+ loaderDeps: '@/_/routes/search.contract',
148
+ },
149
+ {
150
+ type: 'nested',
151
+ id: '(app)/docs/splat/page',
152
+ path: 'docs/*',
153
+ _component: '@/_/routes/(app)/docs/splat',
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ },
159
+ ] as any,
160
+ });
161
+
162
+ return routerGenTs;
163
+ }
12
164
 
13
165
  describe('tanstack router type generation', () => {
14
166
  let tempDir: string | undefined;
15
167
 
16
168
  afterEach(async () => {
169
+ resolverDelays.clear();
17
170
  if (tempDir) {
18
171
  await rm(tempDir, { recursive: true, force: true });
19
172
  tempDir = undefined;
@@ -77,6 +230,15 @@ describe('tanstack router type generation', () => {
77
230
  expect(routerGenTs).not.toContain('function modernLoaderToTanstack');
78
231
  expect(routerGenTs).not.toContain('function createRouteStaticData');
79
232
  expect(routerGenTs).not.toContain('function throwTanstackRedirect');
233
+ expect(routerGenTs).not.toContain('new URL(target)');
234
+ expect(routerGenTs).not.toContain('redirect({ to: target })');
235
+
236
+ const redirectError = catchThrown(() =>
237
+ throwTanstackRedirect('https://example.com/absolute'),
238
+ ) as RedirectLike;
239
+ expect(isRedirect(redirectError)).toBe(true);
240
+ expect(redirectError.options?.href).toBe('https://example.com/absolute');
241
+ expect(redirectError.options?.to).toBeUndefined();
80
242
  });
81
243
 
82
244
  test('emits resolvable relative component imports for routes carrying _component', async () => {
@@ -167,6 +329,141 @@ describe('tanstack router type generation', () => {
167
329
  expect(dataOnlyRoute).not.toContain('component:');
168
330
  });
169
331
 
332
+ test('keeps generated source stable when route module resolution races', async () => {
333
+ tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
334
+ const srcDirectory = path.join(tempDir, 'src');
335
+
336
+ for (const componentFile of [
337
+ 'routes/slow-page.tsx',
338
+ 'routes/fast-page.tsx',
339
+ 'routes/shared-page.tsx',
340
+ ]) {
341
+ const componentPath = path.join(srcDirectory, componentFile);
342
+ await mkdir(path.dirname(componentPath), { recursive: true });
343
+ await writeFile(
344
+ componentPath,
345
+ 'export default function Page() { return null; }',
346
+ );
347
+ }
348
+
349
+ const routes = [
350
+ {
351
+ type: 'nested',
352
+ id: 'layout',
353
+ isRoot: true,
354
+ children: [
355
+ {
356
+ type: 'nested',
357
+ id: 'slow/page',
358
+ path: 'slow',
359
+ _component: '@/_/routes/slow-page',
360
+ },
361
+ {
362
+ type: 'nested',
363
+ id: 'fast/page',
364
+ path: 'fast',
365
+ _component: '@/_/routes/fast-page',
366
+ },
367
+ {
368
+ type: 'nested',
369
+ id: 'shared-a/page',
370
+ path: 'shared-a',
371
+ _component: '@/_/routes/shared-page',
372
+ },
373
+ {
374
+ type: 'nested',
375
+ id: 'shared-b/page',
376
+ path: 'shared-b',
377
+ _component: '@/_/routes/shared-page',
378
+ },
379
+ ],
380
+ },
381
+ ] as any;
382
+
383
+ const generateWithResolverDelays = async (
384
+ delays: Record<string, number>,
385
+ ) => {
386
+ resolverDelays.clear();
387
+ for (const [suffix, delay] of Object.entries(delays)) {
388
+ resolverDelays.set(suffix, delay);
389
+ }
390
+
391
+ const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
392
+ appContext: {
393
+ srcDirectory,
394
+ internalSrcAlias: '@/_',
395
+ } as any,
396
+ entryName: 'index',
397
+ routes,
398
+ });
399
+
400
+ return routerGenTs;
401
+ };
402
+
403
+ const sequentialOutput = await generateWithResolverDelays({});
404
+ const racedOutput = await generateWithResolverDelays({
405
+ '/routes/slow-page': 25,
406
+ });
407
+
408
+ expect(
409
+ racedOutput.match(/from "\.\.\/\.\.\/routes\/shared-page";/g),
410
+ ).toHaveLength(1);
411
+ expect(extractRouteGenerationOrder(racedOutput)).toEqual(
412
+ extractRouteGenerationOrder(sequentialOutput),
413
+ );
414
+ expect(racedOutput).toBe(sequentialOutput);
415
+ });
416
+
417
+ test('matches checked-in router.gen golden output', async () => {
418
+ tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
419
+ const srcDirectory = path.join(tempDir, 'src');
420
+ const routerGenTs = await generateGoldenRouterGen(srcDirectory);
421
+
422
+ if (process.env.UPDATE_TANSTACK_ROUTER_GOLDEN === '1') {
423
+ await mkdir(path.dirname(routerGenGoldenPath), { recursive: true });
424
+ await writeFile(routerGenGoldenPath, routerGenTs);
425
+ }
426
+
427
+ await expect(readFile(routerGenGoldenPath, 'utf8')).resolves.toBe(
428
+ routerGenTs,
429
+ );
430
+ });
431
+
432
+ test('emits suppression-free router source that passes the generated-artifact lint fixture', async () => {
433
+ tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
434
+ const srcDirectory = path.join(tempDir, 'src');
435
+ const routerGenTs = await generateGoldenRouterGen(srcDirectory);
436
+ const routerGenPath = path.join(
437
+ srcDirectory,
438
+ 'modern-tanstack',
439
+ 'golden',
440
+ 'router.gen.ts',
441
+ );
442
+
443
+ await mkdir(path.dirname(routerGenPath), { recursive: true });
444
+ await writeFile(routerGenPath, routerGenTs);
445
+
446
+ expect(routerGenTs).not.toContain('eslint-disable');
447
+
448
+ const { stderr, stdout } = await execFileAsync(
449
+ process.execPath,
450
+ [
451
+ oxlintCliPath,
452
+ routerGenPath,
453
+ '--config',
454
+ routerGenOxlintConfigPath,
455
+ '--no-ignore',
456
+ '--report-unused-disable-directives-severity',
457
+ 'error',
458
+ '--format',
459
+ 'unix',
460
+ ],
461
+ { cwd: tempDir },
462
+ );
463
+
464
+ expect(`${stdout}${stderr}`).toBe('');
465
+ });
466
+
170
467
  test('typechecks generated TanStack search contracts', async () => {
171
468
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
172
469
  const srcDirectory = path.join(tempDir, 'src');
@@ -1,14 +0,0 @@
1
- import { getGlobalLayoutApp, getGlobalRoutes } from "@modern-js/runtime/context";
2
- import { merge } from "@modern-js/runtime-utils/merge";
3
- function getMergedRouterConfig(api, userConfig) {
4
- const pluginConfig = api.getRuntimeConfig();
5
- return merge(pluginConfig.router || {}, userConfig);
6
- }
7
- function getFinalRouteConfig(mergedConfig) {
8
- return {
9
- routes: getGlobalRoutes(),
10
- globalApp: getGlobalLayoutApp(),
11
- ...mergedConfig.routesConfig
12
- };
13
- }
14
- export { getFinalRouteConfig, getMergedRouterConfig };
@@ -1,15 +0,0 @@
1
- import "node:module";
2
- import { getGlobalLayoutApp, getGlobalRoutes } from "@modern-js/runtime/context";
3
- import { merge } from "@modern-js/runtime-utils/merge";
4
- function getMergedRouterConfig(api, userConfig) {
5
- const pluginConfig = api.getRuntimeConfig();
6
- return merge(pluginConfig.router || {}, userConfig);
7
- }
8
- function getFinalRouteConfig(mergedConfig) {
9
- return {
10
- routes: getGlobalRoutes(),
11
- globalApp: getGlobalLayoutApp(),
12
- ...mergedConfig.routesConfig
13
- };
14
- }
15
- export { getFinalRouteConfig, getMergedRouterConfig };
@@ -1,21 +0,0 @@
1
- import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
2
- import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
3
- import { type TInternalRuntimeContext } from '@modern-js/runtime/context';
4
- import type { RouterExtendsHooks } from './hooks';
5
- import type { RouterConfig } from './types';
6
- export type TanstackRouterRuntimeConfig = {
7
- plugins?: TanstackRouterRuntimePlugin[];
8
- router?: Partial<RouterConfig>;
9
- [key: string]: unknown;
10
- };
11
- type TanstackRouterRuntimeExtends = Required<RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>> & {
12
- extendHooks: RouterExtendsHooks;
13
- };
14
- export type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
15
- export type TanstackRouterRuntimePlugin = Plugin<TanstackRouterPluginAPI, TInternalRuntimeContext>;
16
- export declare function getMergedRouterConfig(api: TanstackRouterPluginAPI, userConfig: Partial<RouterConfig>): RouterConfig;
17
- export declare function getFinalRouteConfig(mergedConfig: RouterConfig): {
18
- routes: (import("@modern-js/types").NestedRoute<string | (() => JSX.Element)> | import("@modern-js/types").PageRoute)[] | undefined;
19
- globalApp: import("react").ComponentClass<{}, any> | import("react").FunctionComponent<{}> | undefined;
20
- };
21
- export {};
@@ -1,48 +0,0 @@
1
- import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
2
- import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
3
- import {
4
- getGlobalLayoutApp,
5
- getGlobalRoutes,
6
- type TInternalRuntimeContext,
7
- } from '@modern-js/runtime/context';
8
- import { merge } from '@modern-js/runtime-utils/merge';
9
- import type { RouterExtendsHooks } from './hooks';
10
- import type { RouterConfig } from './types';
11
-
12
- export type TanstackRouterRuntimeConfig = {
13
- plugins?: TanstackRouterRuntimePlugin[];
14
- router?: Partial<RouterConfig>;
15
- [key: string]: unknown;
16
- };
17
-
18
- type TanstackRouterRuntimeExtends = Required<
19
- RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>
20
- > & {
21
- extendHooks: RouterExtendsHooks;
22
- };
23
-
24
- export type TanstackRouterPluginAPI =
25
- RuntimePluginAPI<TanstackRouterRuntimeExtends>;
26
-
27
- export type TanstackRouterRuntimePlugin = Plugin<
28
- TanstackRouterPluginAPI,
29
- TInternalRuntimeContext
30
- >;
31
-
32
- export function getMergedRouterConfig(
33
- api: TanstackRouterPluginAPI,
34
- userConfig: Partial<RouterConfig>,
35
- ): RouterConfig {
36
- const pluginConfig = api.getRuntimeConfig() as {
37
- router?: Partial<RouterConfig>;
38
- };
39
- return merge(pluginConfig.router || {}, userConfig) as RouterConfig;
40
- }
41
-
42
- export function getFinalRouteConfig(mergedConfig: RouterConfig) {
43
- return {
44
- routes: getGlobalRoutes(),
45
- globalApp: getGlobalLayoutApp(),
46
- ...mergedConfig.routesConfig,
47
- };
48
- }