{"version":3,"file":"renderRouterToStream.cjs","names":[],"sources":["../../../src/ssr/renderRouterToStream.tsx"],"sourcesContent":["import { PassThrough } from 'node:stream'\nimport ReactDOMServer from 'react-dom/server'\nimport { isbot } from 'isbot'\nimport {\n  createSsrStreamResponse,\n  transformPipeableStreamWithRouter,\n  transformReadableStreamWithRouter,\n} from '@tanstack/router-core/ssr/server'\nimport type { AnyRouter } from '@tanstack/router-core'\nimport type { ReadableStream } from 'node:stream/web'\nimport type { ReactNode } from 'react'\n\nconst noop = () => {}\n\n// Bot responses wait for `allReady` so crawlers receive complete HTML.\n// If the request disconnects during that wait, React may not settle quickly;\n// unblock the wait so the response pipeline can abort and clean up.\nasync function waitForReadyOrAbort(\n  ready: Promise<unknown>,\n  signal: AbortSignal,\n) {\n  let cleanup = noop\n  try {\n    await Promise.race([\n      ready,\n      new Promise<void>((resolve) => {\n        const onAbort = () => resolve()\n        cleanup = () => signal.removeEventListener('abort', onAbort)\n        signal.addEventListener('abort', onAbort, { once: true })\n        if (signal.aborted) resolve()\n      }),\n    ])\n  } finally {\n    cleanup()\n  }\n}\n\n// A client disconnecting mid-stream is normal operation, not a render\n// failure; don't let React's onError log it as one.\nconst isAbortError = (request: Request, error: unknown) =>\n  (request.signal.aborted && error === request.signal.reason) ||\n  (error instanceof Error && error.name === 'AbortError')\n\nexport const renderRouterToStream = async ({\n  request,\n  router,\n  responseHeaders,\n  children,\n}: {\n  request: Request\n  router: AnyRouter\n  responseHeaders: Headers\n  children: ReactNode\n}) => {\n  if (typeof ReactDOMServer.renderToReadableStream === 'function') {\n    const stream = await ReactDOMServer.renderToReadableStream(children, {\n      signal: request.signal,\n      nonce: router.options.ssr?.nonce,\n      progressiveChunkSize: Number.POSITIVE_INFINITY,\n      onError: (error, info) => {\n        if (!isAbortError(request, error)) {\n          console.error('Error in renderToReadableStream:', error, info)\n        }\n      },\n    })\n\n    if (isbot(request.headers.get('User-Agent'))) {\n      await waitForReadyOrAbort(stream.allReady, request.signal)\n    }\n\n    const responseStream = transformReadableStreamWithRouter(\n      router,\n      stream as unknown as ReadableStream,\n      { onAbort: () => stream.cancel().catch(() => {}) },\n    )\n    return createSsrStreamResponse(\n      router,\n      new Response(responseStream as any, {\n        status: router.stores.statusCode.get(),\n        headers: responseHeaders,\n      }),\n    )\n  }\n\n  if (typeof ReactDOMServer.renderToPipeableStream === 'function') {\n    const reactAppPassthrough = new PassThrough()\n\n    let pipeable:\n      | ReturnType<typeof ReactDOMServer.renderToPipeableStream>\n      | undefined\n    let responseAttached = false\n    let aborted = false\n    let endedBeforeAttach = false\n    let pendingAbortReason: unknown\n    const toError = (reason: unknown) =>\n      reason instanceof Error\n        ? reason\n        : new Error(String(reason ?? 'SSR aborted'))\n    const destroyError = (reason: unknown) =>\n      reason === undefined ? undefined : toError(reason)\n    const pendingDestroyError = () =>\n      pendingAbortReason === undefined\n        ? toError(pendingAbortReason)\n        : destroyError(pendingAbortReason)\n    const finishPassThrough = (\n      reason: unknown,\n      opts?: { defaultError?: boolean },\n    ) => {\n      if (reactAppPassthrough.destroyed) return\n      if (responseAttached) {\n        reactAppPassthrough.destroy(\n          opts?.defaultError ? toError(reason) : destroyError(reason),\n        )\n      } else {\n        endedBeforeAttach = true\n        // onError can fire synchronously before React returns the pipeable\n        // handle and before Readable.toWeb() is attached. Defer touching the\n        // PassThrough until after the router transform can observe the error.\n      }\n    }\n    const abortPipeable = (\n      reason?: unknown,\n      opts?: { defaultError?: boolean },\n    ) => {\n      if (aborted) return\n      aborted = true\n      pendingAbortReason = reason\n      const err = toError(reason)\n      try {\n        pipeable?.abort(err)\n      } catch {\n        // ignore — React may throw if already aborted/finished\n      }\n      finishPassThrough(reason, opts)\n    }\n\n    // Register before attaching the router transform; the transform may\n    // synchronously cleanup/error, and cleanup must still remove this listener.\n    if (request.signal.aborted) {\n      abortPipeable(request.signal.reason)\n    } else {\n      const onRequestAbort = () => abortPipeable(request.signal.reason)\n      request.signal.addEventListener('abort', onRequestAbort, { once: true })\n      router.serverSsr?.onCleanup(() => {\n        request.signal.removeEventListener('abort', onRequestAbort)\n      })\n    }\n\n    try {\n      pipeable = ReactDOMServer.renderToPipeableStream(children, {\n        nonce: router.options.ssr?.nonce,\n        progressiveChunkSize: Number.POSITIVE_INFINITY,\n        ...(isbot(request.headers.get('User-Agent'))\n          ? {\n              onAllReady() {\n                pipeable!.pipe(reactAppPassthrough)\n              },\n            }\n          : {\n              onShellReady() {\n                pipeable!.pipe(reactAppPassthrough)\n              },\n            }),\n        onError: (error, info) => {\n          if (!isAbortError(request, error)) {\n            console.error('Error in renderToPipeableStream:', error, info)\n          }\n          abortPipeable(error, { defaultError: true })\n        },\n      })\n    } catch (e) {\n      console.error('Error in renderToPipeableStream:', e)\n      router.serverSsr?.cleanup()\n      throw e\n    }\n\n    const responseStream = transformPipeableStreamWithRouter(\n      router,\n      reactAppPassthrough,\n      { onAbort: abortPipeable },\n    )\n    responseAttached = true\n\n    if (endedBeforeAttach) {\n      reactAppPassthrough.destroy(pendingDestroyError())\n    }\n\n    // React's onError may have fired synchronously inside\n    // renderToPipeableStream before `pipeable` was assigned. If so,\n    // abortPipeable ran without a pipeable handle; re-apply the abort now.\n    if (aborted && pipeable) {\n      try {\n        pipeable.abort(toError(pendingAbortReason))\n      } catch {\n        // ignore — React may throw if already aborted/finished\n      }\n    }\n\n    return createSsrStreamResponse(\n      router,\n      new Response(responseStream as any, {\n        status: router.stores.statusCode.get(),\n        headers: responseHeaders,\n      }),\n    )\n  }\n\n  throw new Error(\n    'No renderToReadableStream or renderToPipeableStream found in react-dom/server. Ensure you are using a version of react-dom that supports streaming.',\n  )\n}\n"],"mappings":"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"}