@tanstack/router-core 1.121.33 → 1.121.39

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 (94) hide show
  1. package/dist/cjs/index.d.cts +1 -1
  2. package/dist/cjs/router.cjs +5 -0
  3. package/dist/cjs/router.cjs.map +1 -1
  4. package/dist/cjs/router.d.cts +2 -2
  5. package/dist/cjs/scroll-restoration.cjs +1 -1
  6. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  7. package/dist/cjs/serializer.cjs +146 -0
  8. package/dist/cjs/serializer.cjs.map +1 -0
  9. package/dist/cjs/serializer.d.cts +7 -1
  10. package/dist/cjs/ssr/client.cjs +12 -0
  11. package/dist/cjs/ssr/client.cjs.map +1 -0
  12. package/dist/cjs/ssr/client.d.cts +6 -0
  13. package/dist/cjs/ssr/createRequestHandler.cjs +50 -0
  14. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
  15. package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
  16. package/dist/cjs/ssr/handlerCallback.cjs +7 -0
  17. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
  18. package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
  19. package/dist/cjs/ssr/headers.cjs +39 -0
  20. package/dist/cjs/ssr/headers.cjs.map +1 -0
  21. package/dist/cjs/ssr/headers.d.cts +5 -0
  22. package/dist/cjs/ssr/json.cjs +14 -0
  23. package/dist/cjs/ssr/json.cjs.map +1 -0
  24. package/dist/cjs/ssr/json.d.cts +4 -0
  25. package/dist/cjs/ssr/server.cjs +17 -0
  26. package/dist/cjs/ssr/server.cjs.map +1 -0
  27. package/dist/cjs/ssr/server.d.cts +8 -0
  28. package/dist/cjs/ssr/ssr-client.cjs +131 -0
  29. package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
  30. package/dist/cjs/ssr/ssr-client.d.cts +68 -0
  31. package/dist/cjs/ssr/ssr-server.cjs +248 -0
  32. package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
  33. package/dist/cjs/ssr/ssr-server.d.cts +32 -0
  34. package/dist/cjs/ssr/transformStreamWithRouter.cjs +183 -0
  35. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
  36. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +6 -0
  37. package/dist/cjs/ssr/tsrScript.cjs +4 -0
  38. package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
  39. package/dist/cjs/ssr/tsrScript.d.cts +1 -0
  40. package/dist/esm/index.d.ts +1 -1
  41. package/dist/esm/router.d.ts +2 -2
  42. package/dist/esm/router.js +5 -0
  43. package/dist/esm/router.js.map +1 -1
  44. package/dist/esm/scroll-restoration.js +1 -1
  45. package/dist/esm/scroll-restoration.js.map +1 -1
  46. package/dist/esm/serializer.d.ts +7 -1
  47. package/dist/esm/serializer.js +146 -0
  48. package/dist/esm/serializer.js.map +1 -0
  49. package/dist/esm/ssr/client.d.ts +6 -0
  50. package/dist/esm/ssr/client.js +12 -0
  51. package/dist/esm/ssr/client.js.map +1 -0
  52. package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
  53. package/dist/esm/ssr/createRequestHandler.js +50 -0
  54. package/dist/esm/ssr/createRequestHandler.js.map +1 -0
  55. package/dist/esm/ssr/handlerCallback.d.ts +9 -0
  56. package/dist/esm/ssr/handlerCallback.js +7 -0
  57. package/dist/esm/ssr/handlerCallback.js.map +1 -0
  58. package/dist/esm/ssr/headers.d.ts +5 -0
  59. package/dist/esm/ssr/headers.js +39 -0
  60. package/dist/esm/ssr/headers.js.map +1 -0
  61. package/dist/esm/ssr/json.d.ts +4 -0
  62. package/dist/esm/ssr/json.js +14 -0
  63. package/dist/esm/ssr/json.js.map +1 -0
  64. package/dist/esm/ssr/server.d.ts +8 -0
  65. package/dist/esm/ssr/server.js +17 -0
  66. package/dist/esm/ssr/server.js.map +1 -0
  67. package/dist/esm/ssr/ssr-client.d.ts +68 -0
  68. package/dist/esm/ssr/ssr-client.js +131 -0
  69. package/dist/esm/ssr/ssr-client.js.map +1 -0
  70. package/dist/esm/ssr/ssr-server.d.ts +32 -0
  71. package/dist/esm/ssr/ssr-server.js +248 -0
  72. package/dist/esm/ssr/ssr-server.js.map +1 -0
  73. package/dist/esm/ssr/transformStreamWithRouter.d.ts +6 -0
  74. package/dist/esm/ssr/transformStreamWithRouter.js +183 -0
  75. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
  76. package/dist/esm/ssr/tsrScript.d.ts +1 -0
  77. package/dist/esm/ssr/tsrScript.js +5 -0
  78. package/dist/esm/ssr/tsrScript.js.map +1 -0
  79. package/package.json +29 -2
  80. package/src/index.ts +1 -0
  81. package/src/router.ts +8 -5
  82. package/src/scroll-restoration.ts +1 -1
  83. package/src/serializer.ts +174 -1
  84. package/src/ssr/client.ts +15 -0
  85. package/src/ssr/createRequestHandler.ts +74 -0
  86. package/src/ssr/handlerCallback.ts +15 -0
  87. package/src/ssr/headers.ts +51 -0
  88. package/src/ssr/json.ts +18 -0
  89. package/src/ssr/server.ts +23 -0
  90. package/src/ssr/ssr-client.ts +244 -0
  91. package/src/ssr/ssr-server.ts +345 -0
  92. package/src/ssr/transformStreamWithRouter.ts +258 -0
  93. package/src/ssr/tsrScript.ts +91 -0
  94. package/src/vite-env.d.ts +4 -0
@@ -0,0 +1,258 @@
1
+ import { ReadableStream } from 'node:stream/web'
2
+ import { Readable } from 'node:stream'
3
+ import { createControlledPromise } from '../utils'
4
+ import type { AnyRouter } from '../router'
5
+
6
+ export function transformReadableStreamWithRouter(
7
+ router: AnyRouter,
8
+ routerStream: ReadableStream,
9
+ ) {
10
+ return transformStreamWithRouter(router, routerStream)
11
+ }
12
+
13
+ export function transformPipeableStreamWithRouter(
14
+ router: AnyRouter,
15
+ routerStream: Readable,
16
+ ) {
17
+ return Readable.fromWeb(
18
+ transformStreamWithRouter(router, Readable.toWeb(routerStream)),
19
+ )
20
+ }
21
+
22
+ // regex pattern for matching closing body and html tags
23
+ const patternBodyStart = /(<body)/
24
+ const patternBodyEnd = /(<\/body>)/
25
+ const patternHtmlEnd = /(<\/html>)/
26
+ const patternHeadStart = /(<head.*?>)/
27
+ // regex pattern for matching closing tags
28
+ const patternClosingTag = /(<\/[a-zA-Z][\w:.-]*?>)/g
29
+
30
+ const textDecoder = new TextDecoder()
31
+
32
+ type ReadablePassthrough = {
33
+ stream: ReadableStream
34
+ write: (chunk: string) => void
35
+ end: (chunk?: string) => void
36
+ destroy: (error: unknown) => void
37
+ destroyed: boolean
38
+ }
39
+
40
+ function createPassthrough() {
41
+ let controller: ReadableStreamDefaultController<any>
42
+ const encoder = new TextEncoder()
43
+ const stream = new ReadableStream({
44
+ start(c) {
45
+ controller = c
46
+ },
47
+ })
48
+
49
+ const res: ReadablePassthrough = {
50
+ stream,
51
+ write: (chunk) => {
52
+ controller.enqueue(encoder.encode(chunk))
53
+ },
54
+ end: (chunk) => {
55
+ if (chunk) {
56
+ controller.enqueue(encoder.encode(chunk))
57
+ }
58
+ controller.close()
59
+ res.destroyed = true
60
+ },
61
+ destroy: (error) => {
62
+ controller.error(error)
63
+ },
64
+ destroyed: false,
65
+ }
66
+
67
+ return res
68
+ }
69
+
70
+ async function readStream(
71
+ stream: ReadableStream,
72
+ opts: {
73
+ onData?: (chunk: ReadableStreamReadValueResult<any>) => void
74
+ onEnd?: () => void
75
+ onError?: (error: unknown) => void
76
+ },
77
+ ) {
78
+ try {
79
+ const reader = stream.getReader()
80
+ let chunk
81
+ while (!(chunk = await reader.read()).done) {
82
+ opts.onData?.(chunk)
83
+ }
84
+ opts.onEnd?.()
85
+ } catch (error) {
86
+ opts.onError?.(error)
87
+ }
88
+ }
89
+
90
+ export function transformStreamWithRouter(
91
+ router: AnyRouter,
92
+ appStream: ReadableStream,
93
+ ) {
94
+ const finalPassThrough = createPassthrough()
95
+
96
+ let isAppRendering = true as boolean
97
+ let routerStreamBuffer = ''
98
+ let pendingClosingTags = ''
99
+ let bodyStarted = false as boolean
100
+ let headStarted = false as boolean
101
+ let leftover = ''
102
+ let leftoverHtml = ''
103
+
104
+ function getBufferedRouterStream() {
105
+ const html = routerStreamBuffer
106
+ routerStreamBuffer = ''
107
+ return html
108
+ }
109
+
110
+ function decodeChunk(chunk: unknown): string {
111
+ if (chunk instanceof Uint8Array) {
112
+ return textDecoder.decode(chunk)
113
+ }
114
+ return String(chunk)
115
+ }
116
+
117
+ const injectedHtmlDonePromise = createControlledPromise<void>()
118
+
119
+ let processingCount = 0
120
+
121
+ // Process any already-injected HTML
122
+ router.serverSsr!.injectedHtml.forEach((promise) => {
123
+ handleInjectedHtml(promise)
124
+ })
125
+
126
+ // Listen for any new injected HTML
127
+ const stopListeningToInjectedHtml = router.subscribe(
128
+ 'onInjectedHtml',
129
+ (e) => {
130
+ handleInjectedHtml(e.promise)
131
+ },
132
+ )
133
+
134
+ function handleInjectedHtml(promise: Promise<string>) {
135
+ processingCount++
136
+
137
+ promise
138
+ .then((html) => {
139
+ if (!bodyStarted) {
140
+ routerStreamBuffer += html
141
+ } else {
142
+ finalPassThrough.write(html)
143
+ }
144
+ })
145
+ .catch(injectedHtmlDonePromise.reject)
146
+ .finally(() => {
147
+ processingCount--
148
+
149
+ if (!isAppRendering && processingCount === 0) {
150
+ stopListeningToInjectedHtml()
151
+ injectedHtmlDonePromise.resolve()
152
+ }
153
+ })
154
+ }
155
+
156
+ injectedHtmlDonePromise
157
+ .then(() => {
158
+ const finalHtml =
159
+ leftoverHtml + getBufferedRouterStream() + pendingClosingTags
160
+
161
+ finalPassThrough.end(finalHtml)
162
+ })
163
+ .catch((err) => {
164
+ console.error('Error reading routerStream:', err)
165
+ finalPassThrough.destroy(err)
166
+ })
167
+
168
+ // Transform the appStream
169
+ readStream(appStream, {
170
+ onData: (chunk) => {
171
+ const text = decodeChunk(chunk.value)
172
+
173
+ let chunkString = leftover + text
174
+ const bodyEndMatch = chunkString.match(patternBodyEnd)
175
+ const htmlEndMatch = chunkString.match(patternHtmlEnd)
176
+
177
+ if (!bodyStarted) {
178
+ const bodyStartMatch = chunkString.match(patternBodyStart)
179
+ if (bodyStartMatch) {
180
+ bodyStarted = true
181
+ }
182
+ }
183
+
184
+ if (!headStarted) {
185
+ const headStartMatch = chunkString.match(patternHeadStart)
186
+ if (headStartMatch) {
187
+ headStarted = true
188
+ const index = headStartMatch.index!
189
+ const headTag = headStartMatch[0]
190
+ const remaining = chunkString.slice(index + headTag.length)
191
+ finalPassThrough.write(
192
+ chunkString.slice(0, index) + headTag + getBufferedRouterStream(),
193
+ )
194
+ // make sure to only write `remaining` until the next closing tag
195
+ chunkString = remaining
196
+ }
197
+ }
198
+
199
+ if (!bodyStarted) {
200
+ finalPassThrough.write(chunkString)
201
+ leftover = ''
202
+ return
203
+ }
204
+
205
+ // If either the body end or html end is in the chunk,
206
+ // We need to get all of our data in asap
207
+ if (
208
+ bodyEndMatch &&
209
+ htmlEndMatch &&
210
+ bodyEndMatch.index! < htmlEndMatch.index!
211
+ ) {
212
+ const bodyEndIndex = bodyEndMatch.index!
213
+ pendingClosingTags = chunkString.slice(bodyEndIndex)
214
+
215
+ finalPassThrough.write(
216
+ chunkString.slice(0, bodyEndIndex) + getBufferedRouterStream(),
217
+ )
218
+
219
+ leftover = ''
220
+ return
221
+ }
222
+
223
+ let result: RegExpExecArray | null
224
+ let lastIndex = 0
225
+ while ((result = patternClosingTag.exec(chunkString)) !== null) {
226
+ lastIndex = result.index + result[0].length
227
+ }
228
+
229
+ if (lastIndex > 0) {
230
+ const processed =
231
+ chunkString.slice(0, lastIndex) +
232
+ getBufferedRouterStream() +
233
+ leftoverHtml
234
+
235
+ finalPassThrough.write(processed)
236
+ leftover = chunkString.slice(lastIndex)
237
+ } else {
238
+ leftover = chunkString
239
+ leftoverHtml += getBufferedRouterStream()
240
+ }
241
+ },
242
+ onEnd: () => {
243
+ // Mark the app as done rendering
244
+ isAppRendering = false
245
+
246
+ // If there are no pending promises, resolve the injectedHtmlDonePromise
247
+ if (processingCount === 0) {
248
+ injectedHtmlDonePromise.resolve()
249
+ }
250
+ },
251
+ onError: (error) => {
252
+ console.error('Error reading appStream:', error)
253
+ finalPassThrough.destroy(error)
254
+ },
255
+ })
256
+
257
+ return finalPassThrough.stream
258
+ }
@@ -0,0 +1,91 @@
1
+ import type { ControllablePromise } from '../router'
2
+ import type { TsrSsrGlobal } from './ssr-client'
3
+
4
+ const __TSR_SSR__: TsrSsrGlobal = {
5
+ matches: [],
6
+ streamedValues: {},
7
+ initMatch: (match) => {
8
+ __TSR_SSR__.matches.push(match)
9
+
10
+ match.extracted?.forEach((ex) => {
11
+ if (ex.type === 'stream') {
12
+ let controller
13
+ ex.value = new ReadableStream({
14
+ start(c) {
15
+ controller = {
16
+ enqueue: (chunk: unknown) => {
17
+ try {
18
+ c.enqueue(chunk)
19
+ } catch {}
20
+ },
21
+ close: () => {
22
+ try {
23
+ c.close()
24
+ } catch {}
25
+ },
26
+ }
27
+ },
28
+ })
29
+ ex.value.controller = controller
30
+ } else {
31
+ let resolve: ControllablePromise['reject'] | undefined
32
+ let reject: ControllablePromise['reject'] | undefined
33
+
34
+ ex.value = new Promise((_resolve, _reject) => {
35
+ reject = _reject
36
+ resolve = _resolve
37
+ }) as ControllablePromise
38
+ ex.value.reject = reject!
39
+ ex.value.resolve = resolve!
40
+ }
41
+ })
42
+
43
+ return true
44
+ },
45
+ resolvePromise: ({ matchId, id, promiseState }) => {
46
+ const match = __TSR_SSR__.matches.find((m) => m.id === matchId)
47
+ if (match) {
48
+ const ex = match.extracted?.[id]
49
+ if (
50
+ ex &&
51
+ ex.type === 'promise' &&
52
+ ex.value &&
53
+ promiseState.status === 'success'
54
+ ) {
55
+ ex.value.resolve(promiseState.data)
56
+ return true
57
+ }
58
+ }
59
+ return false
60
+ },
61
+ injectChunk: ({ matchId, id, chunk }) => {
62
+ const match = __TSR_SSR__.matches.find((m) => m.id === matchId)
63
+
64
+ if (match) {
65
+ const ex = match.extracted?.[id]
66
+ if (ex && ex.type === 'stream' && ex.value?.controller) {
67
+ ex.value.controller.enqueue(new TextEncoder().encode(chunk.toString()))
68
+ return true
69
+ }
70
+ }
71
+ return false
72
+ },
73
+ closeStream: ({ matchId, id }) => {
74
+ const match = __TSR_SSR__.matches.find((m) => m.id === matchId)
75
+ if (match) {
76
+ const ex = match.extracted?.[id]
77
+ if (ex && ex.type === 'stream' && ex.value?.controller) {
78
+ ex.value.controller.close()
79
+ return true
80
+ }
81
+ }
82
+ return false
83
+ },
84
+ cleanScripts: () => {
85
+ document.querySelectorAll('.tsr-once').forEach((el) => {
86
+ el.remove()
87
+ })
88
+ },
89
+ }
90
+
91
+ window.__TSR_SSR__ = __TSR_SSR__
@@ -0,0 +1,4 @@
1
+ declare module '*?script-string' {
2
+ const content: string
3
+ export default content
4
+ }