UNPKG

@tanstack/router-core

Version:

Modern and scalable routing for React applications

1 lines 20.1 kB
{"version":3,"file":"transformStreamWithRouter.cjs","names":[],"sources":["../../../src/ssr/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { TSR_SCRIPT_BARRIER_ID } from './constants'\nimport type { AnyRouter } from '../router'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}