@tanstack/react-start-rsc 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/esm/ClientSlot.js +19 -0
  2. package/dist/esm/ClientSlot.js.map +1 -0
  3. package/dist/esm/CompositeComponent.js +93 -0
  4. package/dist/esm/CompositeComponent.js.map +1 -0
  5. package/dist/esm/ReplayableStream.js +147 -0
  6. package/dist/esm/ReplayableStream.js.map +1 -0
  7. package/dist/esm/RscNodeRenderer.js +46 -0
  8. package/dist/esm/RscNodeRenderer.js.map +1 -0
  9. package/dist/esm/ServerComponentTypes.js +22 -0
  10. package/dist/esm/ServerComponentTypes.js.map +1 -0
  11. package/dist/esm/SlotContext.js +30 -0
  12. package/dist/esm/SlotContext.js.map +1 -0
  13. package/dist/esm/awaitLazyElements.js +41 -0
  14. package/dist/esm/awaitLazyElements.js.map +1 -0
  15. package/dist/esm/createCompositeComponent.js +205 -0
  16. package/dist/esm/createCompositeComponent.js.map +1 -0
  17. package/dist/esm/createCompositeComponent.stub.js +15 -0
  18. package/dist/esm/createCompositeComponent.stub.js.map +1 -0
  19. package/dist/esm/createRscProxy.js +138 -0
  20. package/dist/esm/createRscProxy.js.map +1 -0
  21. package/dist/esm/createServerComponentFromStream.js +74 -0
  22. package/dist/esm/createServerComponentFromStream.js.map +1 -0
  23. package/dist/esm/entry/rsc.js +21 -0
  24. package/dist/esm/entry/rsc.js.map +1 -0
  25. package/dist/esm/flight.js +56 -0
  26. package/dist/esm/flight.js.map +1 -0
  27. package/dist/esm/flight.rsc.js +2 -0
  28. package/dist/esm/flight.stub.js +15 -0
  29. package/dist/esm/flight.stub.js.map +1 -0
  30. package/dist/esm/index.js +7 -0
  31. package/dist/esm/index.rsc.js +6 -0
  32. package/dist/esm/plugin/vite.js +172 -0
  33. package/dist/esm/plugin/vite.js.map +1 -0
  34. package/dist/esm/reactSymbols.js +8 -0
  35. package/dist/esm/reactSymbols.js.map +1 -0
  36. package/dist/esm/renderServerComponent.js +58 -0
  37. package/dist/esm/renderServerComponent.js.map +1 -0
  38. package/dist/esm/renderServerComponent.stub.js +16 -0
  39. package/dist/esm/renderServerComponent.stub.js.map +1 -0
  40. package/dist/esm/serialization.client.js +21 -0
  41. package/dist/esm/serialization.client.js.map +1 -0
  42. package/dist/esm/serialization.server.js +121 -0
  43. package/dist/esm/serialization.server.js.map +1 -0
  44. package/dist/esm/slotUsageSanitizer.js +33 -0
  45. package/dist/esm/slotUsageSanitizer.js.map +1 -0
  46. package/dist/esm/src/ClientSlot.d.ts +5 -0
  47. package/dist/esm/src/CompositeComponent.d.ts +28 -0
  48. package/dist/esm/src/ReplayableStream.d.ts +76 -0
  49. package/dist/esm/src/RscNodeRenderer.d.ts +7 -0
  50. package/dist/esm/src/ServerComponentTypes.d.ts +99 -0
  51. package/dist/esm/src/SlotContext.d.ts +21 -0
  52. package/dist/esm/src/awaitLazyElements.d.ts +17 -0
  53. package/dist/esm/src/createCompositeComponent.d.ts +32 -0
  54. package/dist/esm/src/createCompositeComponent.stub.d.ts +9 -0
  55. package/dist/esm/src/createRscProxy.d.ts +18 -0
  56. package/dist/esm/src/createServerComponentFromStream.d.ts +24 -0
  57. package/dist/esm/src/entry/rsc.d.ts +7 -0
  58. package/dist/esm/src/flight.d.ts +41 -0
  59. package/dist/esm/src/flight.rsc.d.ts +17 -0
  60. package/dist/esm/src/flight.stub.d.ts +8 -0
  61. package/dist/esm/src/index.d.ts +7 -0
  62. package/dist/esm/src/index.rsc.d.ts +6 -0
  63. package/dist/esm/src/plugin/vite.d.ts +9 -0
  64. package/dist/esm/src/reactSymbols.d.ts +3 -0
  65. package/dist/esm/src/renderServerComponent.d.ts +33 -0
  66. package/dist/esm/src/renderServerComponent.stub.d.ts +9 -0
  67. package/dist/esm/src/rscSsrHandler.d.ts +24 -0
  68. package/dist/esm/src/serialization.client.d.ts +11 -0
  69. package/dist/esm/src/serialization.server.d.ts +10 -0
  70. package/dist/esm/src/slotUsageSanitizer.d.ts +1 -0
  71. package/dist/esm/src/types.d.ts +13 -0
  72. package/dist/plugin/entry/rsc.tsx +23 -0
  73. package/package.json +108 -0
  74. package/src/ClientSlot.tsx +34 -0
  75. package/src/CompositeComponent.tsx +165 -0
  76. package/src/ReplayableStream.ts +249 -0
  77. package/src/RscNodeRenderer.tsx +76 -0
  78. package/src/ServerComponentTypes.ts +226 -0
  79. package/src/SlotContext.tsx +42 -0
  80. package/src/awaitLazyElements.ts +91 -0
  81. package/src/createCompositeComponent.stub.ts +20 -0
  82. package/src/createCompositeComponent.ts +338 -0
  83. package/src/createRscProxy.tsx +294 -0
  84. package/src/createServerComponentFromStream.ts +105 -0
  85. package/src/entry/rsc.tsx +23 -0
  86. package/src/entry/virtual-modules.d.ts +12 -0
  87. package/src/flight.rsc.ts +17 -0
  88. package/src/flight.stub.ts +15 -0
  89. package/src/flight.ts +68 -0
  90. package/src/global.d.ts +75 -0
  91. package/src/index.rsc.ts +25 -0
  92. package/src/index.ts +26 -0
  93. package/src/plugin/vite.ts +241 -0
  94. package/src/reactSymbols.ts +6 -0
  95. package/src/renderServerComponent.stub.ts +26 -0
  96. package/src/renderServerComponent.ts +110 -0
  97. package/src/rscSsrHandler.ts +39 -0
  98. package/src/serialization.client.ts +43 -0
  99. package/src/serialization.server.ts +193 -0
  100. package/src/slotUsageSanitizer.ts +62 -0
  101. package/src/types.ts +15 -0
@@ -0,0 +1,294 @@
1
+ 'use client'
2
+
3
+ import { createElement } from 'react'
4
+ import { RscNodeRenderer } from './RscNodeRenderer'
5
+ import {
6
+ RENDERABLE_RSC,
7
+ RSC_PROXY_GET_TREE,
8
+ RSC_PROXY_PATH,
9
+ RSC_SLOT_USAGES,
10
+ RSC_SLOT_USAGES_STREAM,
11
+ SERVER_COMPONENT_CSS_HREFS,
12
+ SERVER_COMPONENT_JS_PRELOADS,
13
+ SERVER_COMPONENT_STREAM,
14
+ } from './ServerComponentTypes'
15
+ import type { RscSlotUsageEvent } from './ServerComponentTypes'
16
+
17
+ export interface RscProxyOptions {
18
+ stream?: unknown // The stream to attach for serialization
19
+ cssHrefs?: ReadonlySet<string> // CSS hrefs collected from the RSC stream
20
+ jsPreloads?: ReadonlySet<string> // JS hrefs collected from the RSC stream
21
+ renderable?: boolean // If true, proxy masquerades as React element
22
+ slotUsagesStream?: ReadableStream<RscSlotUsageEvent> // Dev only: slot usage event stream
23
+ }
24
+
25
+ /**
26
+ * Creates a recursive Proxy for RSC data.
27
+ *
28
+ * If `renderable: true`, returns a React element that can be rendered as `{data}`.
29
+ * The element also has proxy-like behavior for nested access like `data.foo.bar`.
30
+ *
31
+ * If `renderable: false` (default), the proxy is NOT directly renderable and
32
+ * must be used with `<CompositeComponent src={...} />`.
33
+ */
34
+ export function createRscProxy<T>(
35
+ getTree: () => T,
36
+ options: RscProxyOptions = {},
37
+ ): any {
38
+ if (options.renderable) {
39
+ // For renderable mode, create a real React element with proxy access
40
+ return createRenderableElement(
41
+ getTree,
42
+ [],
43
+ options.stream,
44
+ options.cssHrefs,
45
+ options.jsPreloads,
46
+ )
47
+ }
48
+ let slotUsages: Array<RscSlotUsageEvent> | undefined = undefined
49
+
50
+ if (
51
+ process.env.NODE_ENV === 'development' &&
52
+ typeof window !== 'undefined' &&
53
+ options.slotUsagesStream
54
+ ) {
55
+ slotUsages = []
56
+ void consumeSlotUsages(options.slotUsagesStream, slotUsages)
57
+ }
58
+
59
+ // Non-renderable mode: plain proxy
60
+ return createRscProxyWithPath(
61
+ getTree,
62
+ [],
63
+ options.stream,
64
+ options.cssHrefs,
65
+ options.jsPreloads,
66
+ slotUsages,
67
+ options.slotUsagesStream,
68
+ )
69
+ }
70
+
71
+ /**
72
+ * Creates a React element that's also a Proxy for nested access.
73
+ * This is used by renderable RSCs so they work as both {data} and {data.foo.bar}.
74
+ */
75
+ type CreateProxyOptions = {
76
+ getTree: () => unknown
77
+ path: Array<string>
78
+ stream: unknown | undefined
79
+ cssHrefs: ReadonlySet<string> | undefined
80
+ jsPreloads: ReadonlySet<string> | undefined
81
+ renderable: boolean
82
+ slotUsages: Array<RscSlotUsageEvent> | undefined
83
+ slotUsagesStream: ReadableStream<RscSlotUsageEvent> | undefined
84
+ }
85
+
86
+ const UNHANDLED = Symbol('tanstack.rsc.proxy.unhandled')
87
+
88
+ function handleProxyTrap(
89
+ kind: 'get' | 'has',
90
+ prop: PropertyKey,
91
+ options: CreateProxyOptions,
92
+ ): unknown | boolean | typeof UNHANDLED {
93
+ switch (prop) {
94
+ // Seroval (>=1.5) uses string sentinels and `in` checks for internal types.
95
+ // These proxies must never look like streams/sequences.
96
+ case '__SEROVAL_STREAM__':
97
+ case '__SEROVAL_SEQUENCE__':
98
+ return kind === 'get' ? undefined : false
99
+
100
+ // Seroval >=1.5 also checks iterability via `Symbol.iterator in value`.
101
+ case Symbol.iterator:
102
+ case Symbol.asyncIterator:
103
+ return kind === 'get' ? undefined : false
104
+
105
+ // Our proxy branding/properties
106
+ case SERVER_COMPONENT_STREAM:
107
+ return kind === 'get' ? options.stream : options.stream !== undefined
108
+ case SERVER_COMPONENT_CSS_HREFS:
109
+ return kind === 'get' ? options.cssHrefs : options.cssHrefs !== undefined
110
+ case SERVER_COMPONENT_JS_PRELOADS:
111
+ return kind === 'get'
112
+ ? options.jsPreloads
113
+ : options.jsPreloads !== undefined
114
+ case RSC_PROXY_GET_TREE:
115
+ return kind === 'get' ? options.getTree : true
116
+ case RSC_PROXY_PATH:
117
+ return kind === 'get' ? options.path : true
118
+ case RENDERABLE_RSC:
119
+ return kind === 'get' ? options.renderable : true
120
+ case RSC_SLOT_USAGES:
121
+ return kind === 'get'
122
+ ? options.slotUsages
123
+ : options.slotUsages !== undefined
124
+
125
+ case RSC_SLOT_USAGES_STREAM:
126
+ return kind === 'get'
127
+ ? options.slotUsagesStream
128
+ : options.slotUsagesStream !== undefined
129
+
130
+ // Avoid promise-like checks
131
+ case 'then':
132
+ return kind === 'get' ? undefined : UNHANDLED
133
+
134
+ // Avoid breaking primitive coercion (eg String(proxy)).
135
+ // Without these, nested-selection proxies can shadow these keys and
136
+ // cause `Cannot convert object to primitive value` errors.
137
+ case 'toString':
138
+ return kind === 'get' ? Object.prototype.toString : UNHANDLED
139
+ case 'valueOf':
140
+ return kind === 'get' ? Object.prototype.valueOf : UNHANDLED
141
+ case 'constructor':
142
+ return kind === 'get' ? Object : UNHANDLED
143
+ }
144
+
145
+ // Non-renderable proxies claim all string keys exist for nested selection,
146
+ // but symbol presence checks should be accurate.
147
+ if (typeof prop === 'symbol') {
148
+ return kind === 'get' ? undefined : false
149
+ }
150
+
151
+ return UNHANDLED
152
+ }
153
+
154
+ function createRscProxyInternal(options: CreateProxyOptions): any {
155
+ // Per-proxy cache so repeated property access is referentially stable.
156
+ const childCache = new Map<string, any>()
157
+
158
+ const getChild = (key: string) => {
159
+ const cached = childCache.get(key)
160
+ if (cached) return cached
161
+
162
+ const next = createRscProxyInternal({
163
+ ...options,
164
+ path: [...options.path, key],
165
+ })
166
+ childCache.set(key, next)
167
+ return next
168
+ }
169
+
170
+ const dataProxy = options.renderable
171
+ ? createRscProxyInternal({ ...options, renderable: false })
172
+ : undefined
173
+
174
+ // Use a React element (renderable) or plain object (non-renderable) as Proxy target.
175
+ const proxyTarget = options.renderable
176
+ ? createElement(RscNodeRenderer, { data: dataProxy })
177
+ : ({} as any)
178
+
179
+ return new Proxy(proxyTarget, {
180
+ get(target, prop) {
181
+ const handled = handleProxyTrap('get', prop, options)
182
+ if (handled !== UNHANDLED) return handled
183
+
184
+ if (options.renderable) {
185
+ // Proxy invariants: if target has a non-configurable, read-only property,
186
+ // we must return the *exact* value for that property.
187
+ if (prop === 'props') {
188
+ return target.props
189
+ }
190
+
191
+ if (prop === 'data') {
192
+ return dataProxy
193
+ }
194
+
195
+ if (prop in target) {
196
+ return target[prop]
197
+ }
198
+ }
199
+
200
+ return getChild(String(prop))
201
+ },
202
+
203
+ has(target, prop) {
204
+ const handled = handleProxyTrap('has', prop, options)
205
+ if (handled !== UNHANDLED) return handled as boolean
206
+
207
+ if (options.renderable) {
208
+ if (prop in target) return true
209
+ if (typeof prop === 'string') return true
210
+ return false
211
+ }
212
+
213
+ // Allow any property access for nested selection.
214
+ return true
215
+ },
216
+
217
+ getPrototypeOf(target) {
218
+ return options.renderable
219
+ ? Object.getPrototypeOf(target)
220
+ : Object.prototype
221
+ },
222
+
223
+ getOwnPropertyDescriptor(target, prop) {
224
+ return options.renderable
225
+ ? Object.getOwnPropertyDescriptor(target, prop)
226
+ : undefined
227
+ },
228
+
229
+ ownKeys(target) {
230
+ return options.renderable ? Reflect.ownKeys(target) : []
231
+ },
232
+ })
233
+ }
234
+
235
+ /**
236
+ * Creates a React element that's also a Proxy for nested access.
237
+ * This is used by renderable RSCs so they work as both {data} and {data.foo.bar}.
238
+ */
239
+ function createRenderableElement(
240
+ getTree: () => unknown,
241
+ path: Array<string>,
242
+ stream: unknown | undefined,
243
+ cssHrefs: ReadonlySet<string> | undefined,
244
+ jsPreloads: ReadonlySet<string> | undefined,
245
+ ): any {
246
+ return createRscProxyInternal({
247
+ getTree,
248
+ path,
249
+ stream,
250
+ cssHrefs,
251
+ jsPreloads,
252
+ renderable: true,
253
+ slotUsages: undefined,
254
+ slotUsagesStream: undefined,
255
+ })
256
+ }
257
+
258
+ function createRscProxyWithPath(
259
+ getTree: () => unknown,
260
+ path: Array<string>,
261
+ stream: unknown | undefined,
262
+ cssHrefs: ReadonlySet<string> | undefined,
263
+ jsPreloads: ReadonlySet<string> | undefined,
264
+ slotUsages: Array<RscSlotUsageEvent> | undefined,
265
+ slotUsagesStream: ReadableStream<RscSlotUsageEvent> | undefined,
266
+ ): any {
267
+ return createRscProxyInternal({
268
+ getTree,
269
+ path,
270
+ stream,
271
+ cssHrefs,
272
+ jsPreloads,
273
+ renderable: false,
274
+ slotUsages,
275
+ slotUsagesStream,
276
+ })
277
+ }
278
+
279
+ async function consumeSlotUsages(
280
+ stream: ReadableStream<RscSlotUsageEvent>,
281
+ slotUsages: Array<RscSlotUsageEvent>,
282
+ ): Promise<void> {
283
+ try {
284
+ const reader = stream.getReader()
285
+ for (;;) {
286
+ const { value, done } = await reader.read()
287
+ if (done) break
288
+ if (!value.slot) continue
289
+ slotUsages.push(value)
290
+ }
291
+ } catch {
292
+ // Ignore - dev-only best effort
293
+ }
294
+ }
@@ -0,0 +1,105 @@
1
+ 'use client'
2
+
3
+ import { use } from 'react'
4
+ import { trackPostProcessPromise } from '@tanstack/start-client-core'
5
+ import { createFromReadableStream as browserDecode } from 'virtual:tanstack-rsc-browser-decode'
6
+
7
+ import { awaitLazyElements } from './awaitLazyElements'
8
+ import { createRscProxy } from './createRscProxy'
9
+ import type {
10
+ AnyCompositeComponent,
11
+ RscSlotUsageEvent,
12
+ ServerComponentStream,
13
+ } from './ServerComponentTypes'
14
+
15
+ /**
16
+ * Creates a renderable RSC proxy from a raw Flight stream.
17
+ * Client-side only - used by the client serialization adapter for `renderServerComponent`.
18
+ *
19
+ * Returns a Proxy that:
20
+ * - Can be rendered directly as `{data}` in JSX
21
+ * - Supports nested access: `{data.foo.bar}`
22
+ * - Masquerades as a React element
23
+ */
24
+ export function createRenderableFromStream(
25
+ stream: ReadableStream<Uint8Array>,
26
+ ): any {
27
+ const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream)
28
+
29
+ return createRscProxy(getTree, {
30
+ stream: streamWrapper,
31
+ cssHrefs,
32
+ renderable: true,
33
+ })
34
+ }
35
+
36
+ /**
37
+ * Creates a composite RSC proxy from a raw Flight stream.
38
+ * Client-side only - used by the client serialization adapter for `createCompositeComponent`.
39
+ *
40
+ * Returns a Proxy that:
41
+ * - NOT directly renderable
42
+ * - Supports nested access: `src.foo.bar`
43
+ * - Must be rendered via `<CompositeComponent src={...} />`
44
+ */
45
+ export function createCompositeFromStream(
46
+ stream: ReadableStream<Uint8Array>,
47
+ options?: {
48
+ slotUsagesStream?: ReadableStream<RscSlotUsageEvent>
49
+ },
50
+ ): AnyCompositeComponent {
51
+ const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream)
52
+
53
+ return createRscProxy(getTree, {
54
+ stream: streamWrapper,
55
+ cssHrefs,
56
+ renderable: false,
57
+ slotUsagesStream: options?.slotUsagesStream,
58
+ })
59
+ }
60
+
61
+ /**
62
+ * Shared stream decode setup for both renderable and composite.
63
+ */
64
+ function setupStreamDecode(stream: ReadableStream<Uint8Array>): {
65
+ getTree: () => unknown
66
+ streamWrapper: ServerComponentStream
67
+ cssHrefs: Set<string> | undefined
68
+ } {
69
+ // Start decoding eagerly during deserialization
70
+ const decodeThenable = browserDecode(stream)
71
+ const cssHrefs = new Set<string>()
72
+
73
+ // Synchronous cache for the decoded tree.
74
+ let cachedTree: unknown = undefined
75
+ let cacheReady = false
76
+
77
+ // Promise for the tree with lazy elements awaited.
78
+ const transformedTreePromise = Promise.resolve(decodeThenable).then(
79
+ async (result) => {
80
+ await awaitLazyElements(result, (href) => {
81
+ cssHrefs.add(href)
82
+ })
83
+ cachedTree = result
84
+ cacheReady = true
85
+ return result
86
+ },
87
+ )
88
+
89
+ // Track the lazy element loading - prevents flash
90
+ trackPostProcessPromise(transformedTreePromise)
91
+
92
+ const streamWrapper: ServerComponentStream = {
93
+ createReplayStream: () => stream,
94
+ }
95
+
96
+ const getTree = () => {
97
+ if (cacheReady) return cachedTree
98
+ // eslint-disable-next-line react-hooks/rules-of-hooks
99
+ return use(transformedTreePromise)
100
+ }
101
+
102
+ return { getTree, streamWrapper, cssHrefs }
103
+ }
104
+ // Legacy export for backwards compatibility during migration
105
+ export const createServerComponentFromStream = createCompositeFromStream
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared RSC (React Server Components) entry point.
3
+ *
4
+ * This file exports the functions needed for the active RSC environment:
5
+ * - getServerFnById: Resolves server functions by their encoded ID
6
+ * - render: Renders a React node to an RSC Flight stream
7
+ */
8
+
9
+ import { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'
10
+ import type React from 'react'
11
+
12
+ // Re-export getServerFnById from the virtual module which handles both dev and production
13
+ // In dev: dynamic import with base64url-decoded file path
14
+ // In production: manifest-based lookup with bundled chunks
15
+ export { getServerFnById } from '#tanstack-start-server-fn-resolver'
16
+
17
+ /**
18
+ * Renders a React node to an RSC Flight stream.
19
+ * Used internally for streaming server component output.
20
+ */
21
+ export function render(node: React.ReactNode): ReadableStream<Uint8Array> {
22
+ return renderToReadableStream(node)
23
+ }
@@ -0,0 +1,12 @@
1
+ // Type declarations for virtual modules used in RSC entry
2
+ // These modules are provided by the active bundler adapter at runtime
3
+
4
+ declare module '#tanstack-start-server-fn-resolver' {
5
+ export type ServerFnLookupAccess = { origin: 'client' } | { origin: 'server' }
6
+
7
+ export type ServerFn = (...args: Array<any>) => Promise<any>
8
+ export function getServerFnById(
9
+ id: string,
10
+ access: ServerFnLookupAccess,
11
+ ): Promise<ServerFn>
12
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Low-level Flight stream API for RSC (React Server Components) environment.
3
+ *
4
+ * This exports renderToReadableStream which generates a Flight stream from
5
+ * React elements. Only available in RSC context (react-server condition).
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { renderToReadableStream } from '@tanstack/react-start/rsc'
10
+ *
11
+ * const stream = renderToReadableStream(<MyServerComponent />)
12
+ * return new Response(stream, {
13
+ * headers: { 'Content-Type': 'text/x-component' }
14
+ * })
15
+ * ```
16
+ */
17
+ export { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Client stub for renderToReadableStream.
3
+ *
4
+ * This function should never be called at runtime on the client.
5
+ * It exists only to provide types for bundler imports in client bundles.
6
+ * The real implementation only runs inside RSC context (server functions).
7
+ */
8
+ export function renderToReadableStream(
9
+ _node: React.ReactNode,
10
+ ): ReadableStream<Uint8Array> {
11
+ throw new Error(
12
+ 'renderToReadableStream cannot be called on the client. ' +
13
+ 'This function should only be called inside RSC context (server functions).',
14
+ )
15
+ }
package/src/flight.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Low-level Flight stream APIs for decoding RSC streams.
3
+ *
4
+ * These functions provide direct access to RSC Flight stream decoding,
5
+ * allowing advanced use cases like:
6
+ * - Server functions returning raw Flight Response
7
+ * - API routes streaming Flight payloads
8
+ * - Custom Flight stream handling via RawStream
9
+ *
10
+ * `createFromReadableStream` works in both SSR and browser contexts.
11
+ * `createFromFetch` is browser-only.
12
+ *
13
+ * NOTE: Dynamic imports keep decode initialisation runtime-specific. The
14
+ * concrete implementation comes from bundler-owned virtual modules.
15
+ */
16
+
17
+ import {
18
+ createClientOnlyFn,
19
+ createIsomorphicFn,
20
+ } from '@tanstack/start-fn-stubs'
21
+
22
+ /**
23
+ * Decode a Flight stream into React elements.
24
+ * Works in both SSR and browser contexts.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * const rawStream = await getRscRawStream()
29
+ * const tree = await createFromReadableStream(rawStream)
30
+ * return <>{tree}</>
31
+ * ```
32
+ */
33
+ export const createFromReadableStream = createIsomorphicFn()
34
+ .server(
35
+ async (stream: ReadableStream<Uint8Array>): Promise<React.ReactNode> => {
36
+ const { createFromReadableStream: decode } =
37
+ await import('virtual:tanstack-rsc-ssr-decode')
38
+ return decode(stream) as React.ReactNode
39
+ },
40
+ )
41
+ .client(
42
+ async (stream: ReadableStream<Uint8Array>): Promise<React.ReactNode> => {
43
+ const { createFromReadableStream: decode } =
44
+ await import('virtual:tanstack-rsc-browser-decode')
45
+ return decode(stream)
46
+ },
47
+ )
48
+
49
+ /**
50
+ * Decode a Flight stream from a fetch Response.
51
+ * Browser only - will throw if called on the server.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // From server function returning raw Response
56
+ * const tree = await createFromFetch(getFlightResponse())
57
+ *
58
+ * // From API route
59
+ * const tree = await createFromFetch(fetch('/api/rsc-flight'))
60
+ * ```
61
+ */
62
+ export const createFromFetch = createClientOnlyFn(
63
+ async (fetchPromise: Promise<Response>): Promise<React.ReactNode> => {
64
+ const { createFromFetch: decode } =
65
+ await import('virtual:tanstack-rsc-browser-decode')
66
+ return decode(fetchPromise)
67
+ },
68
+ )
@@ -0,0 +1,75 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ /**
4
+ * Type declarations for virtual:tanstack-rsc-runtime
5
+ *
6
+ * This virtual module is provided by the active Start bundler adapter
7
+ * and re-exports RSC runtime functions from that adapter's Flight runtime.
8
+ *
9
+ * Using a virtual module allows the imports to be resolved at runtime within
10
+ * the correct server environment context (react-server conditions).
11
+ */
12
+ declare module 'virtual:tanstack-rsc-runtime' {
13
+ export function renderToReadableStream<T>(
14
+ data: T,
15
+ options?: object,
16
+ ): ReadableStream<Uint8Array>
17
+ export function createFromReadableStream<T = unknown>(
18
+ stream: ReadableStream<Uint8Array>,
19
+ options?: object,
20
+ ): Promise<T>
21
+ export function createTemporaryReferenceSet(): object
22
+ export function decodeReply<T = unknown>(
23
+ body: string | FormData,
24
+ options?: object,
25
+ ): Promise<T>
26
+ export function loadServerAction(id: string): Promise<(...args: any) => any>
27
+ export function decodeAction<T = unknown>(
28
+ body: FormData,
29
+ serverManifest?: unknown,
30
+ ): Promise<() => T>
31
+ export function decodeFormState<T = unknown>(
32
+ actionResult: T,
33
+ body: FormData,
34
+ serverManifest?: unknown,
35
+ ): Promise<unknown>
36
+ }
37
+
38
+ declare module 'virtual:tanstack-rsc-browser-decode' {
39
+ export function createFromReadableStream<T = unknown>(
40
+ stream: ReadableStream<Uint8Array>,
41
+ options?: object,
42
+ ): Promise<T>
43
+ export function createFromFetch<T = unknown>(
44
+ response: Promise<Response>,
45
+ ): Promise<T>
46
+ }
47
+
48
+ declare module 'virtual:tanstack-rsc-ssr-decode' {
49
+ export function setOnClientReference(
50
+ callback:
51
+ | ((reference: {
52
+ id: string
53
+ deps: { js: Array<string>; css: Array<string> }
54
+ runtime?: 'rsbuild'
55
+ }) => void)
56
+ | undefined,
57
+ ): void
58
+ export function createFromReadableStream<T = unknown>(
59
+ stream: ReadableStream<Uint8Array>,
60
+ options?: object,
61
+ ): Promise<T>
62
+ }
63
+
64
+ /**
65
+ * Type declarations for virtual:tanstack-rsc-hmr
66
+ *
67
+ * This virtual module is provided by the active Start bundler adapter
68
+ * and sets up the RSC HMR listener in dev mode. It listens for 'rsc:update'
69
+ * events and invalidates the router to refetch server components.
70
+ *
71
+ * In production builds, this module is empty.
72
+ */
73
+ declare module 'virtual:tanstack-rsc-hmr' {
74
+ export function setupRscHmr(): void
75
+ }
@@ -0,0 +1,25 @@
1
+ // Server-side exports (react-server condition)
2
+ // This file is used when importing from RSC (React Server Components) context
3
+
4
+ // Types are always available
5
+ export type { AnyCompositeComponent } from './ServerComponentTypes'
6
+
7
+ // New API: renderServerComponent - renders element to renderable proxy
8
+ export { renderServerComponent } from './renderServerComponent.js'
9
+
10
+ // New API: createCompositeComponent - creates composite with slot support
11
+ export { createCompositeComponent } from './createCompositeComponent.js'
12
+
13
+ // Renderer for composite RSC data (client/SSR)
14
+ export { CompositeComponent } from './CompositeComponent.js'
15
+
16
+ // Low-level Flight stream API (RSC only)
17
+ export { renderToReadableStream } from './flight.rsc.js'
18
+
19
+ // Low-level Flight stream APIs (also available in RSC for decode operations)
20
+ export { createFromReadableStream, createFromFetch } from './flight'
21
+
22
+ // Note: rscSerializationAdapter is intentionally NOT exported here.
23
+ // It uses createSerializationAdapter from react-router which is marked as
24
+ // client-only in RSC environment. The adapter is only needed by SSR/client
25
+ // for serializing/deserializing server components, not by RSC itself.
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ // Default exports (used by client bundles and SSR)
2
+ // This file is used when importing outside of RSC (React Server Components) context
3
+
4
+ // Types are always available
5
+ export type { AnyCompositeComponent } from './ServerComponentTypes'
6
+
7
+ // CSS hrefs symbol for type-safe access
8
+ export { SERVER_COMPONENT_CSS_HREFS } from './ServerComponentTypes'
9
+
10
+ // Stubs for RSC-only functions - throw if called outside RSC context
11
+ export { renderServerComponent } from './renderServerComponent.stub.js'
12
+ export { createCompositeComponent } from './createCompositeComponent.stub.js'
13
+
14
+ // Renderer for composite RSC data (client/SSR)
15
+ export { CompositeComponent } from './CompositeComponent.js'
16
+
17
+ // Low-level Flight stream APIs (client/SSR)
18
+ export { createFromReadableStream, createFromFetch } from './flight'
19
+
20
+ // Stub for renderToReadableStream - throws if called outside RSC context
21
+ export { renderToReadableStream } from './flight.stub.js'
22
+
23
+ // Note: rscSerializationAdapter is intentionally NOT exported here.
24
+ // It imports virtual:tanstack-rsc-hmr which is client-only (not available in SSR).
25
+ // Import directly from '@tanstack/react-start-rsc/serialization.client' or
26
+ // '@tanstack/react-start-rsc/serialization.server' as needed.