@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.4 → 3.5.0-ultramodern.41

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/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 +256 -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 +117 -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 +1 -47
  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/rsc/CompositeComponent.js +18 -13
  28. package/dist/cjs/runtime/rsc/ReplayableStream.js +0 -4
  29. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +5 -16
  30. package/dist/cjs/runtime/rsc/client.js +8 -25
  31. package/dist/cjs/runtime/rsc/flightSerialization.js +132 -0
  32. package/dist/cjs/runtime/rsc/payloadErrors.js +99 -0
  33. package/dist/cjs/runtime/rsc/payloadFetch.js +105 -0
  34. package/dist/cjs/runtime/rsc/payloadRouter.js +21 -142
  35. package/dist/cjs/runtime/rsc/payloadRoutes.js +94 -0
  36. package/dist/cjs/runtime/rsc/payloadStorage.js +58 -0
  37. package/dist/cjs/runtime/rsc/server.js +14 -18
  38. package/dist/cjs/runtime/rsc/shared.js +68 -0
  39. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +1 -0
  40. package/dist/cjs/runtime/rsc/treeGetter.js +72 -0
  41. package/dist/cjs/runtime/shared/isAbsoluteUrl.js +47 -0
  42. package/dist/cjs/runtime/ssrManagedTags.js +103 -0
  43. package/dist/cjs/runtime/ssrPreload.js +89 -0
  44. package/dist/cjs/runtime/ssrTypes.js +18 -0
  45. package/dist/cjs/runtime/state.js +1 -1
  46. package/dist/cjs/runtime/submitAction.js +161 -0
  47. package/dist/cjs/runtime/utils.js +49 -86
  48. package/dist/esm/cli/artifacts.mjs +103 -0
  49. package/dist/esm/cli/index.mjs +8 -82
  50. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +75 -0
  51. package/dist/esm/cli/tanstackTypes/shared.mjs +61 -0
  52. package/dist/esm/cli/tanstackTypes/source.mjs +208 -0
  53. package/dist/esm/cli/tanstackTypes.mjs +2 -339
  54. package/dist/esm/runtime/blockingSubscribe.mjs +19 -0
  55. package/dist/esm/runtime/clientBasepath.mjs +10 -0
  56. package/dist/esm/runtime/clientHydration.mjs +70 -0
  57. package/dist/esm/runtime/dataMutation.mjs +2 -167
  58. package/dist/esm/runtime/formData.mjs +63 -0
  59. package/dist/esm/runtime/loaderBridge.mjs +5 -9
  60. package/dist/esm/runtime/plugin.mjs +14 -118
  61. package/dist/esm/runtime/plugin.node.mjs +41 -121
  62. package/dist/esm/runtime/pluginShared.mjs +40 -0
  63. package/dist/esm/runtime/prefetchLink.mjs +18 -5
  64. package/dist/esm/runtime/register.mjs +0 -14
  65. package/dist/esm/runtime/routeTree/components.mjs +55 -0
  66. package/dist/esm/runtime/routeTree/construction.mjs +111 -0
  67. package/dist/esm/runtime/routeTree/index.mjs +2 -0
  68. package/dist/esm/runtime/routeTree/loaders.mjs +117 -0
  69. package/dist/esm/runtime/routeTree/paths.mjs +16 -0
  70. package/dist/esm/runtime/routeTree/staticData.mjs +22 -0
  71. package/dist/esm/runtime/routeTree/tanstackRoutes.mjs +13 -0
  72. package/dist/esm/runtime/routeTree/types.mjs +0 -0
  73. package/dist/esm/runtime/routeTree.mjs +1 -318
  74. package/dist/esm/runtime/rsc/CompositeComponent.mjs +18 -13
  75. package/dist/esm/runtime/rsc/ReplayableStream.mjs +1 -1
  76. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +4 -15
  77. package/dist/esm/runtime/rsc/client.mjs +6 -26
  78. package/dist/esm/runtime/rsc/flightSerialization.mjs +88 -0
  79. package/dist/esm/runtime/rsc/payloadErrors.mjs +49 -0
  80. package/dist/esm/runtime/rsc/payloadFetch.mjs +55 -0
  81. package/dist/esm/runtime/rsc/payloadRouter.mjs +12 -133
  82. package/dist/esm/runtime/rsc/payloadRoutes.mjs +50 -0
  83. package/dist/esm/runtime/rsc/payloadStorage.mjs +14 -0
  84. package/dist/esm/runtime/rsc/server.mjs +17 -21
  85. package/dist/esm/runtime/rsc/shared.mjs +21 -0
  86. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  87. package/dist/esm/runtime/rsc/treeGetter.mjs +34 -0
  88. package/dist/esm/runtime/shared/isAbsoluteUrl.mjs +9 -0
  89. package/dist/esm/runtime/ssrManagedTags.mjs +53 -0
  90. package/dist/esm/runtime/ssrPreload.mjs +51 -0
  91. package/dist/esm/runtime/ssrTypes.mjs +0 -0
  92. package/dist/esm/runtime/state.mjs +1 -1
  93. package/dist/esm/runtime/submitAction.mjs +120 -0
  94. package/dist/esm/runtime/utils.mjs +47 -75
  95. package/dist/esm-node/cli/artifacts.mjs +104 -0
  96. package/dist/esm-node/cli/index.mjs +8 -82
  97. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +76 -0
  98. package/dist/esm-node/cli/tanstackTypes/shared.mjs +62 -0
  99. package/dist/esm-node/cli/tanstackTypes/source.mjs +209 -0
  100. package/dist/esm-node/cli/tanstackTypes.mjs +2 -339
  101. package/dist/esm-node/runtime/blockingSubscribe.mjs +20 -0
  102. package/dist/esm-node/runtime/clientBasepath.mjs +11 -0
  103. package/dist/esm-node/runtime/clientHydration.mjs +71 -0
  104. package/dist/esm-node/runtime/dataMutation.mjs +2 -167
  105. package/dist/esm-node/runtime/formData.mjs +64 -0
  106. package/dist/esm-node/runtime/loaderBridge.mjs +5 -9
  107. package/dist/esm-node/runtime/plugin.mjs +14 -118
  108. package/dist/esm-node/runtime/plugin.node.mjs +41 -121
  109. package/dist/esm-node/runtime/pluginShared.mjs +41 -0
  110. package/dist/esm-node/runtime/prefetchLink.mjs +18 -5
  111. package/dist/esm-node/runtime/register.mjs +0 -14
  112. package/dist/esm-node/runtime/routeTree/components.mjs +56 -0
  113. package/dist/esm-node/runtime/routeTree/construction.mjs +112 -0
  114. package/dist/esm-node/runtime/routeTree/index.mjs +3 -0
  115. package/dist/esm-node/runtime/routeTree/loaders.mjs +118 -0
  116. package/dist/esm-node/runtime/routeTree/paths.mjs +17 -0
  117. package/dist/esm-node/runtime/routeTree/staticData.mjs +23 -0
  118. package/dist/esm-node/runtime/routeTree/tanstackRoutes.mjs +14 -0
  119. package/dist/esm-node/runtime/routeTree/types.mjs +1 -0
  120. package/dist/esm-node/runtime/routeTree.mjs +1 -318
  121. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +18 -13
  122. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +1 -1
  123. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +4 -15
  124. package/dist/esm-node/runtime/rsc/client.mjs +6 -26
  125. package/dist/esm-node/runtime/rsc/flightSerialization.mjs +89 -0
  126. package/dist/esm-node/runtime/rsc/payloadErrors.mjs +50 -0
  127. package/dist/esm-node/runtime/rsc/payloadFetch.mjs +56 -0
  128. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +12 -133
  129. package/dist/esm-node/runtime/rsc/payloadRoutes.mjs +51 -0
  130. package/dist/esm-node/runtime/rsc/payloadStorage.mjs +15 -0
  131. package/dist/esm-node/runtime/rsc/server.mjs +17 -21
  132. package/dist/esm-node/runtime/rsc/shared.mjs +22 -0
  133. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  134. package/dist/esm-node/runtime/rsc/treeGetter.mjs +35 -0
  135. package/dist/esm-node/runtime/shared/isAbsoluteUrl.mjs +10 -0
  136. package/dist/esm-node/runtime/ssrManagedTags.mjs +54 -0
  137. package/dist/esm-node/runtime/ssrPreload.mjs +52 -0
  138. package/dist/esm-node/runtime/ssrTypes.mjs +1 -0
  139. package/dist/esm-node/runtime/state.mjs +1 -1
  140. package/dist/esm-node/runtime/submitAction.mjs +121 -0
  141. package/dist/esm-node/runtime/utils.mjs +47 -75
  142. package/dist/types/cli/artifacts.d.ts +33 -0
  143. package/dist/types/cli/index.d.ts +2 -22
  144. package/dist/types/cli/tanstackTypes/canonicalRoutes.d.ts +22 -0
  145. package/dist/types/cli/tanstackTypes/shared.d.ts +20 -0
  146. package/dist/types/cli/tanstackTypes/source.d.ts +12 -0
  147. package/dist/types/cli/tanstackTypes.d.ts +3 -31
  148. package/dist/types/runtime/blockingSubscribe.d.ts +1 -0
  149. package/dist/types/runtime/clientBasepath.d.ts +1 -0
  150. package/dist/types/runtime/clientHydration.d.ts +10 -0
  151. package/dist/types/runtime/dataMutation.d.ts +3 -12
  152. package/dist/types/runtime/formData.d.ts +24 -0
  153. package/dist/types/runtime/loaderBridge.d.ts +2 -1
  154. package/dist/types/runtime/plugin.d.ts +1 -1
  155. package/dist/types/runtime/plugin.node.d.ts +1 -1
  156. package/dist/types/runtime/pluginShared.d.ts +38 -0
  157. package/dist/types/runtime/register.d.ts +1 -9
  158. package/dist/types/runtime/routeTree/components.d.ts +7 -0
  159. package/dist/types/runtime/routeTree/construction.d.ts +5 -0
  160. package/dist/types/runtime/routeTree/index.d.ts +2 -0
  161. package/dist/types/runtime/routeTree/loaders.d.ts +4 -0
  162. package/dist/types/runtime/routeTree/paths.d.ts +3 -0
  163. package/dist/types/runtime/routeTree/staticData.d.ts +19 -0
  164. package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +4 -0
  165. package/dist/types/runtime/routeTree/types.d.ts +76 -0
  166. package/dist/types/runtime/routeTree.d.ts +1 -9
  167. package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -2
  168. package/dist/types/runtime/rsc/ReplayableStream.d.ts +3 -2
  169. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +2 -3
  170. package/dist/types/runtime/rsc/SlotContext.d.ts +2 -1
  171. package/dist/types/runtime/rsc/client.d.ts +2 -0
  172. package/dist/types/runtime/rsc/flightSerialization.d.ts +3 -0
  173. package/dist/types/runtime/rsc/payloadErrors.d.ts +6 -0
  174. package/dist/types/runtime/rsc/payloadFetch.d.ts +8 -0
  175. package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -10
  176. package/dist/types/runtime/rsc/payloadRoutes.d.ts +40 -0
  177. package/dist/types/runtime/rsc/payloadStorage.d.ts +5 -0
  178. package/dist/types/runtime/rsc/shared.d.ts +18 -0
  179. package/dist/types/runtime/rsc/treeGetter.d.ts +1 -0
  180. package/dist/types/runtime/shared/isAbsoluteUrl.d.ts +1 -0
  181. package/dist/types/runtime/ssrManagedTags.d.ts +7 -0
  182. package/dist/types/runtime/ssrPreload.d.ts +2 -0
  183. package/dist/types/runtime/ssrTypes.d.ts +44 -0
  184. package/dist/types/runtime/state.d.ts +1 -1
  185. package/dist/types/runtime/submitAction.d.ts +14 -0
  186. package/dist/types/runtime/types.d.ts +4 -10
  187. package/dist/types/runtime/utils.d.ts +4 -24
  188. package/package.json +35 -14
  189. package/rstest.config.mts +6 -0
  190. package/src/cli/artifacts.ts +255 -0
  191. package/src/cli/index.ts +7 -185
  192. package/src/cli/tanstackTypes/canonicalRoutes.ts +157 -0
  193. package/src/cli/tanstackTypes/shared.ts +113 -0
  194. package/src/cli/tanstackTypes/source.ts +415 -0
  195. package/src/cli/tanstackTypes.ts +4 -647
  196. package/src/runtime/blockingSubscribe.ts +48 -0
  197. package/src/runtime/clientBasepath.ts +12 -0
  198. package/src/runtime/clientHydration.ts +146 -0
  199. package/src/runtime/dataMutation.tsx +9 -306
  200. package/src/runtime/formData.ts +153 -0
  201. package/src/runtime/loaderBridge.ts +11 -12
  202. package/src/runtime/plugin.node.tsx +76 -291
  203. package/src/runtime/plugin.tsx +21 -257
  204. package/src/runtime/pluginShared.ts +112 -0
  205. package/src/runtime/prefetchLink.tsx +78 -15
  206. package/src/runtime/register.ts +5 -37
  207. package/src/runtime/routeTree/components.ts +130 -0
  208. package/src/runtime/routeTree/construction.ts +238 -0
  209. package/src/runtime/routeTree/index.ts +7 -0
  210. package/src/runtime/routeTree/loaders.ts +232 -0
  211. package/src/runtime/routeTree/paths.ts +36 -0
  212. package/src/runtime/routeTree/staticData.ts +66 -0
  213. package/src/runtime/routeTree/tanstackRoutes.ts +36 -0
  214. package/src/runtime/routeTree/types.ts +87 -0
  215. package/src/runtime/routeTree.ts +5 -750
  216. package/src/runtime/rsc/CompositeComponent.tsx +29 -20
  217. package/src/runtime/rsc/ReplayableStream.ts +2 -2
  218. package/src/runtime/rsc/RscNodeRenderer.tsx +9 -27
  219. package/src/runtime/rsc/SlotContext.tsx +1 -1
  220. package/src/runtime/rsc/client.tsx +12 -40
  221. package/src/runtime/rsc/flightSerialization.ts +158 -0
  222. package/src/runtime/rsc/payloadErrors.ts +73 -0
  223. package/src/runtime/rsc/payloadFetch.ts +92 -0
  224. package/src/runtime/rsc/payloadRouter.ts +29 -234
  225. package/src/runtime/rsc/payloadRoutes.ts +122 -0
  226. package/src/runtime/rsc/payloadStorage.ts +35 -0
  227. package/src/runtime/rsc/server.tsx +20 -29
  228. package/src/runtime/rsc/shared.ts +70 -0
  229. package/src/runtime/rsc/slotUsageSanitizer.ts +4 -0
  230. package/src/runtime/rsc/treeGetter.tsx +52 -0
  231. package/src/runtime/shared/isAbsoluteUrl.ts +8 -0
  232. package/src/runtime/ssrManagedTags.ts +107 -0
  233. package/src/runtime/ssrPreload.ts +125 -0
  234. package/src/runtime/ssrTypes.ts +51 -0
  235. package/src/runtime/state.ts +9 -3
  236. package/src/runtime/submitAction.ts +201 -0
  237. package/src/runtime/types.ts +4 -18
  238. package/src/runtime/utils.tsx +76 -110
  239. package/src/types/react-server-dom-rspack.d.ts +56 -0
  240. package/tests/castRatchet.test.ts +85 -0
  241. package/tests/router/cli.test.ts +7 -0
  242. package/tests/router/dataMutation.test.tsx +59 -0
  243. package/tests/router/fixtures/router-gen.golden.txt +72 -0
  244. package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
  245. package/tests/router/generateRouteArtifacts.test.ts +106 -0
  246. package/tests/router/hooks.test.ts +14 -0
  247. package/tests/router/loaderBridge.test.ts +21 -0
  248. package/tests/router/prefetchLink.test.tsx +106 -59
  249. package/tests/router/prefetchLinkPreload.test.tsx +85 -0
  250. package/tests/router/register.test.ts +1 -39
  251. package/tests/router/routeTree.test.ts +51 -11
  252. package/tests/router/rsc.test.tsx +189 -0
  253. package/tests/router/rscPayloadRouterMatrix.test.ts +197 -0
  254. package/tests/router/slotUsageSanitizer.test.ts +23 -0
  255. package/tests/router/ssrPreload.test.ts +30 -0
  256. package/tests/router/tanstackTypes.test.ts +254 -1
  257. package/dist/esm/runtime/pluginCore.mjs +0 -14
  258. package/dist/esm-node/runtime/pluginCore.mjs +0 -15
  259. package/dist/types/runtime/pluginCore.d.ts +0 -21
  260. package/src/runtime/pluginCore.ts +0 -48
@@ -2,6 +2,10 @@ import { UNSAFE_ErrorResponseImpl as ErrorResponseImpl } from '@modern-js/runtim
2
2
  import type React from 'react';
3
3
  import { isValidElement } from 'react';
4
4
  import { createRscProxy } from '../../src/runtime/rsc/createRscProxy';
5
+ import {
6
+ reviveTanstackRscFlightValues,
7
+ serializeTanstackRscFlightValues,
8
+ } from '../../src/runtime/rsc/flightSerialization';
5
9
  import {
6
10
  __setTanstackRscPayloadDecoderForTests,
7
11
  createTanstackRscServerPayload,
@@ -65,6 +69,54 @@ describe('tanstack rsc runtime helpers', () => {
65
69
  ]);
66
70
  });
67
71
 
72
+ test('revives shared structures that the Flight serializer preserves', async () => {
73
+ const shared = { label: 'shared' };
74
+ const serialized = serializeTanstackRscFlightValues({
75
+ left: shared,
76
+ right: shared,
77
+ }) as { left: unknown; right: unknown };
78
+
79
+ const revived = (await reviveTanstackRscFlightValues(serialized)) as {
80
+ left: unknown;
81
+ right: unknown;
82
+ };
83
+
84
+ expect(serialized.left).toBe(serialized.right);
85
+ expect(revived.left).toBe(revived.right);
86
+ });
87
+
88
+ test('serializes TanStack RSC flight values nested in map and set containers', () => {
89
+ const stream: ServerComponentStream = {
90
+ createReplayStream: () => new ReadableStream<Uint8Array>(),
91
+ };
92
+ const proxy = createRscProxy(() => null, {
93
+ renderable: true,
94
+ stream,
95
+ }) as React.ReactElement & Record<PropertyKey, unknown>;
96
+
97
+ const serialized = serializeTanstackRscFlightValues({
98
+ map: new Map([['slot', proxy]]),
99
+ set: new Set([proxy]),
100
+ }) as {
101
+ map: Map<string, Record<string, unknown>>;
102
+ set: Set<Record<string, unknown>>;
103
+ };
104
+
105
+ const mapValue = serialized.map.get('slot');
106
+ const [setValue] = serialized.set;
107
+
108
+ expect(mapValue).not.toBe(proxy);
109
+ expect(setValue).not.toBe(proxy);
110
+ expect(mapValue).toMatchObject({
111
+ __modernTanstackRsc: true,
112
+ kind: 'renderable',
113
+ });
114
+ expect(setValue).toMatchObject({
115
+ __modernTanstackRsc: true,
116
+ kind: 'renderable',
117
+ });
118
+ });
119
+
68
120
  test('renderable RSC proxies preserve React element behavior and metadata', () => {
69
121
  const stream: ServerComponentStream = {
70
122
  createReplayStream: () => new ReadableStream<Uint8Array>(),
@@ -219,6 +271,36 @@ describe('tanstack rsc runtime helpers', () => {
219
271
  expect(JSON.stringify(payload.errors)).not.toContain('stack secret');
220
272
  });
221
273
 
274
+ test('redacts production TanStack RSC non-Error server payload errors', () => {
275
+ const payload = withNodeEnv('production', () =>
276
+ createTanstackRscServerPayload({
277
+ state: {
278
+ location: { href: '/plain' },
279
+ matches: [
280
+ {
281
+ error: { message: 'plain secret', token: 'token secret' },
282
+ params: {},
283
+ pathname: '/plain',
284
+ pathnameBase: '/plain',
285
+ route: { id: '/plain' },
286
+ routeId: '/plain',
287
+ },
288
+ ],
289
+ },
290
+ }),
291
+ );
292
+
293
+ expect(payload.errors).toMatchObject({
294
+ '/plain': {
295
+ message: 'Unexpected Server Error',
296
+ stack: undefined,
297
+ __type: 'Error',
298
+ },
299
+ });
300
+ expect(JSON.stringify(payload.errors)).not.toContain('plain secret');
301
+ expect(JSON.stringify(payload.errors)).not.toContain('token secret');
302
+ });
303
+
222
304
  test('converts TanStack RSC redirects to Modern RSC navigation headers', () => {
223
305
  const response = handleTanstackRscRedirect(
224
306
  new Headers({ Location: '/base/login' }),
@@ -232,6 +314,54 @@ describe('tanstack rsc runtime helpers', () => {
232
314
  expect(response.headers.get('X-Modernjs-BaseUrl')).toBe('/base');
233
315
  });
234
316
 
317
+ test('preserves TanStack RSC redirect paths that do not start with the basename', () => {
318
+ const response = handleTanstackRscRedirect(
319
+ new Headers({ Location: '/shop/base/login' }),
320
+ '/base',
321
+ 302,
322
+ );
323
+
324
+ expect(response.headers.get('X-Modernjs-Redirect')).toBe(
325
+ '/shop/base/login',
326
+ );
327
+ });
328
+
329
+ test('preserves RSC redirect response status and headers in TanStack redirects', async () => {
330
+ rstest.stubGlobal(
331
+ 'fetch',
332
+ rstest.fn(() =>
333
+ Promise.resolve(
334
+ new Response(null, {
335
+ headers: {
336
+ 'X-Modernjs-Redirect': '/login',
337
+ 'X-Trace': 'preserved',
338
+ },
339
+ status: 308,
340
+ }),
341
+ ),
342
+ ),
343
+ );
344
+
345
+ const thrown = (await loadTanstackRscRouteData({
346
+ loadClientData: async () => ({ fallback: true }),
347
+ request: new Request('http://localhost/products'),
348
+ routeId: '/products',
349
+ }).then(
350
+ () => {
351
+ throw new Error('expected redirect');
352
+ },
353
+ err => err,
354
+ )) as Response & {
355
+ options?: { statusCode?: number; to?: string };
356
+ };
357
+
358
+ expect(thrown).toBeInstanceOf(Response);
359
+ expect(thrown.status).toBe(308);
360
+ expect(thrown.headers.get('X-Trace')).toBe('preserved');
361
+ expect(thrown.options?.statusCode).toBe(308);
362
+ expect(thrown.options?.to).toBe('/login');
363
+ });
364
+
235
365
  test('loads one RSC payload for multiple server route loaders', async () => {
236
366
  const payload = {
237
367
  type: 'render',
@@ -275,6 +405,65 @@ describe('tanstack rsc runtime helpers', () => {
275
405
  expect(decodeMock).toHaveBeenCalledTimes(1);
276
406
  });
277
407
 
408
+ test('separates RSC payload cache entries by forwarded request headers', async () => {
409
+ const payloadByUser: Record<string, unknown> = {
410
+ alice: {
411
+ type: 'render',
412
+ actionData: null,
413
+ errors: null,
414
+ loaderData: {
415
+ '/profile': { user: 'alice' },
416
+ },
417
+ location: { href: '/profile' },
418
+ routes: [{ id: '/profile', hasLoader: true }],
419
+ },
420
+ bob: {
421
+ type: 'render',
422
+ actionData: null,
423
+ errors: null,
424
+ loaderData: {
425
+ '/profile': { user: 'bob' },
426
+ },
427
+ location: { href: '/profile' },
428
+ routes: [{ id: '/profile', hasLoader: true }],
429
+ },
430
+ };
431
+ const fetchMock = rstest.fn(
432
+ (_url: string | URL | Request, init?: RequestInit) => {
433
+ const user =
434
+ init?.headers instanceof Headers ? init.headers.get('x-user') : null;
435
+ return Promise.resolve(
436
+ new Response(JSON.stringify(payloadByUser[user || ''])),
437
+ );
438
+ },
439
+ );
440
+ rstest.stubGlobal('fetch', fetchMock);
441
+ __setTanstackRscPayloadDecoderForTests(async stream =>
442
+ JSON.parse(await new Response(stream).text()),
443
+ );
444
+
445
+ const [aliceData, bobData] = await Promise.all([
446
+ loadTanstackRscRouteData({
447
+ loadClientData: async () => ({ fallback: 'alice' }),
448
+ request: new Request('http://localhost/profile', {
449
+ headers: { 'x-user': 'alice' },
450
+ }),
451
+ routeId: '/profile',
452
+ }),
453
+ loadTanstackRscRouteData({
454
+ loadClientData: async () => ({ fallback: 'bob' }),
455
+ request: new Request('http://localhost/profile', {
456
+ headers: { 'x-user': 'bob' },
457
+ }),
458
+ routeId: '/profile',
459
+ }),
460
+ ]);
461
+
462
+ expect(aliceData).toEqual({ user: 'alice' });
463
+ expect(bobData).toEqual({ user: 'bob' });
464
+ expect(fetchMock).toHaveBeenCalledTimes(2);
465
+ });
466
+
278
467
  test('uses client loader data without requesting an RSC payload for client-loader routes', async () => {
279
468
  const fetchMock = rstest.fn();
280
469
  const loadClientData = rstest.fn(async () => ({ client: true }));
@@ -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
+ });