@tanstack/react-start-server 1.114.4 → 1.114.6

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.
Files changed (65) hide show
  1. package/dist/cjs/defaultRenderHandler.cjs +2 -2
  2. package/dist/cjs/defaultRenderHandler.cjs.map +1 -1
  3. package/dist/cjs/defaultRenderHandler.d.cts +1 -1
  4. package/dist/cjs/defaultStreamHandler.cjs +4 -5
  5. package/dist/cjs/defaultStreamHandler.cjs.map +1 -1
  6. package/dist/cjs/defaultStreamHandler.d.cts +1 -1
  7. package/dist/cjs/index.cjs +7 -401
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.cts +1 -109
  10. package/dist/esm/defaultRenderHandler.d.ts +1 -1
  11. package/dist/esm/defaultRenderHandler.js +1 -1
  12. package/dist/esm/defaultRenderHandler.js.map +1 -1
  13. package/dist/esm/defaultStreamHandler.d.ts +1 -1
  14. package/dist/esm/defaultStreamHandler.js +1 -2
  15. package/dist/esm/defaultStreamHandler.js.map +1 -1
  16. package/dist/esm/index.d.ts +1 -109
  17. package/dist/esm/index.js +2 -289
  18. package/dist/esm/index.js.map +1 -1
  19. package/package.json +6 -6
  20. package/src/defaultRenderHandler.tsx +1 -1
  21. package/src/defaultStreamHandler.tsx +3 -4
  22. package/src/index.tsx +1 -505
  23. package/dist/cjs/createRequestHandler.cjs +0 -52
  24. package/dist/cjs/createRequestHandler.cjs.map +0 -1
  25. package/dist/cjs/createRequestHandler.d.cts +0 -8
  26. package/dist/cjs/createStartHandler.cjs +0 -56
  27. package/dist/cjs/createStartHandler.cjs.map +0 -1
  28. package/dist/cjs/createStartHandler.d.cts +0 -8
  29. package/dist/cjs/handlerCallback.cjs +0 -7
  30. package/dist/cjs/handlerCallback.cjs.map +0 -1
  31. package/dist/cjs/handlerCallback.d.cts +0 -10
  32. package/dist/cjs/ssr-server.cjs +0 -246
  33. package/dist/cjs/ssr-server.cjs.map +0 -1
  34. package/dist/cjs/ssr-server.d.cts +0 -29
  35. package/dist/cjs/transformStreamWithRouter.cjs +0 -183
  36. package/dist/cjs/transformStreamWithRouter.cjs.map +0 -1
  37. package/dist/cjs/transformStreamWithRouter.d.cts +0 -6
  38. package/dist/cjs/tsrScript.cjs +0 -4
  39. package/dist/cjs/tsrScript.cjs.map +0 -1
  40. package/dist/cjs/tsrScript.d.cts +0 -1
  41. package/dist/esm/createRequestHandler.d.ts +0 -8
  42. package/dist/esm/createRequestHandler.js +0 -52
  43. package/dist/esm/createRequestHandler.js.map +0 -1
  44. package/dist/esm/createStartHandler.d.ts +0 -8
  45. package/dist/esm/createStartHandler.js +0 -56
  46. package/dist/esm/createStartHandler.js.map +0 -1
  47. package/dist/esm/handlerCallback.d.ts +0 -10
  48. package/dist/esm/handlerCallback.js +0 -7
  49. package/dist/esm/handlerCallback.js.map +0 -1
  50. package/dist/esm/ssr-server.d.ts +0 -29
  51. package/dist/esm/ssr-server.js +0 -246
  52. package/dist/esm/ssr-server.js.map +0 -1
  53. package/dist/esm/transformStreamWithRouter.d.ts +0 -6
  54. package/dist/esm/transformStreamWithRouter.js +0 -183
  55. package/dist/esm/transformStreamWithRouter.js.map +0 -1
  56. package/dist/esm/tsrScript.d.ts +0 -1
  57. package/dist/esm/tsrScript.js +0 -5
  58. package/dist/esm/tsrScript.js.map +0 -1
  59. package/src/createRequestHandler.ts +0 -75
  60. package/src/createStartHandler.ts +0 -82
  61. package/src/handlerCallback.ts +0 -22
  62. package/src/ssr-server.ts +0 -350
  63. package/src/transformStreamWithRouter.ts +0 -258
  64. package/src/tsrScript.ts +0 -91
  65. package/src/vite-env.d.ts +0 -4
@@ -1 +0,0 @@
1
- {"version":3,"file":"transformStreamWithRouter.js","sources":["../../src/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { createControlledPromise } from '@tanstack/router-core'\nimport type { AnyRouter } from '@tanstack/router-core'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// regex pattern for matching closing body and html tags\nconst patternBodyStart = /(<body)/\nconst patternBodyEnd = /(<\\/body>)/\nconst patternHtmlEnd = /(<\\/html>)/\nconst patternHeadStart = /(<head.*?>)/\n// regex pattern for matching closing tags\nconst patternClosingTag = /(<\\/[a-zA-Z][\\w:.-]*?>)/g\n\nconst textDecoder = new TextDecoder()\n\ntype ReadablePassthrough = {\n stream: ReadableStream\n write: (chunk: string) => void\n end: (chunk?: string) => void\n destroy: (error: unknown) => void\n destroyed: boolean\n}\n\nfunction createPassthrough() {\n let controller: ReadableStreamDefaultController<any>\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n start(c) {\n controller = c\n },\n })\n\n const res: ReadablePassthrough = {\n stream,\n write: (chunk) => {\n controller.enqueue(encoder.encode(chunk))\n },\n end: (chunk) => {\n if (chunk) {\n controller.enqueue(encoder.encode(chunk))\n }\n controller.close()\n res.destroyed = true\n },\n destroy: (error) => {\n controller.error(error)\n },\n destroyed: false,\n }\n\n return res\n}\n\nasync function readStream(\n stream: ReadableStream,\n opts: {\n onData?: (chunk: ReadableStreamReadValueResult<any>) => void\n onEnd?: () => void\n onError?: (error: unknown) => void\n },\n) {\n try {\n const reader = stream.getReader()\n let chunk\n while (!(chunk = await reader.read()).done) {\n opts.onData?.(chunk)\n }\n opts.onEnd?.()\n } catch (error) {\n opts.onError?.(error)\n }\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n) {\n const finalPassThrough = createPassthrough()\n\n let isAppRendering = true as boolean\n let routerStreamBuffer = ''\n let pendingClosingTags = ''\n let bodyStarted = false as boolean\n let headStarted = false as boolean\n let leftover = ''\n let leftoverHtml = ''\n\n function getBufferedRouterStream() {\n const html = routerStreamBuffer\n routerStreamBuffer = ''\n return html\n }\n\n function decodeChunk(chunk: unknown): string {\n if (chunk instanceof Uint8Array) {\n return textDecoder.decode(chunk)\n }\n return String(chunk)\n }\n\n const injectedHtmlDonePromise = createControlledPromise<void>()\n\n let processingCount = 0\n\n // Process any already-injected HTML\n router.serverSsr!.injectedHtml.forEach((promise) => {\n handleInjectedHtml(promise)\n })\n\n // Listen for any new injected HTML\n const stopListeningToInjectedHtml = router.subscribe(\n 'onInjectedHtml',\n (e) => {\n handleInjectedHtml(e.promise)\n },\n )\n\n function handleInjectedHtml(promise: Promise<string>) {\n processingCount++\n\n promise\n .then((html) => {\n if (!bodyStarted) {\n routerStreamBuffer += html\n } else {\n finalPassThrough.write(html)\n }\n })\n .catch(injectedHtmlDonePromise.reject)\n .finally(() => {\n processingCount--\n\n if (!isAppRendering && processingCount === 0) {\n stopListeningToInjectedHtml()\n injectedHtmlDonePromise.resolve()\n }\n })\n }\n\n injectedHtmlDonePromise\n .then(() => {\n const finalHtml =\n leftoverHtml + getBufferedRouterStream() + pendingClosingTags\n\n finalPassThrough.end(finalHtml)\n })\n .catch((err) => {\n console.error('Error reading routerStream:', err)\n finalPassThrough.destroy(err)\n })\n\n // Transform the appStream\n readStream(appStream, {\n onData: (chunk) => {\n const text = decodeChunk(chunk.value)\n\n let chunkString = leftover + text\n const bodyEndMatch = chunkString.match(patternBodyEnd)\n const htmlEndMatch = chunkString.match(patternHtmlEnd)\n\n if (!bodyStarted) {\n const bodyStartMatch = chunkString.match(patternBodyStart)\n if (bodyStartMatch) {\n bodyStarted = true\n }\n }\n\n if (!headStarted) {\n const headStartMatch = chunkString.match(patternHeadStart)\n if (headStartMatch) {\n headStarted = true\n const index = headStartMatch.index!\n const headTag = headStartMatch[0]\n const remaining = chunkString.slice(index + headTag.length)\n finalPassThrough.write(\n chunkString.slice(0, index) + headTag + getBufferedRouterStream(),\n )\n // make sure to only write `remaining` until the next closing tag\n chunkString = remaining\n }\n }\n\n if (!bodyStarted) {\n finalPassThrough.write(chunkString)\n leftover = ''\n return\n }\n\n // If either the body end or html end is in the chunk,\n // We need to get all of our data in asap\n if (\n bodyEndMatch &&\n htmlEndMatch &&\n bodyEndMatch.index! < htmlEndMatch.index!\n ) {\n const bodyEndIndex = bodyEndMatch.index!\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n\n finalPassThrough.write(\n chunkString.slice(0, bodyEndIndex) + getBufferedRouterStream(),\n )\n\n leftover = ''\n return\n }\n\n let result: RegExpExecArray | null\n let lastIndex = 0\n while ((result = patternClosingTag.exec(chunkString)) !== null) {\n lastIndex = result.index + result[0].length\n }\n\n if (lastIndex > 0) {\n const processed =\n chunkString.slice(0, lastIndex) +\n getBufferedRouterStream() +\n leftoverHtml\n\n finalPassThrough.write(processed)\n leftover = chunkString.slice(lastIndex)\n } else {\n leftover = chunkString\n leftoverHtml += getBufferedRouterStream()\n }\n },\n onEnd: () => {\n // Mark the app as done rendering\n isAppRendering = false\n\n // If there are no pending promises, resolve the injectedHtmlDonePromise\n if (processingCount === 0) {\n injectedHtmlDonePromise.resolve()\n }\n },\n onError: (error) => {\n console.error('Error reading appStream:', error)\n finalPassThrough.destroy(error)\n },\n })\n\n return finalPassThrough.stream\n}\n"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- const minifiedTsrBootStrapScript = 'const __TSR_SSR__={matches:[],streamedValues:{},initMatch:o=>(__TSR_SSR__.matches.push(o),o.extracted?.forEach(l=>{if(l.type==="stream"){let r;l.value=new ReadableStream({start(e){r={enqueue:t=>{try{e.enqueue(t)}catch{}},close:()=>{try{e.close()}catch{}}}}}),l.value.controller=r}else{let r,e;l.value=new Promise((t,a)=>{e=a,r=t}),l.value.reject=e,l.value.resolve=r}}),!0),resolvePromise:({matchId:o,id:l,promiseState:r})=>{const e=__TSR_SSR__.matches.find(t=>t.id===o);if(e){const t=e.extracted?.[l];if(t&&t.type==="promise"&&t.value&&r.status==="success")return t.value.resolve(r.data),!0}return!1},injectChunk:({matchId:o,id:l,chunk:r})=>{const e=__TSR_SSR__.matches.find(t=>t.id===o);if(e){const t=e.extracted?.[l];if(t&&t.type==="stream"&&t.value?.controller)return t.value.controller.enqueue(new TextEncoder().encode(r.toString())),!0}return!1},closeStream:({matchId:o,id:l})=>{const r=__TSR_SSR__.matches.find(e=>e.id===o);if(r){const e=r.extracted?.[l];if(e&&e.type==="stream"&&e.value?.controller)return e.value.controller.close(),!0}return!1},cleanScripts:()=>{document.querySelectorAll(".tsr-once").forEach(o=>{o.remove()})}};window.__TSR_SSR__=__TSR_SSR__;\n';
2
- export {
3
- minifiedTsrBootStrapScript as default
4
- };
5
- //# sourceMappingURL=tsrScript.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tsrScript.js","sources":["../../src/tsrScript.ts?script-string"],"sourcesContent":["import type { ControllablePromise } from '@tanstack/router-core'\nimport type { StartSsrGlobal } from '@tanstack/start-client-core'\n\nconst __TSR_SSR__: StartSsrGlobal = {\n matches: [],\n streamedValues: {},\n initMatch: (match) => {\n __TSR_SSR__.matches.push(match)\n\n match.extracted?.forEach((ex) => {\n if (ex.type === 'stream') {\n let controller\n ex.value = new ReadableStream({\n start(c) {\n controller = {\n enqueue: (chunk: unknown) => {\n try {\n c.enqueue(chunk)\n } catch {}\n },\n close: () => {\n try {\n c.close()\n } catch {}\n },\n }\n },\n })\n ex.value.controller = controller\n } else {\n let resolve: ControllablePromise['reject'] | undefined\n let reject: ControllablePromise['reject'] | undefined\n\n ex.value = new Promise((_resolve, _reject) => {\n reject = _reject\n resolve = _resolve\n }) as ControllablePromise\n ex.value.reject = reject!\n ex.value.resolve = resolve!\n }\n })\n\n return true\n },\n resolvePromise: ({ matchId, id, promiseState }) => {\n const match = __TSR_SSR__.matches.find((m) => m.id === matchId)\n if (match) {\n const ex = match.extracted?.[id]\n if (\n ex &&\n ex.type === 'promise' &&\n ex.value &&\n promiseState.status === 'success'\n ) {\n ex.value.resolve(promiseState.data)\n return true\n }\n }\n return false\n },\n injectChunk: ({ matchId, id, chunk }) => {\n const match = __TSR_SSR__.matches.find((m) => m.id === matchId)\n\n if (match) {\n const ex = match.extracted?.[id]\n if (ex && ex.type === 'stream' && ex.value?.controller) {\n ex.value.controller.enqueue(new TextEncoder().encode(chunk.toString()))\n return true\n }\n }\n return false\n },\n closeStream: ({ matchId, id }) => {\n const match = __TSR_SSR__.matches.find((m) => m.id === matchId)\n if (match) {\n const ex = match.extracted?.[id]\n if (ex && ex.type === 'stream' && ex.value?.controller) {\n ex.value.controller.close()\n return true\n }\n }\n return false\n },\n cleanScripts: () => {\n document.querySelectorAll('.tsr-once').forEach((el) => {\n el.remove()\n })\n },\n}\n\nwindow.__TSR_SSR__ = __TSR_SSR__\n"],"names":[],"mappings":"AAAA,MAAA,6BAAe;"}
@@ -1,75 +0,0 @@
1
- import { createMemoryHistory } from '@tanstack/history'
2
- import { mergeHeaders } from '@tanstack/start-client-core'
3
- import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server'
4
- import type { HandlerCallback } from './handlerCallback'
5
- import type { AnyRouter, Manifest } from '@tanstack/router-core'
6
-
7
- export type RequestHandler<TRouter extends AnyRouter> = (
8
- cb: HandlerCallback<TRouter>,
9
- ) => Promise<Response>
10
-
11
- export function createRequestHandler<TRouter extends AnyRouter>({
12
- createRouter,
13
- request,
14
- getRouterManifest,
15
- }: {
16
- createRouter: () => TRouter
17
- request: Request
18
- getRouterManifest?: () => Manifest | Promise<Manifest>
19
- }): RequestHandler<TRouter> {
20
- return async (cb) => {
21
- const router = createRouter()
22
-
23
- attachRouterServerSsrUtils(router, await getRouterManifest?.())
24
-
25
- const url = new URL(request.url, 'http://localhost')
26
-
27
- const href = url.href.replace(url.origin, '')
28
-
29
- // Create a history for the router
30
- const history = createMemoryHistory({
31
- initialEntries: [href],
32
- })
33
-
34
- // Update the router with the history and context
35
- router.update({
36
- history,
37
- })
38
-
39
- await router.load()
40
-
41
- dehydrateRouter(router)
42
-
43
- const responseHeaders = getRequestHeaders({
44
- router,
45
- })
46
-
47
- return cb({
48
- request,
49
- router,
50
- responseHeaders,
51
- } as any)
52
- }
53
- }
54
-
55
- function getRequestHeaders(opts: { router: AnyRouter }): Headers {
56
- let headers = mergeHeaders(
57
- {
58
- 'Content-Type': 'text/html; charset=UTF-8',
59
- },
60
- ...opts.router.state.matches.map((match) => {
61
- return match.headers
62
- }),
63
- )
64
-
65
- // Handle Redirects
66
- const { redirect } = opts.router.state
67
-
68
- if (redirect) {
69
- headers = mergeHeaders(headers, redirect.headers, {
70
- Location: redirect.href,
71
- })
72
- }
73
-
74
- return headers
75
- }
@@ -1,82 +0,0 @@
1
- import { createMemoryHistory } from '@tanstack/history'
2
- import { mergeHeaders } from '@tanstack/start-client-core'
3
- import { eventHandler, getResponseHeaders, toWebRequest } from 'h3'
4
- import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server'
5
- import type { HandlerCallback } from './handlerCallback'
6
- import type { EventHandlerResponse, H3Event } from 'h3'
7
- import type { AnyRouter, Manifest } from '@tanstack/router-core'
8
-
9
- export type CustomizeStartHandler<
10
- TRouter extends AnyRouter,
11
- TResponse extends EventHandlerResponse = EventHandlerResponse,
12
- > = (cb: HandlerCallback<TRouter, TResponse>) => ReturnType<typeof eventHandler>
13
-
14
- export function createStartHandler<
15
- TRouter extends AnyRouter,
16
- TResponse extends EventHandlerResponse = EventHandlerResponse,
17
- >({
18
- createRouter,
19
- getRouterManifest,
20
- }: {
21
- createRouter: () => TRouter
22
- getRouterManifest?: () => Manifest | Promise<Manifest>
23
- }): CustomizeStartHandler<TRouter, TResponse> {
24
- return (cb) => {
25
- return eventHandler(async (event) => {
26
- const request = toWebRequest(event)
27
-
28
- const url = new URL(request.url)
29
- const href = url.href.replace(url.origin, '')
30
-
31
- // Create a history for the router
32
- const history = createMemoryHistory({
33
- initialEntries: [href],
34
- })
35
-
36
- const router = createRouter()
37
-
38
- attachRouterServerSsrUtils(router, await getRouterManifest?.())
39
-
40
- // Update the router with the history and context
41
- router.update({
42
- history,
43
- })
44
-
45
- await router.load()
46
-
47
- dehydrateRouter(router)
48
-
49
- const responseHeaders = getStartResponseHeaders({ event, router })
50
- const response = await cb({
51
- request,
52
- router,
53
- responseHeaders,
54
- })
55
-
56
- return response
57
- })
58
- }
59
- }
60
-
61
- function getStartResponseHeaders(opts: { event: H3Event; router: AnyRouter }) {
62
- let headers = mergeHeaders(
63
- getResponseHeaders(opts.event),
64
- (opts.event as any).___ssrRpcResponseHeaders,
65
- {
66
- 'Content-Type': 'text/html; charset=UTF-8',
67
- },
68
- ...opts.router.state.matches.map((match) => {
69
- return match.headers
70
- }),
71
- )
72
-
73
- // Handle Redirects
74
- const { redirect } = opts.router.state
75
-
76
- if (redirect) {
77
- headers = mergeHeaders(headers, redirect.headers, {
78
- Location: redirect.href,
79
- })
80
- }
81
- return headers
82
- }
@@ -1,22 +0,0 @@
1
- import type { EventHandlerResponse } from 'h3'
2
- import type { AnyRouter } from '@tanstack/router-core'
3
-
4
- export interface HandlerCallback<
5
- TRouter extends AnyRouter,
6
- TResponse extends EventHandlerResponse = EventHandlerResponse,
7
- > {
8
- (ctx: {
9
- request: Request
10
- router: TRouter
11
- responseHeaders: Headers
12
- }): TResponse
13
- }
14
-
15
- export function defineHandlerCallback<
16
- TRouter extends AnyRouter,
17
- TResponse = EventHandlerResponse,
18
- >(
19
- handler: HandlerCallback<TRouter, TResponse>,
20
- ): HandlerCallback<TRouter, TResponse> {
21
- return handler
22
- }
package/src/ssr-server.ts DELETED
@@ -1,350 +0,0 @@
1
- import { default as warning } from 'tiny-warning'
2
- import {
3
- TSR_DEFERRED_PROMISE,
4
- defer,
5
- isPlainArray,
6
- isPlainObject,
7
- pick,
8
- } from '@tanstack/router-core'
9
- import jsesc from 'jsesc'
10
- import { startSerializer } from '@tanstack/start-client-core'
11
- import minifiedTsrBootStrapScript from './tsrScript?script-string'
12
- import type {
13
- ClientExtractedBaseEntry,
14
- DehydratedRouter,
15
- ResolvePromiseState,
16
- SsrMatch,
17
- } from '@tanstack/start-client-core'
18
- import type {
19
- AnyRouteMatch,
20
- AnyRouter,
21
- DeferredPromise,
22
- Manifest,
23
- } from '@tanstack/router-core'
24
-
25
- export type ServerExtractedEntry =
26
- | ServerExtractedStream
27
- | ServerExtractedPromise
28
-
29
- export interface ServerExtractedBaseEntry extends ClientExtractedBaseEntry {
30
- id: number
31
- matchIndex: number
32
- }
33
-
34
- export interface ServerExtractedStream extends ServerExtractedBaseEntry {
35
- type: 'stream'
36
- stream: ReadableStream
37
- }
38
-
39
- export interface ServerExtractedPromise extends ServerExtractedBaseEntry {
40
- type: 'promise'
41
- promise: DeferredPromise<any>
42
- }
43
-
44
- export function attachRouterServerSsrUtils(
45
- router: AnyRouter,
46
- manifest: Manifest | undefined,
47
- ) {
48
- router.ssr = {
49
- manifest,
50
- serializer: startSerializer,
51
- }
52
-
53
- router.serverSsr = {
54
- injectedHtml: [],
55
- streamedKeys: new Set(),
56
- injectHtml: (getHtml) => {
57
- const promise = Promise.resolve().then(getHtml)
58
- router.serverSsr!.injectedHtml.push(promise)
59
- router.emit({
60
- type: 'onInjectedHtml',
61
- promise,
62
- })
63
-
64
- return promise.then(() => {})
65
- },
66
- injectScript: (getScript, opts) => {
67
- return router.serverSsr!.injectHtml(async () => {
68
- const script = await getScript()
69
- return `<script class='tsr-once'>${script}${
70
- process.env.NODE_ENV === 'development' && (opts?.logScript ?? true)
71
- ? `; console.info(\`Injected From Server:
72
- ${jsesc(script, { quotes: 'backtick' })}\`)`
73
- : ''
74
- }; if (typeof __TSR_SSR__ !== 'undefined') __TSR_SSR__.cleanScripts()</script>`
75
- })
76
- },
77
- streamValue: (key, value) => {
78
- warning(
79
- !router.serverSsr!.streamedKeys.has(key),
80
- 'Key has already been streamed: ' + key,
81
- )
82
-
83
- router.serverSsr!.streamedKeys.add(key)
84
- router.serverSsr!.injectScript(
85
- () =>
86
- `__TSR_SSR__.streamedValues['${key}'] = { value: ${jsesc(
87
- router.ssr!.serializer.stringify(value),
88
- {
89
- isScriptContext: true,
90
- wrap: true,
91
- json: true,
92
- },
93
- )}}`,
94
- )
95
- },
96
- onMatchSettled,
97
- }
98
-
99
- router.serverSsr.injectScript(() => minifiedTsrBootStrapScript, {
100
- logScript: false,
101
- })
102
- }
103
-
104
- export function dehydrateRouter(router: AnyRouter) {
105
- const dehydratedRouter: DehydratedRouter = {
106
- manifest: router.ssr!.manifest,
107
- dehydratedData: router.options.dehydrate?.(),
108
- }
109
-
110
- router.serverSsr!.injectScript(
111
- () =>
112
- `__TSR_SSR__.dehydrated = ${jsesc(
113
- router.ssr!.serializer.stringify(dehydratedRouter),
114
- {
115
- isScriptContext: true,
116
- wrap: true,
117
- json: true,
118
- },
119
- )}`,
120
- )
121
- }
122
-
123
- export function extractAsyncLoaderData(
124
- loaderData: any,
125
- ctx: {
126
- match: AnyRouteMatch
127
- router: AnyRouter
128
- },
129
- ) {
130
- const extracted: Array<ServerExtractedEntry> = []
131
-
132
- const replaced = replaceBy(loaderData, (value, path) => {
133
- // If it's a stream, we need to tee it so we can read it multiple times
134
- if (value instanceof ReadableStream) {
135
- const [copy1, copy2] = value.tee()
136
- const entry: ServerExtractedStream = {
137
- type: 'stream',
138
- path,
139
- id: extracted.length,
140
- matchIndex: ctx.match.index,
141
- stream: copy2,
142
- }
143
-
144
- extracted.push(entry)
145
- return copy1
146
- } else if (value instanceof Promise) {
147
- const deferredPromise = defer(value)
148
- const entry: ServerExtractedPromise = {
149
- type: 'promise',
150
- path,
151
- id: extracted.length,
152
- matchIndex: ctx.match.index,
153
- promise: deferredPromise,
154
- }
155
- extracted.push(entry)
156
- }
157
-
158
- return value
159
- })
160
-
161
- return { replaced, extracted }
162
- }
163
-
164
- export function onMatchSettled(opts: {
165
- router: AnyRouter
166
- match: AnyRouteMatch
167
- }) {
168
- const { router, match } = opts
169
-
170
- let extracted: Array<ServerExtractedEntry> | undefined = undefined
171
- let serializedLoaderData: any = undefined
172
- if (match.loaderData !== undefined) {
173
- const result = extractAsyncLoaderData(match.loaderData, {
174
- router,
175
- match,
176
- })
177
- match.loaderData = result.replaced
178
- extracted = result.extracted
179
- serializedLoaderData = extracted.reduce(
180
- (acc: any, entry: ServerExtractedEntry) => {
181
- return deepImmutableSetByPath(acc, ['temp', ...entry.path], undefined)
182
- },
183
- { temp: result.replaced },
184
- ).temp
185
- }
186
-
187
- const initCode = `__TSR_SSR__.initMatch(${jsesc(
188
- {
189
- id: match.id,
190
- __beforeLoadContext: router.ssr!.serializer.stringify(
191
- match.__beforeLoadContext,
192
- ),
193
- loaderData: router.ssr!.serializer.stringify(serializedLoaderData),
194
- error: router.ssr!.serializer.stringify(match.error),
195
- extracted: extracted?.map((entry) => pick(entry, ['type', 'path'])),
196
- updatedAt: match.updatedAt,
197
- status: match.status,
198
- } satisfies SsrMatch,
199
- {
200
- isScriptContext: true,
201
- wrap: true,
202
- json: true,
203
- },
204
- )})`
205
-
206
- router.serverSsr!.injectScript(() => initCode)
207
-
208
- if (extracted) {
209
- extracted.forEach((entry) => {
210
- if (entry.type === 'promise') return injectPromise(entry)
211
- return injectStream(entry)
212
- })
213
- }
214
-
215
- function injectPromise(entry: ServerExtractedPromise) {
216
- router.serverSsr!.injectScript(async () => {
217
- await entry.promise
218
-
219
- return `__TSR_SSR__.resolvePromise(${jsesc(
220
- {
221
- matchId: match.id,
222
- id: entry.id,
223
- promiseState: entry.promise[TSR_DEFERRED_PROMISE],
224
- } satisfies ResolvePromiseState,
225
- {
226
- isScriptContext: true,
227
- wrap: true,
228
- json: true,
229
- },
230
- )})`
231
- })
232
- }
233
-
234
- function injectStream(entry: ServerExtractedStream) {
235
- // Inject a promise that resolves when the stream is done
236
- // We do this to keep the stream open until we're done
237
- router.serverSsr!.injectHtml(async () => {
238
- //
239
- try {
240
- const reader = entry.stream.getReader()
241
- let chunk: ReadableStreamReadResult<any> | null = null
242
- while (!(chunk = await reader.read()).done) {
243
- if (chunk.value) {
244
- const code = `__TSR_SSR__.injectChunk(${jsesc(
245
- {
246
- matchId: match.id,
247
- id: entry.id,
248
- chunk: chunk.value,
249
- },
250
- {
251
- isScriptContext: true,
252
- wrap: true,
253
- json: true,
254
- },
255
- )})`
256
-
257
- router.serverSsr!.injectScript(() => code)
258
- }
259
- }
260
-
261
- router.serverSsr!.injectScript(
262
- () =>
263
- `__TSR_SSR__.closeStream(${jsesc(
264
- {
265
- matchId: match.id,
266
- id: entry.id,
267
- },
268
- {
269
- isScriptContext: true,
270
- wrap: true,
271
- json: true,
272
- },
273
- )})`,
274
- )
275
- } catch (err) {
276
- console.error('stream read error', err)
277
- }
278
-
279
- return ''
280
- })
281
- }
282
- }
283
-
284
- function deepImmutableSetByPath<T>(obj: T, path: Array<string>, value: any): T {
285
- // immutable set by path retaining array and object references
286
- if (path.length === 0) {
287
- return value
288
- }
289
-
290
- const [key, ...rest] = path
291
-
292
- if (Array.isArray(obj)) {
293
- return obj.map((item, i) => {
294
- if (i === Number(key)) {
295
- return deepImmutableSetByPath(item, rest, value)
296
- }
297
- return item
298
- }) as T
299
- }
300
-
301
- if (isPlainObject(obj)) {
302
- return {
303
- ...obj,
304
- [key!]: deepImmutableSetByPath((obj as any)[key!], rest, value),
305
- }
306
- }
307
-
308
- return obj
309
- }
310
-
311
- export function replaceBy<T>(
312
- obj: T,
313
- cb: (value: any, path: Array<string>) => any,
314
- path: Array<string> = [],
315
- ): T {
316
- if (isPlainArray(obj)) {
317
- return obj.map((value, i) => replaceBy(value, cb, [...path, `${i}`])) as any
318
- }
319
-
320
- if (isPlainObject(obj)) {
321
- // Do not allow objects with illegal
322
- const newObj: any = {}
323
-
324
- for (const key in obj) {
325
- newObj[key] = replaceBy(obj[key], cb, [...path, key])
326
- }
327
-
328
- return newObj
329
- }
330
-
331
- // // Detect classes, functions, and other non-serializable objects
332
- // // and return undefined. Exclude some known types that are serializable
333
- // if (
334
- // typeof obj === 'function' ||
335
- // (typeof obj === 'object' &&
336
- // ![Object, Promise, ReadableStream].includes((obj as any)?.constructor))
337
- // ) {
338
- // console.info(obj)
339
- // warning(false, `Non-serializable value ☝️ found at ${path.join('.')}`)
340
- // return undefined as any
341
- // }
342
-
343
- const newObj = cb(obj, path)
344
-
345
- if (newObj !== obj) {
346
- return newObj
347
- }
348
-
349
- return obj
350
- }