@tanstack/router-core 1.168.2 → 1.168.4

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.
@@ -13,6 +13,7 @@ const require_stores = require("./stores.cjs");
13
13
  const require_router = require("./router.cjs");
14
14
  const require_defer = require("./defer.cjs");
15
15
  const require_link = require("./link.cjs");
16
+ const require_manifest = require("./manifest.cjs");
16
17
  const require_Matches = require("./Matches.cjs");
17
18
  const require_route = require("./route.cjs");
18
19
  const require_config = require("./config.cjs");
@@ -52,6 +53,7 @@ exports.escapeHtml = require_utils.escapeHtml;
52
53
  exports.exactPathTest = require_path.exactPathTest;
53
54
  exports.executeRewriteInput = require_rewrite.executeRewriteInput;
54
55
  exports.functionalUpdate = require_utils.functionalUpdate;
56
+ exports.getAssetCrossOrigin = require_manifest.getAssetCrossOrigin;
55
57
  exports.getCssSelector = require_scroll_restoration.getCssSelector;
56
58
  exports.getInitialRouterState = require_router.getInitialRouterState;
57
59
  exports.getLocationChangeInfo = require_router.getLocationChangeInfo;
@@ -78,6 +80,7 @@ exports.preloadWarning = require_link.preloadWarning;
78
80
  exports.redirect = require_redirect.redirect;
79
81
  exports.removeTrailingSlash = require_path.removeTrailingSlash;
80
82
  exports.replaceEqualDeep = require_utils.replaceEqualDeep;
83
+ exports.resolveManifestAssetLink = require_manifest.resolveManifestAssetLink;
81
84
  exports.resolvePath = require_path.resolvePath;
82
85
  exports.restoreScroll = require_scroll_restoration.restoreScroll;
83
86
  exports.retainSearchParams = require_searchMiddleware.retainSearchParams;
@@ -7,7 +7,8 @@ export type { IsRequiredParams, AddTrailingSlash, RemoveTrailingSlashes, AddLead
7
7
  export type { RouteToPath, TrailingSlashOptionByRouter, ParseRoute, CodeRouteToPath, RouteIds, FullSearchSchema, FullSearchSchemaInput, AllParams, RouteById, AllContext, RoutePaths, RoutesById, RoutesByPath, AllLoaderData, RouteByPath, } from './routeInfo.cjs';
8
8
  export type { InferFileRouteTypes, FileRouteTypes, FileRoutesByPath, CreateFileRoute, LazyRoute, LazyRouteOptions, CreateLazyFileRoute, } from './fileRoute.cjs';
9
9
  export type { ParsedLocation } from './location.cjs';
10
- export type { Manifest, RouterManagedTag } from './manifest.cjs';
10
+ export type { Manifest, RouterManagedTag, AssetCrossOrigin, AssetCrossOriginConfig, ManifestAssetLink, } from './manifest.cjs';
11
+ export { getAssetCrossOrigin, resolveManifestAssetLink } from './manifest.cjs';
11
12
  export { isMatch } from './Matches.cjs';
12
13
  export type { AnyMatchAndValue, FindValueByIndex, FindValueByKey, CreateMatchAndValue, NextMatchAndValue, IsMatchKeyOf, IsMatchPath, IsMatchResult, IsMatchParse, IsMatch, RouteMatch, RouteMatchExtensions, MakeRouteMatchUnion, MakeRouteMatch, AnyRouteMatch, MakeRouteMatchFromRoute, MatchRouteOptions, } from './Matches.cjs';
13
14
  export { joinPaths, cleanPath, trimPathLeft, trimPathRight, trimPath, removeTrailingSlash, exactPathTest, resolvePath, interpolatePath, } from './path.cjs';
@@ -0,0 +1,18 @@
1
+ //#region src/manifest.ts
2
+ function getAssetCrossOrigin(assetCrossOrigin, kind) {
3
+ if (!assetCrossOrigin) return;
4
+ if (typeof assetCrossOrigin === "string") return assetCrossOrigin;
5
+ return assetCrossOrigin[kind];
6
+ }
7
+ function resolveManifestAssetLink(link) {
8
+ if (typeof link === "string") return {
9
+ href: link,
10
+ crossOrigin: void 0
11
+ };
12
+ return link;
13
+ }
14
+ //#endregion
15
+ exports.getAssetCrossOrigin = getAssetCrossOrigin;
16
+ exports.resolveManifestAssetLink = resolveManifestAssetLink;
17
+
18
+ //# sourceMappingURL=manifest.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.cjs","names":[],"sources":["../../src/manifest.ts"],"sourcesContent":["export type AssetCrossOrigin = 'anonymous' | 'use-credentials'\n\nexport type AssetCrossOriginConfig =\n | AssetCrossOrigin\n | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>\n\nexport type ManifestAssetLink =\n | string\n | {\n href: string\n crossOrigin?: AssetCrossOrigin\n }\n\nexport function getAssetCrossOrigin(\n assetCrossOrigin: AssetCrossOriginConfig | undefined,\n kind: 'modulepreload' | 'stylesheet',\n): AssetCrossOrigin | undefined {\n if (!assetCrossOrigin) {\n return undefined\n }\n\n if (typeof assetCrossOrigin === 'string') {\n return assetCrossOrigin\n }\n\n return assetCrossOrigin[kind]\n}\n\nexport function resolveManifestAssetLink(link: ManifestAssetLink) {\n if (typeof link === 'string') {\n return { href: link, crossOrigin: undefined }\n }\n\n return link\n}\n\nexport type Manifest = {\n routes: Record<\n string,\n {\n filePath?: string\n preloads?: Array<ManifestAssetLink>\n assets?: Array<RouterManagedTag>\n }\n >\n}\n\nexport type RouterManagedTag =\n | {\n tag: 'title'\n attrs?: Record<string, any>\n children: string\n }\n | {\n tag: 'meta' | 'link'\n attrs?: Record<string, any>\n children?: never\n }\n | {\n tag: 'script'\n attrs?: Record<string, any>\n children?: string\n }\n | {\n tag: 'style'\n attrs?: Record<string, any>\n children?: string\n }\n"],"mappings":";AAaA,SAAgB,oBACd,kBACA,MAC8B;AAC9B,KAAI,CAAC,iBACH;AAGF,KAAI,OAAO,qBAAqB,SAC9B,QAAO;AAGT,QAAO,iBAAiB;;AAG1B,SAAgB,yBAAyB,MAAyB;AAChE,KAAI,OAAO,SAAS,SAClB,QAAO;EAAE,MAAM;EAAM,aAAa,KAAA;EAAW;AAG/C,QAAO"}
@@ -1,7 +1,18 @@
1
+ export type AssetCrossOrigin = 'anonymous' | 'use-credentials';
2
+ export type AssetCrossOriginConfig = AssetCrossOrigin | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>;
3
+ export type ManifestAssetLink = string | {
4
+ href: string;
5
+ crossOrigin?: AssetCrossOrigin;
6
+ };
7
+ export declare function getAssetCrossOrigin(assetCrossOrigin: AssetCrossOriginConfig | undefined, kind: 'modulepreload' | 'stylesheet'): AssetCrossOrigin | undefined;
8
+ export declare function resolveManifestAssetLink(link: ManifestAssetLink): {
9
+ href: string;
10
+ crossOrigin?: AssetCrossOrigin;
11
+ };
1
12
  export type Manifest = {
2
13
  routes: Record<string, {
3
14
  filePath?: string;
4
- preloads?: Array<string>;
15
+ preloads?: Array<ManifestAssetLink>;
5
16
  assets?: Array<RouterManagedTag>;
6
17
  }>;
7
18
  };
@@ -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 function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}
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 function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c: ReadableStreamDefaultController<Uint8Array>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c: ReadableStreamDefaultController<any>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}
@@ -7,7 +7,8 @@ export type { IsRequiredParams, AddTrailingSlash, RemoveTrailingSlashes, AddLead
7
7
  export type { RouteToPath, TrailingSlashOptionByRouter, ParseRoute, CodeRouteToPath, RouteIds, FullSearchSchema, FullSearchSchemaInput, AllParams, RouteById, AllContext, RoutePaths, RoutesById, RoutesByPath, AllLoaderData, RouteByPath, } from './routeInfo.js';
8
8
  export type { InferFileRouteTypes, FileRouteTypes, FileRoutesByPath, CreateFileRoute, LazyRoute, LazyRouteOptions, CreateLazyFileRoute, } from './fileRoute.js';
9
9
  export type { ParsedLocation } from './location.js';
10
- export type { Manifest, RouterManagedTag } from './manifest.js';
10
+ export type { Manifest, RouterManagedTag, AssetCrossOrigin, AssetCrossOriginConfig, ManifestAssetLink, } from './manifest.js';
11
+ export { getAssetCrossOrigin, resolveManifestAssetLink } from './manifest.js';
11
12
  export { isMatch } from './Matches.js';
12
13
  export type { AnyMatchAndValue, FindValueByIndex, FindValueByKey, CreateMatchAndValue, NextMatchAndValue, IsMatchKeyOf, IsMatchPath, IsMatchResult, IsMatchParse, IsMatch, RouteMatch, RouteMatchExtensions, MakeRouteMatchUnion, MakeRouteMatch, AnyRouteMatch, MakeRouteMatchFromRoute, MatchRouteOptions, } from './Matches.js';
13
14
  export { joinPaths, cleanPath, trimPathLeft, trimPathRight, trimPath, removeTrailingSlash, exactPathTest, resolvePath, interpolatePath, } from './path.js';
package/dist/esm/index.js CHANGED
@@ -12,6 +12,7 @@ import { createNonReactiveMutableStore, createNonReactiveReadonlyStore } from ".
12
12
  import { PathParamError, RouterCore, SearchParamError, defaultSerializeError, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, lazyFn, trailingSlashOptions } from "./router.js";
13
13
  import { TSR_DEFERRED_PROMISE, defer } from "./defer.js";
14
14
  import { preloadWarning } from "./link.js";
15
+ import { getAssetCrossOrigin, resolveManifestAssetLink } from "./manifest.js";
15
16
  import { isMatch } from "./Matches.js";
16
17
  import { BaseRootRoute, BaseRoute, BaseRouteApi } from "./route.js";
17
18
  import { createRouterConfig } from "./config.js";
@@ -19,4 +20,4 @@ import { retainSearchParams, stripSearchParams } from "./searchMiddleware.js";
19
20
  import { createSerializationAdapter, makeSerovalPlugin, makeSsrSerovalPlugin } from "./ssr/serializer/transformer.js";
20
21
  import { RawStream, createRawStreamDeserializePlugin, createRawStreamRPCPlugin } from "./ssr/serializer/RawStream.js";
21
22
  import { defaultSerovalPlugins } from "./ssr/serializer/seroval-plugins.js";
22
- export { BaseRootRoute, BaseRoute, BaseRouteApi, DEFAULT_PROTOCOL_ALLOWLIST, PathParamError, RawStream, RouterCore, SearchParamError, TSR_DEFERRED_PROMISE, buildDevStylesUrl, cleanPath, composeRewrites, createControlledPromise, createNonReactiveMutableStore, createNonReactiveReadonlyStore, createRawStreamDeserializePlugin, createRawStreamRPCPlugin, createRouterConfig, createSerializationAdapter, decode, deepEqual, defaultGetScrollRestorationKey, defaultParseSearch, defaultSerializeError, defaultSerovalPlugins, defaultStringifySearch, defer, encode, escapeHtml, exactPathTest, executeRewriteInput, functionalUpdate, getCssSelector, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, handleHashScroll, interpolatePath, invariant, isDangerousProtocol, isMatch, isModuleNotFoundError, isNotFound, isPlainArray, isPlainObject, isRedirect, isResolvedRedirect, joinPaths, lazyFn, makeSerovalPlugin, makeSsrSerovalPlugin, notFound, parseRedirect, parseSearchWith, preloadWarning, redirect, removeTrailingSlash, replaceEqualDeep, resolvePath, restoreScroll, retainSearchParams, rootRouteId, scrollRestorationCache, setupScrollRestoration, storageKey, stringifySearchWith, stripSearchParams, trailingSlashOptions, trimPath, trimPathLeft, trimPathRight };
23
+ export { BaseRootRoute, BaseRoute, BaseRouteApi, DEFAULT_PROTOCOL_ALLOWLIST, PathParamError, RawStream, RouterCore, SearchParamError, TSR_DEFERRED_PROMISE, buildDevStylesUrl, cleanPath, composeRewrites, createControlledPromise, createNonReactiveMutableStore, createNonReactiveReadonlyStore, createRawStreamDeserializePlugin, createRawStreamRPCPlugin, createRouterConfig, createSerializationAdapter, decode, deepEqual, defaultGetScrollRestorationKey, defaultParseSearch, defaultSerializeError, defaultSerovalPlugins, defaultStringifySearch, defer, encode, escapeHtml, exactPathTest, executeRewriteInput, functionalUpdate, getAssetCrossOrigin, getCssSelector, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, handleHashScroll, interpolatePath, invariant, isDangerousProtocol, isMatch, isModuleNotFoundError, isNotFound, isPlainArray, isPlainObject, isRedirect, isResolvedRedirect, joinPaths, lazyFn, makeSerovalPlugin, makeSsrSerovalPlugin, notFound, parseRedirect, parseSearchWith, preloadWarning, redirect, removeTrailingSlash, replaceEqualDeep, resolveManifestAssetLink, resolvePath, restoreScroll, retainSearchParams, rootRouteId, scrollRestorationCache, setupScrollRestoration, storageKey, stringifySearchWith, stripSearchParams, trailingSlashOptions, trimPath, trimPathLeft, trimPathRight };
@@ -1,7 +1,18 @@
1
+ export type AssetCrossOrigin = 'anonymous' | 'use-credentials';
2
+ export type AssetCrossOriginConfig = AssetCrossOrigin | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>;
3
+ export type ManifestAssetLink = string | {
4
+ href: string;
5
+ crossOrigin?: AssetCrossOrigin;
6
+ };
7
+ export declare function getAssetCrossOrigin(assetCrossOrigin: AssetCrossOriginConfig | undefined, kind: 'modulepreload' | 'stylesheet'): AssetCrossOrigin | undefined;
8
+ export declare function resolveManifestAssetLink(link: ManifestAssetLink): {
9
+ href: string;
10
+ crossOrigin?: AssetCrossOrigin;
11
+ };
1
12
  export type Manifest = {
2
13
  routes: Record<string, {
3
14
  filePath?: string;
4
- preloads?: Array<string>;
15
+ preloads?: Array<ManifestAssetLink>;
5
16
  assets?: Array<RouterManagedTag>;
6
17
  }>;
7
18
  };
@@ -0,0 +1,17 @@
1
+ //#region src/manifest.ts
2
+ function getAssetCrossOrigin(assetCrossOrigin, kind) {
3
+ if (!assetCrossOrigin) return;
4
+ if (typeof assetCrossOrigin === "string") return assetCrossOrigin;
5
+ return assetCrossOrigin[kind];
6
+ }
7
+ function resolveManifestAssetLink(link) {
8
+ if (typeof link === "string") return {
9
+ href: link,
10
+ crossOrigin: void 0
11
+ };
12
+ return link;
13
+ }
14
+ //#endregion
15
+ export { getAssetCrossOrigin, resolveManifestAssetLink };
16
+
17
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","names":[],"sources":["../../src/manifest.ts"],"sourcesContent":["export type AssetCrossOrigin = 'anonymous' | 'use-credentials'\n\nexport type AssetCrossOriginConfig =\n | AssetCrossOrigin\n | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>\n\nexport type ManifestAssetLink =\n | string\n | {\n href: string\n crossOrigin?: AssetCrossOrigin\n }\n\nexport function getAssetCrossOrigin(\n assetCrossOrigin: AssetCrossOriginConfig | undefined,\n kind: 'modulepreload' | 'stylesheet',\n): AssetCrossOrigin | undefined {\n if (!assetCrossOrigin) {\n return undefined\n }\n\n if (typeof assetCrossOrigin === 'string') {\n return assetCrossOrigin\n }\n\n return assetCrossOrigin[kind]\n}\n\nexport function resolveManifestAssetLink(link: ManifestAssetLink) {\n if (typeof link === 'string') {\n return { href: link, crossOrigin: undefined }\n }\n\n return link\n}\n\nexport type Manifest = {\n routes: Record<\n string,\n {\n filePath?: string\n preloads?: Array<ManifestAssetLink>\n assets?: Array<RouterManagedTag>\n }\n >\n}\n\nexport type RouterManagedTag =\n | {\n tag: 'title'\n attrs?: Record<string, any>\n children: string\n }\n | {\n tag: 'meta' | 'link'\n attrs?: Record<string, any>\n children?: never\n }\n | {\n tag: 'script'\n attrs?: Record<string, any>\n children?: string\n }\n | {\n tag: 'style'\n attrs?: Record<string, any>\n children?: string\n }\n"],"mappings":";AAaA,SAAgB,oBACd,kBACA,MAC8B;AAC9B,KAAI,CAAC,iBACH;AAGF,KAAI,OAAO,qBAAqB,SAC9B,QAAO;AAGT,QAAO,iBAAiB;;AAG1B,SAAgB,yBAAyB,MAAyB;AAChE,KAAI,OAAO,SAAS,SAClB,QAAO;EAAE,MAAM;EAAM,aAAa,KAAA;EAAW;AAG/C,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"transformStreamWithRouter.js","names":[],"sources":["../../../src/ssr/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { TSR_SCRIPT_BARRIER_ID } from './constants'\nimport type { AnyRouter } from '../router'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transformStreamWithRouter.js","names":[],"sources":["../../../src/ssr/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { TSR_SCRIPT_BARRIER_ID } from './constants'\nimport type { AnyRouter } from '../router'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c: ReadableStreamDefaultController<Uint8Array>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c: ReadableStreamDefaultController<any>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.168.2",
3
+ "version": "1.168.4",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -153,8 +153,9 @@
153
153
  "@tanstack/history": "1.161.6"
154
154
  },
155
155
  "devDependencies": {
156
- "@tanstack/store": "^0.9.2",
157
156
  "@tanstack/intent": "^0.0.14",
157
+ "@tanstack/store": "^0.9.3",
158
+ "@types/node": "25.0.9",
158
159
  "esbuild": "^0.27.4",
159
160
  "vite": "*"
160
161
  },
@@ -165,12 +166,12 @@
165
166
  "clean": "rimraf ./dist && rimraf ./coverage",
166
167
  "test:eslint": "eslint ./src",
167
168
  "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"",
168
- "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js",
169
169
  "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js",
170
170
  "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js",
171
171
  "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js",
172
172
  "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js",
173
- "test:types:ts59": "tsc",
173
+ "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js",
174
+ "test:types:ts60": "tsc",
174
175
  "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .",
175
176
  "test:unit": "vitest",
176
177
  "test:unit:dev": "pnpm run test:unit --watch",
package/src/index.ts CHANGED
@@ -68,7 +68,14 @@ export type {
68
68
  } from './fileRoute'
69
69
 
70
70
  export type { ParsedLocation } from './location'
71
- export type { Manifest, RouterManagedTag } from './manifest'
71
+ export type {
72
+ Manifest,
73
+ RouterManagedTag,
74
+ AssetCrossOrigin,
75
+ AssetCrossOriginConfig,
76
+ ManifestAssetLink,
77
+ } from './manifest'
78
+ export { getAssetCrossOrigin, resolveManifestAssetLink } from './manifest'
72
79
  export { isMatch } from './Matches'
73
80
  export type {
74
81
  AnyMatchAndValue,
package/src/manifest.ts CHANGED
@@ -1,9 +1,45 @@
1
+ export type AssetCrossOrigin = 'anonymous' | 'use-credentials'
2
+
3
+ export type AssetCrossOriginConfig =
4
+ | AssetCrossOrigin
5
+ | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>
6
+
7
+ export type ManifestAssetLink =
8
+ | string
9
+ | {
10
+ href: string
11
+ crossOrigin?: AssetCrossOrigin
12
+ }
13
+
14
+ export function getAssetCrossOrigin(
15
+ assetCrossOrigin: AssetCrossOriginConfig | undefined,
16
+ kind: 'modulepreload' | 'stylesheet',
17
+ ): AssetCrossOrigin | undefined {
18
+ if (!assetCrossOrigin) {
19
+ return undefined
20
+ }
21
+
22
+ if (typeof assetCrossOrigin === 'string') {
23
+ return assetCrossOrigin
24
+ }
25
+
26
+ return assetCrossOrigin[kind]
27
+ }
28
+
29
+ export function resolveManifestAssetLink(link: ManifestAssetLink) {
30
+ if (typeof link === 'string') {
31
+ return { href: link, crossOrigin: undefined }
32
+ }
33
+
34
+ return link
35
+ }
36
+
1
37
  export type Manifest = {
2
38
  routes: Record<
3
39
  string,
4
40
  {
5
41
  filePath?: string
6
- preloads?: Array<string>
42
+ preloads?: Array<ManifestAssetLink>
7
43
  assets?: Array<RouterManagedTag>
8
44
  }
9
45
  >
@@ -167,7 +167,7 @@ export function transformStreamWithRouter(
167
167
  }, lifetimeMs)
168
168
 
169
169
  const stream = new ReadableStream<Uint8Array>({
170
- start(c) {
170
+ start(c: ReadableStreamDefaultController<Uint8Array>) {
171
171
  controller = c
172
172
  },
173
173
  cancel() {
@@ -299,7 +299,7 @@ export function transformStreamWithRouter(
299
299
  }
300
300
 
301
301
  const stream = new ReadableStream({
302
- start(c) {
302
+ start(c: ReadableStreamDefaultController<any>) {
303
303
  controller = c
304
304
  },
305
305
  cancel() {