@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":"ssr-server.js","names":[],"sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport { invariant } from '../invariant'\nimport {\n createInlineCssPlaceholderAsset,\n createInlineCssStyleAsset,\n getStylesheetHref,\n} from '../manifest'\nimport { decodePath } from '../utils'\nimport { createLRUCache } from '../lru-cache'\nimport { rootRouteId } from '../root'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'\nimport { dehydrateSsrMatchId } from './ssr-match-id'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { LRUCache } from '../lru-cache'\nimport type { DehydratedMatch, DehydratedRouter } from './types'\nimport type { AnySerializationAdapter } from './serializer/transformer'\nimport type { AnyRouter, ServerSsr } from '../router'\nimport type { AnyRouteMatch } from '../Matches'\nimport type {\n Manifest,\n ManifestRoute,\n ManifestRouteAssets,\n RouterManagedTag,\n ServerManifest,\n} from '../manifest'\n\nconst SCOPE_ID = 'tsr'\n\nconst TSR_PREFIX = GLOBAL_TSR + '.router='\nconst P_PREFIX = GLOBAL_TSR + '.p(()=>'\nconst P_SUFFIX = ')'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: dehydrateSsrMatchId(match.id),\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n if (match.globalNotFound) {\n dehydratedMatch.g = true\n }\n return dehydratedMatch\n}\n\nconst INITIAL_SCRIPTS = [\n getCrossReferenceHeader(SCOPE_ID),\n minifiedTsrBootStrapScript,\n]\n\nclass ScriptBuffer {\n private injectScript: ((script: string) => void) | undefined\n private _queue: Array<string>\n private _scriptBarrierLifted = false\n private _cleanedUp = false\n private _microtaskVersion = 0\n private _pendingMicrotaskVersion = 0\n\n constructor(injectScript: (script: string) => void) {\n this.injectScript = injectScript\n // Copy INITIAL_SCRIPTS to avoid mutating the shared array\n this._queue = INITIAL_SCRIPTS.slice()\n }\n\n enqueue(script: string) {\n if (this._cleanedUp) return\n this._queue.push(script)\n if (this._scriptBarrierLifted) {\n this.scheduleInjectBufferedScripts()\n }\n }\n\n liftBarrier() {\n if (this._scriptBarrierLifted || this._cleanedUp) return\n this._scriptBarrierLifted = true\n if (this._queue.length > 0) {\n this.scheduleInjectBufferedScripts()\n }\n }\n\n scheduleInjectBufferedScripts() {\n if (this._pendingMicrotaskVersion !== 0) return\n const pendingVersion = ++this._microtaskVersion\n this._pendingMicrotaskVersion = pendingVersion\n queueMicrotask(() => {\n if (this._pendingMicrotaskVersion !== pendingVersion) return\n this._pendingMicrotaskVersion = 0\n this.injectBufferedScripts()\n })\n }\n\n clearPendingMicrotask() {\n if (this._pendingMicrotaskVersion === 0) return\n this._pendingMicrotaskVersion = 0\n this._microtaskVersion++\n }\n\n /**\n * Flushes any pending scripts synchronously.\n * Call this before signaling serialization finished to ensure all scripts are injected.\n *\n * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,\n * scripts should remain in the queue so takeBufferedScripts() can retrieve them\n */\n flush() {\n if (!this._scriptBarrierLifted) return\n if (this._cleanedUp) return\n this.clearPendingMicrotask()\n this.injectBufferedScripts()\n }\n\n takeAll() {\n return this.takeScripts(this._queue.length)\n }\n\n takeScripts(count: number) {\n if (count <= 0) return undefined\n const bufferedScripts = this._queue.splice(0, count)\n if (bufferedScripts.length === 0) {\n return undefined\n }\n // Optimization: if only one script, avoid join\n if (bufferedScripts.length === 1) {\n return bufferedScripts[0] + ';document.currentScript.remove()'\n }\n // Append cleanup script and join - avoid push() to not mutate then iterate\n return bufferedScripts.join(';') + ';document.currentScript.remove()'\n }\n\n hasPending() {\n return this._queue.length > 0\n }\n\n injectBufferedScripts() {\n if (this._cleanedUp) return\n // Early return if queue is empty (avoids unnecessary takeAll() call)\n if (this._queue.length === 0) return\n const scriptsToInject = this.takeAll()\n if (scriptsToInject) {\n this.injectScript?.(scriptsToInject)\n }\n }\n\n cleanup() {\n this._cleanedUp = true\n this.clearPendingMicrotask()\n this._queue = []\n this.injectScript = undefined\n }\n}\n\nconst isProd = process.env.NODE_ENV === 'production'\n\ntype FilteredRoutes = Manifest['routes']\n\ntype PreparedMatchedManifestRoutes = {\n routes: FilteredRoutes\n hasStrippedRoutes: boolean\n inlineCssHrefs?: Array<string>\n inlineCss?: string\n}\n\ntype ManifestLRU = LRUCache<string, PreparedMatchedManifestRoutes>\n\nconst MANIFEST_CACHE_SIZE = 100\nconst manifestCaches = new WeakMap<ServerManifest, ManifestLRU>()\n\nfunction getManifestCache(manifest: ServerManifest): ManifestLRU {\n const cache = manifestCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, PreparedMatchedManifestRoutes>(\n MANIFEST_CACHE_SIZE,\n )\n manifestCaches.set(manifest, newCache)\n return newCache\n}\n\nfunction getInlineCssForPreparedRoutes(\n manifest: ServerManifest,\n preparedRoutes: PreparedMatchedManifestRoutes,\n) {\n if (preparedRoutes.inlineCss !== undefined) return preparedRoutes.inlineCss\n\n const styles = manifest.inlineCss?.styles\n const hrefs = preparedRoutes.inlineCssHrefs\n if (!styles || !hrefs?.length) return undefined\n\n let css = ''\n for (const href of hrefs) {\n css += styles[href]!\n }\n\n preparedRoutes.inlineCss = css\n return css\n}\n\nfunction getInlineCssAssetForPreparedRoutes(\n manifest: ServerManifest,\n preparedRoutes: PreparedMatchedManifestRoutes,\n) {\n const css = getInlineCssForPreparedRoutes(manifest, preparedRoutes)\n\n return css === undefined ? undefined : createInlineCssStyleAsset(css)\n}\n\nfunction getMatchedRoutesCacheKey(matches: Array<AnyRouteMatch>) {\n let cacheKey = ''\n for (let i = 0; i < matches.length; i++) {\n cacheKey += (i === 0 ? '' : '\\0') + matches[i]!.routeId\n }\n return cacheKey\n}\n\nfunction getPreparedMatchedManifestRoutes(\n manifest: ServerManifest,\n matches: Array<AnyRouteMatch>,\n cacheKey: string,\n) {\n if (isProd) {\n const cached = getManifestCache(manifest).get(cacheKey)\n if (cached) {\n return cached\n }\n }\n\n const preparedRoutes = prepareMatchedManifestRoutes(manifest, matches)\n\n if (isProd) {\n getManifestCache(manifest).set(cacheKey, preparedRoutes)\n }\n\n return preparedRoutes\n}\n\nfunction prepareMatchedManifestRoutes(\n manifest: ServerManifest,\n matches: Array<AnyRouteMatch>,\n): PreparedMatchedManifestRoutes {\n const inlineStyles = manifest.inlineCss?.styles\n const routes: FilteredRoutes = {}\n\n if (!inlineStyles) {\n for (const match of matches) {\n const route = manifest.routes[match.routeId]\n if (route) {\n routes[match.routeId] = route\n }\n }\n return { routes, hasStrippedRoutes: false }\n }\n\n const inlineCssHrefs: Array<string> = []\n const seenInlineCssHrefs = new Set<string>()\n let hasStrippedRoutes = false\n\n for (const match of matches) {\n const routeId = match.routeId\n const route = manifest.routes[routeId]\n if (!route) {\n continue\n }\n\n const nextRoute = stripInlinedStylesheetAssetsFromRoute(\n inlineStyles,\n route,\n inlineCssHrefs,\n seenInlineCssHrefs,\n )\n\n if (nextRoute !== route) {\n hasStrippedRoutes = true\n }\n routes[routeId] = nextRoute\n }\n\n return {\n routes,\n hasStrippedRoutes,\n ...(inlineCssHrefs.length ? { inlineCssHrefs } : {}),\n }\n}\n\nfunction stripInlinedStylesheetAssetsFromRoute(\n inlineStyles: Record<string, string>,\n route: ManifestRoute,\n inlineCssHrefs: Array<string>,\n seenInlineCssHrefs: Set<string>,\n): ManifestRoute {\n const css = route.css\n if (!css) {\n return route\n }\n\n if (css.length === 0) {\n const nextRoute = { ...route }\n delete nextRoute.css\n return nextRoute\n }\n\n let cssLinks: typeof css | undefined\n for (let i = 0; i < css.length; i++) {\n const link = css[i]!\n const href = getStylesheetHref(link)\n if (inlineStyles[href] === undefined) {\n if (cssLinks) {\n cssLinks.push(link)\n }\n continue\n }\n\n if (!seenInlineCssHrefs.has(href)) {\n seenInlineCssHrefs.add(href)\n inlineCssHrefs.push(href)\n }\n\n if (!cssLinks) {\n cssLinks = css.slice(0, i)\n }\n }\n\n if (!cssLinks) {\n return route\n }\n\n if (cssLinks.length > 0) {\n return { ...route, css: cssLinks }\n }\n\n const nextRoute = { ...route }\n delete nextRoute.css\n return nextRoute\n}\n\nfunction hasRouteAssets(route: ManifestRoute) {\n return !!route.scripts?.length || !!route.css?.length\n}\n\nfunction hasRequestAssets(assets: ManifestRouteAssets | undefined) {\n return !!assets && (!!assets.preloads?.length || hasRouteAssets(assets))\n}\n\nfunction mergeRequestAssetsIntoRootRoute(\n rootRoute: ManifestRoute | undefined,\n requestAssets: ManifestRouteAssets | undefined,\n): ManifestRoute {\n const preloads = requestAssets?.preloads?.length\n ? [...requestAssets.preloads, ...(rootRoute?.preloads ?? [])]\n : rootRoute?.preloads\n const scripts = requestAssets?.scripts?.length\n ? [...requestAssets.scripts, ...(rootRoute?.scripts ?? [])]\n : rootRoute?.scripts\n const cssLinks = requestAssets?.css?.length\n ? [...requestAssets.css, ...(rootRoute?.css ?? [])]\n : rootRoute?.css\n\n return {\n ...(rootRoute ?? {}),\n ...(preloads?.length ? { preloads } : {}),\n ...(scripts?.length ? { scripts } : {}),\n ...(cssLinks?.length ? { css: cssLinks } : {}),\n }\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n getRequestAssets,\n}: {\n router: AnyRouter\n manifest: ServerManifest | undefined\n getRequestAssets?: () => ManifestRouteAssets | undefined\n}) {\n router.ssr = {\n get manifest() {\n if (!manifest) return manifest\n\n const requestAssets = getRequestAssets?.()\n const matches = router.stores.matches.get()\n const hasAssets = hasRequestAssets(requestAssets)\n\n if (!hasAssets && !manifest.inlineCss) {\n return manifest\n }\n\n let inlineCssAsset: Manifest['inlineStyle'] | undefined\n let routes = manifest.routes\n if (manifest.inlineCss) {\n const cacheKey = getMatchedRoutesCacheKey(matches)\n const preparedManifest = getPreparedMatchedManifestRoutes(\n manifest,\n matches,\n cacheKey,\n )\n inlineCssAsset = getInlineCssAssetForPreparedRoutes(\n manifest,\n preparedManifest,\n )\n if (preparedManifest.hasStrippedRoutes) {\n routes = { ...manifest.routes, ...preparedManifest.routes }\n }\n }\n\n if (!hasAssets) {\n return {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),\n routes,\n }\n }\n\n const rootRoute = routes[rootRouteId]\n\n // Merge request-scoped assets into root route without mutating cached manifest\n return {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),\n routes: {\n ...routes,\n [rootRouteId]: mergeRequestAssetsIntoRootRoute(\n rootRoute,\n requestAssets,\n ),\n },\n }\n },\n }\n let _dehydrated = false\n let _serializationFinished = false\n let streamFastPathReserved = false\n const renderFinishedListeners: Array<() => void> = []\n const injectedHtmlListeners: Array<() => void> = []\n const serializationFinishedListeners: Array<() => void> = []\n const cleanupListeners: Array<() => void> = []\n let cleanupStarted = false\n let injectedHtmlBuffer = ''\n\n const callListeners = (listeners: Array<() => void>, errorPrefix: string) => {\n const snapshot = listeners.slice()\n for (const l of snapshot) {\n try {\n l()\n } catch (err) {\n console.error(`${errorPrefix}:`, err)\n }\n }\n }\n\n const removeListener = (\n listeners: Array<() => void>,\n listener: () => void,\n ) => {\n const index = listeners.indexOf(listener)\n if (index >= 0) listeners.splice(index, 1)\n }\n\n const scriptBuffer = new ScriptBuffer((script) => {\n serverSsr.injectScript(script)\n })\n\n const serverSsr: ServerSsr = {\n injectHtml: (html: string) => {\n if (!html || cleanupStarted) return\n // Buffer the HTML so it can be retrieved via takeBufferedHtml()\n injectedHtmlBuffer += html\n callListeners(injectedHtmlListeners, 'SSR injected HTML listener error')\n },\n injectScript: (script: string) => {\n if (!script || cleanupStarted) return\n const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`\n serverSsr.injectHtml(html)\n },\n dehydrate: async (opts?: { requestAssets?: ManifestRouteAssets }) => {\n if (_dehydrated) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: router is already dehydrated!')\n }\n\n invariant()\n }\n let matchesToDehydrate = router.stores.matches.get()\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n let manifestToDehydrate: Manifest | undefined = undefined\n // Only currently matched routes are dehydrated. Other route assets are\n // loaded through dynamic imports when those routes become active.\n if (manifest) {\n const cacheKey = getMatchedRoutesCacheKey(matchesToDehydrate)\n const preparedManifest = getPreparedMatchedManifestRoutes(\n manifest,\n matchesToDehydrate,\n cacheKey,\n )\n\n manifestToDehydrate = {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(preparedManifest.inlineCssHrefs\n ? { inlineStyle: createInlineCssPlaceholderAsset() }\n : {}),\n routes: preparedManifest.routes,\n }\n\n // Merge request-scoped assets into root route (without mutating cached manifest)\n const requestAssets = opts?.requestAssets\n if (hasRequestAssets(requestAssets)) {\n const existingRoot = manifestToDehydrate.routes[rootRouteId]\n manifestToDehydrate.routes = {\n ...manifestToDehydrate.routes,\n [rootRouteId]: mergeRequestAssetsIntoRootRoute(\n existingRoot,\n requestAssets,\n ),\n }\n }\n }\n const dehydratedRouter: DehydratedRouter = {\n manifest: manifestToDehydrate,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId)\n }\n const dehydratedData = await router.options.dehydrate?.()\n if (dehydratedData) {\n dehydratedRouter.dehydratedData = dehydratedData\n }\n _dehydrated = true\n\n const trackPlugins = { didRun: false }\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n const plugins = serializationAdapters\n ? serializationAdapters\n .map((t) => makeSsrSerovalPlugin(t, trackPlugins))\n .concat(defaultSerovalPlugins)\n : defaultSerovalPlugins\n\n let serializationCompleteSignaled = false\n const signalSerializationComplete = () => {\n if (serializationCompleteSignaled || cleanupStarted) return\n serializationCompleteSignaled = true\n _serializationFinished = true\n\n const listeners = serializationFinishedListeners.slice()\n serializationFinishedListeners.length = 0\n\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n console.error('Serialization listener error:', err)\n }\n }\n }\n\n const finishScriptSerialization = () => {\n if (serializationCompleteSignaled || cleanupStarted) return\n scriptBuffer.enqueue(GLOBAL_TSR + '.e()')\n // Must synchronously notify injected HTML listeners before signaling\n // completion; otherwise the held </body> tail could flush ahead of the\n // end script.\n scriptBuffer.flush()\n signalSerializationComplete()\n }\n\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins,\n onSerialize: (data, initial) => {\n let serialized = initial ? TSR_PREFIX + data : data\n if (trackPlugins.didRun) {\n serialized = P_PREFIX + serialized + P_SUFFIX\n }\n scriptBuffer.enqueue(serialized)\n },\n onError: (err: unknown) => {\n console.error('Serialization error:', err)\n if (err && (err as any).stack) {\n console.error((err as any).stack)\n }\n finishScriptSerialization()\n },\n scopeId: SCOPE_ID,\n onDone: () => {\n finishScriptSerialization()\n },\n })\n },\n isDehydrated() {\n return _dehydrated\n },\n isSerializationFinished() {\n return _serializationFinished\n },\n reserveStreamFastPath() {\n if (\n !cleanupStarted &&\n _serializationFinished &&\n !streamFastPathReserved &&\n renderFinishedListeners.length === 0 &&\n !injectedHtmlBuffer &&\n !scriptBuffer.hasPending()\n ) {\n streamFastPathReserved = true\n return true\n }\n return false\n },\n onInjectedHtml: (listener) => {\n if (cleanupStarted) return () => {}\n injectedHtmlListeners.push(listener)\n return () => removeListener(injectedHtmlListeners, listener)\n },\n onRenderFinished: (listener) => {\n if (cleanupStarted || streamFastPathReserved) return\n renderFinishedListeners.push(listener)\n },\n onSerializationFinished: (listener) => {\n if (cleanupStarted) return () => {}\n if (_serializationFinished && !cleanupStarted) {\n try {\n listener()\n } catch (err) {\n console.error('Serialization listener error:', err)\n }\n return () => {}\n }\n serializationFinishedListeners.push(listener)\n return () => removeListener(serializationFinishedListeners, listener)\n },\n onCleanup: (listener) => {\n if (cleanupStarted) return\n cleanupListeners.push(listener)\n },\n setRenderFinished: () => {\n if (cleanupStarted) return\n scriptBuffer.liftBarrier()\n const listeners = renderFinishedListeners.slice()\n renderFinishedListeners.length = 0\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n console.error('Error in render finished listener:', err)\n }\n }\n if (_serializationFinished) {\n scriptBuffer.flush()\n }\n },\n takeBufferedScripts() {\n const scripts = scriptBuffer.takeAll()\n if (!scripts) return undefined\n const serverBufferedScript: RouterManagedTag = {\n tag: 'script',\n attrs: {\n nonce: router.options.ssr?.nonce,\n className: '$tsr',\n id: TSR_SCRIPT_BARRIER_ID,\n },\n children: scripts,\n }\n return serverBufferedScript\n },\n liftScriptBarrier() {\n scriptBuffer.liftBarrier()\n },\n takeBufferedHtml() {\n if (!injectedHtmlBuffer) {\n return undefined\n }\n const buffered = injectedHtmlBuffer\n injectedHtmlBuffer = ''\n return buffered\n },\n cleanup() {\n // Guard against multiple/reentrant cleanup calls. A listener could call\n // cleanup() again indirectly; snapshot + clear before invoking so each\n // listener runs exactly once and reentry is a no-op.\n if (cleanupStarted) return\n cleanupStarted = true\n const listeners = cleanupListeners.slice()\n cleanupListeners.length = 0\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n console.error('Error in SSR cleanup listener:', err)\n }\n }\n renderFinishedListeners.length = 0\n injectedHtmlListeners.length = 0\n serializationFinishedListeners.length = 0\n injectedHtmlBuffer = ''\n scriptBuffer.cleanup()\n router.serverSsr = undefined\n },\n }\n\n router.serverSsr = serverSsr\n for (const listener of router.serverSsrLifecycle?.onServerSsrAttach ?? []) {\n try {\n listener(serverSsr)\n } catch (err) {\n console.error('SSR attach listener error:', err)\n }\n }\n}\n\n/**\n * Get the origin for the request.\n *\n * SECURITY: We intentionally do NOT trust the Origin header for determining\n * the router's origin. The Origin header can be spoofed by attackers, which\n * could lead to SSRF-like vulnerabilities where redirects are constructed\n * using a malicious origin (CVE-2024-34351).\n *\n * Instead, we derive the origin from request.url, which is typically set by\n * the server infrastructure (not client-controlled headers).\n *\n * For applications behind proxies that need to trust forwarded headers,\n * use the router's `origin` option to explicitly configure a trusted origin.\n */\nexport function getOrigin(request: Request) {\n try {\n return new URL(request.url).origin\n } catch {}\n return 'http://localhost'\n}\n\n// server and browser can decode/encode characters differently in paths and search params.\n// Server generally strictly follows the WHATWG URL Standard, while browsers may differ for legacy reasons.\n// for example, in paths \"|\" is not encoded on the server but is encoded on chromium (and not on firefox) while \"대\" is encoded on both sides.\n// Another anomaly is that in Node new URLSearchParams and new URL also decode/encode characters differently.\n// new URLSearchParams() encodes \"|\" while new URL() does not, and in this instance\n// chromium treats search params differently than paths, i.e. \"|\" is not encoded in search params.\nexport function getNormalizedURL(url: string | URL, base?: string | URL) {\n // ensure backslashes are encoded correctly in the URL\n if (typeof url === 'string') url = url.replace('\\\\', '%5C')\n\n const rawUrl = new URL(url, base)\n const { path: decodedPathname, handledProtocolRelativeURL } = decodePath(\n rawUrl.pathname,\n )\n const searchParams = new URLSearchParams(rawUrl.search)\n const normalizedHref =\n decodedPathname +\n (searchParams.size > 0 ? '?' : '') +\n searchParams.toString() +\n rawUrl.hash\n\n return {\n url: new URL(normalizedHref, rawUrl.origin),\n handledProtocolRelativeURL,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr-server.js","names":[],"sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport { invariant } from '../invariant'\nimport {\n createInlineCssPlaceholderAsset,\n createInlineCssStyleAsset,\n getStylesheetHref,\n} from '../manifest'\nimport { decodePath } from '../utils'\nimport { createLRUCache } from '../lru-cache'\nimport { rootRouteId } from '../root'\nimport { _getRenderedMatches } from '../load-client'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'\nimport { dehydrateSsrMatchId } from './ssr-match-id'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { LRUCache } from '../lru-cache'\nimport type { DehydratedMatch, DehydratedRouter } from './types'\nimport type { AnySerializationAdapter } from './serializer/transformer'\nimport type { AnyRouter, ServerSsr } from '../router'\nimport type { AnyRouteMatch } from '../Matches'\nimport type {\n Manifest,\n ManifestRoute,\n ManifestRouteAssets,\n RouterManagedTag,\n ServerManifest,\n} from '../manifest'\n\nconst SCOPE_ID = 'tsr'\n\nconst TSR_PREFIX = GLOBAL_TSR + '.router='\nconst P_PREFIX = GLOBAL_TSR + '.p(()=>'\nconst P_SUFFIX = ')'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: dehydrateSsrMatchId(match.id),\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n if (match._notFound) {\n dehydratedMatch.g = true\n }\n return dehydratedMatch\n}\n\nconst INITIAL_SCRIPTS = [\n getCrossReferenceHeader(SCOPE_ID),\n minifiedTsrBootStrapScript,\n]\n\nclass ScriptBuffer {\n private injectScript: ((script: string) => void) | undefined\n private _queue: Array<string>\n private _scriptBarrierLifted = false\n private _cleanedUp = false\n private _microtaskVersion = 0\n private _pendingMicrotaskVersion = 0\n\n constructor(injectScript: (script: string) => void) {\n this.injectScript = injectScript\n // Copy INITIAL_SCRIPTS to avoid mutating the shared array\n this._queue = INITIAL_SCRIPTS.slice()\n }\n\n enqueue(script: string) {\n if (this._cleanedUp) return\n this._queue.push(script)\n if (this._scriptBarrierLifted) {\n this.scheduleInjectBufferedScripts()\n }\n }\n\n liftBarrier() {\n if (this._scriptBarrierLifted || this._cleanedUp) return\n this._scriptBarrierLifted = true\n if (this._queue.length > 0) {\n this.scheduleInjectBufferedScripts()\n }\n }\n\n scheduleInjectBufferedScripts() {\n if (this._pendingMicrotaskVersion !== 0) return\n const pendingVersion = ++this._microtaskVersion\n this._pendingMicrotaskVersion = pendingVersion\n queueMicrotask(() => {\n if (this._pendingMicrotaskVersion !== pendingVersion) return\n this._pendingMicrotaskVersion = 0\n this.injectBufferedScripts()\n })\n }\n\n clearPendingMicrotask() {\n if (this._pendingMicrotaskVersion === 0) return\n this._pendingMicrotaskVersion = 0\n this._microtaskVersion++\n }\n\n /**\n * Flushes any pending scripts synchronously.\n * Call this before signaling serialization finished to ensure all scripts are injected.\n *\n * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,\n * scripts should remain in the queue so takeBufferedScripts() can retrieve them\n */\n flush() {\n if (!this._scriptBarrierLifted) return\n if (this._cleanedUp) return\n this.clearPendingMicrotask()\n this.injectBufferedScripts()\n }\n\n takeAll() {\n return this.takeScripts(this._queue.length)\n }\n\n takeScripts(count: number) {\n if (count <= 0) return undefined\n const bufferedScripts = this._queue.splice(0, count)\n if (bufferedScripts.length === 0) {\n return undefined\n }\n // Optimization: if only one script, avoid join\n if (bufferedScripts.length === 1) {\n return bufferedScripts[0] + ';document.currentScript.remove()'\n }\n // Append cleanup script and join - avoid push() to not mutate then iterate\n return bufferedScripts.join(';') + ';document.currentScript.remove()'\n }\n\n hasPending() {\n return this._queue.length > 0\n }\n\n injectBufferedScripts() {\n if (this._cleanedUp) return\n // Early return if queue is empty (avoids unnecessary takeAll() call)\n if (this._queue.length === 0) return\n const scriptsToInject = this.takeAll()\n if (scriptsToInject) {\n this.injectScript?.(scriptsToInject)\n }\n }\n\n cleanup() {\n this._cleanedUp = true\n this.clearPendingMicrotask()\n this._queue = []\n this.injectScript = undefined\n }\n}\n\nconst isProd = process.env.NODE_ENV === 'production'\n\ntype FilteredRoutes = Manifest['routes']\n\ntype PreparedMatchedManifestRoutes = {\n routes: FilteredRoutes\n hasStrippedRoutes: boolean\n inlineCssHrefs?: Array<string>\n inlineCss?: string\n}\n\ntype ManifestLRU = LRUCache<string, PreparedMatchedManifestRoutes>\n\nconst MANIFEST_CACHE_SIZE = 100\nconst manifestCaches = new WeakMap<ServerManifest, ManifestLRU>()\n\nfunction getManifestCache(manifest: ServerManifest): ManifestLRU {\n const cache = manifestCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, PreparedMatchedManifestRoutes>(\n MANIFEST_CACHE_SIZE,\n )\n manifestCaches.set(manifest, newCache)\n return newCache\n}\n\nfunction getInlineCssForPreparedRoutes(\n manifest: ServerManifest,\n preparedRoutes: PreparedMatchedManifestRoutes,\n) {\n if (preparedRoutes.inlineCss !== undefined) return preparedRoutes.inlineCss\n\n const styles = manifest.inlineCss?.styles\n const hrefs = preparedRoutes.inlineCssHrefs\n if (!styles || !hrefs?.length) return undefined\n\n let css = ''\n for (const href of hrefs) {\n css += styles[href]!\n }\n\n preparedRoutes.inlineCss = css\n return css\n}\n\nfunction getInlineCssAssetForPreparedRoutes(\n manifest: ServerManifest,\n preparedRoutes: PreparedMatchedManifestRoutes,\n) {\n const css = getInlineCssForPreparedRoutes(manifest, preparedRoutes)\n\n return css === undefined ? undefined : createInlineCssStyleAsset(css)\n}\n\nfunction getMatchedRoutesCacheKey(matches: Array<AnyRouteMatch>) {\n let cacheKey = ''\n for (let i = 0; i < matches.length; i++) {\n cacheKey += (i === 0 ? '' : '\\0') + matches[i]!.routeId\n }\n return cacheKey\n}\n\nfunction getPreparedMatchedManifestRoutes(\n manifest: ServerManifest,\n matches: Array<AnyRouteMatch>,\n cacheKey: string,\n) {\n if (isProd) {\n const cached = getManifestCache(manifest).get(cacheKey)\n if (cached) {\n return cached\n }\n }\n\n const preparedRoutes = prepareMatchedManifestRoutes(manifest, matches)\n\n if (isProd) {\n getManifestCache(manifest).set(cacheKey, preparedRoutes)\n }\n\n return preparedRoutes\n}\n\nfunction prepareMatchedManifestRoutes(\n manifest: ServerManifest,\n matches: Array<AnyRouteMatch>,\n): PreparedMatchedManifestRoutes {\n const inlineStyles = manifest.inlineCss?.styles\n const routes: FilteredRoutes = {}\n\n if (!inlineStyles) {\n for (const match of matches) {\n const route = manifest.routes[match.routeId]\n if (route) {\n routes[match.routeId] = route\n }\n }\n return { routes, hasStrippedRoutes: false }\n }\n\n const inlineCssHrefs: Array<string> = []\n const seenInlineCssHrefs = new Set<string>()\n let hasStrippedRoutes = false\n\n for (const match of matches) {\n const routeId = match.routeId\n const route = manifest.routes[routeId]\n if (!route) {\n continue\n }\n\n const nextRoute = stripInlinedStylesheetAssetsFromRoute(\n inlineStyles,\n route,\n inlineCssHrefs,\n seenInlineCssHrefs,\n )\n\n if (nextRoute !== route) {\n hasStrippedRoutes = true\n }\n routes[routeId] = nextRoute\n }\n\n return {\n routes,\n hasStrippedRoutes,\n ...(inlineCssHrefs.length ? { inlineCssHrefs } : {}),\n }\n}\n\nfunction stripInlinedStylesheetAssetsFromRoute(\n inlineStyles: Record<string, string>,\n route: ManifestRoute,\n inlineCssHrefs: Array<string>,\n seenInlineCssHrefs: Set<string>,\n): ManifestRoute {\n const css = route.css\n if (!css) {\n return route\n }\n\n if (css.length === 0) {\n const nextRoute = { ...route }\n delete nextRoute.css\n return nextRoute\n }\n\n let cssLinks: typeof css | undefined\n for (let i = 0; i < css.length; i++) {\n const link = css[i]!\n const href = getStylesheetHref(link)\n if (inlineStyles[href] === undefined) {\n if (cssLinks) {\n cssLinks.push(link)\n }\n continue\n }\n\n if (!seenInlineCssHrefs.has(href)) {\n seenInlineCssHrefs.add(href)\n inlineCssHrefs.push(href)\n }\n\n if (!cssLinks) {\n cssLinks = css.slice(0, i)\n }\n }\n\n if (!cssLinks) {\n return route\n }\n\n if (cssLinks.length > 0) {\n return { ...route, css: cssLinks }\n }\n\n const nextRoute = { ...route }\n delete nextRoute.css\n return nextRoute\n}\n\nfunction hasRouteAssets(route: ManifestRoute) {\n return !!route.scripts?.length || !!route.css?.length\n}\n\nfunction hasRequestAssets(assets: ManifestRouteAssets | undefined) {\n return !!assets && (!!assets.preloads?.length || hasRouteAssets(assets))\n}\n\nfunction mergeRequestAssetsIntoRootRoute(\n rootRoute: ManifestRoute | undefined,\n requestAssets: ManifestRouteAssets | undefined,\n): ManifestRoute {\n const preloads = requestAssets?.preloads?.length\n ? [...requestAssets.preloads, ...(rootRoute?.preloads ?? [])]\n : rootRoute?.preloads\n const scripts = requestAssets?.scripts?.length\n ? [...requestAssets.scripts, ...(rootRoute?.scripts ?? [])]\n : rootRoute?.scripts\n const cssLinks = requestAssets?.css?.length\n ? [...requestAssets.css, ...(rootRoute?.css ?? [])]\n : rootRoute?.css\n\n return {\n ...(rootRoute ?? {}),\n ...(preloads?.length ? { preloads } : {}),\n ...(scripts?.length ? { scripts } : {}),\n ...(cssLinks?.length ? { css: cssLinks } : {}),\n }\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n getRequestAssets,\n}: {\n router: AnyRouter\n manifest: ServerManifest | undefined\n getRequestAssets?: () => ManifestRouteAssets | undefined\n}) {\n router.ssr = {\n get manifest() {\n if (!manifest) return manifest\n\n const requestAssets = getRequestAssets?.()\n const matches = _getRenderedMatches(router.stores.matches.get())\n const hasAssets = hasRequestAssets(requestAssets)\n\n if (!hasAssets && !manifest.inlineCss) {\n return manifest\n }\n\n let inlineCssAsset: Manifest['inlineStyle'] | undefined\n let routes = manifest.routes\n if (manifest.inlineCss) {\n const cacheKey = getMatchedRoutesCacheKey(matches)\n const preparedManifest = getPreparedMatchedManifestRoutes(\n manifest,\n matches,\n cacheKey,\n )\n inlineCssAsset = getInlineCssAssetForPreparedRoutes(\n manifest,\n preparedManifest,\n )\n if (preparedManifest.hasStrippedRoutes) {\n routes = { ...manifest.routes, ...preparedManifest.routes }\n }\n }\n\n if (!hasAssets) {\n return {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),\n routes,\n }\n }\n\n const rootRoute = routes[rootRouteId]\n\n // Merge request-scoped assets into root route without mutating cached manifest\n return {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),\n routes: {\n ...routes,\n [rootRouteId]: mergeRequestAssetsIntoRootRoute(\n rootRoute,\n requestAssets,\n ),\n },\n }\n },\n }\n let _dehydrated = false\n let _serializationFinished = false\n let streamFastPathReserved = false\n const renderFinishedListeners: Array<() => void> = []\n const injectedHtmlListeners: Array<() => void> = []\n const serializationFinishedListeners: Array<() => void> = []\n const cleanupListeners: Array<() => void> = []\n let cleanupStarted = false\n let injectedHtmlBuffer = ''\n\n const callListeners = (listeners: Array<() => void>, errorPrefix: string) => {\n const snapshot = listeners.slice()\n for (const l of snapshot) {\n try {\n l()\n } catch (err) {\n console.error(`${errorPrefix}:`, err)\n }\n }\n }\n\n const removeListener = (\n listeners: Array<() => void>,\n listener: () => void,\n ) => {\n const index = listeners.indexOf(listener)\n if (index >= 0) listeners.splice(index, 1)\n }\n\n const scriptBuffer = new ScriptBuffer((script) => {\n serverSsr.injectScript(script)\n })\n\n const serverSsr: ServerSsr = {\n injectHtml: (html: string) => {\n if (!html || cleanupStarted) return\n // Buffer the HTML so it can be retrieved via takeBufferedHtml()\n injectedHtmlBuffer += html\n callListeners(injectedHtmlListeners, 'SSR injected HTML listener error')\n },\n injectScript: (script: string) => {\n if (!script || cleanupStarted) return\n const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`\n serverSsr.injectHtml(html)\n },\n dehydrate: async (opts?: { requestAssets?: ManifestRouteAssets }) => {\n if (_dehydrated) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: router is already dehydrated!')\n }\n\n invariant()\n }\n let matchesToDehydrate = _getRenderedMatches(router.stores.matches.get())\n const isShell = router.isShell()\n if (isShell) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n let manifestToDehydrate: Manifest | undefined = undefined\n // Only currently matched routes are dehydrated. Other route assets are\n // loaded through dynamic imports when those routes become active.\n if (manifest) {\n const cacheKey = getMatchedRoutesCacheKey(matchesToDehydrate)\n const preparedManifest = getPreparedMatchedManifestRoutes(\n manifest,\n matchesToDehydrate,\n cacheKey,\n )\n\n manifestToDehydrate = {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(preparedManifest.inlineCssHrefs\n ? { inlineStyle: createInlineCssPlaceholderAsset() }\n : {}),\n routes: preparedManifest.routes,\n }\n\n // Merge request-scoped assets into root route (without mutating cached manifest)\n const requestAssets = opts?.requestAssets\n if (hasRequestAssets(requestAssets)) {\n const existingRoot = manifestToDehydrate.routes[rootRouteId]\n manifestToDehydrate.routes = {\n ...manifestToDehydrate.routes,\n [rootRouteId]: mergeRequestAssetsIntoRootRoute(\n existingRoot,\n requestAssets,\n ),\n }\n }\n }\n const dehydratedRouter: DehydratedRouter = {\n manifest: manifestToDehydrate,\n matches,\n }\n const dehydratedData = await router.options.dehydrate?.()\n if (cleanupStarted) {\n return\n }\n if (dehydratedData) {\n dehydratedRouter.dehydratedData = dehydratedData\n }\n _dehydrated = true\n\n const trackPlugins = { didRun: false }\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n const plugins = serializationAdapters\n ? serializationAdapters\n .map((t) => makeSsrSerovalPlugin(t, trackPlugins))\n .concat(defaultSerovalPlugins)\n : defaultSerovalPlugins\n\n let serializationCompleteSignaled = false\n const signalSerializationComplete = () => {\n if (serializationCompleteSignaled || cleanupStarted) return\n serializationCompleteSignaled = true\n _serializationFinished = true\n\n const listeners = serializationFinishedListeners.slice()\n serializationFinishedListeners.length = 0\n\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n console.error('Serialization listener error:', err)\n }\n }\n }\n\n const finishScriptSerialization = () => {\n if (serializationCompleteSignaled || cleanupStarted) return\n scriptBuffer.enqueue(GLOBAL_TSR + '.e()')\n // Must synchronously notify injected HTML listeners before signaling\n // completion; otherwise the held </body> tail could flush ahead of the\n // end script.\n scriptBuffer.flush()\n signalSerializationComplete()\n }\n\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins,\n onSerialize: (data, initial) => {\n let serialized = initial ? TSR_PREFIX + data : data\n if (trackPlugins.didRun) {\n serialized = P_PREFIX + serialized + P_SUFFIX\n }\n scriptBuffer.enqueue(serialized)\n },\n onError: (err: unknown) => {\n console.error('Serialization error:', err)\n if (err && (err as any).stack) {\n console.error((err as any).stack)\n }\n finishScriptSerialization()\n },\n scopeId: SCOPE_ID,\n onDone: () => {\n finishScriptSerialization()\n },\n })\n },\n isDehydrated() {\n return _dehydrated\n },\n isSerializationFinished() {\n return _serializationFinished\n },\n reserveStreamFastPath() {\n if (\n !cleanupStarted &&\n _serializationFinished &&\n !streamFastPathReserved &&\n renderFinishedListeners.length === 0 &&\n !injectedHtmlBuffer &&\n !scriptBuffer.hasPending()\n ) {\n streamFastPathReserved = true\n return true\n }\n return false\n },\n onInjectedHtml: (listener) => {\n if (cleanupStarted) return () => {}\n injectedHtmlListeners.push(listener)\n return () => removeListener(injectedHtmlListeners, listener)\n },\n onRenderFinished: (listener) => {\n if (cleanupStarted || streamFastPathReserved) return\n renderFinishedListeners.push(listener)\n },\n onSerializationFinished: (listener) => {\n if (cleanupStarted) return () => {}\n if (_serializationFinished && !cleanupStarted) {\n try {\n listener()\n } catch (err) {\n console.error('Serialization listener error:', err)\n }\n return () => {}\n }\n serializationFinishedListeners.push(listener)\n return () => removeListener(serializationFinishedListeners, listener)\n },\n onCleanup: (listener) => {\n if (cleanupStarted) return\n cleanupListeners.push(listener)\n },\n setRenderFinished: () => {\n if (cleanupStarted) return\n scriptBuffer.liftBarrier()\n const listeners = renderFinishedListeners.slice()\n renderFinishedListeners.length = 0\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n console.error('Error in render finished listener:', err)\n }\n }\n if (_serializationFinished) {\n scriptBuffer.flush()\n }\n },\n takeBufferedScripts() {\n const scripts = scriptBuffer.takeAll()\n if (!scripts) return undefined\n const serverBufferedScript: RouterManagedTag = {\n tag: 'script',\n attrs: {\n nonce: router.options.ssr?.nonce,\n className: '$tsr',\n id: TSR_SCRIPT_BARRIER_ID,\n },\n children: scripts,\n }\n return serverBufferedScript\n },\n liftScriptBarrier() {\n scriptBuffer.liftBarrier()\n },\n takeBufferedHtml() {\n if (!injectedHtmlBuffer) {\n return undefined\n }\n const buffered = injectedHtmlBuffer\n injectedHtmlBuffer = ''\n return buffered\n },\n cleanup() {\n // Guard against multiple/reentrant cleanup calls. A listener could call\n // cleanup() again indirectly; snapshot + clear before invoking so each\n // listener runs exactly once and reentry is a no-op.\n if (cleanupStarted) return\n cleanupStarted = true\n const listeners = cleanupListeners.slice()\n cleanupListeners.length = 0\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n console.error('Error in SSR cleanup listener:', err)\n }\n }\n renderFinishedListeners.length = 0\n injectedHtmlListeners.length = 0\n serializationFinishedListeners.length = 0\n injectedHtmlBuffer = ''\n scriptBuffer.cleanup()\n router.serverSsr = undefined\n },\n }\n\n router.serverSsr = serverSsr\n for (const listener of router.serverSsrLifecycle?.onServerSsrAttach ?? []) {\n try {\n listener(serverSsr)\n } catch (err) {\n console.error('SSR attach listener error:', err)\n }\n }\n}\n\n/**\n * Get the origin for the request.\n *\n * SECURITY: We intentionally do NOT trust the Origin header for determining\n * the router's origin. The Origin header can be spoofed by attackers, which\n * could lead to SSRF-like vulnerabilities where redirects are constructed\n * using a malicious origin (CVE-2024-34351).\n *\n * Instead, we derive the origin from request.url, which is typically set by\n * the server infrastructure (not client-controlled headers).\n *\n * For applications behind proxies that need to trust forwarded headers,\n * use the router's `origin` option to explicitly configure a trusted origin.\n */\nexport function getOrigin(request: Request) {\n try {\n return new URL(request.url).origin\n } catch {}\n return 'http://localhost'\n}\n\n// server and browser can decode/encode characters differently in paths and search params.\n// Server generally strictly follows the WHATWG URL Standard, while browsers may differ for legacy reasons.\n// for example, in paths \"|\" is not encoded on the server but is encoded on chromium (and not on firefox) while \"대\" is encoded on both sides.\n// Another anomaly is that in Node new URLSearchParams and new URL also decode/encode characters differently.\n// new URLSearchParams() encodes \"|\" while new URL() does not, and in this instance\n// chromium treats search params differently than paths, i.e. \"|\" is not encoded in search params.\nexport function getNormalizedURL(url: string | URL, base?: string | URL) {\n // ensure backslashes are encoded correctly in the URL\n if (typeof url === 'string') url = url.replace('\\\\', '%5C')\n\n const rawUrl = new URL(url, base)\n const { path: decodedPathname, handledProtocolRelativeURL } = decodePath(\n rawUrl.pathname,\n )\n const searchParams = new URLSearchParams(rawUrl.search)\n const normalizedHref =\n decodedPathname +\n (searchParams.size > 0 ? '?' : '') +\n searchParams.toString() +\n rawUrl.hash\n\n return {\n url: new URL(normalizedHref, rawUrl.origin),\n handledProtocolRelativeURL,\n }\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.js';
|
|
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. */
|
|
@@ -99,6 +99,16 @@ function createAbortNotifier(opts) {
|
|
|
99
99
|
} catch {}
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
function listenToAbort(signal, onAbort) {
|
|
103
|
+
if (!signal) return;
|
|
104
|
+
if (signal.aborted) {
|
|
105
|
+
onAbort(signal.reason);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const listener = () => onAbort(signal.reason);
|
|
109
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
110
|
+
return () => signal.removeEventListener("abort", listener);
|
|
111
|
+
}
|
|
102
112
|
function transformStreamWithRouter(router, appStream, opts) {
|
|
103
113
|
const serverSsr = router.serverSsr;
|
|
104
114
|
if (!serverSsr) throw new Error("Invariant failed: router.serverSsr is required");
|
|
@@ -110,6 +120,7 @@ function makeFastPathStream(appStream, opts, serverSsr) {
|
|
|
110
120
|
let controller;
|
|
111
121
|
let state = MergeState.ReadingBody;
|
|
112
122
|
let lifetimeTimeoutHandle;
|
|
123
|
+
let stopListeningToAbort;
|
|
113
124
|
let stopListeningToInjectedHtml;
|
|
114
125
|
const readerState = createReaderState(appStream);
|
|
115
126
|
const notifyAbort = createAbortNotifier(opts);
|
|
@@ -134,6 +145,8 @@ function makeFastPathStream(appStream, opts, serverSsr) {
|
|
|
134
145
|
clearTimeout(lifetimeTimeoutHandle);
|
|
135
146
|
lifetimeTimeoutHandle = void 0;
|
|
136
147
|
}
|
|
148
|
+
stopListeningToAbort?.();
|
|
149
|
+
stopListeningToAbort = void 0;
|
|
137
150
|
try {
|
|
138
151
|
stopListeningToInjectedHtml?.();
|
|
139
152
|
} catch {}
|
|
@@ -175,7 +188,7 @@ function makeFastPathStream(appStream, opts, serverSsr) {
|
|
|
175
188
|
cleanup(err);
|
|
176
189
|
}
|
|
177
190
|
}, lifetimeMs);
|
|
178
|
-
|
|
191
|
+
const stream = new ReadableStream({
|
|
179
192
|
start(c) {
|
|
180
193
|
controller = c;
|
|
181
194
|
},
|
|
@@ -208,12 +221,18 @@ function makeFastPathStream(appStream, opts, serverSsr) {
|
|
|
208
221
|
return cleanup(reason);
|
|
209
222
|
}
|
|
210
223
|
});
|
|
224
|
+
stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {
|
|
225
|
+
safeError(reason);
|
|
226
|
+
cleanup(reason);
|
|
227
|
+
});
|
|
228
|
+
return stream;
|
|
211
229
|
}
|
|
212
230
|
function makeMainStream(serverSsr, appStream, opts) {
|
|
213
231
|
let stopListeningToInjectedHtml;
|
|
214
232
|
let stopListeningToSerializationFinished;
|
|
215
233
|
let serializationTimeoutHandle;
|
|
216
234
|
let lifetimeTimeoutHandle;
|
|
235
|
+
let stopListeningToAbort;
|
|
217
236
|
let cleanedUp = false;
|
|
218
237
|
let controller;
|
|
219
238
|
let closeWhenDrained = false;
|
|
@@ -312,6 +331,8 @@ function makeMainStream(serverSsr, appStream, opts) {
|
|
|
312
331
|
} catch {}
|
|
313
332
|
stopListeningToInjectedHtml = void 0;
|
|
314
333
|
stopListeningToSerializationFinished = void 0;
|
|
334
|
+
stopListeningToAbort?.();
|
|
335
|
+
stopListeningToAbort = void 0;
|
|
315
336
|
if (serializationTimeoutHandle !== void 0) {
|
|
316
337
|
clearTimeout(serializationTimeoutHandle);
|
|
317
338
|
serializationTimeoutHandle = void 0;
|
|
@@ -496,6 +517,11 @@ function makeMainStream(serverSsr, appStream, opts) {
|
|
|
496
517
|
drainRouterHtml();
|
|
497
518
|
if (cleanedUp || isDone()) return stream;
|
|
498
519
|
}
|
|
520
|
+
stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {
|
|
521
|
+
safeError(reason);
|
|
522
|
+
cleanup(reason);
|
|
523
|
+
});
|
|
524
|
+
if (cleanedUp || isDone()) return stream;
|
|
499
525
|
(async () => {
|
|
500
526
|
try {
|
|
501
527
|
while (true) {
|
|
@@ -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 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":"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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 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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"}
|
package/dist/esm/ssr/types.d.ts
CHANGED
package/dist/esm/stores.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { AnyRoute } from './route.js';
|
|
|
2
2
|
import { RouterState } from './router.js';
|
|
3
3
|
import { FullSearchSchema } from './routeInfo.js';
|
|
4
4
|
import { ParsedLocation } from './location.js';
|
|
5
|
-
import { AnyRedirect } from './redirect.js';
|
|
6
5
|
import { AnyRouteMatch } from './Matches.js';
|
|
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 {};
|