@tanstack/react-start-rsc 0.0.21 → 0.0.22

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ServerComponentTypes.js","names":[],"sources":["../../src/ServerComponentTypes.ts"],"sourcesContent":["import type {\n Constrain,\n LooseAsyncReturnType,\n LooseReturnType,\n ValidateSerializable,\n} from '@tanstack/router-core'\nimport type { ComponentProps, ComponentType } from 'react'\n\nexport interface ServerComponentStream {\n createReplayStream: () => ReadableStream<Uint8Array>\n}\n\n// Symbol to attach stream to component for serialization\nexport const SERVER_COMPONENT_STREAM = Symbol.for('tanstack.rsc.stream')\n\n// Symbol to attach collected CSS hrefs to component\nexport const SERVER_COMPONENT_CSS_HREFS = Symbol.for('tanstack.rsc.cssHrefs')\n\n// Symbol to attach collected JS modulepreload hrefs to component\nexport const SERVER_COMPONENT_JS_PRELOADS = Symbol.for(\n 'tanstack.rsc.jsPreloads',\n)\n\n// Symbol to attach a nested selection path to the RSC data proxy\nexport const RSC_PROXY_PATH = Symbol.for('tanstack.rsc.path')\n\n// Symbol to attach the root tree getter to every nested proxy\nexport const RSC_PROXY_GET_TREE = Symbol.for('tanstack.rsc.getTree')\n\n// Symbol to mark a proxy as \"renderable\" (for renderServerComponent output)\n// When true: from renderServerComponent (directly renderable)\n// When false/undefined: from createCompositeComponent (needs <CompositeComponent src={...} />)\nexport const RENDERABLE_RSC = Symbol.for('tanstack.rsc.renderable')\n\n// Dev-only: collected slot usage data for devtools (client-side cache)\nexport const RSC_SLOT_USAGES = Symbol.for('tanstack.rsc.slotUsages')\n\n// Dev-only: stream of slot usage preview events for devtools\nexport const RSC_SLOT_USAGES_STREAM = Symbol.for(\n 'tanstack.rsc.slotUsages.stream',\n)\n\nexport type RscSlotUsageEvent = {\n slot: string\n // Raw args passed to the slot call (must be serializable by the transport)\n args?: Array<any>\n}\n\n/**\n * Type guard to check if a value is a ServerComponent (Proxy with attached stream).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nexport function isServerComponent(\n value: unknown,\n): value is AnyCompositeComponent {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return (\n SERVER_COMPONENT_STREAM in value &&\n (value as any)[SERVER_COMPONENT_STREAM] !== undefined\n )\n}\n\n/**\n * Type guard to check if a value is a RenderableRsc (renderable proxy from renderServerComponent).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nexport function isRenderableRsc(value: unknown): boolean {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return RENDERABLE_RSC in value && (value as any)[RENDERABLE_RSC] === true\n}\n\nexport type ValidateCompositeComponent<TComp> = Constrain<\n TComp,\n (\n props: ValidateCompositeComponentProps<TComp>,\n ) => ValidateCompositeComponentReturnType<TComp>\n>\n\nexport type ValidateCompositeComponentProps<TComp> = unknown extends TComp\n ? TComp\n : ValidateCompositeComponentPropsObject<CompositeComponentProps<TComp>>\n\nexport type ValidateCompositeComponentPropsObject<TProps> =\n unknown extends TProps\n ? TProps\n : {\n [TKey in keyof TProps]: ValidateCompositeComponentProp<TProps[TKey]>\n }\n\nexport type CompositeComponentProps<TComp> = TComp extends (\n props: infer TProps,\n) => any\n ? TProps\n : unknown\n\nexport type ValidateCompositeComponentProp<TProp> = TProp extends (\n ...args: Array<any>\n) => any\n ? (...args: ValidateReactSerializable<Parameters<TProp>>) => React.ReactNode\n : TProp extends ComponentType<any>\n ? ComponentType<ValidateReactSerializable<ComponentProps<TProp>>>\n : TProp extends React.ReactNode\n ? TProp\n : React.ReactNode\n\nexport type ValidateReactSerializable<T> = ValidateSerializable<\n T,\n ReactSerializable\n>\n\nexport type ReactSerializable =\n | number\n | string\n | bigint\n | boolean\n | null\n | undefined\n | React.ReactNode\n\nexport type ValidateCompositeComponentReturnType<TComp> = unknown extends TComp\n ? React.ReactNode\n : ValidateCompositeComponentResult<LooseReturnType<TComp>>\n\nexport type ValidateCompositeComponentResult<TNode> =\n ValidateServerComponentResult<TNode>\n\nexport type ValidateServerComponentResult<TNode> =\n TNode extends Promise<any>\n ? ValidateCompositeComponentPromiseResult<TNode>\n : TNode extends React.ReactNode\n ? TNode\n : TNode extends (...args: Array<any>) => any\n ? React.ReactNode\n : TNode extends object\n ? ValidateCompositeComponentObjectResult<TNode>\n : React.ReactNode\n\nexport type ValidateCompositeComponentPromiseResult<TPromise> =\n TPromise extends Promise<infer T>\n ? Promise<ValidateCompositeComponentResult<T>>\n : never\n\nexport type ValidateCompositeComponentObjectResult<TObject> = {\n [TKey in keyof TObject]: ValidateCompositeComponentResult<TObject[TKey]>\n}\n\nexport type CompositeComponentResult<TComp> = CompositeComponentBuilder<\n TComp,\n LooseAsyncReturnType<TComp>\n>\n\nexport type CompositeComponentBuilder<TComp, TReturn> =\n TReturn extends React.ReactNode\n ? CompositeComponent<TComp, TReturn>\n : {\n [TKey in keyof TReturn]: CompositeComponentBuilder<TComp, TReturn[TKey]>\n }\n\nexport interface CompositeComponent<in out TComp, in out TReturn> {\n '~types': {\n props: CompositeComponentProps<TComp>\n return: TReturn\n }\n\n [SERVER_COMPONENT_STREAM]?: ServerComponentStream\n\n /**\n * Root decoded tree getter.\n */\n [RSC_PROXY_GET_TREE]?: () => unknown\n /**\n * Nested selection path (eg ['content','Stats']).\n * Used by <CompositeComponent/> to render a sub-tree.\n */\n [RSC_PROXY_PATH]?: Array<string>\n /**\n * CSS hrefs collected from the RSC stream.\n * Can be used for preloading in <head> or emitting 103 Early Hints.\n */\n [SERVER_COMPONENT_CSS_HREFS]?: ReadonlySet<string>\n\n /**\n * JS hrefs collected from the RSC stream.\n * Emitted as modulepreload links only if the decoded tree is rendered in SSR.\n */\n [SERVER_COMPONENT_JS_PRELOADS]?: ReadonlySet<string>\n\n /**\n * Dev-only: async stream of slot usage preview events.\n * Used by devtools to show slot names and previewed call args without\n * buffering/draining the Flight stream.\n */\n [RSC_SLOT_USAGES_STREAM]?: ReadableStream<RscSlotUsageEvent>\n}\n\nexport type ValidateRenderableServerComponent<TNode> =\n ValidateServerComponentResult<TNode>\n\nexport type RenderableServerComponentBuilder<T> = T extends React.ReactNode\n ? RenderableServerComponent<T>\n : { [TKey in keyof T]: RenderableServerComponentBuilder<T[TKey]> }\n\nexport type RenderableServerComponent<TNode extends React.ReactNode> = TNode &\n RenderableServerComponentAttributes<TNode>\n\nexport interface RenderableServerComponentAttributes<TNode> {\n '~types': {\n node: TNode\n }\n [SERVER_COMPONENT_STREAM]: ServerComponentStream\n [RENDERABLE_RSC]: true\n}\n\ndeclare module '@tanstack/router-core' {\n export interface SerializableExtensions {\n CompositeComponent: AnyCompositeComponent\n RenderableServerComponent: AnyRenderableServerComponent\n }\n}\n\nexport type AnyCompositeComponent = CompositeComponent<any, any>\n\nexport type AnyRenderableServerComponent =\n RenderableServerComponentAttributes<any>\n"],"mappings":";AAaA,IAAa,0BAA0B,OAAO,IAAI,sBAAsB;AAGxE,IAAa,6BAA6B,OAAO,IAAI,wBAAwB;AAG7E,IAAa,+BAA+B,OAAO,IACjD,0BACD;AAGD,IAAa,iBAAiB,OAAO,IAAI,oBAAoB;AAG7D,IAAa,qBAAqB,OAAO,IAAI,uBAAuB;AAKpE,IAAa,iBAAiB,OAAO,IAAI,0BAA0B;AAGnE,IAAa,kBAAkB,OAAO,IAAI,0BAA0B;AAGpE,IAAa,yBAAyB,OAAO,IAC3C,iCACD;;;;;AAYD,SAAgB,kBACd,OACgC;AAChC,KAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO;AAClD,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAY,QAAO;AACrE,QACE,2BAA2B,SAC1B,MAAc,6BAA6B,KAAA"}
1
+ {"version":3,"file":"ServerComponentTypes.js","names":[],"sources":["../../src/ServerComponentTypes.ts"],"sourcesContent":["import type {\n Constrain,\n LooseAsyncReturnType,\n LooseReturnType,\n ValidateSerializable,\n} from '@tanstack/router-core'\nimport type { ComponentProps, ComponentType } from 'react'\n\nexport interface ServerComponentStream {\n createReplayStream: () => ReadableStream<Uint8Array>\n}\n\n// Symbol to attach stream to component for serialization\nexport const SERVER_COMPONENT_STREAM = Symbol.for('tanstack.rsc.stream')\n\n// Symbol to attach collected CSS hrefs to component\nexport const SERVER_COMPONENT_CSS_HREFS = Symbol.for('tanstack.rsc.cssHrefs')\n\n// Symbol to attach collected JS modulepreload hrefs to component\nexport const SERVER_COMPONENT_JS_PRELOADS = Symbol.for(\n 'tanstack.rsc.jsPreloads',\n)\n\n// Symbol to attach a nested selection path to the RSC data proxy\nexport const RSC_PROXY_PATH = Symbol.for('tanstack.rsc.path')\n\n// Symbol to attach the root tree getter to every nested proxy\nexport const RSC_PROXY_GET_TREE = Symbol.for('tanstack.rsc.getTree')\n\n// Symbol to mark a proxy as \"renderable\" (for renderServerComponent output)\n// When true: from renderServerComponent (directly renderable)\n// When false/undefined: from createCompositeComponent (needs <CompositeComponent src={...} />)\nexport const RENDERABLE_RSC = Symbol.for('tanstack.rsc.renderable')\n\n// Dev-only: collected slot usage data for devtools (client-side cache)\nexport const RSC_SLOT_USAGES = Symbol.for('tanstack.rsc.slotUsages')\n\n// Dev-only: stream of slot usage preview events for devtools\nexport const RSC_SLOT_USAGES_STREAM = Symbol.for(\n 'tanstack.rsc.slotUsages.stream',\n)\n\nexport type RscSlotUsageEvent = {\n slot: string\n // Raw args passed to the slot call (must be serializable by the transport)\n args?: Array<any>\n}\n\n/**\n * Type guard to check if a value is a ServerComponent (Proxy with attached stream).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nexport function isServerComponent(\n value: unknown,\n): value is AnyCompositeComponent {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return (\n SERVER_COMPONENT_STREAM in value &&\n (value as any)[SERVER_COMPONENT_STREAM] !== undefined\n )\n}\n\nexport type ValidateCompositeComponent<TComp> = Constrain<\n TComp,\n (\n props: ValidateCompositeComponentProps<TComp>,\n ) => ValidateCompositeComponentReturnType<TComp>\n>\n\nexport type ValidateCompositeComponentProps<TComp> = unknown extends TComp\n ? TComp\n : ValidateCompositeComponentPropsObject<CompositeComponentProps<TComp>>\n\nexport type ValidateCompositeComponentPropsObject<TProps> =\n unknown extends TProps\n ? TProps\n : {\n [TKey in keyof TProps]: ValidateCompositeComponentProp<TProps[TKey]>\n }\n\nexport type CompositeComponentProps<TComp> = TComp extends (\n props: infer TProps,\n) => any\n ? TProps\n : unknown\n\nexport type ValidateCompositeComponentProp<TProp> = TProp extends (\n ...args: Array<any>\n) => any\n ? (...args: ValidateReactSerializable<Parameters<TProp>>) => React.ReactNode\n : TProp extends ComponentType<any>\n ? ComponentType<ValidateReactSerializable<ComponentProps<TProp>>>\n : TProp extends React.ReactNode\n ? TProp\n : React.ReactNode\n\nexport type ValidateReactSerializable<T> = ValidateSerializable<\n T,\n ReactSerializable\n>\n\nexport type ReactSerializable =\n | number\n | string\n | bigint\n | boolean\n | null\n | undefined\n | React.ReactNode\n\nexport type ValidateCompositeComponentReturnType<TComp> = unknown extends TComp\n ? React.ReactNode\n : ValidateCompositeComponentResult<LooseReturnType<TComp>>\n\nexport type ValidateCompositeComponentResult<TNode> =\n ValidateServerComponentResult<TNode>\n\nexport type ValidateServerComponentResult<TNode> =\n TNode extends Promise<any>\n ? ValidateCompositeComponentPromiseResult<TNode>\n : TNode extends React.ReactNode\n ? TNode\n : TNode extends (...args: Array<any>) => any\n ? React.ReactNode\n : TNode extends object\n ? ValidateCompositeComponentObjectResult<TNode>\n : React.ReactNode\n\nexport type ValidateCompositeComponentPromiseResult<TPromise> =\n TPromise extends Promise<infer T>\n ? Promise<ValidateCompositeComponentResult<T>>\n : never\n\nexport type ValidateCompositeComponentObjectResult<TObject> = {\n [TKey in keyof TObject]: ValidateCompositeComponentResult<TObject[TKey]>\n}\n\nexport type CompositeComponentResult<TComp> = CompositeComponentBuilder<\n TComp,\n LooseAsyncReturnType<TComp>\n>\n\nexport type CompositeComponentBuilder<TComp, TReturn> =\n TReturn extends React.ReactNode\n ? CompositeComponent<TComp, TReturn>\n : {\n [TKey in keyof TReturn]: CompositeComponentBuilder<TComp, TReturn[TKey]>\n }\n\nexport interface CompositeComponent<in out TComp, in out TReturn> {\n '~types': {\n props: CompositeComponentProps<TComp>\n return: TReturn\n }\n\n [SERVER_COMPONENT_STREAM]?: ServerComponentStream\n\n /**\n * Root decoded tree getter.\n */\n [RSC_PROXY_GET_TREE]?: () => unknown\n /**\n * Nested selection path (eg ['content','Stats']).\n * Used by <CompositeComponent/> to render a sub-tree.\n */\n [RSC_PROXY_PATH]?: Array<string>\n /**\n * CSS hrefs collected from the RSC stream.\n * Can be used for preloading in <head> or emitting 103 Early Hints.\n */\n [SERVER_COMPONENT_CSS_HREFS]?: ReadonlySet<string>\n\n /**\n * JS hrefs collected from the RSC stream.\n * Emitted as modulepreload links only if the decoded tree is rendered in SSR.\n */\n [SERVER_COMPONENT_JS_PRELOADS]?: ReadonlySet<string>\n\n /**\n * Dev-only: async stream of slot usage preview events.\n * Used by devtools to show slot names and previewed call args without\n * buffering/draining the Flight stream.\n */\n [RSC_SLOT_USAGES_STREAM]?: ReadableStream<RscSlotUsageEvent>\n}\n\nexport type ValidateRenderableServerComponent<TNode> =\n ValidateServerComponentResult<TNode>\n\nexport type RenderableServerComponentBuilder<T> = T extends React.ReactNode\n ? RenderableServerComponent<T>\n : { [TKey in keyof T]: RenderableServerComponentBuilder<T[TKey]> }\n\nexport type RenderableServerComponent<TNode extends React.ReactNode> = TNode &\n RenderableServerComponentAttributes<TNode>\n\nexport interface RenderableServerComponentAttributes<TNode> {\n '~types': {\n node: TNode\n }\n [SERVER_COMPONENT_STREAM]: ServerComponentStream\n [RENDERABLE_RSC]: true\n}\n\ndeclare module '@tanstack/router-core' {\n export interface SerializableExtensions {\n CompositeComponent: AnyCompositeComponent\n RenderableServerComponent: AnyRenderableServerComponent\n }\n}\n\nexport type AnyCompositeComponent = CompositeComponent<any, any>\n\nexport type AnyRenderableServerComponent =\n RenderableServerComponentAttributes<any>\n"],"mappings":";AAaA,IAAa,0BAA0B,OAAO,IAAI,sBAAsB;AAGxE,IAAa,6BAA6B,OAAO,IAAI,wBAAwB;AAG7E,IAAa,+BAA+B,OAAO,IACjD,0BACD;AAGD,IAAa,iBAAiB,OAAO,IAAI,oBAAoB;AAG7D,IAAa,qBAAqB,OAAO,IAAI,uBAAuB;AAKpE,IAAa,iBAAiB,OAAO,IAAI,0BAA0B;AAGnE,IAAa,kBAAkB,OAAO,IAAI,0BAA0B;AAGpE,IAAa,yBAAyB,OAAO,IAC3C,iCACD;;;;;AAYD,SAAgB,kBACd,OACgC;AAChC,KAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO;AAClD,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAY,QAAO;AACrE,QACE,2BAA2B,SAC1B,MAAc,6BAA6B,KAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"createCompositeComponent.js","names":[],"sources":["../../src/createCompositeComponent.ts"],"sourcesContent":["import { createElement } from 'react'\nimport { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'\nimport { getRequest } from '@tanstack/start-server-core'\nimport { getStartContext } from '@tanstack/start-storage-context'\nimport { sanitizeSlotArgs } from './slotUsageSanitizer'\n\nimport { ReplayableStream } from './ReplayableStream'\nimport { ClientSlot } from './ClientSlot'\nimport {\n RSC_SLOT_USAGES_STREAM,\n SERVER_COMPONENT_STREAM,\n} from './ServerComponentTypes'\nimport type {\n AnyCompositeComponent,\n CompositeComponentResult,\n RscSlotUsageEvent,\n ServerComponentStream,\n ValidateCompositeComponent,\n} from './ServerComponentTypes'\n\nimport './rscSsrHandler' // Import for global declaration side effect\n\n/**\n * Creates a composite server component with slot support.\n *\n * Supports returning:\n * - A ReactNode directly\n * - An object structure with ReactNodes: accessed as `src.Foo`\n * - Nested structures: accessed as `src.x.Bar`\n *\n * Props that are functions become slots - they render as ClientSlot placeholders\n * in the RSC output, filled in by the consumer with actual implementations.\n *\n * The returned value is NOT directly renderable. Use `<CompositeComponent src={...} />`.\n *\n * @example\n * ```tsx\n * const src = await createCompositeComponent((props) => (\n * <div>\n * <header>{props.header('Dashboard')}</header>\n * <main>{props.children}</main>\n * </div>\n * ))\n *\n * // In route component\n * return (\n * <CompositeComponent src={src} header={(title) => <h1>{title}</h1>}>\n * <p>Main content</p>\n * </CompositeComponent>\n * )\n * ```\n */\nexport async function createCompositeComponent<TComp>(\n component: ValidateCompositeComponent<TComp>,\n): Promise<CompositeComponentResult<TComp>> {\n const isDev = process.env.NODE_ENV === 'development'\n\n // Dev-only: stream slot usage events (slot + raw args)\n const slotUsagesEmitter = isDev\n ? createReadableStreamEmitter<RscSlotUsageEvent>()\n : null\n\n // Create a wrapper component that will be rendered inside React's Flight context.\n // This ensures React.cache works properly since the component is called during\n // renderToReadableStream's render phase, not before it.\n const { proxy: proxyProps } = createSlotProxy<{}>({\n onSlotCall: slotUsagesEmitter\n ? (name, args) => {\n const sanitizedArgs = sanitizeSlotArgs(args)\n slotUsagesEmitter.emit({\n slot: name,\n args: sanitizedArgs.length ? sanitizedArgs : undefined,\n })\n }\n : undefined,\n })\n\n // Wrapper that renders the user's component inside Flight render context\n async function ServerComponentWrapper() {\n return (component as React.FC)(proxyProps)\n }\n\n // Render using createElement so React calls our component during Flight rendering\n // This is critical for React.cache to work - the component must be invoked\n // during renderToReadableStream's execution, not before\n const flightStream = renderToReadableStream(\n createElement(ServerComponentWrapper),\n )\n\n // Check if this is an SSR request (router) or a direct server function call\n const ctx = getStartContext({ throwIfNotFound: false })\n const isRouterRequest = ctx?.handlerType === 'router'\n const ssrHandler = globalThis.__RSC_SSR__\n\n // SSR path: buffer stream for replay, pre-decode for synchronous rendering\n if (isRouterRequest && ssrHandler) {\n const signal = getRequest().signal\n const stream = new ReplayableStream(flightStream, { signal })\n\n // Pre-decode during loader phase for synchronous SSR rendering\n const decoded = await ssrHandler.decode(stream)\n\n // For SSR we know decode fully consumed the Flight stream.\n slotUsagesEmitter?.close()\n\n const proxy = ssrHandler.createCompositeProxy(\n stream,\n decoded,\n slotUsagesEmitter?.stream,\n )\n return proxy as CompositeComponentResult<TComp>\n }\n\n // Server function call path:\n // The serialization adapter will stream to the client.\n const monitoredFlightStream =\n isDev && slotUsagesEmitter\n ? wrapReadableStream(flightStream, {\n onDone: () => {\n slotUsagesEmitter.close()\n },\n onCancel: () => {\n slotUsagesEmitter.close()\n },\n onError: () => {\n slotUsagesEmitter.close()\n },\n })\n : flightStream\n\n return createCompositeHandle(monitoredFlightStream, {\n slotUsagesStream: slotUsagesEmitter?.stream,\n }) as CompositeComponentResult<TComp>\n}\n\n/**\n * Creates a composite handle for server function responses.\n * No proxy needed - the client will decode and create its own proxy.\n */\nfunction createCompositeHandle(\n flightStream: ReadableStream<Uint8Array>,\n options?: {\n slotUsagesStream?: ReadableStream<RscSlotUsageEvent>\n },\n): AnyCompositeComponent {\n // Simple single-use stream wrapper. For server function calls, the stream\n // is consumed exactly once by the serialization adapter for transport.\n const streamWrapper: ServerComponentStream = {\n createReplayStream: () => flightStream,\n }\n\n // Create a stub function with the stream attached for serialization.\n // This will never be rendered directly - it goes through serialization\n // which extracts the stream and sends it to the client.\n const stub = function CompositeComponentStub(): never {\n throw new Error(\n 'CompositeComponent from server function cannot be rendered on server. ' +\n 'It should be serialized and sent to the client.',\n )\n }\n\n ;(stub as any)[SERVER_COMPONENT_STREAM] = streamWrapper\n // Note: RENDERABLE_RSC is not set (or implicitly false), indicating this is a composite component\n\n if (options?.slotUsagesStream) {\n ;(stub as any)[RSC_SLOT_USAGES_STREAM] = options.slotUsagesStream\n }\n\n return stub as unknown as AnyCompositeComponent\n}\n\n/**\n * Base slot props type - functions that become ClientSlot placeholders\n */\ninterface SlotPropsBase {\n [key: string]:\n | ((...args: Array<any>) => React.ReactNode)\n | React.ReactNode\n | undefined\n children?: React.ReactNode\n}\n\ninterface SlotProxyResult<TSlotProps extends object> {\n proxy: TSlotProps & SlotPropsBase\n}\n\n/**\n * Proxy that turns property access into ClientSlot renders.\n * Also tracks accessed slot names for devtools.\n */\nfunction createSlotProxy<TSlotProps extends object>(options?: {\n onSlotCall?: (name: string, args: Array<any>) => void\n}): SlotProxyResult<TSlotProps> {\n const cache = new Map<string, (...args: Array<any>) => React.ReactNode>()\n\n const proxy = new Proxy({} as TSlotProps & SlotPropsBase, {\n get(_target, prop) {\n if (prop === 'then' || typeof prop !== 'string') return undefined\n\n if (prop === 'children') {\n options?.onSlotCall?.('children', [])\n return createElement(ClientSlot, { slot: 'children', args: [] })\n }\n\n let fn = cache.get(prop)\n if (!fn) {\n fn = (...args: Array<any>) => {\n options?.onSlotCall?.(prop, args)\n return createElement(ClientSlot, { slot: prop, args })\n }\n cache.set(prop, fn)\n }\n return fn\n },\n })\n\n return {\n proxy,\n }\n}\n\nfunction createReadableStreamEmitter<T>(): {\n stream: ReadableStream<T>\n emit: (value: T) => void\n close: () => void\n} {\n let closed = false\n const queue: Array<T> = []\n let controller: ReadableStreamDefaultController<T> | null = null\n\n const stream = new ReadableStream<T>({\n start(ctrl) {\n controller = ctrl\n for (const value of queue) {\n try {\n ctrl.enqueue(value)\n } catch {\n // Ignore\n }\n }\n queue.length = 0\n if (closed) {\n try {\n ctrl.close()\n } catch {\n // Ignore\n }\n }\n },\n cancel() {\n closed = true\n controller = null\n queue.length = 0\n },\n })\n\n const emit = (value: T) => {\n if (closed) return\n if (!controller) {\n queue.push(value)\n return\n }\n try {\n controller.enqueue(value)\n } catch {\n // Ignore\n }\n }\n\n const close = () => {\n if (closed) return\n closed = true\n if (controller) {\n try {\n controller.close()\n } catch {\n // Ignore\n }\n controller = null\n }\n }\n\n return { stream, emit, close }\n}\n\nfunction wrapReadableStream<T>(\n source: ReadableStream<T>,\n handlers: {\n onDone?: () => void\n onCancel?: () => void\n onError?: () => void\n },\n): ReadableStream<T> {\n const reader = source.getReader()\n let finished = false\n\n const finish = () => {\n if (finished) return\n finished = true\n handlers.onDone?.()\n try {\n reader.releaseLock()\n } catch {\n // Ignore\n }\n }\n\n return new ReadableStream<T>({\n async pull(controller) {\n try {\n const { value, done } = await reader.read()\n if (done) {\n controller.close()\n finish()\n return\n }\n controller.enqueue(value)\n } catch (err) {\n try {\n controller.error(err)\n } catch {\n // Ignore\n }\n handlers.onError?.()\n finish()\n }\n },\n async cancel(reason) {\n handlers.onCancel?.()\n try {\n await reader.cancel(reason)\n } catch {\n // Ignore\n }\n finish()\n },\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"createCompositeComponent.js","names":[],"sources":["../../src/createCompositeComponent.ts"],"sourcesContent":["import { createElement } from 'react'\nimport { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'\nimport { getRequest } from '@tanstack/start-server-core'\nimport { getStartContext } from '@tanstack/start-storage-context'\nimport { sanitizeSlotArgs } from './slotUsageSanitizer'\nimport { ReplayableStream } from './ReplayableStream'\nimport { ClientSlot } from './ClientSlot'\nimport {\n RSC_SLOT_USAGES_STREAM,\n SERVER_COMPONENT_STREAM,\n} from './ServerComponentTypes'\nimport type {\n AnyCompositeComponent,\n CompositeComponentResult,\n RscSlotUsageEvent,\n ServerComponentStream,\n ValidateCompositeComponent,\n} from './ServerComponentTypes'\n\nimport './rscSsrHandler' // Import for global declaration side effect\n\n/**\n * Creates a composite server component with slot support.\n *\n * Supports returning:\n * - A ReactNode directly\n * - An object structure with ReactNodes: accessed as `src.Foo`\n * - Nested structures: accessed as `src.x.Bar`\n *\n * Props that are functions become slots - they render as ClientSlot placeholders\n * in the RSC output, filled in by the consumer with actual implementations.\n *\n * The returned value is NOT directly renderable. Use `<CompositeComponent src={...} />`.\n *\n * @example\n * ```tsx\n * const src = await createCompositeComponent((props) => (\n * <div>\n * <header>{props.header('Dashboard')}</header>\n * <main>{props.children}</main>\n * </div>\n * ))\n *\n * // In route component\n * return (\n * <CompositeComponent src={src} header={(title) => <h1>{title}</h1>}>\n * <p>Main content</p>\n * </CompositeComponent>\n * )\n * ```\n */\nexport async function createCompositeComponent<TComp>(\n component: ValidateCompositeComponent<TComp>,\n): Promise<CompositeComponentResult<TComp>> {\n const isDev = process.env.NODE_ENV === 'development'\n\n // Dev-only: stream slot usage events (slot + raw args)\n const slotUsagesEmitter = isDev\n ? createReadableStreamEmitter<RscSlotUsageEvent>()\n : null\n\n // Create a wrapper component that will be rendered inside React's Flight context.\n // This ensures React.cache works properly since the component is called during\n // renderToReadableStream's render phase, not before it.\n const { proxy: proxyProps } = createSlotProxy<{}>({\n onSlotCall: slotUsagesEmitter\n ? (name, args) => {\n const sanitizedArgs = sanitizeSlotArgs(args)\n slotUsagesEmitter.emit({\n slot: name,\n args: sanitizedArgs.length ? sanitizedArgs : undefined,\n })\n }\n : undefined,\n })\n\n // Wrapper that renders the user's component inside Flight render context\n async function ServerComponentWrapper() {\n return (component as React.FC)(proxyProps)\n }\n\n // Render using createElement so React calls our component during Flight rendering\n // This is critical for React.cache to work - the component must be invoked\n // during renderToReadableStream's execution, not before\n const flightStream = renderToReadableStream(\n createElement(ServerComponentWrapper),\n )\n\n // Check if this is an SSR request (router) or a direct server function call\n const ctx = getStartContext({ throwIfNotFound: false })\n const isRouterRequest = ctx?.handlerType === 'router'\n const ssrHandler = globalThis.__RSC_SSR__\n\n // SSR path: buffer stream for replay, pre-decode for synchronous rendering\n if (isRouterRequest && ssrHandler) {\n const signal = getRequest().signal\n const stream = new ReplayableStream(flightStream, { signal })\n\n // Pre-decode during loader phase for synchronous SSR rendering\n const decoded = await ssrHandler.decode(stream)\n\n // For SSR we know decode fully consumed the Flight stream.\n slotUsagesEmitter?.close()\n\n return ssrHandler.createCompositeProxy(\n stream,\n decoded,\n slotUsagesEmitter?.stream,\n ) as CompositeComponentResult<TComp>\n }\n\n // Server function call path:\n // The serialization adapter will stream to the client.\n const monitoredFlightStream =\n isDev && slotUsagesEmitter\n ? wrapReadableStream(flightStream, {\n onDone: () => {\n slotUsagesEmitter.close()\n },\n onCancel: () => {\n slotUsagesEmitter.close()\n },\n onError: () => {\n slotUsagesEmitter.close()\n },\n })\n : flightStream\n\n return createCompositeHandle(monitoredFlightStream, {\n slotUsagesStream: slotUsagesEmitter?.stream,\n }) as CompositeComponentResult<TComp>\n}\n\n/**\n * Creates a composite handle for server function responses.\n * No proxy needed - the client will decode and create its own proxy.\n */\nfunction createCompositeHandle(\n flightStream: ReadableStream<Uint8Array>,\n options?: {\n slotUsagesStream?: ReadableStream<RscSlotUsageEvent>\n },\n): AnyCompositeComponent {\n // Simple single-use stream wrapper. For server function calls, the stream\n // is consumed exactly once by the serialization adapter for transport.\n const streamWrapper: ServerComponentStream = {\n createReplayStream: () => flightStream,\n }\n\n // Create a stub function with the stream attached for serialization.\n // This will never be rendered directly - it goes through serialization\n // which extracts the stream and sends it to the client.\n const stub = function CompositeComponentStub(): never {\n throw new Error(\n 'CompositeComponent from server function cannot be rendered on server. ' +\n 'It should be serialized and sent to the client.',\n )\n }\n\n ;(stub as any)[SERVER_COMPONENT_STREAM] = streamWrapper\n // Note: RENDERABLE_RSC is not set (or implicitly false), indicating this is a composite component\n\n if (options?.slotUsagesStream) {\n ;(stub as any)[RSC_SLOT_USAGES_STREAM] = options.slotUsagesStream\n }\n\n return stub as unknown as AnyCompositeComponent\n}\n\n/**\n * Base slot props type - functions that become ClientSlot placeholders\n */\ninterface SlotPropsBase {\n [key: string]:\n | ((...args: Array<any>) => React.ReactNode)\n | React.ReactNode\n | undefined\n children?: React.ReactNode\n}\n\ninterface SlotProxyResult<TSlotProps extends object> {\n proxy: TSlotProps & SlotPropsBase\n}\n\n/**\n * Proxy that turns property access into ClientSlot renders.\n * Also tracks accessed slot names for devtools.\n */\nfunction createSlotProxy<TSlotProps extends object>(options?: {\n onSlotCall?: (name: string, args: Array<any>) => void\n}): SlotProxyResult<TSlotProps> {\n const cache = new Map<string, (...args: Array<any>) => React.ReactNode>()\n\n const proxy = new Proxy({} as TSlotProps & SlotPropsBase, {\n get(_target, prop) {\n if (prop === 'then' || typeof prop !== 'string') return undefined\n\n if (prop === 'children') {\n options?.onSlotCall?.('children', [])\n return createElement(ClientSlot, { slot: 'children', args: [] })\n }\n\n let fn = cache.get(prop)\n if (!fn) {\n fn = (...args: Array<any>) => {\n options?.onSlotCall?.(prop, args)\n return createElement(ClientSlot, { slot: prop, args })\n }\n cache.set(prop, fn)\n }\n return fn\n },\n })\n\n return {\n proxy,\n }\n}\n\nfunction createReadableStreamEmitter<T>(): {\n stream: ReadableStream<T>\n emit: (value: T) => void\n close: () => void\n} {\n let closed = false\n const queue: Array<T> = []\n let controller: ReadableStreamDefaultController<T> | null = null\n\n const stream = new ReadableStream<T>({\n start(ctrl) {\n controller = ctrl\n for (const value of queue) {\n try {\n ctrl.enqueue(value)\n } catch {\n // Ignore\n }\n }\n queue.length = 0\n if (closed) {\n try {\n ctrl.close()\n } catch {\n // Ignore\n }\n }\n },\n cancel() {\n closed = true\n controller = null\n queue.length = 0\n },\n })\n\n const emit = (value: T) => {\n if (closed) return\n if (!controller) {\n queue.push(value)\n return\n }\n try {\n controller.enqueue(value)\n } catch {\n // Ignore\n }\n }\n\n const close = () => {\n if (closed) return\n closed = true\n if (controller) {\n try {\n controller.close()\n } catch {\n // Ignore\n }\n controller = null\n }\n }\n\n return { stream, emit, close }\n}\n\nfunction wrapReadableStream<T>(\n source: ReadableStream<T>,\n handlers: {\n onDone?: () => void\n onCancel?: () => void\n onError?: () => void\n },\n): ReadableStream<T> {\n const reader = source.getReader()\n let finished = false\n\n const finish = () => {\n if (finished) return\n finished = true\n handlers.onDone?.()\n try {\n reader.releaseLock()\n } catch {\n // Ignore\n }\n }\n\n return new ReadableStream<T>({\n async pull(controller) {\n try {\n const { value, done } = await reader.read()\n if (done) {\n controller.close()\n finish()\n return\n }\n controller.enqueue(value)\n } catch (err) {\n try {\n controller.error(err)\n } catch {\n // Ignore\n }\n handlers.onError?.()\n finish()\n }\n },\n async cancel(reason) {\n handlers.onCancel?.()\n try {\n await reader.cancel(reason)\n } catch {\n // Ignore\n }\n finish()\n },\n })\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"renderServerComponent.js","names":[],"sources":["../../src/renderServerComponent.ts"],"sourcesContent":["import { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'\nimport { getRequest } from '@tanstack/start-server-core'\nimport { getStartContext } from '@tanstack/start-storage-context'\n\nimport { ReplayableStream } from './ReplayableStream'\nimport { RENDERABLE_RSC, SERVER_COMPONENT_STREAM } from './ServerComponentTypes'\nimport type {\n AnyRenderableServerComponent,\n RenderableServerComponentBuilder,\n ServerComponentStream,\n ValidateRenderableServerComponent,\n} from './ServerComponentTypes'\n\nimport './rscSsrHandler'\n// Import for global declaration side effect\nexport type { RscSsrHandler, RscDecodeResult } from './rscSsrHandler'\n\n/**\n * Renderable RSC handle type - used for serialization detection.\n */\n\n/**\n * Type guard for renderable RSC handle.\n */\nexport function isRenderableRscHandle(\n value: unknown,\n): value is AnyRenderableServerComponent {\n return (\n typeof value === 'function' &&\n SERVER_COMPONENT_STREAM in value &&\n RENDERABLE_RSC in value &&\n (value as any)[RENDERABLE_RSC] === true\n )\n}\n\n/**\n * Renders a React element to an RSC Flight stream.\n *\n * Returns a \"renderable proxy\" that can be:\n * - Rendered directly as `{data}` in JSX\n * - Accessed for nested selections: `{data.foo.bar.Hello}`\n *\n * No slot support - for slots use `createCompositeComponent`.\n *\n * @example\n * ```tsx\n * // In a loader or server function\n * const data = await renderServerComponent(<MyServerComponent foo=\"bar\" />)\n *\n * // In the route component\n * return (\n * <div>\n * {data}\n * {data.sidebar.Menu}\n * </div>\n * )\n * ```\n */\nexport async function renderServerComponent<TNode>(\n node: ValidateRenderableServerComponent<TNode>,\n): Promise<RenderableServerComponentBuilder<TNode>> {\n // Render the element directly to a Flight stream\n const flightStream = renderToReadableStream(node)\n\n // Check if this is an SSR request (router) or a direct server function call\n const ctx = getStartContext({ throwIfNotFound: false })\n const isRouterRequest = ctx?.handlerType === 'router'\n const ssrHandler = globalThis.__RSC_SSR__\n\n // SSR path: buffer stream for replay, pre-decode for synchronous rendering\n if (isRouterRequest && ssrHandler) {\n const signal = getRequest().signal\n const stream = new ReplayableStream(flightStream, { signal })\n\n // Pre-decode during loader phase for synchronous SSR rendering\n const decoded = await ssrHandler.decode(stream)\n return ssrHandler.createRenderableProxy(\n stream,\n decoded,\n ) as RenderableServerComponentBuilder<TNode>\n }\n\n // Server function call path: return a handle for serialization\n return createRenderableHandle(\n flightStream,\n ) as unknown as RenderableServerComponentBuilder<TNode>\n}\n\n/**\n * Creates a renderable handle for server function responses.\n * Tagged with RENDERABLE_RSC for the serialization adapter.\n */\nfunction createRenderableHandle(\n flightStream: ReadableStream<Uint8Array>,\n): AnyRenderableServerComponent {\n const streamWrapper: ServerComponentStream = {\n createReplayStream: () => flightStream,\n }\n\n const stub = function RenderableRscStub(): never {\n throw new Error(\n 'Renderable RSC from server function cannot be rendered on server. ' +\n 'It should be serialized and sent to the client.',\n )\n }\n\n ;(stub as any)[SERVER_COMPONENT_STREAM] = streamWrapper\n ;(stub as any)[RENDERABLE_RSC] = true\n return stub as unknown as AnyRenderableServerComponent\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,eAAsB,sBACpB,MACkD;CAElD,MAAM,eAAe,uBAAuB,KAAK;CAIjD,MAAM,kBADM,gBAAgB,EAAE,iBAAiB,OAAO,CAAC,EAC1B,gBAAgB;CAC7C,MAAM,aAAa,WAAW;AAG9B,KAAI,mBAAmB,YAAY;EACjC,MAAM,SAAS,YAAY,CAAC;EAC5B,MAAM,SAAS,IAAI,iBAAiB,cAAc,EAAE,QAAQ,CAAC;EAG7D,MAAM,UAAU,MAAM,WAAW,OAAO,OAAO;AAC/C,SAAO,WAAW,sBAChB,QACA,QACD;;AAIH,QAAO,uBACL,aACD;;;;;;AAOH,SAAS,uBACP,cAC8B;CAC9B,MAAM,gBAAuC,EAC3C,0BAA0B,cAC3B;CAED,MAAM,OAAO,SAAS,oBAA2B;AAC/C,QAAM,IAAI,MACR,oHAED;;AAGD,MAAa,2BAA2B;AACxC,MAAa,kBAAkB;AACjC,QAAO"}
1
+ {"version":3,"file":"renderServerComponent.js","names":[],"sources":["../../src/renderServerComponent.ts"],"sourcesContent":["import { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'\nimport { getRequest } from '@tanstack/start-server-core'\nimport { getStartContext } from '@tanstack/start-storage-context'\nimport { ReplayableStream } from './ReplayableStream'\nimport { RENDERABLE_RSC, SERVER_COMPONENT_STREAM } from './ServerComponentTypes'\nimport type {\n AnyRenderableServerComponent,\n RenderableServerComponentBuilder,\n ServerComponentStream,\n ValidateRenderableServerComponent,\n} from './ServerComponentTypes'\n\nimport './rscSsrHandler'\n// Import for global declaration side effect\nexport type { RscSsrHandler, RscDecodeResult } from './rscSsrHandler'\n\n/**\n * Renderable RSC handle type - used for serialization detection.\n */\n\n/**\n * Type guard for renderable RSC handle.\n */\nexport function isRenderableRscHandle(\n value: unknown,\n): value is AnyRenderableServerComponent {\n return (\n typeof value === 'function' &&\n SERVER_COMPONENT_STREAM in value &&\n RENDERABLE_RSC in value &&\n (value as any)[RENDERABLE_RSC] === true\n )\n}\n\n/**\n * Renders a React element to an RSC Flight stream.\n *\n * Returns a \"renderable proxy\" that can be:\n * - Rendered directly as `{data}` in JSX\n * - Accessed for nested selections: `{data.foo.bar.Hello}`\n *\n * No slot support - for slots use `createCompositeComponent`.\n *\n * @example\n * ```tsx\n * // In a loader or server function\n * const data = await renderServerComponent(<MyServerComponent foo=\"bar\" />)\n *\n * // In the route component\n * return (\n * <div>\n * {data}\n * {data.sidebar.Menu}\n * </div>\n * )\n * ```\n */\nexport async function renderServerComponent<TNode>(\n node: ValidateRenderableServerComponent<TNode>,\n): Promise<RenderableServerComponentBuilder<TNode>> {\n const flightStream = renderToReadableStream(node)\n\n // Check if this is an SSR request (router) or a direct server function call\n const ctx = getStartContext({ throwIfNotFound: false })\n const isRouterRequest = ctx?.handlerType === 'router'\n const ssrHandler = globalThis.__RSC_SSR__\n\n // SSR path: buffer stream for replay, pre-decode for synchronous rendering\n if (isRouterRequest && ssrHandler) {\n const signal = getRequest().signal\n const stream = new ReplayableStream(flightStream, { signal })\n\n // Pre-decode during loader phase for synchronous SSR rendering\n const decoded = await ssrHandler.decode(stream)\n return ssrHandler.createRenderableProxy(\n stream,\n decoded,\n ) as RenderableServerComponentBuilder<TNode>\n }\n\n // Server function call path: return a handle for serialization\n return createRenderableHandle(\n flightStream,\n ) as unknown as RenderableServerComponentBuilder<TNode>\n}\n\n/**\n * Creates a renderable handle for server function responses.\n * Tagged with RENDERABLE_RSC for the serialization adapter.\n */\nfunction createRenderableHandle(\n flightStream: ReadableStream<Uint8Array>,\n): AnyRenderableServerComponent {\n const streamWrapper: ServerComponentStream = {\n createReplayStream: () => flightStream,\n }\n\n const stub = function RenderableRscStub(): never {\n throw new Error(\n 'Renderable RSC from server function cannot be rendered on server. ' +\n 'It should be serialized and sent to the client.',\n )\n }\n\n ;(stub as any)[SERVER_COMPONENT_STREAM] = streamWrapper\n ;(stub as any)[RENDERABLE_RSC] = true\n return stub as unknown as AnyRenderableServerComponent\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,eAAsB,sBACpB,MACkD;CAClD,MAAM,eAAe,uBAAuB,KAAK;CAIjD,MAAM,kBADM,gBAAgB,EAAE,iBAAiB,OAAO,CAAC,EAC1B,gBAAgB;CAC7C,MAAM,aAAa,WAAW;AAG9B,KAAI,mBAAmB,YAAY;EACjC,MAAM,SAAS,YAAY,CAAC;EAC5B,MAAM,SAAS,IAAI,iBAAiB,cAAc,EAAE,QAAQ,CAAC;EAG7D,MAAM,UAAU,MAAM,WAAW,OAAO,OAAO;AAC/C,SAAO,WAAW,sBAChB,QACA,QACD;;AAIH,QAAO,uBACL,aACD;;;;;;AAOH,SAAS,uBACP,cAC8B;CAC9B,MAAM,gBAAuC,EAC3C,0BAA0B,cAC3B;CAED,MAAM,OAAO,SAAS,oBAA2B;AAC/C,QAAM,IAAI,MACR,oHAED;;AAGD,MAAa,2BAA2B;AACxC,MAAa,kBAAkB;AACjC,QAAO"}
@@ -0,0 +1,2 @@
1
+ import { createFromFetch, createFromReadableStream } from "react-server-dom-rspack/client.browser";
2
+ export { createFromFetch, createFromReadableStream };
@@ -0,0 +1,13 @@
1
+ import { setOnClientReference } from "@rspack/core/rsc/ssr";
2
+ import { createFromReadableStream } from "react-server-dom-rspack/client.node";
3
+ //#region src/rsbuild/ssr-decode.ts
4
+ /**
5
+ * Rsbuild SSR decode implementation.
6
+ *
7
+ * Bundler-owned rsbuild virtual modules re-export this module for SSR-side
8
+ * Flight decode.
9
+ */
10
+ //#endregion
11
+ export { createFromReadableStream, setOnClientReference };
12
+
13
+ //# sourceMappingURL=ssr-decode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr-decode.js","names":[],"sources":["../../../src/rsbuild/ssr-decode.ts"],"sourcesContent":["/**\n * Rsbuild SSR decode implementation.\n *\n * Bundler-owned rsbuild virtual modules re-export this module for SSR-side\n * Flight decode.\n */\n\nimport { setOnClientReference } from '@rspack/core/rsc/ssr'\nimport { createFromReadableStream } from 'react-server-dom-rspack/client.node'\n\nexport { createFromReadableStream, setOnClientReference }\n"],"mappings":""}
@@ -20,11 +20,6 @@ export type RscSlotUsageEvent = {
20
20
  * The value can be either an object (proxy target) or a function (stub for server functions).
21
21
  */
22
22
  export declare function isServerComponent(value: unknown): value is AnyCompositeComponent;
23
- /**
24
- * Type guard to check if a value is a RenderableRsc (renderable proxy from renderServerComponent).
25
- * The value can be either an object (proxy target) or a function (stub for server functions).
26
- */
27
- export declare function isRenderableRsc(value: unknown): boolean;
28
23
  export type ValidateCompositeComponent<TComp> = Constrain<TComp, (props: ValidateCompositeComponentProps<TComp>) => ValidateCompositeComponentReturnType<TComp>>;
29
24
  export type ValidateCompositeComponentProps<TComp> = unknown extends TComp ? TComp : ValidateCompositeComponentPropsObject<CompositeComponentProps<TComp>>;
30
25
  export type ValidateCompositeComponentPropsObject<TProps> = unknown extends TProps ? TProps : {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Rsbuild browser decode implementation.
3
+ *
4
+ * Bundler-owned rsbuild virtual modules re-export this module for browser-side
5
+ * Flight decode.
6
+ */
7
+ export { createFromReadableStream, createFromFetch, } from 'react-server-dom-rspack/client.browser';
@@ -0,0 +1,3 @@
1
+ import { setOnClientReference } from '@rspack/core/rsc/ssr';
2
+ import { createFromReadableStream } from 'react-server-dom-rspack/client.node';
3
+ export { createFromReadableStream, setOnClientReference };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-start-rsc",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "React Server Components support for TanStack Start",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -53,6 +53,18 @@
53
53
  "default": "./dist/esm/plugin/vite.js"
54
54
  }
55
55
  },
56
+ "./rsbuild/ssr-decode": {
57
+ "import": {
58
+ "types": "./dist/esm/src/rsbuild/ssr-decode.d.ts",
59
+ "default": "./dist/esm/rsbuild/ssr-decode.js"
60
+ }
61
+ },
62
+ "./rsbuild/browser-decode": {
63
+ "import": {
64
+ "types": "./dist/esm/src/rsbuild/browser-decode.d.ts",
65
+ "default": "./dist/esm/rsbuild/browser-decode.js"
66
+ }
67
+ },
56
68
  "./package.json": "./package.json"
57
69
  },
58
70
  "files": [
@@ -64,29 +76,39 @@
64
76
  },
65
77
  "dependencies": {
66
78
  "pathe": "^2.0.3",
67
- "@tanstack/router-core": "1.168.15",
68
79
  "@tanstack/react-router": "1.168.23",
69
80
  "@tanstack/react-start-server": "1.166.41",
81
+ "@tanstack/router-core": "1.168.15",
82
+ "@tanstack/router-utils": "1.161.7",
70
83
  "@tanstack/start-client-core": "1.167.17",
71
84
  "@tanstack/start-fn-stubs": "1.161.6",
85
+ "@tanstack/start-plugin-core": "1.168.0",
72
86
  "@tanstack/start-server-core": "1.167.19",
73
- "@tanstack/start-storage-context": "1.166.29",
74
- "@tanstack/router-utils": "1.161.6",
75
- "@tanstack/start-plugin-core": "1.167.35"
87
+ "@tanstack/start-storage-context": "1.166.29"
76
88
  },
77
89
  "devDependencies": {
90
+ "@rspack/core": "2.0.0",
78
91
  "@testing-library/react": "^16.2.0",
79
92
  "@vitejs/plugin-react": "^4.3.4",
80
- "@vitejs/plugin-rsc": "^0.5.20"
93
+ "@vitejs/plugin-rsc": "^0.5.20",
94
+ "react-server-dom-rspack": "^0.0.2"
81
95
  },
82
96
  "peerDependencies": {
97
+ "@rspack/core": ">=2.0.0-0",
83
98
  "@vitejs/plugin-rsc": ">=0.5.20",
84
99
  "react": ">=18.0.0 || >=19.0.0",
85
- "react-dom": ">=18.0.0 || >=19.0.0"
100
+ "react-dom": ">=18.0.0 || >=19.0.0",
101
+ "react-server-dom-rspack": ">=0.0.2"
86
102
  },
87
103
  "peerDependenciesMeta": {
104
+ "@rspack/core": {
105
+ "optional": true
106
+ },
88
107
  "@vitejs/plugin-rsc": {
89
108
  "optional": true
109
+ },
110
+ "react-server-dom-rspack": {
111
+ "optional": true
90
112
  }
91
113
  },
92
114
  "scripts": {
@@ -61,16 +61,6 @@ export function isServerComponent(
61
61
  )
62
62
  }
63
63
 
64
- /**
65
- * Type guard to check if a value is a RenderableRsc (renderable proxy from renderServerComponent).
66
- * The value can be either an object (proxy target) or a function (stub for server functions).
67
- */
68
- export function isRenderableRsc(value: unknown): boolean {
69
- if (value === null || value === undefined) return false
70
- if (typeof value !== 'object' && typeof value !== 'function') return false
71
- return RENDERABLE_RSC in value && (value as any)[RENDERABLE_RSC] === true
72
- }
73
-
74
64
  export type ValidateCompositeComponent<TComp> = Constrain<
75
65
  TComp,
76
66
  (
@@ -3,7 +3,6 @@ import { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'
3
3
  import { getRequest } from '@tanstack/start-server-core'
4
4
  import { getStartContext } from '@tanstack/start-storage-context'
5
5
  import { sanitizeSlotArgs } from './slotUsageSanitizer'
6
-
7
6
  import { ReplayableStream } from './ReplayableStream'
8
7
  import { ClientSlot } from './ClientSlot'
9
8
  import {
@@ -103,12 +102,11 @@ export async function createCompositeComponent<TComp>(
103
102
  // For SSR we know decode fully consumed the Flight stream.
104
103
  slotUsagesEmitter?.close()
105
104
 
106
- const proxy = ssrHandler.createCompositeProxy(
105
+ return ssrHandler.createCompositeProxy(
107
106
  stream,
108
107
  decoded,
109
108
  slotUsagesEmitter?.stream,
110
- )
111
- return proxy as CompositeComponentResult<TComp>
109
+ ) as CompositeComponentResult<TComp>
112
110
  }
113
111
 
114
112
  // Server function call path:
package/src/global.d.ts CHANGED
@@ -1,5 +1,29 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
+ declare module '@rspack/core/rsc/ssr' {
4
+ export function setOnClientReference(
5
+ callback:
6
+ | ((reference: {
7
+ id: string
8
+ deps: { js: Array<string>; css: Array<string> }
9
+ runtime: 'rsbuild'
10
+ }) => void)
11
+ | null
12
+ | undefined,
13
+ ): void
14
+ export function getManifest(): {
15
+ moduleCssFiles?: Record<string, Array<string>>
16
+ [key: string]: unknown
17
+ }
18
+ }
19
+
20
+ declare module '@rspack/core/rsc/browser' {
21
+ export function getManifest(): {
22
+ moduleCssFiles?: Record<string, Array<string>>
23
+ [key: string]: unknown
24
+ }
25
+ }
26
+
3
27
  /**
4
28
  * Type declarations for virtual:tanstack-rsc-runtime
5
29
  *
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Type declarations for react-server-dom-rspack.
3
+ *
4
+ * This package ships no .d.ts files. Declarations here cover only the
5
+ * subset of the API surface that TanStack Start uses.
6
+ */
7
+
8
+ declare module 'react-server-dom-rspack/client.node' {
9
+ export function createFromReadableStream<T = unknown>(
10
+ stream: ReadableStream<Uint8Array>,
11
+ options?: object,
12
+ ): Promise<T>
13
+ }
14
+
15
+ declare module 'react-server-dom-rspack/client.browser' {
16
+ export function createFromReadableStream<T = unknown>(
17
+ stream: ReadableStream<Uint8Array>,
18
+ options?: object,
19
+ ): Promise<T>
20
+
21
+ export function createFromFetch<T = unknown>(
22
+ fetchPromise: Promise<Response>,
23
+ options?: object,
24
+ ): Promise<T>
25
+ }
26
+
27
+ declare module 'react-server-dom-rspack/server' {
28
+ export function renderToReadableStream<T>(
29
+ data: T,
30
+ options?: object,
31
+ ): ReadableStream<Uint8Array>
32
+
33
+ export function createFromReadableStream<T = unknown>(
34
+ stream: ReadableStream<Uint8Array>,
35
+ options?: object,
36
+ ): Promise<T>
37
+
38
+ export function createTemporaryReferenceSet(): object
39
+
40
+ export function decodeReply<T = unknown>(
41
+ body: string | FormData,
42
+ options?: object,
43
+ ): Promise<T>
44
+
45
+ export function loadServerAction(id: string): Promise<(...args: any) => any>
46
+
47
+ export function decodeAction<T = unknown>(
48
+ body: FormData,
49
+ serverManifest?: unknown,
50
+ ): Promise<() => T>
51
+
52
+ export function decodeFormState<T = unknown>(
53
+ actionResult: T,
54
+ body: FormData,
55
+ serverManifest?: unknown,
56
+ ): Promise<unknown>
57
+ }
@@ -1,7 +1,6 @@
1
1
  import { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'
2
2
  import { getRequest } from '@tanstack/start-server-core'
3
3
  import { getStartContext } from '@tanstack/start-storage-context'
4
-
5
4
  import { ReplayableStream } from './ReplayableStream'
6
5
  import { RENDERABLE_RSC, SERVER_COMPONENT_STREAM } from './ServerComponentTypes'
7
6
  import type {
@@ -59,7 +58,6 @@ export function isRenderableRscHandle(
59
58
  export async function renderServerComponent<TNode>(
60
59
  node: ValidateRenderableServerComponent<TNode>,
61
60
  ): Promise<RenderableServerComponentBuilder<TNode>> {
62
- // Render the element directly to a Flight stream
63
61
  const flightStream = renderToReadableStream(node)
64
62
 
65
63
  // Check if this is an SSR request (router) or a direct server function call
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Rsbuild browser decode implementation.
3
+ *
4
+ * Bundler-owned rsbuild virtual modules re-export this module for browser-side
5
+ * Flight decode.
6
+ */
7
+
8
+ export {
9
+ createFromReadableStream,
10
+ createFromFetch,
11
+ } from 'react-server-dom-rspack/client.browser'
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Rsbuild SSR decode implementation.
3
+ *
4
+ * Bundler-owned rsbuild virtual modules re-export this module for SSR-side
5
+ * Flight decode.
6
+ */
7
+
8
+ import { setOnClientReference } from '@rspack/core/rsc/ssr'
9
+ import { createFromReadableStream } from 'react-server-dom-rspack/client.node'
10
+
11
+ export { createFromReadableStream, setOnClientReference }