@tanstack/router-core 1.171.14 → 1.171.16-pre.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.
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +3 -12
- package/dist/cjs/await-signal.cjs +21 -0
- package/dist/cjs/await-signal.cjs.map +1 -0
- package/dist/cjs/await-signal.d.cts +1 -0
- package/dist/cjs/index.cjs +3 -1
- package/dist/cjs/index.d.cts +3 -2
- package/dist/cjs/isServer/client.cjs +2 -0
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +2 -0
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +2 -0
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/load-client.cjs +1470 -0
- package/dist/cjs/load-client.cjs.map +1 -0
- package/dist/cjs/load-client.d.cts +147 -0
- package/dist/cjs/load-server.cjs +565 -0
- package/dist/cjs/load-server.cjs.map +1 -0
- package/dist/cjs/load-server.d.cts +16 -0
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +0 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs +187 -445
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +62 -52
- package/dist/cjs/scroll-restoration.cjs +27 -34
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/ssr/client.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs +60 -9
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.d.cts +1 -0
- package/dist/cjs/ssr/handlerCallback.cjs +49 -4
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
- package/dist/cjs/ssr/handlerCallback.d.cts +3 -0
- package/dist/cjs/ssr/server.cjs +4 -0
- package/dist/cjs/ssr/server.d.cts +2 -2
- package/dist/cjs/ssr/ssr-client.d.cts +1 -10
- package/dist/cjs/ssr/ssr-match-id.cjs +2 -2
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +6 -6
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +27 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +2 -0
- package/dist/cjs/ssr/types.d.cts +0 -1
- package/dist/cjs/stores.cjs +30 -104
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +7 -32
- package/dist/cjs/utils.cjs +0 -4
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/Matches.d.ts +3 -12
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/await-signal.d.ts +1 -0
- package/dist/esm/await-signal.js +21 -0
- package/dist/esm/await-signal.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +2 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +2 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +2 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/load-client.d.ts +147 -0
- package/dist/esm/load-client.js +1462 -0
- package/dist/esm/load-client.js.map +1 -0
- package/dist/esm/load-server.d.ts +16 -0
- package/dist/esm/load-server.js +565 -0
- package/dist/esm/load-server.js.map +1 -0
- package/dist/esm/redirect.d.ts +0 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +62 -52
- package/dist/esm/router.js +188 -446
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +27 -34
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/client.js +1 -1
- package/dist/esm/ssr/createRequestHandler.d.ts +1 -0
- package/dist/esm/ssr/createRequestHandler.js +61 -11
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/handlerCallback.d.ts +3 -0
- package/dist/esm/ssr/handlerCallback.js +47 -5
- package/dist/esm/ssr/handlerCallback.js.map +1 -1
- package/dist/esm/ssr/server.d.ts +2 -2
- package/dist/esm/ssr/server.js +3 -3
- package/dist/esm/ssr/ssr-client.d.ts +1 -10
- package/dist/esm/ssr/ssr-match-id.js +2 -2
- package/dist/esm/ssr/ssr-match-id.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +6 -6
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +2 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +27 -1
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/ssr/types.d.ts +0 -1
- package/dist/esm/stores.d.ts +7 -32
- package/dist/esm/stores.js +30 -104
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.js +1 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/Matches.ts +6 -21
- package/src/await-signal.ts +27 -0
- package/src/index.ts +1 -3
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +1 -0
- package/src/isServer/server.ts +1 -0
- package/src/load-client.ts +2701 -0
- package/src/load-server.ts +849 -0
- package/src/redirect.ts +0 -1
- package/src/route.ts +2 -11
- package/src/router.ts +380 -774
- package/src/scroll-restoration.ts +40 -56
- package/src/ssr/createRequestHandler.ts +109 -16
- package/src/ssr/handlerCallback.ts +76 -10
- package/src/ssr/server.ts +4 -1
- package/src/ssr/ssr-client.ts +1 -310
- package/src/ssr/ssr-match-id.ts +11 -2
- package/src/ssr/ssr-server.ts +9 -8
- package/src/ssr/transformStreamWithRouter.ts +37 -1
- package/src/ssr/types.ts +0 -1
- package/src/stores.ts +46 -208
- package/dist/cjs/load-matches.cjs +0 -658
- package/dist/cjs/load-matches.cjs.map +0 -1
- package/dist/cjs/load-matches.d.cts +0 -18
- package/dist/cjs/ssr/ssr-client.cjs +0 -183
- package/dist/cjs/ssr/ssr-client.cjs.map +0 -1
- package/dist/esm/load-matches.d.ts +0 -18
- package/dist/esm/load-matches.js +0 -656
- package/dist/esm/load-matches.js.map +0 -1
- package/dist/esm/ssr/ssr-client.js +0 -183
- package/dist/esm/ssr/ssr-client.js.map +0 -1
- package/src/load-matches.ts +0 -1278
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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 type TransformStreamWithRouterOptions = {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 120000ms). Safety net for cleanup. */\n lifetimeMs?: number\n /**\n * Called exactly once when the stream is torn down due to abort/error/\n * cancel/timeout — NOT on natural successful completion. Use this to\n * abort a hidden producer upstream of any PassThrough you passed in\n * (e.g. React `renderToPipeableStream`'s `abort()`).\n * Errors thrown from this callback are swallowed.\n */\n onAbort?: (reason?: unknown) => void\n}\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n opts?: TransformStreamWithRouterOptions,\n) {\n return transformStreamWithRouter(router, routerStream, opts)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n opts?: TransformStreamWithRouterOptions,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream), opts),\n )\n}\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 = DEFAULT_SERIALIZATION_TIMEOUT_MS * 2\nconst MAX_LEFTOVER_CHARS = 2048\nconst MAX_TAIL_CHARS = 64 * 1024\nconst MAX_ROUTER_HTML_CHARS = 16 * 1024 * 1024\nconst MAX_PENDING_WRITE_CHARS = 16 * 1024 * 1024\n\n// Merge lifecycle: body bytes can stream, router HTML must precede tail,\n// terminal states own close/error/cleanup exactly once.\nconst MergeState = {\n ReadingBody: 0,\n HoldingTail: 1,\n AppDone: 2,\n Draining: 3,\n Done: 4,\n} as const\n\ntype MergeState = (typeof MergeState)[keyof typeof MergeState]\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\nconst noop = () => {}\nconst resolvedPromise = Promise.resolve()\n\n// Returns -bodyEndIndex - 2 when </body> is found; otherwise returns\n// the position after the last valid closing tag, or -1 when none exists.\nfunction findHtmlBoundary(str: string): number {\n let lastClosingTagEnd = -1\n let searchFrom = str.length - MIN_CLOSING_TAG_LENGTH\n\n while (searchFrom >= 0) {\n const openSlash = str.lastIndexOf('</', searchFrom)\n if (openSlash === -1) break\n\n // Fast case-insensitive match for </body>. Negative return encodes the\n // body start index without allocating a result object.\n if (\n (str.charCodeAt(openSlash + 2) | 32) === 98 &&\n (str.charCodeAt(openSlash + 3) | 32) === 111 &&\n (str.charCodeAt(openSlash + 4) | 32) === 100 &&\n (str.charCodeAt(openSlash + 5) | 32) === 121 &&\n str.charCodeAt(openSlash + 6) === 62\n ) {\n return -openSlash - 2\n }\n\n if (lastClosingTagEnd === -1) {\n let i = openSlash + 2\n const startCode = str.charCodeAt(i)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n i++\n while (i < str.length) {\n const code = str.charCodeAt(i)\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 i++\n } else {\n break\n }\n }\n\n if (str.charCodeAt(i) === 62) {\n lastClosingTagEnd = i + 1\n }\n }\n }\n\n searchFrom = openSlash - 1\n }\n\n return lastClosingTagEnd\n}\n\n/**\n * Releasing the lock can throw if a pending read is still settling or if the\n * lock was already released.\n */\ntype ReaderOps = {\n cancel: (reason?: unknown) => Promise<unknown>\n releaseLock: () => void\n}\n\nfunction safeReleaseReader(reader: ReaderOps) {\n try {\n reader.releaseLock()\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Cancel a reader without producing an unhandled rejection. `reader.cancel()`\n * can reject (e.g. when the underlying source's cancel() throws), and\n * downstream cancel() should still wait for upstream teardown when possible.\n */\nfunction safeCancelReader(reader: ReaderOps, reason?: unknown): Promise<void> {\n let cancelPromise: Promise<unknown> | undefined\n try {\n cancelPromise = reader.cancel(reason)\n } catch {\n // ignore\n }\n\n if (!safeReleaseReader(reader) && cancelPromise) {\n return cancelPromise.then(noop, noop).then(() => {\n safeReleaseReader(reader)\n })\n }\n\n return cancelPromise ? cancelPromise.then(noop, noop) : resolvedPromise\n}\n\nfunction createReaderState<T>(appStream: ReadableStream<T>) {\n const reader = appStream.getReader()\n let released = false\n\n return {\n reader,\n cancel: (reason?: unknown) => {\n if (released) return resolvedPromise\n released = true\n return safeCancelReader(reader, reason)\n },\n release: () => {\n if (released) return\n released = true\n safeReleaseReader(reader)\n },\n }\n}\n\nfunction createAbortNotifier(opts?: TransformStreamWithRouterOptions) {\n let abortNotified = false\n return (reason?: unknown) => {\n if (abortNotified) return\n abortNotified = true\n try {\n opts?.onAbort?.(reason)\n } catch {\n // swallow user errors\n }\n }\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: TransformStreamWithRouterOptions,\n) {\n const serverSsr = router.serverSsr\n if (!serverSsr) {\n throw new Error('Invariant failed: router.serverSsr is required')\n }\n if (serverSsr.reserveStreamFastPath()) {\n return makeFastPathStream(appStream, opts, serverSsr)\n }\n\n return makeMainStream(serverSsr, appStream, opts)\n}\n\n// =====================================================================\n// Fast path: passthrough with cleanup + backpressure on app reads.\n// =====================================================================\nfunction makeFastPathStream(\n appStream: ReadableStream<Uint8Array>,\n opts?: TransformStreamWithRouterOptions,\n serverSsr?: NonNullable<AnyRouter['serverSsr']>,\n) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let state: MergeState = MergeState.ReadingBody\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let stopListeningToInjectedHtml: (() => void) | undefined\n const readerState = createReaderState(appStream)\n const notifyAbort = createAbortNotifier(opts)\n const isDone = () => state === MergeState.Done\n let renderFinished = false\n\n const finishSsrRendering = () => {\n if (!serverSsr || renderFinished) return true\n renderFinished = true\n try {\n serverSsr.setRenderFinished()\n return true\n } catch (error) {\n safeError(error)\n cleanup(error)\n return false\n }\n }\n\n const cleanup = (reason?: unknown, cancelReader = true) => {\n if (cleanedUp) return resolvedPromise\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n try {\n stopListeningToInjectedHtml?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n\n if (cancelReader) {\n // Notify the producer immediately. Reader cancellation may take time to\n // settle, and upstream renderers must tolerate abort + cancel overlap.\n notifyAbort(reason)\n }\n const readerDone = cancelReader\n ? readerState.cancel(reason)\n : (readerState.release(), resolvedPromise)\n if (serverSsr) {\n try {\n serverSsr.cleanup()\n } catch (error) {\n console.error('Error in SSR cleanup:', error)\n }\n }\n return readerDone\n }\n\n const safeClose = () => {\n if (isDone()) return\n state = MergeState.Done\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isDone()) return\n state = MergeState.Done\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n if (serverSsr) {\n stopListeningToInjectedHtml = serverSsr.onInjectedHtml(() => {\n const err = new Error('SSR router HTML injected during fast path')\n safeError(err)\n cleanup(err)\n })\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isDone()) {\n const err = new Error('Stream lifetime exceeded')\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(err)\n cleanup(err)\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n async pull(c) {\n if (cleanedUp || isDone()) return\n try {\n const { done, value } = await readerState.reader.read()\n if (!done) {\n if (!cleanedUp && !isDone()) {\n c.enqueue(value)\n }\n return\n }\n\n if (cleanedUp || isDone()) return\n\n if (!finishSsrRendering()) return\n safeClose()\n return cleanup(undefined, false)\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n if (state < MergeState.AppDone) {\n try {\n serverSsr?.setRenderFinished()\n } catch {\n // ignore\n }\n }\n safeError(error)\n return cleanup(error)\n } finally {\n if (cleanedUp || isDone()) {\n readerState.release()\n }\n }\n },\n cancel(reason) {\n state = MergeState.Done\n return cleanup(reason)\n },\n })\n\n return stream\n}\n\n// =====================================================================\n// Main path: scan + inject router HTML/scripts with full backpressure.\n//\n// ALL output (app chunks AND router-injected HTML/scripts) flows through a\n// single pendingWrites queue and is only enqueued onto the downstream\n// controller when desiredSize > 0. This prevents native-memory growth of\n// queued Uint8Arrays under slow HTTP consumers.\n// =====================================================================\nfunction makeMainStream(\n serverSsr: NonNullable<AnyRouter['serverSsr']>,\n appStream: ReadableStream,\n opts?: TransformStreamWithRouterOptions,\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<Uint8Array> | undefined\n let closeWhenDrained = false\n let state: MergeState = MergeState.ReadingBody\n\n const readerState = createReaderState(appStream)\n const notifyAbort = createAbortNotifier(opts)\n\n // Single output queue: app chunks + router-injected HTML/scripts.\n // Stored as STRINGS to avoid holding native-backed Uint8Arrays in our queue\n // while waiting for downstream capacity. Encoding happens at enqueue time\n // (drainPending) so the bytes live only inside the controller's internal\n // queue, not in two places.\n //\n // Uses an index pointer instead of Array.prototype.shift() (which is O(n))\n // so many small router-injected script chunks stay O(1) per chunk.\n const pendingWrites: Array<string> = []\n let pendingWriteHead = 0\n let pendingWriteChars = 0\n\n function clearPending() {\n pendingWrites.length = 0\n pendingWriteHead = 0\n pendingWriteChars = 0\n }\n\n // Backpressure: pull() resolves drainResolve to let the read loop advance.\n let drainResolve: (() => void) | null = null\n const waitForDrain = () =>\n new Promise<void>((r) => {\n drainResolve = r\n })\n const signalDrain = () => {\n if (drainResolve) {\n const r = drainResolve\n drainResolve = null\n r()\n }\n }\n\n const isDone = () => state === MergeState.Done\n\n function drainPending() {\n if (!controller || isDone()) return\n while (pendingWriteHead < pendingWrites.length) {\n const ds = controller.desiredSize\n if (ds !== null && ds <= 0) return\n const next = pendingWrites[pendingWriteHead]!\n // Release reference for GC; compact when fully drained.\n pendingWrites[pendingWriteHead] = ''\n pendingWriteHead++\n pendingWriteChars -= next.length\n try {\n controller.enqueue(textEncoder.encode(next))\n } catch (error) {\n safeError(error)\n cleanup(error)\n return\n }\n }\n // Fully drained: reset array so it doesn't grow unbounded across SSR.\n if (pendingWriteHead >= pendingWrites.length) {\n pendingWrites.length = 0\n pendingWriteHead = 0\n }\n // If we've flushed everything and tryFinish requested close, close now.\n if (closeWhenDrained && pendingWriteHead >= pendingWrites.length) {\n closeWhenDrained = false\n safeClose()\n cleanup(undefined, false)\n }\n }\n\n /**\n * Enqueue a string chunk through the backpressure queue. Stored as a\n * string and encoded only when the downstream actually accepts the chunk\n * — keeps native-memory pressure inside the controller's queue (which\n * honors desiredSize) rather than ours.\n */\n function writeChunk(chunk: string) {\n if (cleanedUp || isDone()) return\n if (!chunk.length) return\n if (pendingWriteChars + chunk.length > MAX_PENDING_WRITE_CHARS) {\n const err = new Error('SSR stream pending output exceeded maximum buffer')\n safeError(err)\n cleanup(err)\n return\n }\n pendingWrites.push(chunk)\n pendingWriteChars += chunk.length\n drainPending()\n }\n\n function safeClose() {\n if (isDone()) return\n state = MergeState.Done\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isDone()) return\n state = MergeState.Done\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(reason?: unknown, cancelReader = true) {\n if (cleanedUp) return resolvedPromise\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 clearPendingRouterHtml()\n leftover = ''\n pendingTail = ''\n clearPending()\n\n if (cancelReader) {\n // Notify the producer immediately. Reader cancellation may take time to\n // settle, and upstream renderers must tolerate abort + cancel overlap.\n notifyAbort(reason)\n }\n const readerDone = cancelReader\n ? readerState.cancel(reason)\n : (readerState.release(), resolvedPromise)\n signalDrain()\n try {\n serverSsr.cleanup()\n } catch (error) {\n console.error('Error in SSR cleanup:', error)\n }\n return readerDone\n }\n\n const textDecoder = new TextDecoder()\n\n // Router-injected scripts/HTML waiting for the next safe body boundary.\n // Keep chunks separate so flushing does not flatten a large rope string.\n const pendingRouterHtml: Array<string> = []\n let pendingRouterHtmlChars = 0\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured bytes from </body> onward; must stay behind router scripts.\n let pendingTail = ''\n\n let streamBarrierLifted = false\n let streamBarrierMarkerSeen = false\n let serializationFinished = false\n\n function noteBarrierMarker(chunk: string) {\n if (streamBarrierMarkerSeen) return\n if (chunk.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierMarkerSeen = true\n }\n }\n\n function liftBarrierAfterBoundary() {\n if (streamBarrierLifted) return\n if (!streamBarrierMarkerSeen) return\n streamBarrierLifted = true\n serverSsr.liftScriptBarrier()\n }\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n // If anything queued before start (shouldn't happen but be safe), drain.\n drainPending()\n },\n pull() {\n // Consumer has capacity; flush queue then unblock read loop.\n drainPending()\n signalDrain()\n },\n cancel(reason) {\n state = MergeState.Done\n return cleanup(reason)\n },\n })\n\n function drainRouterHtml() {\n if (cleanedUp || isDone()) return\n let html: string | undefined\n try {\n html = serverSsr.takeBufferedHtml()\n } catch (error) {\n safeError(error)\n cleanup(error)\n return\n }\n if (!html) return\n if (state >= MergeState.Draining) {\n // At this point final tail/close has already been queued. Emitting late\n // router HTML would put scripts after </body> or drop them silently.\n const err = new Error(\n 'SSR router HTML injected after stream finalization',\n )\n safeError(err)\n cleanup(err)\n return\n }\n if (state === MergeState.HoldingTail) {\n flushPendingRouterHtml()\n writeChunk(html)\n } else {\n if (pendingRouterHtmlChars + html.length > MAX_ROUTER_HTML_CHARS) {\n const err = new Error('SSR router HTML exceeded maximum buffer')\n safeError(err)\n cleanup(err)\n return\n }\n pendingRouterHtml.push(html)\n pendingRouterHtmlChars += html.length\n }\n }\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml.length) return\n for (const html of pendingRouterHtml) {\n writeChunk(html)\n }\n clearPendingRouterHtml()\n }\n\n function clearPendingRouterHtml() {\n pendingRouterHtml.length = 0\n pendingRouterHtmlChars = 0\n }\n\n function appendTail(chunk: string) {\n pendingTail += chunk\n if (pendingTail.length > MAX_TAIL_CHARS) {\n throw new Error('SSR stream tail exceeded maximum buffer')\n }\n }\n\n function waitForBackpressure() {\n return !!(\n controller &&\n controller.desiredSize !== null &&\n controller.desiredSize <= 0\n )\n }\n\n function startSerializationTimeout() {\n if (cleanedUp || isDone()) return\n if (serializationTimeoutHandle !== undefined) return\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isDone()) {\n const err = new Error('Serialization timeout after app render finished')\n console.error('Serialization timeout after app render finished')\n safeError(err)\n cleanup(err)\n }\n }, timeoutMs)\n }\n\n /**\n * Finish only when app done and serialization complete. Queues final\n * output and requests close-when-drained so we don't close ahead of\n * pending writes still waiting on downstream capacity.\n */\n function tryFinish() {\n if (state !== MergeState.AppDone || !serializationFinished) return\n if (cleanedUp || isDone()) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n drainRouterHtml()\n if (cleanedUp || isDone()) return\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) writeChunk(leftover)\n if (cleanedUp || isDone()) return\n if (decoderRemainder) writeChunk(decoderRemainder)\n if (cleanedUp || isDone()) return\n flushPendingRouterHtml()\n if (cleanedUp || isDone()) return\n if (pendingTail) writeChunk(pendingTail)\n if (cleanedUp || isDone()) return\n\n leftover = ''\n pendingTail = ''\n\n state = MergeState.Draining\n closeWhenDrained = true\n // Try immediately; if queue not drained yet, pull() will retry.\n drainPending()\n }\n\n function finishAppRendering() {\n if (state >= MergeState.AppDone) return\n state = MergeState.AppDone\n try {\n serverSsr.setRenderFinished()\n } catch (error) {\n safeError(error)\n cleanup(error)\n return\n }\n drainRouterHtml()\n if (cleanedUp || isDone()) return\n serializationFinished =\n serializationFinished || serverSsr.isSerializationFinished()\n if (serializationFinished) {\n tryFinish()\n } else {\n startSerializationTimeout()\n }\n }\n\n // Safety net: cleanup even if consumer never reads\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n const lifetimeMs = opts?.lifetimeMs ?? timeoutMs * 2\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isDone()) {\n const err = new Error('Stream lifetime exceeded')\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(err)\n cleanup(err)\n }\n }, lifetimeMs)\n\n stopListeningToInjectedHtml = serverSsr.onInjectedHtml(() => {\n drainRouterHtml()\n })\n\n stopListeningToSerializationFinished = serverSsr.onSerializationFinished(\n () => {\n serializationFinished = true\n drainRouterHtml()\n tryFinish()\n },\n )\n\n // Subscriptions are installed before snapshots, so missed events are\n // recovered by these synchronous drains/rechecks.\n drainRouterHtml()\n if (cleanedUp || isDone()) return stream\n serializationFinished =\n serializationFinished || serverSsr.isSerializationFinished()\n if (serializationFinished) {\n drainRouterHtml()\n if (cleanedUp || isDone()) return stream\n }\n\n // Transform the appStream\n ;(async () => {\n try {\n while (true) {\n // Backpressure: pause upstream reads while downstream is full.\n if (waitForBackpressure()) {\n await waitForDrain()\n if (cleanedUp || isDone()) return\n }\n\n const { done, value } = await readerState.reader.read()\n if (done) break\n\n if (cleanedUp || isDone()) return\n\n const text =\n typeof value === 'string'\n ? value\n : textDecoder.decode(value as ArrayBufferView, { stream: true })\n\n const chunkString = leftover ? leftover + text : text\n\n // If we already saw </body>, everything else is tail. Keep it bounded\n // and held until router scripts are ready so injection remains before </body>.\n if (state >= MergeState.HoldingTail) {\n appendTail(chunkString)\n leftover = ''\n continue\n }\n\n const boundary = findHtmlBoundary(chunkString)\n if (boundary < -1) {\n const bodyEndIndex = -boundary - 2\n state = MergeState.HoldingTail\n appendTail(chunkString.slice(bodyEndIndex))\n const bodyChunk = chunkString.slice(0, bodyEndIndex)\n writeChunk(bodyChunk)\n if (cleanedUp || isDone()) return\n noteBarrierMarker(bodyChunk)\n liftBarrierAfterBoundary()\n if (cleanedUp || isDone()) return\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = boundary\n\n if (lastClosingTagEnd > 0) {\n const safeChunk = chunkString.slice(0, lastClosingTagEnd)\n writeChunk(safeChunk)\n if (cleanedUp || isDone()) return\n noteBarrierMarker(safeChunk)\n liftBarrierAfterBoundary()\n if (cleanedUp || isDone()) return\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 noteBarrierMarker(leftover)\n const flushed = leftover.slice(\n 0,\n leftover.length - MAX_LEFTOVER_CHARS,\n )\n writeChunk(flushed)\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 noteBarrierMarker(combined)\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n const flushed = combined.slice(0, flushUpto)\n writeChunk(flushed)\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isDone()) return\n\n finishAppRendering()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n if (state < MergeState.AppDone) {\n try {\n serverSsr.setRenderFinished()\n } catch {\n // ignore\n }\n }\n safeError(error)\n cleanup(error)\n } finally {\n readerState.release()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup(error)\n })\n\n return stream\n}\n"],"mappings":";;;;AAoBA,SAAgB,kCACd,QACA,cACA,MACA;CACA,OAAO,0BAA0B,QAAQ,cAAc,IAAI;AAC7D;AAEA,SAAgB,kCACd,QACA,cACA,MACA;CACA,OAAO,YAAA,SAAS,QACd,0BAA0B,QAAQ,YAAA,SAAS,MAAM,YAAY,GAAG,IAAI,CACtE;AACF;AAGA,MAAM,yBAAyB;AAG/B,MAAM,mCAAmC;AACzC,MAAM,8BAA8B,mCAAmC;AACvE,MAAM,qBAAqB;AAC3B,MAAM,iBAAiB,KAAK;AAC5B,MAAM,wBAAwB,KAAK,OAAO;AAC1C,MAAM,0BAA0B,KAAK,OAAO;AAI5C,MAAM,aAAa;CACjB,aAAa;CACb,aAAa;CACb,SAAS;CACT,UAAU;CACV,MAAM;AACR;AAKA,MAAM,cAAc,IAAI,YAAY;AAEpC,MAAM,aAAa,CAAC;AACpB,MAAM,kBAAkB,QAAQ,QAAQ;AAIxC,SAAS,iBAAiB,KAAqB;CAC7C,IAAI,oBAAoB;CACxB,IAAI,aAAa,IAAI,SAAS;CAE9B,OAAO,cAAc,GAAG;EACtB,MAAM,YAAY,IAAI,YAAY,MAAM,UAAU;EAClD,IAAI,cAAc,IAAI;EAItB,KACG,IAAI,WAAW,YAAY,CAAC,IAAI,QAAQ,OACxC,IAAI,WAAW,YAAY,CAAC,IAAI,QAAQ,QACxC,IAAI,WAAW,YAAY,CAAC,IAAI,QAAQ,QACxC,IAAI,WAAW,YAAY,CAAC,IAAI,QAAQ,OACzC,IAAI,WAAW,YAAY,CAAC,MAAM,IAElC,OAAO,CAAC,YAAY;EAGtB,IAAI,sBAAsB,IAAI;GAC5B,IAAI,IAAI,YAAY;GACpB,MAAM,YAAY,IAAI,WAAW,CAAC;GAClC,IACG,aAAa,MAAM,aAAa,OAChC,aAAa,MAAM,aAAa,IACjC;IACA;IACA,OAAO,IAAI,IAAI,QAAQ;KACrB,MAAM,OAAO,IAAI,WAAW,CAAC;KAC7B,IACG,QAAQ,MAAM,QAAQ,OACtB,QAAQ,MAAM,QAAQ,MACtB,QAAQ,MAAM,QAAQ,MACvB,SAAS,MACT,SAAS,MACT,SAAS,MACT,SAAS,IAET;UAEA;IAEJ;IAEA,IAAI,IAAI,WAAW,CAAC,MAAM,IACxB,oBAAoB,IAAI;GAE5B;EACF;EAEA,aAAa,YAAY;CAC3B;CAEA,OAAO;AACT;AAWA,SAAS,kBAAkB,QAAmB;CAC5C,IAAI;EACF,OAAO,YAAY;EACnB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAS,iBAAiB,QAAmB,QAAiC;CAC5E,IAAI;CACJ,IAAI;EACF,gBAAgB,OAAO,OAAO,MAAM;CACtC,QAAQ,CAER;CAEA,IAAI,CAAC,kBAAkB,MAAM,KAAK,eAChC,OAAO,cAAc,KAAK,MAAM,IAAI,EAAE,WAAW;EAC/C,kBAAkB,MAAM;CAC1B,CAAC;CAGH,OAAO,gBAAgB,cAAc,KAAK,MAAM,IAAI,IAAI;AAC1D;AAEA,SAAS,kBAAqB,WAA8B;CAC1D,MAAM,SAAS,UAAU,UAAU;CACnC,IAAI,WAAW;CAEf,OAAO;EACL;EACA,SAAS,WAAqB;GAC5B,IAAI,UAAU,OAAO;GACrB,WAAW;GACX,OAAO,iBAAiB,QAAQ,MAAM;EACxC;EACA,eAAe;GACb,IAAI,UAAU;GACd,WAAW;GACX,kBAAkB,MAAM;EAC1B;CACF;AACF;AAEA,SAAS,oBAAoB,MAAyC;CACpE,IAAI,gBAAgB;CACpB,QAAQ,WAAqB;EAC3B,IAAI,eAAe;EACnB,gBAAgB;EAChB,IAAI;GACF,MAAM,UAAU,MAAM;EACxB,QAAQ,CAER;CACF;AACF;AAEA,SAAgB,0BACd,QACA,WACA,MACA;CACA,MAAM,YAAY,OAAO;CACzB,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,gDAAgD;CAElE,IAAI,UAAU,sBAAsB,GAClC,OAAO,mBAAmB,WAAW,MAAM,SAAS;CAGtD,OAAO,eAAe,WAAW,WAAW,IAAI;AAClD;AAKA,SAAS,mBACP,WACA,MACA,WACA;CACA,IAAI,YAAY;CAChB,IAAI;CACJ,IAAI,QAAoB,WAAW;CACnC,IAAI;CACJ,IAAI;CACJ,MAAM,cAAc,kBAAkB,SAAS;CAC/C,MAAM,cAAc,oBAAoB,IAAI;CAC5C,MAAM,eAAe,UAAU,WAAW;CAC1C,IAAI,iBAAiB;CAErB,MAAM,2BAA2B;EAC/B,IAAI,CAAC,aAAa,gBAAgB,OAAO;EACzC,iBAAiB;EACjB,IAAI;GACF,UAAU,kBAAkB;GAC5B,OAAO;EACT,SAAS,OAAO;GACd,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,OAAO;EACT;CACF;CAEA,MAAM,WAAW,QAAkB,eAAe,SAAS;EACzD,IAAI,WAAW,OAAO;EACtB,YAAY;EAEZ,IAAI,0BAA0B,KAAA,GAAW;GACvC,aAAa,qBAAqB;GAClC,wBAAwB,KAAA;EAC1B;EACA,IAAI;GACF,8BAA8B;EAChC,QAAQ,CAER;EACA,8BAA8B,KAAA;EAE9B,IAAI,cAGF,YAAY,MAAM;EAEpB,MAAM,aAAa,eACf,YAAY,OAAO,MAAM,KACxB,YAAY,QAAQ,GAAG;EAC5B,IAAI,WACF,IAAI;GACF,UAAU,QAAQ;EACpB,SAAS,OAAO;GACd,QAAQ,MAAM,yBAAyB,KAAK;EAC9C;EAEF,OAAO;CACT;CAEA,MAAM,kBAAkB;EACtB,IAAI,OAAO,GAAG;EACd,QAAQ,WAAW;EACnB,IAAI;GACF,YAAY,MAAM;EACpB,QAAQ,CAER;CACF;CAEA,MAAM,aAAa,UAAmB;EACpC,IAAI,OAAO,GAAG;EACd,QAAQ,WAAW;EACnB,IAAI;GACF,YAAY,MAAM,KAAK;EACzB,QAAQ,CAER;CACF;CAEA,IAAI,WACF,8BAA8B,UAAU,qBAAqB;EAC3D,MAAM,sBAAM,IAAI,MAAM,2CAA2C;EACjE,UAAU,GAAG;EACb,QAAQ,GAAG;CACb,CAAC;CAGH,MAAM,aAAa,MAAM,cAAc;CACvC,wBAAwB,iBAAiB;EACvC,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG;GAC3B,MAAM,sBAAM,IAAI,MAAM,0BAA0B;GAChD,QAAQ,KACN,mDAAmD,WAAW,qBAChE;GACA,UAAU,GAAG;GACb,QAAQ,GAAG;EACb;CACF,GAAG,UAAU;CA8Cb,OAAO,IA5CY,gBAAA,eAA2B;EAC5C,MAAM,GAAG;GACP,aAAa;EACf;EACA,MAAM,KAAK,GAAG;GACZ,IAAI,aAAa,OAAO,GAAG;GAC3B,IAAI;IACF,MAAM,EAAE,MAAM,UAAU,MAAM,YAAY,OAAO,KAAK;IACtD,IAAI,CAAC,MAAM;KACT,IAAI,CAAC,aAAa,CAAC,OAAO,GACxB,EAAE,QAAQ,KAAK;KAEjB;IACF;IAEA,IAAI,aAAa,OAAO,GAAG;IAE3B,IAAI,CAAC,mBAAmB,GAAG;IAC3B,UAAU;IACV,OAAO,QAAQ,KAAA,GAAW,KAAK;GACjC,SAAS,OAAO;IACd,IAAI,WAAW;IACf,QAAQ,MAAM,4BAA4B,KAAK;IAC/C,IAAI,QAAQ,WAAW,SACrB,IAAI;KACF,WAAW,kBAAkB;IAC/B,QAAQ,CAER;IAEF,UAAU,KAAK;IACf,OAAO,QAAQ,KAAK;GACtB,UAAU;IACR,IAAI,aAAa,OAAO,GACtB,YAAY,QAAQ;GAExB;EACF;EACA,OAAO,QAAQ;GACb,QAAQ,WAAW;GACnB,OAAO,QAAQ,MAAM;EACvB;CACF,CAEO;AACT;AAUA,SAAS,eACP,WACA,WACA,MACA;CACA,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI,YAAY;CAEhB,IAAI;CACJ,IAAI,mBAAmB;CACvB,IAAI,QAAoB,WAAW;CAEnC,MAAM,cAAc,kBAAkB,SAAS;CAC/C,MAAM,cAAc,oBAAoB,IAAI;CAU5C,MAAM,gBAA+B,CAAC;CACtC,IAAI,mBAAmB;CACvB,IAAI,oBAAoB;CAExB,SAAS,eAAe;EACtB,cAAc,SAAS;EACvB,mBAAmB;EACnB,oBAAoB;CACtB;CAGA,IAAI,eAAoC;CACxC,MAAM,qBACJ,IAAI,SAAe,MAAM;EACvB,eAAe;CACjB,CAAC;CACH,MAAM,oBAAoB;EACxB,IAAI,cAAc;GAChB,MAAM,IAAI;GACV,eAAe;GACf,EAAE;EACJ;CACF;CAEA,MAAM,eAAe,UAAU,WAAW;CAE1C,SAAS,eAAe;EACtB,IAAI,CAAC,cAAc,OAAO,GAAG;EAC7B,OAAO,mBAAmB,cAAc,QAAQ;GAC9C,MAAM,KAAK,WAAW;GACtB,IAAI,OAAO,QAAQ,MAAM,GAAG;GAC5B,MAAM,OAAO,cAAc;GAE3B,cAAc,oBAAoB;GAClC;GACA,qBAAqB,KAAK;GAC1B,IAAI;IACF,WAAW,QAAQ,YAAY,OAAO,IAAI,CAAC;GAC7C,SAAS,OAAO;IACd,UAAU,KAAK;IACf,QAAQ,KAAK;IACb;GACF;EACF;EAEA,IAAI,oBAAoB,cAAc,QAAQ;GAC5C,cAAc,SAAS;GACvB,mBAAmB;EACrB;EAEA,IAAI,oBAAoB,oBAAoB,cAAc,QAAQ;GAChE,mBAAmB;GACnB,UAAU;GACV,QAAQ,KAAA,GAAW,KAAK;EAC1B;CACF;;;;;;;CAQA,SAAS,WAAW,OAAe;EACjC,IAAI,aAAa,OAAO,GAAG;EAC3B,IAAI,CAAC,MAAM,QAAQ;EACnB,IAAI,oBAAoB,MAAM,SAAS,yBAAyB;GAC9D,MAAM,sBAAM,IAAI,MAAM,mDAAmD;GACzE,UAAU,GAAG;GACb,QAAQ,GAAG;GACX;EACF;EACA,cAAc,KAAK,KAAK;EACxB,qBAAqB,MAAM;EAC3B,aAAa;CACf;CAEA,SAAS,YAAY;EACnB,IAAI,OAAO,GAAG;EACd,QAAQ,WAAW;EACnB,IAAI;GACF,YAAY,MAAM;EACpB,QAAQ,CAER;CACF;CAEA,SAAS,UAAU,OAAgB;EACjC,IAAI,OAAO,GAAG;EACd,QAAQ,WAAW;EACnB,IAAI;GACF,YAAY,MAAM,KAAK;EACzB,QAAQ,CAER;CACF;;;;CAKA,SAAS,QAAQ,QAAkB,eAAe,MAAM;EACtD,IAAI,WAAW,OAAO;EACtB,YAAY;EAEZ,IAAI;GACF,8BAA8B;GAC9B,uCAAuC;EACzC,QAAQ,CAER;EACA,8BAA8B,KAAA;EAC9B,uCAAuC,KAAA;EAEvC,IAAI,+BAA+B,KAAA,GAAW;GAC5C,aAAa,0BAA0B;GACvC,6BAA6B,KAAA;EAC/B;EACA,IAAI,0BAA0B,KAAA,GAAW;GACvC,aAAa,qBAAqB;GAClC,wBAAwB,KAAA;EAC1B;EAEA,uBAAuB;EACvB,WAAW;EACX,cAAc;EACd,aAAa;EAEb,IAAI,cAGF,YAAY,MAAM;EAEpB,MAAM,aAAa,eACf,YAAY,OAAO,MAAM,KACxB,YAAY,QAAQ,GAAG;EAC5B,YAAY;EACZ,IAAI;GACF,UAAU,QAAQ;EACpB,SAAS,OAAO;GACd,QAAQ,MAAM,yBAAyB,KAAK;EAC9C;EACA,OAAO;CACT;CAEA,MAAM,cAAc,IAAI,YAAY;CAIpC,MAAM,oBAAmC,CAAC;CAC1C,IAAI,yBAAyB;CAG7B,IAAI,WAAW;CAGf,IAAI,cAAc;CAElB,IAAI,sBAAsB;CAC1B,IAAI,0BAA0B;CAC9B,IAAI,wBAAwB;CAE5B,SAAS,kBAAkB,OAAe;EACxC,IAAI,yBAAyB;EAC7B,IAAI,MAAM,SAAA,qBAA8B,GACtC,0BAA0B;CAE9B;CAEA,SAAS,2BAA2B;EAClC,IAAI,qBAAqB;EACzB,IAAI,CAAC,yBAAyB;EAC9B,sBAAsB;EACtB,UAAU,kBAAkB;CAC9B;CAEA,MAAM,SAAS,IAAI,gBAAA,eAA2B;EAC5C,MAAM,GAAG;GACP,aAAa;GAEb,aAAa;EACf;EACA,OAAO;GAEL,aAAa;GACb,YAAY;EACd;EACA,OAAO,QAAQ;GACb,QAAQ,WAAW;GACnB,OAAO,QAAQ,MAAM;EACvB;CACF,CAAC;CAED,SAAS,kBAAkB;EACzB,IAAI,aAAa,OAAO,GAAG;EAC3B,IAAI;EACJ,IAAI;GACF,OAAO,UAAU,iBAAiB;EACpC,SAAS,OAAO;GACd,UAAU,KAAK;GACf,QAAQ,KAAK;GACb;EACF;EACA,IAAI,CAAC,MAAM;EACX,IAAI,SAAS,WAAW,UAAU;GAGhC,MAAM,sBAAM,IAAI,MACd,oDACF;GACA,UAAU,GAAG;GACb,QAAQ,GAAG;GACX;EACF;EACA,IAAI,UAAU,WAAW,aAAa;GACpC,uBAAuB;GACvB,WAAW,IAAI;EACjB,OAAO;GACL,IAAI,yBAAyB,KAAK,SAAS,uBAAuB;IAChE,MAAM,sBAAM,IAAI,MAAM,yCAAyC;IAC/D,UAAU,GAAG;IACb,QAAQ,GAAG;IACX;GACF;GACA,kBAAkB,KAAK,IAAI;GAC3B,0BAA0B,KAAK;EACjC;CACF;CAEA,SAAS,yBAAyB;EAChC,IAAI,CAAC,kBAAkB,QAAQ;EAC/B,KAAK,MAAM,QAAQ,mBACjB,WAAW,IAAI;EAEjB,uBAAuB;CACzB;CAEA,SAAS,yBAAyB;EAChC,kBAAkB,SAAS;EAC3B,yBAAyB;CAC3B;CAEA,SAAS,WAAW,OAAe;EACjC,eAAe;EACf,IAAI,YAAY,SAAS,gBACvB,MAAM,IAAI,MAAM,yCAAyC;CAE7D;CAEA,SAAS,sBAAsB;EAC7B,OAAO,CAAC,EACN,cACA,WAAW,gBAAgB,QAC3B,WAAW,eAAe;CAE9B;CAEA,SAAS,4BAA4B;EACnC,IAAI,aAAa,OAAO,GAAG;EAC3B,IAAI,+BAA+B,KAAA,GAAW;EAC9C,MAAM,YAAY,MAAM,aAAa;EACrC,6BAA6B,iBAAiB;GAC5C,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG;IAC3B,MAAM,sBAAM,IAAI,MAAM,iDAAiD;IACvE,QAAQ,MAAM,iDAAiD;IAC/D,UAAU,GAAG;IACb,QAAQ,GAAG;GACb;EACF,GAAG,SAAS;CACd;;;;;;CAOA,SAAS,YAAY;EACnB,IAAI,UAAU,WAAW,WAAW,CAAC,uBAAuB;EAC5D,IAAI,aAAa,OAAO,GAAG;EAE3B,IAAI,+BAA+B,KAAA,GAAW;GAC5C,aAAa,0BAA0B;GACvC,6BAA6B,KAAA;EAC/B;EAEA,gBAAgB;EAChB,IAAI,aAAa,OAAO,GAAG;EAG3B,MAAM,mBAAmB,YAAY,OAAO;EAE5C,IAAI,UAAU,WAAW,QAAQ;EACjC,IAAI,aAAa,OAAO,GAAG;EAC3B,IAAI,kBAAkB,WAAW,gBAAgB;EACjD,IAAI,aAAa,OAAO,GAAG;EAC3B,uBAAuB;EACvB,IAAI,aAAa,OAAO,GAAG;EAC3B,IAAI,aAAa,WAAW,WAAW;EACvC,IAAI,aAAa,OAAO,GAAG;EAE3B,WAAW;EACX,cAAc;EAEd,QAAQ,WAAW;EACnB,mBAAmB;EAEnB,aAAa;CACf;CAEA,SAAS,qBAAqB;EAC5B,IAAI,SAAS,WAAW,SAAS;EACjC,QAAQ,WAAW;EACnB,IAAI;GACF,UAAU,kBAAkB;EAC9B,SAAS,OAAO;GACd,UAAU,KAAK;GACf,QAAQ,KAAK;GACb;EACF;EACA,gBAAgB;EAChB,IAAI,aAAa,OAAO,GAAG;EAC3B,wBACE,yBAAyB,UAAU,wBAAwB;EAC7D,IAAI,uBACF,UAAU;OAEV,0BAA0B;CAE9B;CAGA,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,aAAa,MAAM,cAAc,YAAY;CACnD,wBAAwB,iBAAiB;EACvC,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG;GAC3B,MAAM,sBAAM,IAAI,MAAM,0BAA0B;GAChD,QAAQ,KACN,mDAAmD,WAAW,qBAChE;GACA,UAAU,GAAG;GACb,QAAQ,GAAG;EACb;CACF,GAAG,UAAU;CAEb,8BAA8B,UAAU,qBAAqB;EAC3D,gBAAgB;CAClB,CAAC;CAED,uCAAuC,UAAU,8BACzC;EACJ,wBAAwB;EACxB,gBAAgB;EAChB,UAAU;CACZ,CACF;CAIA,gBAAgB;CAChB,IAAI,aAAa,OAAO,GAAG,OAAO;CAClC,wBACE,yBAAyB,UAAU,wBAAwB;CAC7D,IAAI,uBAAuB;EACzB,gBAAgB;EAChB,IAAI,aAAa,OAAO,GAAG,OAAO;CACpC;CAGC,CAAC,YAAY;EACZ,IAAI;GACF,OAAO,MAAM;IAEX,IAAI,oBAAoB,GAAG;KACzB,MAAM,aAAa;KACnB,IAAI,aAAa,OAAO,GAAG;IAC7B;IAEA,MAAM,EAAE,MAAM,UAAU,MAAM,YAAY,OAAO,KAAK;IACtD,IAAI,MAAM;IAEV,IAAI,aAAa,OAAO,GAAG;IAE3B,MAAM,OACJ,OAAO,UAAU,WACb,QACA,YAAY,OAAO,OAA0B,EAAE,QAAQ,KAAK,CAAC;IAEnE,MAAM,cAAc,WAAW,WAAW,OAAO;IAIjD,IAAI,SAAS,WAAW,aAAa;KACnC,WAAW,WAAW;KACtB,WAAW;KACX;IACF;IAEA,MAAM,WAAW,iBAAiB,WAAW;IAC7C,IAAI,WAAW,IAAI;KACjB,MAAM,eAAe,CAAC,WAAW;KACjC,QAAQ,WAAW;KACnB,WAAW,YAAY,MAAM,YAAY,CAAC;KAC1C,MAAM,YAAY,YAAY,MAAM,GAAG,YAAY;KACnD,WAAW,SAAS;KACpB,IAAI,aAAa,OAAO,GAAG;KAC3B,kBAAkB,SAAS;KAC3B,yBAAyB;KACzB,IAAI,aAAa,OAAO,GAAG;KAC3B,uBAAuB;KACvB,WAAW;KACX;IACF;IAEA,MAAM,oBAAoB;IAE1B,IAAI,oBAAoB,GAAG;KACzB,MAAM,YAAY,YAAY,MAAM,GAAG,iBAAiB;KACxD,WAAW,SAAS;KACpB,IAAI,aAAa,OAAO,GAAG;KAC3B,kBAAkB,SAAS;KAC3B,yBAAyB;KACzB,IAAI,aAAa,OAAO,GAAG;KAC3B,uBAAuB;KAEvB,WAAW,YAAY,MAAM,iBAAiB;KAC9C,IAAI,SAAS,SAAS,oBAAoB;MAGxC,kBAAkB,QAAQ;MAK1B,WAJgB,SAAS,MACvB,GACA,SAAS,SAAS,kBAET,CAAO;MAClB,WAAW,SAAS,MAAM,KAAmB;KAC/C;IACF,OAAO;KAGL,MAAM,WAAW;KACjB,IAAI,SAAS,SAAS,oBAAoB;MACxC,kBAAkB,QAAQ;MAC1B,MAAM,YAAY,SAAS,SAAS;MAEpC,WADgB,SAAS,MAAM,GAAG,SACvB,CAAO;MAClB,WAAW,SAAS,MAAM,SAAS;KACrC,OACE,WAAW;IAEf;GACF;GAEA,IAAI,aAAa,OAAO,GAAG;GAE3B,mBAAmB;EACrB,SAAS,OAAO;GACd,IAAI,WAAW;GACf,QAAQ,MAAM,4BAA4B,KAAK;GAC/C,IAAI,QAAQ,WAAW,SACrB,IAAI;IACF,UAAU,kBAAkB;GAC9B,QAAQ,CAER;GAEF,UAAU,KAAK;GACf,QAAQ,KAAK;EACf,UAAU;GACR,YAAY,QAAQ;EACtB;CACF,GAAG,EAAE,OAAO,UAAU;EACpB,IAAI,WAAW;EACf,QAAQ,MAAM,8BAA8B,KAAK;EACjD,UAAU,KAAK;EACf,QAAQ,KAAK;CACf,CAAC;CAED,OAAO;AACT"}
|
|
1
|
+
{"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 type TransformStreamWithRouterOptions = {\n /** The request lifetime that owns this response stream. */\n signal?: AbortSignal\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 120000ms). Safety net for cleanup. */\n lifetimeMs?: number\n /**\n * Called exactly once when the stream is torn down due to abort/error/\n * cancel/timeout — NOT on natural successful completion. Use this to\n * abort a hidden producer upstream of any PassThrough you passed in\n * (e.g. React `renderToPipeableStream`'s `abort()`).\n * Errors thrown from this callback are swallowed.\n */\n onAbort?: (reason?: unknown) => void\n}\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n opts?: TransformStreamWithRouterOptions,\n) {\n return transformStreamWithRouter(router, routerStream, opts)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n opts?: TransformStreamWithRouterOptions,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream), opts),\n )\n}\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 = DEFAULT_SERIALIZATION_TIMEOUT_MS * 2\nconst MAX_LEFTOVER_CHARS = 2048\nconst MAX_TAIL_CHARS = 64 * 1024\nconst MAX_ROUTER_HTML_CHARS = 16 * 1024 * 1024\nconst MAX_PENDING_WRITE_CHARS = 16 * 1024 * 1024\n\n// Merge lifecycle: body bytes can stream, router HTML must precede tail,\n// terminal states own close/error/cleanup exactly once.\nconst MergeState = {\n ReadingBody: 0,\n HoldingTail: 1,\n AppDone: 2,\n Draining: 3,\n Done: 4,\n} as const\n\ntype MergeState = (typeof MergeState)[keyof typeof MergeState]\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\nconst noop = () => {}\nconst resolvedPromise = Promise.resolve()\n\n// Returns -bodyEndIndex - 2 when </body> is found; otherwise returns\n// the position after the last valid closing tag, or -1 when none exists.\nfunction findHtmlBoundary(str: string): number {\n let lastClosingTagEnd = -1\n let searchFrom = str.length - MIN_CLOSING_TAG_LENGTH\n\n while (searchFrom >= 0) {\n const openSlash = str.lastIndexOf('</', searchFrom)\n if (openSlash === -1) break\n\n // Fast case-insensitive match for </body>. Negative return encodes the\n // body start index without allocating a result object.\n if (\n (str.charCodeAt(openSlash + 2) | 32) === 98 &&\n (str.charCodeAt(openSlash + 3) | 32) === 111 &&\n (str.charCodeAt(openSlash + 4) | 32) === 100 &&\n (str.charCodeAt(openSlash + 5) | 32) === 121 &&\n str.charCodeAt(openSlash + 6) === 62\n ) {\n return -openSlash - 2\n }\n\n if (lastClosingTagEnd === -1) {\n let i = openSlash + 2\n const startCode = str.charCodeAt(i)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n i++\n while (i < str.length) {\n const code = str.charCodeAt(i)\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 i++\n } else {\n break\n }\n }\n\n if (str.charCodeAt(i) === 62) {\n lastClosingTagEnd = i + 1\n }\n }\n }\n\n searchFrom = openSlash - 1\n }\n\n return lastClosingTagEnd\n}\n\n/**\n * Releasing the lock can throw if a pending read is still settling or if the\n * lock was already released.\n */\ntype ReaderOps = {\n cancel: (reason?: unknown) => Promise<unknown>\n releaseLock: () => void\n}\n\nfunction safeReleaseReader(reader: ReaderOps) {\n try {\n reader.releaseLock()\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Cancel a reader without producing an unhandled rejection. `reader.cancel()`\n * can reject (e.g. when the underlying source's cancel() throws), and\n * downstream cancel() should still wait for upstream teardown when possible.\n */\nfunction safeCancelReader(reader: ReaderOps, reason?: unknown): Promise<void> {\n let cancelPromise: Promise<unknown> | undefined\n try {\n cancelPromise = reader.cancel(reason)\n } catch {\n // ignore\n }\n\n if (!safeReleaseReader(reader) && cancelPromise) {\n return cancelPromise.then(noop, noop).then(() => {\n safeReleaseReader(reader)\n })\n }\n\n return cancelPromise ? cancelPromise.then(noop, noop) : resolvedPromise\n}\n\nfunction createReaderState<T>(appStream: ReadableStream<T>) {\n const reader = appStream.getReader()\n let released = false\n\n return {\n reader,\n cancel: (reason?: unknown) => {\n if (released) return resolvedPromise\n released = true\n return safeCancelReader(reader, reason)\n },\n release: () => {\n if (released) return\n released = true\n safeReleaseReader(reader)\n },\n }\n}\n\nfunction createAbortNotifier(opts?: TransformStreamWithRouterOptions) {\n let abortNotified = false\n return (reason?: unknown) => {\n if (abortNotified) return\n abortNotified = true\n try {\n opts?.onAbort?.(reason)\n } catch {\n // swallow user errors\n }\n }\n}\n\nfunction listenToAbort(\n signal: AbortSignal | undefined,\n onAbort: (reason?: unknown) => void,\n) {\n if (!signal) {\n return\n }\n if (signal.aborted) {\n onAbort(signal.reason)\n return\n }\n const listener = () => onAbort(signal.reason)\n signal.addEventListener('abort', listener, { once: true })\n return () => signal.removeEventListener('abort', listener)\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: TransformStreamWithRouterOptions,\n) {\n const serverSsr = router.serverSsr\n if (!serverSsr) {\n throw new Error('Invariant failed: router.serverSsr is required')\n }\n if (serverSsr.reserveStreamFastPath()) {\n return makeFastPathStream(appStream, opts, serverSsr)\n }\n\n return makeMainStream(serverSsr, appStream, opts)\n}\n\n// =====================================================================\n// Fast path: passthrough with cleanup + backpressure on app reads.\n// =====================================================================\nfunction makeFastPathStream(\n appStream: ReadableStream<Uint8Array>,\n opts?: TransformStreamWithRouterOptions,\n serverSsr?: NonNullable<AnyRouter['serverSsr']>,\n) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let state: MergeState = MergeState.ReadingBody\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let stopListeningToAbort: (() => void) | undefined\n let stopListeningToInjectedHtml: (() => void) | undefined\n const readerState = createReaderState(appStream)\n const notifyAbort = createAbortNotifier(opts)\n const isDone = () => state === MergeState.Done\n let renderFinished = false\n\n const finishSsrRendering = () => {\n if (!serverSsr || renderFinished) return true\n renderFinished = true\n try {\n serverSsr.setRenderFinished()\n return true\n } catch (error) {\n safeError(error)\n cleanup(error)\n return false\n }\n }\n\n const cleanup = (reason?: unknown, cancelReader = true) => {\n if (cleanedUp) return resolvedPromise\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n stopListeningToAbort?.()\n stopListeningToAbort = undefined\n try {\n stopListeningToInjectedHtml?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n\n if (cancelReader) {\n // Notify the producer immediately. Reader cancellation may take time to\n // settle, and upstream renderers must tolerate abort + cancel overlap.\n notifyAbort(reason)\n }\n const readerDone = cancelReader\n ? readerState.cancel(reason)\n : (readerState.release(), resolvedPromise)\n if (serverSsr) {\n try {\n serverSsr.cleanup()\n } catch (error) {\n console.error('Error in SSR cleanup:', error)\n }\n }\n return readerDone\n }\n\n const safeClose = () => {\n if (isDone()) return\n state = MergeState.Done\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isDone()) return\n state = MergeState.Done\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n if (serverSsr) {\n stopListeningToInjectedHtml = serverSsr.onInjectedHtml(() => {\n const err = new Error('SSR router HTML injected during fast path')\n safeError(err)\n cleanup(err)\n })\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isDone()) {\n const err = new Error('Stream lifetime exceeded')\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(err)\n cleanup(err)\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n async pull(c) {\n if (cleanedUp || isDone()) return\n try {\n const { done, value } = await readerState.reader.read()\n if (!done) {\n if (!cleanedUp && !isDone()) {\n c.enqueue(value)\n }\n return\n }\n\n if (cleanedUp || isDone()) return\n\n if (!finishSsrRendering()) return\n safeClose()\n return cleanup(undefined, false)\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n if (state < MergeState.AppDone) {\n try {\n serverSsr?.setRenderFinished()\n } catch {\n // ignore\n }\n }\n safeError(error)\n return cleanup(error)\n } finally {\n if (cleanedUp || isDone()) {\n readerState.release()\n }\n }\n },\n cancel(reason) {\n state = MergeState.Done\n return cleanup(reason)\n },\n })\n\n stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {\n safeError(reason)\n cleanup(reason)\n })\n\n return stream\n}\n\n// =====================================================================\n// Main path: scan + inject router HTML/scripts with full backpressure.\n//\n// ALL output (app chunks AND router-injected HTML/scripts) flows through a\n// single pendingWrites queue and is only enqueued onto the downstream\n// controller when desiredSize > 0. This prevents native-memory growth of\n// queued Uint8Arrays under slow HTTP consumers.\n// =====================================================================\nfunction makeMainStream(\n serverSsr: NonNullable<AnyRouter['serverSsr']>,\n appStream: ReadableStream,\n opts?: TransformStreamWithRouterOptions,\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 stopListeningToAbort: (() => void) | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let closeWhenDrained = false\n let state: MergeState = MergeState.ReadingBody\n\n const readerState = createReaderState(appStream)\n const notifyAbort = createAbortNotifier(opts)\n\n // Single output queue: app chunks + router-injected HTML/scripts.\n // Stored as STRINGS to avoid holding native-backed Uint8Arrays in our queue\n // while waiting for downstream capacity. Encoding happens at enqueue time\n // (drainPending) so the bytes live only inside the controller's internal\n // queue, not in two places.\n //\n // Uses an index pointer instead of Array.prototype.shift() (which is O(n))\n // so many small router-injected script chunks stay O(1) per chunk.\n const pendingWrites: Array<string> = []\n let pendingWriteHead = 0\n let pendingWriteChars = 0\n\n function clearPending() {\n pendingWrites.length = 0\n pendingWriteHead = 0\n pendingWriteChars = 0\n }\n\n // Backpressure: pull() resolves drainResolve to let the read loop advance.\n let drainResolve: (() => void) | null = null\n const waitForDrain = () =>\n new Promise<void>((r) => {\n drainResolve = r\n })\n const signalDrain = () => {\n if (drainResolve) {\n const r = drainResolve\n drainResolve = null\n r()\n }\n }\n\n const isDone = () => state === MergeState.Done\n\n function drainPending() {\n if (!controller || isDone()) return\n while (pendingWriteHead < pendingWrites.length) {\n const ds = controller.desiredSize\n if (ds !== null && ds <= 0) return\n const next = pendingWrites[pendingWriteHead]!\n // Release reference for GC; compact when fully drained.\n pendingWrites[pendingWriteHead] = ''\n pendingWriteHead++\n pendingWriteChars -= next.length\n try {\n controller.enqueue(textEncoder.encode(next))\n } catch (error) {\n safeError(error)\n cleanup(error)\n return\n }\n }\n // Fully drained: reset array so it doesn't grow unbounded across SSR.\n if (pendingWriteHead >= pendingWrites.length) {\n pendingWrites.length = 0\n pendingWriteHead = 0\n }\n // If we've flushed everything and tryFinish requested close, close now.\n if (closeWhenDrained && pendingWriteHead >= pendingWrites.length) {\n closeWhenDrained = false\n safeClose()\n cleanup(undefined, false)\n }\n }\n\n /**\n * Enqueue a string chunk through the backpressure queue. Stored as a\n * string and encoded only when the downstream actually accepts the chunk\n * — keeps native-memory pressure inside the controller's queue (which\n * honors desiredSize) rather than ours.\n */\n function writeChunk(chunk: string) {\n if (cleanedUp || isDone()) return\n if (!chunk.length) return\n if (pendingWriteChars + chunk.length > MAX_PENDING_WRITE_CHARS) {\n const err = new Error('SSR stream pending output exceeded maximum buffer')\n safeError(err)\n cleanup(err)\n return\n }\n pendingWrites.push(chunk)\n pendingWriteChars += chunk.length\n drainPending()\n }\n\n function safeClose() {\n if (isDone()) return\n state = MergeState.Done\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isDone()) return\n state = MergeState.Done\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(reason?: unknown, cancelReader = true) {\n if (cleanedUp) return resolvedPromise\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n stopListeningToAbort?.()\n stopListeningToAbort = 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 clearPendingRouterHtml()\n leftover = ''\n pendingTail = ''\n clearPending()\n\n if (cancelReader) {\n // Notify the producer immediately. Reader cancellation may take time to\n // settle, and upstream renderers must tolerate abort + cancel overlap.\n notifyAbort(reason)\n }\n const readerDone = cancelReader\n ? readerState.cancel(reason)\n : (readerState.release(), resolvedPromise)\n signalDrain()\n try {\n serverSsr.cleanup()\n } catch (error) {\n console.error('Error in SSR cleanup:', error)\n }\n return readerDone\n }\n\n const textDecoder = new TextDecoder()\n\n // Router-injected scripts/HTML waiting for the next safe body boundary.\n // Keep chunks separate so flushing does not flatten a large rope string.\n const pendingRouterHtml: Array<string> = []\n let pendingRouterHtmlChars = 0\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured bytes from </body> onward; must stay behind router scripts.\n let pendingTail = ''\n\n let streamBarrierLifted = false\n let streamBarrierMarkerSeen = false\n let serializationFinished = false\n\n function noteBarrierMarker(chunk: string) {\n if (streamBarrierMarkerSeen) return\n if (chunk.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierMarkerSeen = true\n }\n }\n\n function liftBarrierAfterBoundary() {\n if (streamBarrierLifted) return\n if (!streamBarrierMarkerSeen) return\n streamBarrierLifted = true\n serverSsr.liftScriptBarrier()\n }\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n // If anything queued before start (shouldn't happen but be safe), drain.\n drainPending()\n },\n pull() {\n // Consumer has capacity; flush queue then unblock read loop.\n drainPending()\n signalDrain()\n },\n cancel(reason) {\n state = MergeState.Done\n return cleanup(reason)\n },\n })\n\n function drainRouterHtml() {\n if (cleanedUp || isDone()) return\n let html: string | undefined\n try {\n html = serverSsr.takeBufferedHtml()\n } catch (error) {\n safeError(error)\n cleanup(error)\n return\n }\n if (!html) return\n if (state >= MergeState.Draining) {\n // At this point final tail/close has already been queued. Emitting late\n // router HTML would put scripts after </body> or drop them silently.\n const err = new Error(\n 'SSR router HTML injected after stream finalization',\n )\n safeError(err)\n cleanup(err)\n return\n }\n if (state === MergeState.HoldingTail) {\n flushPendingRouterHtml()\n writeChunk(html)\n } else {\n if (pendingRouterHtmlChars + html.length > MAX_ROUTER_HTML_CHARS) {\n const err = new Error('SSR router HTML exceeded maximum buffer')\n safeError(err)\n cleanup(err)\n return\n }\n pendingRouterHtml.push(html)\n pendingRouterHtmlChars += html.length\n }\n }\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml.length) return\n for (const html of pendingRouterHtml) {\n writeChunk(html)\n }\n clearPendingRouterHtml()\n }\n\n function clearPendingRouterHtml() {\n pendingRouterHtml.length = 0\n pendingRouterHtmlChars = 0\n }\n\n function appendTail(chunk: string) {\n pendingTail += chunk\n if (pendingTail.length > MAX_TAIL_CHARS) {\n throw new Error('SSR stream tail exceeded maximum buffer')\n }\n }\n\n function waitForBackpressure() {\n return !!(\n controller &&\n controller.desiredSize !== null &&\n controller.desiredSize <= 0\n )\n }\n\n function startSerializationTimeout() {\n if (cleanedUp || isDone()) return\n if (serializationTimeoutHandle !== undefined) return\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isDone()) {\n const err = new Error('Serialization timeout after app render finished')\n console.error('Serialization timeout after app render finished')\n safeError(err)\n cleanup(err)\n }\n }, timeoutMs)\n }\n\n /**\n * Finish only when app done and serialization complete. Queues final\n * output and requests close-when-drained so we don't close ahead of\n * pending writes still waiting on downstream capacity.\n */\n function tryFinish() {\n if (state !== MergeState.AppDone || !serializationFinished) return\n if (cleanedUp || isDone()) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n drainRouterHtml()\n if (cleanedUp || isDone()) return\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) writeChunk(leftover)\n if (cleanedUp || isDone()) return\n if (decoderRemainder) writeChunk(decoderRemainder)\n if (cleanedUp || isDone()) return\n flushPendingRouterHtml()\n if (cleanedUp || isDone()) return\n if (pendingTail) writeChunk(pendingTail)\n if (cleanedUp || isDone()) return\n\n leftover = ''\n pendingTail = ''\n\n state = MergeState.Draining\n closeWhenDrained = true\n // Try immediately; if queue not drained yet, pull() will retry.\n drainPending()\n }\n\n function finishAppRendering() {\n if (state >= MergeState.AppDone) return\n state = MergeState.AppDone\n try {\n serverSsr.setRenderFinished()\n } catch (error) {\n safeError(error)\n cleanup(error)\n return\n }\n drainRouterHtml()\n if (cleanedUp || isDone()) return\n serializationFinished =\n serializationFinished || serverSsr.isSerializationFinished()\n if (serializationFinished) {\n tryFinish()\n } else {\n startSerializationTimeout()\n }\n }\n\n // Safety net: cleanup even if consumer never reads\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n const lifetimeMs = opts?.lifetimeMs ?? timeoutMs * 2\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isDone()) {\n const err = new Error('Stream lifetime exceeded')\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(err)\n cleanup(err)\n }\n }, lifetimeMs)\n\n stopListeningToInjectedHtml = serverSsr.onInjectedHtml(() => {\n drainRouterHtml()\n })\n\n stopListeningToSerializationFinished = serverSsr.onSerializationFinished(\n () => {\n serializationFinished = true\n drainRouterHtml()\n tryFinish()\n },\n )\n\n // Subscriptions are installed before snapshots, so missed events are\n // recovered by these synchronous drains/rechecks.\n drainRouterHtml()\n if (cleanedUp || isDone()) return stream\n serializationFinished =\n serializationFinished || serverSsr.isSerializationFinished()\n if (serializationFinished) {\n drainRouterHtml()\n if (cleanedUp || isDone()) return stream\n }\n\n stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {\n safeError(reason)\n cleanup(reason)\n })\n if (cleanedUp || isDone())\n return stream\n\n // Transform the appStream\n ;(async () => {\n try {\n while (true) {\n // Backpressure: pause upstream reads while downstream is full.\n if (waitForBackpressure()) {\n await waitForDrain()\n if (cleanedUp || isDone()) return\n }\n\n const { done, value } = await readerState.reader.read()\n if (done) break\n\n if (cleanedUp || isDone()) return\n\n const text =\n typeof value === 'string'\n ? value\n : textDecoder.decode(value as ArrayBufferView, { stream: true })\n\n const chunkString = leftover ? leftover + text : text\n\n // If we already saw </body>, everything else is tail. Keep it bounded\n // and held until router scripts are ready so injection remains before </body>.\n if (state >= MergeState.HoldingTail) {\n appendTail(chunkString)\n leftover = ''\n continue\n }\n\n const boundary = findHtmlBoundary(chunkString)\n if (boundary < -1) {\n const bodyEndIndex = -boundary - 2\n state = MergeState.HoldingTail\n appendTail(chunkString.slice(bodyEndIndex))\n const bodyChunk = chunkString.slice(0, bodyEndIndex)\n writeChunk(bodyChunk)\n if (cleanedUp || isDone()) return\n noteBarrierMarker(bodyChunk)\n liftBarrierAfterBoundary()\n if (cleanedUp || isDone()) return\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = boundary\n\n if (lastClosingTagEnd > 0) {\n const safeChunk = chunkString.slice(0, lastClosingTagEnd)\n writeChunk(safeChunk)\n if (cleanedUp || isDone()) return\n noteBarrierMarker(safeChunk)\n liftBarrierAfterBoundary()\n if (cleanedUp || isDone()) return\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 noteBarrierMarker(leftover)\n const flushed = leftover.slice(\n 0,\n leftover.length - MAX_LEFTOVER_CHARS,\n )\n writeChunk(flushed)\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 noteBarrierMarker(combined)\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n const flushed = combined.slice(0, flushUpto)\n writeChunk(flushed)\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isDone()) return\n\n finishAppRendering()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n if (state < MergeState.AppDone) {\n try {\n serverSsr.setRenderFinished()\n } catch {\n // ignore\n }\n }\n safeError(error)\n cleanup(error)\n } finally {\n readerState.release()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup(error)\n })\n\n return stream\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import { ReadableStream } from 'node:stream/web';
|
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
3
|
import { AnyRouter } from '../router.cjs';
|
|
4
4
|
export type TransformStreamWithRouterOptions = {
|
|
5
|
+
/** The request lifetime that owns this response stream. */
|
|
6
|
+
signal?: AbortSignal;
|
|
5
7
|
/** Timeout for serialization to complete after app render finishes (default: 60000ms) */
|
|
6
8
|
timeoutMs?: number;
|
|
7
9
|
/** Maximum lifetime of the stream transform (default: 120000ms). Safety net for cleanup. */
|
package/dist/cjs/ssr/types.d.cts
CHANGED
package/dist/cjs/stores.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const require_utils = require("./utils.cjs");
|
|
2
|
-
const require_lru_cache = require("./lru-cache.cjs");
|
|
3
2
|
//#region src/stores.ts
|
|
4
3
|
/** SSR non-reactive createMutableStore */
|
|
5
4
|
function createNonReactiveMutableStore(initialValue) {
|
|
@@ -19,127 +18,54 @@ function createNonReactiveReadonlyStore(read) {
|
|
|
19
18
|
return read();
|
|
20
19
|
} };
|
|
21
20
|
}
|
|
22
|
-
function createRouterStores(
|
|
23
|
-
const { createMutableStore, createReadonlyStore, batch
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const isTransitioning = createMutableStore(initialState.isTransitioning);
|
|
31
|
-
const location = createMutableStore(initialState.location);
|
|
32
|
-
const resolvedLocation = createMutableStore(initialState.resolvedLocation);
|
|
33
|
-
const statusCode = createMutableStore(initialState.statusCode);
|
|
34
|
-
const redirect = createMutableStore(initialState.redirect);
|
|
35
|
-
const matchesId = createMutableStore([]);
|
|
36
|
-
const pendingIds = createMutableStore([]);
|
|
37
|
-
const cachedIds = createMutableStore([]);
|
|
38
|
-
const matches = createReadonlyStore(() => readPoolMatches(matchStores, matchesId.get()));
|
|
39
|
-
const pendingMatches = createReadonlyStore(() => readPoolMatches(pendingMatchStores, pendingIds.get()));
|
|
40
|
-
const cachedMatches = createReadonlyStore(() => readPoolMatches(cachedMatchStores, cachedIds.get()));
|
|
41
|
-
const firstId = createReadonlyStore(() => matchesId.get()[0]);
|
|
42
|
-
const hasPending = createReadonlyStore(() => matchesId.get().some((matchId) => {
|
|
43
|
-
return matchStores.get(matchId)?.get().status === "pending";
|
|
44
|
-
}));
|
|
45
|
-
const matchRouteDeps = createReadonlyStore(() => ({
|
|
46
|
-
locationHref: location.get().href,
|
|
47
|
-
resolvedLocationHref: resolvedLocation.get()?.href,
|
|
48
|
-
status: status.get()
|
|
49
|
-
}));
|
|
21
|
+
function createRouterStores(initialLocation, config) {
|
|
22
|
+
const { createMutableStore, createReadonlyStore, batch } = config;
|
|
23
|
+
const byRoute = /* @__PURE__ */ new Map();
|
|
24
|
+
const status = createMutableStore("idle");
|
|
25
|
+
const location = createMutableStore(initialLocation);
|
|
26
|
+
const resolvedLocation = createMutableStore(void 0);
|
|
27
|
+
const ids = createMutableStore([]);
|
|
28
|
+
const matches = createReadonlyStore(() => ids.get().map((id) => byRoute.get(id).get()));
|
|
50
29
|
const __store = createReadonlyStore(() => ({
|
|
51
30
|
status: status.get(),
|
|
52
|
-
|
|
53
|
-
isLoading: isLoading.get(),
|
|
54
|
-
isTransitioning: isTransitioning.get(),
|
|
31
|
+
isLoading: status.get() === "pending",
|
|
55
32
|
matches: matches.get(),
|
|
56
33
|
location: location.get(),
|
|
57
|
-
resolvedLocation: resolvedLocation.get()
|
|
58
|
-
statusCode: statusCode.get(),
|
|
59
|
-
redirect: redirect.get()
|
|
34
|
+
resolvedLocation: resolvedLocation.get()
|
|
60
35
|
}));
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const ids = matchesId.get();
|
|
67
|
-
for (const id of ids) {
|
|
68
|
-
const matchStore = matchStores.get(id);
|
|
69
|
-
if (matchStore && matchStore.routeId === routeId) return matchStore.get();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
matchStoreByRouteIdCache.set(routeId, cached);
|
|
36
|
+
function getMatchStore(routeId) {
|
|
37
|
+
let matchStore = byRoute.get(routeId);
|
|
38
|
+
if (!matchStore) {
|
|
39
|
+
matchStore = createMutableStore(void 0);
|
|
40
|
+
byRoute.set(routeId, matchStore);
|
|
73
41
|
}
|
|
74
|
-
return
|
|
42
|
+
return matchStore;
|
|
75
43
|
}
|
|
76
44
|
const store = {
|
|
77
45
|
status,
|
|
78
|
-
loadedAt,
|
|
79
|
-
isLoading,
|
|
80
|
-
isTransitioning,
|
|
81
46
|
location,
|
|
82
47
|
resolvedLocation,
|
|
83
|
-
|
|
84
|
-
redirect,
|
|
85
|
-
matchesId,
|
|
86
|
-
pendingIds,
|
|
87
|
-
cachedIds,
|
|
48
|
+
ids,
|
|
88
49
|
matches,
|
|
89
|
-
|
|
90
|
-
cachedMatches,
|
|
91
|
-
firstId,
|
|
92
|
-
hasPending,
|
|
93
|
-
matchRouteDeps,
|
|
94
|
-
matchStores,
|
|
95
|
-
pendingMatchStores,
|
|
96
|
-
cachedMatchStores,
|
|
50
|
+
byRoute,
|
|
97
51
|
__store,
|
|
98
|
-
|
|
99
|
-
setMatches
|
|
100
|
-
setPending,
|
|
101
|
-
setCached
|
|
52
|
+
getMatchStore,
|
|
53
|
+
setMatches
|
|
102
54
|
};
|
|
103
|
-
setMatches(initialState.matches);
|
|
104
|
-
init?.(store);
|
|
105
55
|
function setMatches(nextMatches) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
56
|
+
const previousIds = ids.get();
|
|
57
|
+
const nextIds = nextMatches.map((match) => match.routeId);
|
|
58
|
+
batch(() => {
|
|
59
|
+
if (!require_utils.arraysEqual(previousIds, nextIds)) ids.set(nextIds);
|
|
60
|
+
for (const id of previousIds) if (!nextIds.includes(id)) byRoute.get(id).set(() => void 0);
|
|
61
|
+
for (const nextMatch of nextMatches) {
|
|
62
|
+
const matchStore = getMatchStore(nextMatch.routeId);
|
|
63
|
+
if (matchStore.get() !== nextMatch) matchStore.set(nextMatch);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
113
66
|
}
|
|
114
67
|
return store;
|
|
115
68
|
}
|
|
116
|
-
function readPoolMatches(pool, ids) {
|
|
117
|
-
const matches = [];
|
|
118
|
-
for (const id of ids) {
|
|
119
|
-
const matchStore = pool.get(id);
|
|
120
|
-
if (matchStore) matches.push(matchStore.get());
|
|
121
|
-
}
|
|
122
|
-
return matches;
|
|
123
|
-
}
|
|
124
|
-
function reconcileMatchPool(nextMatches, pool, idStore, createMutableStore, batch) {
|
|
125
|
-
const nextIds = nextMatches.map((d) => d.id);
|
|
126
|
-
const nextIdSet = new Set(nextIds);
|
|
127
|
-
batch(() => {
|
|
128
|
-
for (const id of pool.keys()) if (!nextIdSet.has(id)) pool.delete(id);
|
|
129
|
-
for (const nextMatch of nextMatches) {
|
|
130
|
-
const existing = pool.get(nextMatch.id);
|
|
131
|
-
if (!existing) {
|
|
132
|
-
const matchStore = createMutableStore(nextMatch);
|
|
133
|
-
matchStore.routeId = nextMatch.routeId;
|
|
134
|
-
pool.set(nextMatch.id, matchStore);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
existing.routeId = nextMatch.routeId;
|
|
138
|
-
if (existing.get() !== nextMatch) existing.set(nextMatch);
|
|
139
|
-
}
|
|
140
|
-
if (!require_utils.arraysEqual(idStore.get(), nextIds)) idStore.set(nextIds);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
69
|
//#endregion
|
|
144
70
|
exports.createNonReactiveMutableStore = createNonReactiveMutableStore;
|
|
145
71
|
exports.createNonReactiveReadonlyStore = createNonReactiveReadonlyStore;
|
package/dist/cjs/stores.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stores.cjs","names":[],"sources":["../../src/stores.ts"],"sourcesContent":["import { createLRUCache } from './lru-cache'\nimport { arraysEqual, functionalUpdate } from './utils'\n\nimport type { AnyRoute } from './route'\nimport type { RouterState } from './router'\nimport type { FullSearchSchema } from './routeInfo'\nimport type { ParsedLocation } from './location'\nimport type { AnyRedirect } from './redirect'\nimport type { AnyRouteMatch } from './Matches'\n\nexport interface RouterReadableStore<TValue> {\n get: () => TValue\n}\n\nexport interface RouterWritableStore<\n TValue,\n> extends RouterReadableStore<TValue> {\n set: ((updater: (prev: TValue) => TValue) => void) & ((value: TValue) => void)\n}\n\nexport type RouterBatchFn = (fn: () => void) => void\n\nexport type MutableStoreFactory = <TValue>(\n initialValue: TValue,\n) => RouterWritableStore<TValue>\n\nexport type ReadonlyStoreFactory = <TValue>(\n read: () => TValue,\n) => RouterReadableStore<TValue>\n\nexport type GetStoreConfig = (opts: { isServer?: boolean }) => StoreConfig\n\nexport type StoreConfig = {\n createMutableStore: MutableStoreFactory\n createReadonlyStore: ReadonlyStoreFactory\n batch: RouterBatchFn\n init?: (stores: RouterStores<AnyRoute>) => void\n}\n\ntype MatchStore = RouterWritableStore<AnyRouteMatch> & {\n routeId?: string\n}\ntype ReadableStore<TValue> = RouterReadableStore<TValue>\n\n/** SSR non-reactive createMutableStore */\nexport function createNonReactiveMutableStore<TValue>(\n initialValue: TValue,\n): RouterWritableStore<TValue> {\n let value = initialValue\n\n return {\n get() {\n return value\n },\n set(nextOrUpdater: TValue | ((prev: TValue) => TValue)) {\n value = functionalUpdate(nextOrUpdater, value)\n },\n }\n}\n\n/** SSR non-reactive createReadonlyStore */\nexport function createNonReactiveReadonlyStore<TValue>(\n read: () => TValue,\n): RouterReadableStore<TValue> {\n return {\n get() {\n return read()\n },\n }\n}\n\nexport interface RouterStores<in out TRouteTree extends AnyRoute> {\n status: RouterWritableStore<RouterState<TRouteTree>['status']>\n loadedAt: RouterWritableStore<number>\n isLoading: RouterWritableStore<boolean>\n isTransitioning: RouterWritableStore<boolean>\n location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>\n resolvedLocation: RouterWritableStore<\n ParsedLocation<FullSearchSchema<TRouteTree>> | undefined\n >\n statusCode: RouterWritableStore<number>\n redirect: RouterWritableStore<AnyRedirect | undefined>\n matchesId: RouterWritableStore<Array<string>>\n pendingIds: RouterWritableStore<Array<string>>\n /** @internal */\n cachedIds: RouterWritableStore<Array<string>>\n matches: ReadableStore<Array<AnyRouteMatch>>\n pendingMatches: ReadableStore<Array<AnyRouteMatch>>\n cachedMatches: ReadableStore<Array<AnyRouteMatch>>\n firstId: ReadableStore<string | undefined>\n hasPending: ReadableStore<boolean>\n matchRouteDeps: ReadableStore<{\n locationHref: string\n resolvedLocationHref: string | undefined\n status: RouterState<TRouteTree>['status']\n }>\n __store: RouterReadableStore<RouterState<TRouteTree>>\n\n matchStores: Map<string, MatchStore>\n pendingMatchStores: Map<string, MatchStore>\n cachedMatchStores: Map<string, MatchStore>\n\n /**\n * Get a computed store that resolves a routeId to its current match state.\n * Returns the same cached store instance for repeated calls with the same key.\n * The computed depends on matchesId + the individual match store, so\n * subscribers are only notified when the resolved match state changes.\n */\n getRouteMatchStore: (\n routeId: string,\n ) => RouterReadableStore<AnyRouteMatch | undefined>\n\n setMatches: (nextMatches: Array<AnyRouteMatch>) => void\n setPending: (nextMatches: Array<AnyRouteMatch>) => void\n setCached: (nextMatches: Array<AnyRouteMatch>) => void\n}\n\nexport function createRouterStores<TRouteTree extends AnyRoute>(\n initialState: RouterState<TRouteTree>,\n config: StoreConfig,\n): RouterStores<TRouteTree> {\n const { createMutableStore, createReadonlyStore, batch, init } = config\n\n // non reactive utilities\n const matchStores = new Map<string, MatchStore>()\n const pendingMatchStores = new Map<string, MatchStore>()\n const cachedMatchStores = new Map<string, MatchStore>()\n\n // atoms\n const status = createMutableStore(initialState.status)\n const loadedAt = createMutableStore(initialState.loadedAt)\n const isLoading = createMutableStore(initialState.isLoading)\n const isTransitioning = createMutableStore(initialState.isTransitioning)\n const location = createMutableStore(initialState.location)\n const resolvedLocation = createMutableStore(initialState.resolvedLocation)\n const statusCode = createMutableStore(initialState.statusCode)\n const redirect = createMutableStore(initialState.redirect)\n const matchesId = createMutableStore<Array<string>>([])\n const pendingIds = createMutableStore<Array<string>>([])\n const cachedIds = createMutableStore<Array<string>>([])\n\n // 1st order derived stores\n const matches = createReadonlyStore(() =>\n readPoolMatches(matchStores, matchesId.get()),\n )\n const pendingMatches = createReadonlyStore(() =>\n readPoolMatches(pendingMatchStores, pendingIds.get()),\n )\n const cachedMatches = createReadonlyStore(() =>\n readPoolMatches(cachedMatchStores, cachedIds.get()),\n )\n const firstId = createReadonlyStore(() => matchesId.get()[0])\n const hasPending = createReadonlyStore(() =>\n matchesId.get().some((matchId) => {\n const store = matchStores.get(matchId)\n return store?.get().status === 'pending'\n }),\n )\n const matchRouteDeps = createReadonlyStore(() => ({\n locationHref: location.get().href,\n resolvedLocationHref: resolvedLocation.get()?.href,\n status: status.get(),\n }))\n\n // compatibility \"big\" state store\n const __store = createReadonlyStore(() => ({\n status: status.get(),\n loadedAt: loadedAt.get(),\n isLoading: isLoading.get(),\n isTransitioning: isTransitioning.get(),\n matches: matches.get(),\n location: location.get(),\n resolvedLocation: resolvedLocation.get(),\n statusCode: statusCode.get(),\n redirect: redirect.get(),\n }))\n\n // Per-routeId computed store cache.\n // Each entry resolves routeId → match state through the signal graph,\n // giving consumers a single store to subscribe to instead of the\n // two-level byRouteId → matchStore pattern.\n //\n // 64 max size is arbitrary, this is only for active matches anyway so\n // it should be plenty. And we already have a 32 limit due to route\n // matching bitmask anyway.\n const matchStoreByRouteIdCache = createLRUCache<\n string,\n RouterReadableStore<AnyRouteMatch | undefined>\n >(64)\n\n function getRouteMatchStore(\n routeId: string,\n ): RouterReadableStore<AnyRouteMatch | undefined> {\n let cached = matchStoreByRouteIdCache.get(routeId)\n if (!cached) {\n cached = createReadonlyStore(() => {\n // Reading matchesId.get() tracks it as a dependency.\n // When matchesId changes (navigation), this computed re-evaluates.\n const ids = matchesId.get()\n for (const id of ids) {\n const matchStore = matchStores.get(id)\n if (matchStore && matchStore.routeId === routeId) {\n // Reading matchStore.get() tracks it as a dependency.\n // When the match store's state changes, this re-evaluates.\n return matchStore.get()\n }\n }\n return undefined\n })\n matchStoreByRouteIdCache.set(routeId, cached)\n }\n return cached\n }\n\n const store = {\n // atoms\n status,\n loadedAt,\n isLoading,\n isTransitioning,\n location,\n resolvedLocation,\n statusCode,\n redirect,\n matchesId,\n pendingIds,\n cachedIds,\n\n // derived\n matches,\n pendingMatches,\n cachedMatches,\n firstId,\n hasPending,\n matchRouteDeps,\n\n // non-reactive state\n matchStores,\n pendingMatchStores,\n cachedMatchStores,\n\n // compatibility \"big\" state\n __store,\n\n // per-key computed stores\n getRouteMatchStore,\n\n // methods\n setMatches,\n setPending,\n setCached,\n }\n\n // initialize the active matches\n setMatches(initialState.matches as Array<AnyRouteMatch>)\n init?.(store)\n\n // setters to update non-reactive utilities in sync with the reactive stores\n function setMatches(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n matchStores,\n matchesId,\n createMutableStore,\n batch,\n )\n }\n\n function setPending(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n pendingMatchStores,\n pendingIds,\n createMutableStore,\n batch,\n )\n }\n\n function setCached(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n cachedMatchStores,\n cachedIds,\n createMutableStore,\n batch,\n )\n }\n\n return store\n}\n\nfunction readPoolMatches(\n pool: Map<string, MatchStore>,\n ids: Array<string>,\n): Array<AnyRouteMatch> {\n const matches: Array<AnyRouteMatch> = []\n for (const id of ids) {\n const matchStore = pool.get(id)\n if (matchStore) {\n matches.push(matchStore.get())\n }\n }\n return matches\n}\n\nfunction reconcileMatchPool(\n nextMatches: Array<AnyRouteMatch>,\n pool: Map<string, MatchStore>,\n idStore: RouterWritableStore<Array<string>>,\n createMutableStore: MutableStoreFactory,\n batch: RouterBatchFn,\n): void {\n const nextIds = nextMatches.map((d) => d.id)\n const nextIdSet = new Set(nextIds)\n\n batch(() => {\n for (const id of pool.keys()) {\n if (!nextIdSet.has(id)) {\n pool.delete(id)\n }\n }\n\n for (const nextMatch of nextMatches) {\n const existing = pool.get(nextMatch.id)\n if (!existing) {\n const matchStore = createMutableStore(nextMatch) as MatchStore\n matchStore.routeId = nextMatch.routeId\n pool.set(nextMatch.id, matchStore)\n continue\n }\n\n existing.routeId = nextMatch.routeId\n if (existing.get() !== nextMatch) {\n existing.set(nextMatch)\n }\n }\n\n if (!arraysEqual(idStore.get(), nextIds)) {\n idStore.set(nextIds)\n }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stores.cjs","names":[],"sources":["../../src/stores.ts"],"sourcesContent":["import { arraysEqual, functionalUpdate } from './utils'\n\nimport type { AnyRoute } from './route'\nimport type { RouterState } from './router'\nimport type { FullSearchSchema } from './routeInfo'\nimport type { ParsedLocation } from './location'\nimport type { AnyRouteMatch } from './Matches'\n\nexport interface RouterReadableStore<TValue> {\n get: () => TValue\n}\n\nexport interface RouterWritableStore<\n TValue,\n> extends RouterReadableStore<TValue> {\n set: ((updater: (prev: TValue) => TValue) => void) & ((value: TValue) => void)\n}\n\nexport type RouterBatchFn = (fn: () => void) => void\n\nexport type MutableStoreFactory = <TValue>(\n initialValue: TValue,\n) => RouterWritableStore<TValue>\n\nexport type ReadonlyStoreFactory = <TValue>(\n read: () => TValue,\n) => RouterReadableStore<TValue>\n\nexport type GetStoreConfig = (opts: { isServer?: boolean }) => StoreConfig\n\nexport type StoreConfig = {\n createMutableStore: MutableStoreFactory\n createReadonlyStore: ReadonlyStoreFactory\n batch: RouterBatchFn\n}\n\ntype MatchStore = RouterWritableStore<AnyRouteMatch | undefined>\ntype ReadableStore<TValue> = RouterReadableStore<TValue>\n\n/** SSR non-reactive createMutableStore */\nexport function createNonReactiveMutableStore<TValue>(\n initialValue: TValue,\n): RouterWritableStore<TValue> {\n let value = initialValue\n\n return {\n get() {\n return value\n },\n set(nextOrUpdater: TValue | ((prev: TValue) => TValue)) {\n value = functionalUpdate(nextOrUpdater, value)\n },\n }\n}\n\n/** SSR non-reactive createReadonlyStore */\nexport function createNonReactiveReadonlyStore<TValue>(\n read: () => TValue,\n): RouterReadableStore<TValue> {\n return {\n get() {\n return read()\n },\n }\n}\n\nexport interface RouterStores<in out TRouteTree extends AnyRoute> {\n status: RouterWritableStore<RouterState<TRouteTree>['status']>\n location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>\n resolvedLocation: RouterWritableStore<\n ParsedLocation<FullSearchSchema<TRouteTree>> | undefined\n >\n ids: RouterWritableStore<Array<string>>\n matches: ReadableStore<Array<AnyRouteMatch>>\n __store: RouterReadableStore<RouterState<TRouteTree>>\n\n byRoute: Map<string, MatchStore>\n\n /**\n * Get the stable atom for a route's presented match. The atom remains in the\n * pool when the route leaves and contains `undefined` until it re-enters.\n */\n getMatchStore: (\n routeId: string,\n ) => RouterReadableStore<AnyRouteMatch | undefined>\n\n setMatches: (nextMatches: Array<AnyRouteMatch>) => void\n}\n\nexport function createRouterStores<TRouteTree extends AnyRoute>(\n initialLocation: RouterState<TRouteTree>['location'],\n config: StoreConfig,\n): RouterStores<TRouteTree> {\n const { createMutableStore, createReadonlyStore, batch } = config\n\n // non reactive utilities\n const byRoute = new Map<string, MatchStore>()\n\n // atoms\n const status = createMutableStore<RouterState<TRouteTree>['status']>('idle')\n const location = createMutableStore(initialLocation)\n const resolvedLocation =\n createMutableStore<RouterState<TRouteTree>['resolvedLocation']>(undefined)\n const ids = createMutableStore<Array<string>>([])\n\n // 1st order derived stores\n const matches = createReadonlyStore(() =>\n ids.get().map((id) => byRoute.get(id)!.get()!),\n )\n\n // compatibility \"big\" state store\n const __store = createReadonlyStore(() => ({\n status: status.get(),\n isLoading: status.get() === 'pending',\n matches: matches.get(),\n location: location.get(),\n resolvedLocation: resolvedLocation.get(),\n }))\n\n function getMatchStore(routeId: string): MatchStore {\n let matchStore = byRoute.get(routeId)\n if (!matchStore) {\n matchStore = createMutableStore<AnyRouteMatch | undefined>(undefined)\n byRoute.set(routeId, matchStore)\n }\n return matchStore\n }\n\n const store = {\n // atoms\n status,\n location,\n resolvedLocation,\n ids,\n\n // derived\n matches,\n\n // non-reactive state\n byRoute,\n\n // compatibility \"big\" state\n __store,\n\n // stable per-route presentation atoms\n getMatchStore,\n\n // methods\n setMatches,\n }\n\n // setters to update non-reactive utilities in sync with the reactive stores\n function setMatches(nextMatches: Array<AnyRouteMatch>) {\n const previousIds = ids.get()\n const nextIds = nextMatches.map((match) => match.routeId)\n\n batch(() => {\n // Publish lane membership first so framework trees reconcile departures\n // before observers of a leaving route receive its tombstone.\n if (!arraysEqual(previousIds, nextIds)) {\n ids.set(nextIds)\n }\n\n for (const id of previousIds) {\n if (!nextIds.includes(id)) {\n byRoute.get(id)!.set(() => undefined)\n }\n }\n\n for (const nextMatch of nextMatches) {\n const matchStore = getMatchStore(nextMatch.routeId)\n if (matchStore.get() !== nextMatch) {\n matchStore.set(nextMatch)\n }\n }\n })\n }\n\n return store\n}\n"],"mappings":";;;AAwCA,SAAgB,8BACd,cAC6B;CAC7B,IAAI,QAAQ;CAEZ,OAAO;EACL,MAAM;GACJ,OAAO;EACT;EACA,IAAI,eAAoD;GACtD,QAAQ,cAAA,iBAAiB,eAAe,KAAK;EAC/C;CACF;AACF;;AAGA,SAAgB,+BACd,MAC6B;CAC7B,OAAO,EACL,MAAM;EACJ,OAAO,KAAK;CACd,EACF;AACF;AAyBA,SAAgB,mBACd,iBACA,QAC0B;CAC1B,MAAM,EAAE,oBAAoB,qBAAqB,UAAU;CAG3D,MAAM,0BAAU,IAAI,IAAwB;CAG5C,MAAM,SAAS,mBAAsD,MAAM;CAC3E,MAAM,WAAW,mBAAmB,eAAe;CACnD,MAAM,mBACJ,mBAAgE,KAAA,CAAS;CAC3E,MAAM,MAAM,mBAAkC,CAAC,CAAC;CAGhD,MAAM,UAAU,0BACd,IAAI,IAAI,EAAE,KAAK,OAAO,QAAQ,IAAI,EAAE,EAAG,IAAI,CAAE,CAC/C;CAGA,MAAM,UAAU,2BAA2B;EACzC,QAAQ,OAAO,IAAI;EACnB,WAAW,OAAO,IAAI,MAAM;EAC5B,SAAS,QAAQ,IAAI;EACrB,UAAU,SAAS,IAAI;EACvB,kBAAkB,iBAAiB,IAAI;CACzC,EAAE;CAEF,SAAS,cAAc,SAA6B;EAClD,IAAI,aAAa,QAAQ,IAAI,OAAO;EACpC,IAAI,CAAC,YAAY;GACf,aAAa,mBAA8C,KAAA,CAAS;GACpE,QAAQ,IAAI,SAAS,UAAU;EACjC;EACA,OAAO;CACT;CAEA,MAAM,QAAQ;EAEZ;EACA;EACA;EACA;EAGA;EAGA;EAGA;EAGA;EAGA;CACF;CAGA,SAAS,WAAW,aAAmC;EACrD,MAAM,cAAc,IAAI,IAAI;EAC5B,MAAM,UAAU,YAAY,KAAK,UAAU,MAAM,OAAO;EAExD,YAAY;GAGV,IAAI,CAAC,cAAA,YAAY,aAAa,OAAO,GACnC,IAAI,IAAI,OAAO;GAGjB,KAAK,MAAM,MAAM,aACf,IAAI,CAAC,QAAQ,SAAS,EAAE,GACtB,QAAQ,IAAI,EAAE,EAAG,UAAU,KAAA,CAAS;GAIxC,KAAK,MAAM,aAAa,aAAa;IACnC,MAAM,aAAa,cAAc,UAAU,OAAO;IAClD,IAAI,WAAW,IAAI,MAAM,WACvB,WAAW,IAAI,SAAS;GAE5B;EACF,CAAC;CACH;CAEA,OAAO;AACT"}
|
package/dist/cjs/stores.d.cts
CHANGED
|
@@ -2,7 +2,6 @@ import { AnyRoute } from './route.cjs';
|
|
|
2
2
|
import { RouterState } from './router.cjs';
|
|
3
3
|
import { FullSearchSchema } from './routeInfo.cjs';
|
|
4
4
|
import { ParsedLocation } from './location.cjs';
|
|
5
|
-
import { AnyRedirect } from './redirect.cjs';
|
|
6
5
|
import { AnyRouteMatch } from './Matches.cjs';
|
|
7
6
|
export interface RouterReadableStore<TValue> {
|
|
8
7
|
get: () => TValue;
|
|
@@ -20,11 +19,8 @@ export type StoreConfig = {
|
|
|
20
19
|
createMutableStore: MutableStoreFactory;
|
|
21
20
|
createReadonlyStore: ReadonlyStoreFactory;
|
|
22
21
|
batch: RouterBatchFn;
|
|
23
|
-
init?: (stores: RouterStores<AnyRoute>) => void;
|
|
24
|
-
};
|
|
25
|
-
type MatchStore = RouterWritableStore<AnyRouteMatch> & {
|
|
26
|
-
routeId?: string;
|
|
27
22
|
};
|
|
23
|
+
type MatchStore = RouterWritableStore<AnyRouteMatch | undefined>;
|
|
28
24
|
type ReadableStore<TValue> = RouterReadableStore<TValue>;
|
|
29
25
|
/** SSR non-reactive createMutableStore */
|
|
30
26
|
export declare function createNonReactiveMutableStore<TValue>(initialValue: TValue): RouterWritableStore<TValue>;
|
|
@@ -32,39 +28,18 @@ export declare function createNonReactiveMutableStore<TValue>(initialValue: TVal
|
|
|
32
28
|
export declare function createNonReactiveReadonlyStore<TValue>(read: () => TValue): RouterReadableStore<TValue>;
|
|
33
29
|
export interface RouterStores<in out TRouteTree extends AnyRoute> {
|
|
34
30
|
status: RouterWritableStore<RouterState<TRouteTree>['status']>;
|
|
35
|
-
loadedAt: RouterWritableStore<number>;
|
|
36
|
-
isLoading: RouterWritableStore<boolean>;
|
|
37
|
-
isTransitioning: RouterWritableStore<boolean>;
|
|
38
31
|
location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>;
|
|
39
32
|
resolvedLocation: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>> | undefined>;
|
|
40
|
-
|
|
41
|
-
redirect: RouterWritableStore<AnyRedirect | undefined>;
|
|
42
|
-
matchesId: RouterWritableStore<Array<string>>;
|
|
43
|
-
pendingIds: RouterWritableStore<Array<string>>;
|
|
33
|
+
ids: RouterWritableStore<Array<string>>;
|
|
44
34
|
matches: ReadableStore<Array<AnyRouteMatch>>;
|
|
45
|
-
pendingMatches: ReadableStore<Array<AnyRouteMatch>>;
|
|
46
|
-
cachedMatches: ReadableStore<Array<AnyRouteMatch>>;
|
|
47
|
-
firstId: ReadableStore<string | undefined>;
|
|
48
|
-
hasPending: ReadableStore<boolean>;
|
|
49
|
-
matchRouteDeps: ReadableStore<{
|
|
50
|
-
locationHref: string;
|
|
51
|
-
resolvedLocationHref: string | undefined;
|
|
52
|
-
status: RouterState<TRouteTree>['status'];
|
|
53
|
-
}>;
|
|
54
35
|
__store: RouterReadableStore<RouterState<TRouteTree>>;
|
|
55
|
-
|
|
56
|
-
pendingMatchStores: Map<string, MatchStore>;
|
|
57
|
-
cachedMatchStores: Map<string, MatchStore>;
|
|
36
|
+
byRoute: Map<string, MatchStore>;
|
|
58
37
|
/**
|
|
59
|
-
* Get
|
|
60
|
-
*
|
|
61
|
-
* The computed depends on matchesId + the individual match store, so
|
|
62
|
-
* subscribers are only notified when the resolved match state changes.
|
|
38
|
+
* Get the stable atom for a route's presented match. The atom remains in the
|
|
39
|
+
* pool when the route leaves and contains `undefined` until it re-enters.
|
|
63
40
|
*/
|
|
64
|
-
|
|
41
|
+
getMatchStore: (routeId: string) => RouterReadableStore<AnyRouteMatch | undefined>;
|
|
65
42
|
setMatches: (nextMatches: Array<AnyRouteMatch>) => void;
|
|
66
|
-
setPending: (nextMatches: Array<AnyRouteMatch>) => void;
|
|
67
|
-
setCached: (nextMatches: Array<AnyRouteMatch>) => void;
|
|
68
43
|
}
|
|
69
|
-
export declare function createRouterStores<TRouteTree extends AnyRoute>(
|
|
44
|
+
export declare function createRouterStores<TRouteTree extends AnyRoute>(initialLocation: RouterState<TRouteTree>['location'], config: StoreConfig): RouterStores<TRouteTree>;
|
|
70
45
|
export {};
|
package/dist/cjs/utils.cjs
CHANGED
|
@@ -166,9 +166,6 @@ function isModuleNotFoundError(error) {
|
|
|
166
166
|
if (typeof error?.message !== "string") return false;
|
|
167
167
|
return error.message.startsWith("Failed to fetch dynamically imported module") || error.message.startsWith("error loading dynamically imported module") || error.message.startsWith("Importing a module script failed");
|
|
168
168
|
}
|
|
169
|
-
function isPromise(value) {
|
|
170
|
-
return Boolean(value && typeof value === "object" && typeof value.then === "function");
|
|
171
|
-
}
|
|
172
169
|
function findLast(array, predicate) {
|
|
173
170
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
174
171
|
const item = array[i];
|
|
@@ -346,7 +343,6 @@ exports.isDangerousProtocol = isDangerousProtocol;
|
|
|
346
343
|
exports.isModuleNotFoundError = isModuleNotFoundError;
|
|
347
344
|
exports.isPlainArray = isPlainArray;
|
|
348
345
|
exports.isPlainObject = isPlainObject;
|
|
349
|
-
exports.isPromise = isPromise;
|
|
350
346
|
exports.last = last;
|
|
351
347
|
exports.nullReplaceEqualDeep = nullReplaceEqualDeep;
|
|
352
348
|
exports.replaceEqualDeep = replaceEqualDeep;
|