@tanstack/router-core 1.171.5 → 1.171.7

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 (107) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/defer.cjs.map +1 -1
  4. package/dist/cjs/index.cjs +5 -1
  5. package/dist/cjs/index.d.cts +2 -2
  6. package/dist/cjs/invariant.cjs.map +1 -1
  7. package/dist/cjs/load-matches.cjs.map +1 -1
  8. package/dist/cjs/lru-cache.cjs.map +1 -1
  9. package/dist/cjs/manifest.cjs +43 -17
  10. package/dist/cjs/manifest.cjs.map +1 -1
  11. package/dist/cjs/manifest.d.cts +76 -24
  12. package/dist/cjs/new-process-route-tree.cjs.map +1 -1
  13. package/dist/cjs/not-found.cjs.map +1 -1
  14. package/dist/cjs/path.cjs.map +1 -1
  15. package/dist/cjs/qss.cjs.map +1 -1
  16. package/dist/cjs/redirect.cjs.map +1 -1
  17. package/dist/cjs/rewrite.cjs.map +1 -1
  18. package/dist/cjs/route.cjs.map +1 -1
  19. package/dist/cjs/router.cjs.map +1 -1
  20. package/dist/cjs/router.d.cts +31 -16
  21. package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -1
  22. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
  23. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  24. package/dist/cjs/searchMiddleware.cjs.map +1 -1
  25. package/dist/cjs/searchParams.cjs.map +1 -1
  26. package/dist/cjs/ssr/createRequestHandler.cjs +10 -8
  27. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  28. package/dist/cjs/ssr/createRequestHandler.d.cts +2 -2
  29. package/dist/cjs/ssr/handlerCallback.cjs +46 -0
  30. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
  31. package/dist/cjs/ssr/handlerCallback.d.cts +15 -1
  32. package/dist/cjs/ssr/headers.cjs.map +1 -1
  33. package/dist/cjs/ssr/json.cjs.map +1 -1
  34. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  35. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  36. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  37. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  38. package/dist/cjs/ssr/server.cjs +6 -1
  39. package/dist/cjs/ssr/server.d.cts +3 -2
  40. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  41. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
  42. package/dist/cjs/ssr/ssr-server.cjs +263 -132
  43. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  44. package/dist/cjs/ssr/ssr-server.d.cts +4 -19
  45. package/dist/cjs/ssr/transformStreamWithRouter.cjs +455 -203
  46. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  47. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +14 -5
  48. package/dist/cjs/stores.cjs.map +1 -1
  49. package/dist/cjs/utils.cjs.map +1 -1
  50. package/dist/esm/Matches.js.map +1 -1
  51. package/dist/esm/config.js.map +1 -1
  52. package/dist/esm/defer.js.map +1 -1
  53. package/dist/esm/index.d.ts +2 -2
  54. package/dist/esm/index.js +2 -2
  55. package/dist/esm/invariant.js.map +1 -1
  56. package/dist/esm/load-matches.js.map +1 -1
  57. package/dist/esm/lru-cache.js.map +1 -1
  58. package/dist/esm/manifest.d.ts +76 -24
  59. package/dist/esm/manifest.js +39 -17
  60. package/dist/esm/manifest.js.map +1 -1
  61. package/dist/esm/new-process-route-tree.js.map +1 -1
  62. package/dist/esm/not-found.js.map +1 -1
  63. package/dist/esm/path.js.map +1 -1
  64. package/dist/esm/qss.js.map +1 -1
  65. package/dist/esm/redirect.js.map +1 -1
  66. package/dist/esm/rewrite.js.map +1 -1
  67. package/dist/esm/route.js.map +1 -1
  68. package/dist/esm/router.d.ts +31 -16
  69. package/dist/esm/router.js.map +1 -1
  70. package/dist/esm/scroll-restoration-script/client.js.map +1 -1
  71. package/dist/esm/scroll-restoration-script/server.js.map +1 -1
  72. package/dist/esm/scroll-restoration.js.map +1 -1
  73. package/dist/esm/searchMiddleware.js.map +1 -1
  74. package/dist/esm/searchParams.js.map +1 -1
  75. package/dist/esm/ssr/createRequestHandler.d.ts +2 -2
  76. package/dist/esm/ssr/createRequestHandler.js +10 -8
  77. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  78. package/dist/esm/ssr/handlerCallback.d.ts +15 -1
  79. package/dist/esm/ssr/handlerCallback.js +42 -1
  80. package/dist/esm/ssr/handlerCallback.js.map +1 -1
  81. package/dist/esm/ssr/headers.js.map +1 -1
  82. package/dist/esm/ssr/json.js.map +1 -1
  83. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  84. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  85. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  86. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  87. package/dist/esm/ssr/server.d.ts +3 -2
  88. package/dist/esm/ssr/server.js +2 -2
  89. package/dist/esm/ssr/ssr-client.js.map +1 -1
  90. package/dist/esm/ssr/ssr-match-id.js.map +1 -1
  91. package/dist/esm/ssr/ssr-server.d.ts +4 -19
  92. package/dist/esm/ssr/ssr-server.js +264 -133
  93. package/dist/esm/ssr/ssr-server.js.map +1 -1
  94. package/dist/esm/ssr/transformStreamWithRouter.d.ts +14 -5
  95. package/dist/esm/ssr/transformStreamWithRouter.js +455 -203
  96. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  97. package/dist/esm/stores.js.map +1 -1
  98. package/dist/esm/utils.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/index.ts +21 -1
  101. package/src/manifest.ts +151 -59
  102. package/src/router.ts +37 -19
  103. package/src/ssr/createRequestHandler.ts +14 -13
  104. package/src/ssr/handlerCallback.ts +84 -1
  105. package/src/ssr/server.ts +14 -2
  106. package/src/ssr/ssr-server.ts +418 -222
  107. package/src/ssr/transformStreamWithRouter.ts +662 -281
package/src/manifest.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export type AssetCrossOrigin = 'anonymous' | 'use-credentials'
2
+ export type ScriptFormat = 'module' | 'iife'
3
+
4
+ export const DEV_STYLES_ATTR = 'data-tanstack-router-dev-styles'
2
5
 
3
6
  export type AssetCrossOriginConfig =
4
7
  | AssetCrossOrigin
5
- | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>
8
+ | Partial<Record<'script' | 'stylesheet', AssetCrossOrigin>>
6
9
 
7
10
  export type ManifestAssetLink =
8
11
  | string
@@ -13,7 +16,7 @@ export type ManifestAssetLink =
13
16
 
14
17
  export function getAssetCrossOrigin(
15
18
  assetCrossOrigin: AssetCrossOriginConfig | undefined,
16
- kind: 'modulepreload' | 'stylesheet',
19
+ kind: 'script' | 'stylesheet',
17
20
  ): AssetCrossOrigin | undefined {
18
21
  if (!assetCrossOrigin) {
19
22
  return undefined
@@ -26,6 +29,35 @@ export function getAssetCrossOrigin(
26
29
  return assetCrossOrigin[kind]
27
30
  }
28
31
 
32
+ export function getManifestScriptFormat(
33
+ manifest: { scriptFormat?: ScriptFormat } | undefined,
34
+ ): ScriptFormat {
35
+ return manifest?.scriptFormat ?? 'module'
36
+ }
37
+
38
+ export function getScriptPreloadAttrs(
39
+ manifest: { scriptFormat?: ScriptFormat } | undefined,
40
+ link: ManifestAssetLink,
41
+ assetCrossOrigin?: AssetCrossOriginConfig,
42
+ ): {
43
+ rel: 'modulepreload' | 'preload'
44
+ as?: 'script'
45
+ href: string
46
+ crossOrigin?: AssetCrossOrigin
47
+ } {
48
+ const preloadLink = resolveManifestAssetLink(link)
49
+ const crossOrigin =
50
+ getAssetCrossOrigin(assetCrossOrigin, 'script') ?? preloadLink.crossOrigin
51
+
52
+ return {
53
+ ...(getManifestScriptFormat(manifest) === 'iife'
54
+ ? { rel: 'preload', as: 'script' }
55
+ : { rel: 'modulepreload' }),
56
+ href: preloadLink.href,
57
+ ...(crossOrigin ? { crossOrigin } : {}),
58
+ }
59
+ }
60
+
29
61
  export function resolveManifestAssetLink(link: ManifestAssetLink) {
30
62
  if (typeof link === 'string') {
31
63
  return { href: link, crossOrigin: undefined }
@@ -35,87 +67,147 @@ export function resolveManifestAssetLink(link: ManifestAssetLink) {
35
67
  }
36
68
 
37
69
  export type Manifest = {
38
- inlineCss?: {
39
- styles: Record<string, string>
40
- templates?: Record<
41
- string,
42
- {
43
- strings: Array<string>
44
- urls: Array<string>
45
- }
46
- >
47
- }
48
- routes: Record<
49
- string,
50
- {
51
- filePath?: string
52
- preloads?: Array<ManifestAssetLink>
53
- assets?: Array<RouterManagedTag>
54
- }
55
- >
70
+ scriptFormat?: ScriptFormat
71
+ inlineStyle?: ManifestInlineCss
72
+ routes: Record<string, ManifestRoute>
73
+ }
74
+
75
+ export type ServerManifest = {
76
+ scriptFormat?: ScriptFormat
77
+ inlineCss?: ServerManifestInlineCss
78
+ routes: Record<string, ServerManifestRoute>
79
+ }
80
+
81
+ export type ServerManifestInlineCss = {
82
+ styles: Record<string, string>
83
+ templates?: Record<string, InlineCssTemplate>
84
+ }
85
+
86
+ export type InlineCssTemplate = {
87
+ strings: Array<string>
88
+ urls: Array<string>
89
+ }
90
+
91
+ export type ManifestRoute = {
92
+ filePath?: string
93
+ preloads?: Array<ManifestAssetLink>
94
+ scripts?: Array<ManifestScript>
95
+ css?: Array<ManifestCssLink>
96
+ }
97
+
98
+ export type ServerManifestRoute = ManifestRoute
99
+
100
+ export type ManifestRouteAssets = Pick<
101
+ ManifestRoute,
102
+ 'preloads' | 'scripts' | 'css'
103
+ >
104
+
105
+ export type RouterManagedTitleTag = {
106
+ tag: 'title'
107
+ attrs?: Record<string, any>
108
+ children: string
109
+ }
110
+
111
+ export type RouterManagedMetaTag = {
112
+ tag: 'meta'
113
+ attrs?: Record<string, any>
114
+ children?: never
115
+ }
116
+
117
+ export type RouterManagedLinkTag = {
118
+ tag: 'link'
119
+ attrs?: Record<string, any>
120
+ children?: never
121
+ }
122
+
123
+ export type RouterManagedScriptTag = {
124
+ tag: 'script'
125
+ attrs?: Record<string, any>
126
+ children?: string
127
+ }
128
+
129
+ export type ManifestScript = Omit<RouterManagedScriptTag, 'tag'>
130
+
131
+ export type RouterManagedStyleTag = {
132
+ tag: 'style'
133
+ attrs?: Record<string, any>
134
+ children?: string
135
+ inlineCss?: true
56
136
  }
57
137
 
58
138
  export type RouterManagedTag =
59
- | {
60
- tag: 'title'
61
- attrs?: Record<string, any>
62
- children: string
63
- }
64
- | {
65
- tag: 'meta' | 'link'
66
- attrs?: Record<string, any>
67
- children?: never
68
- }
69
- | {
70
- tag: 'script'
71
- attrs?: Record<string, any>
72
- children?: string
139
+ | RouterManagedTitleTag
140
+ | RouterManagedMetaTag
141
+ | RouterManagedLinkTag
142
+ | RouterManagedScriptTag
143
+ | RouterManagedStyleTag
144
+
145
+ export function appendUniqueUserTags(
146
+ target: Array<RouterManagedTag>,
147
+ tags: Array<RouterManagedTag>,
148
+ ) {
149
+ if (tags.length === 0) {
150
+ return
151
+ }
152
+
153
+ if (tags.length === 1) {
154
+ target.push(tags[0]!)
155
+ return
156
+ }
157
+
158
+ const seen = new Set<string>()
159
+ for (const tag of tags) {
160
+ const key = JSON.stringify(tag)
161
+ if (seen.has(key)) {
162
+ continue
73
163
  }
164
+ seen.add(key)
165
+ target.push(tag)
166
+ }
167
+ }
168
+
169
+ export type ManifestCssLink =
170
+ | string
74
171
  | {
75
- tag: 'style'
76
- attrs?: Record<string, any>
77
- children?: string
78
- inlineCss?: true
172
+ href: string
173
+ crossOrigin?: AssetCrossOrigin
174
+ [DEV_STYLES_ATTR]?: true
79
175
  }
80
176
 
81
- export function getStylesheetHref(asset: RouterManagedTag) {
82
- if (asset.tag !== 'link') return undefined
83
-
84
- const rel = asset.attrs?.rel
85
- const href = asset.attrs?.href
86
- if (typeof href !== 'string') return undefined
177
+ export type ManifestInlineCss = {
178
+ attrs?: Record<string, any>
179
+ children?: string
180
+ }
87
181
 
88
- const relTokens = typeof rel === 'string' ? rel.split(/\s+/) : []
89
- if (!relTokens.includes('stylesheet')) return undefined
182
+ export type RouterManagedInlineCssTag = RouterManagedStyleTag & {
183
+ inlineCss: true
184
+ }
90
185
 
91
- return href
186
+ export function getStylesheetHref(asset: ManifestCssLink) {
187
+ return resolveManifestCssLink(asset).href
92
188
  }
93
189
 
94
- export function isInlinableStylesheet(
95
- manifest: Manifest | undefined,
96
- asset: RouterManagedTag,
97
- ) {
98
- const href = getStylesheetHref(asset)
99
- return !!href && manifest?.inlineCss?.styles[href] !== undefined
190
+ export function resolveManifestCssLink(link: ManifestCssLink) {
191
+ if (typeof link === 'string') {
192
+ return { href: link, crossOrigin: undefined }
193
+ }
194
+
195
+ return link
100
196
  }
101
197
 
102
- export function createInlineCssStyleAsset(css: string): RouterManagedTag {
198
+ export function createInlineCssStyleAsset(css: string): ManifestInlineCss {
103
199
  return {
104
- tag: 'style',
105
200
  attrs: {
106
201
  suppressHydrationWarning: true,
107
202
  },
108
- inlineCss: true,
109
203
  children: css,
110
204
  }
111
205
  }
112
206
 
113
- export function createInlineCssPlaceholderAsset(): RouterManagedTag {
207
+ export function createInlineCssPlaceholderAsset(): ManifestInlineCss {
114
208
  return {
115
- tag: 'style',
116
209
  attrs: {
117
210
  suppressHydrationWarning: true,
118
211
  },
119
- inlineCss: true,
120
212
  }
121
213
  }
package/src/router.ts CHANGED
@@ -98,7 +98,11 @@ import type {
98
98
  CommitLocationOptions,
99
99
  NavigateFn,
100
100
  } from './RouterProvider'
101
- import type { Manifest, RouterManagedTag } from './manifest'
101
+ import type {
102
+ Manifest,
103
+ ManifestRouteAssets,
104
+ RouterManagedTag,
105
+ } from './manifest'
102
106
  import type { AnySchema, AnyValidator } from './validators'
103
107
  import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
104
108
  import type { NotFoundError } from './not-found'
@@ -341,9 +345,8 @@ export interface RouterOptions<
341
345
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
342
346
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
343
347
  */
344
- dehydrate?: () => Constrain<
345
- TDehydrated,
346
- ValidateSerializableInput<Register, TDehydrated>
348
+ dehydrate?: () => Awaitable<
349
+ Constrain<TDehydrated, ValidateSerializableInput<Register, TDehydrated>>
347
350
  >
348
351
  /**
349
352
  * A function that will be called when the router is hydrated.
@@ -787,34 +790,47 @@ export type ClearCacheFn<TRouter extends AnyRouter> = (opts?: {
787
790
  }) => void
788
791
 
789
792
  export interface ServerSsr {
790
- /**
791
- * Injects HTML synchronously into the stream.
792
- * Emits an onInjectedHtml event that listeners can handle.
793
- * If no subscriber is listening, the HTML is buffered and can be retrieved via takeBufferedHtml().
794
- */
793
+ /** Framework-only: injects router-owned HTML into the SSR stream. */
795
794
  injectHtml: (html: string) => void
796
- /**
797
- * Injects a script tag synchronously into the stream.
798
- */
795
+ /** Framework-only: injects a router-owned script tag into the SSR stream. */
799
796
  injectScript: (script: string) => void
800
797
  isDehydrated: () => boolean
801
798
  isSerializationFinished: () => boolean
799
+ /** Framework-only: atomically reserves the pass-through stream path if safe. */
800
+ reserveStreamFastPath: () => boolean
801
+ /** Framework-only. */
802
+ onInjectedHtml: (listener: () => void) => () => void
803
+ /** Framework-only. */
802
804
  onRenderFinished: (listener: () => void) => void
805
+ /** Framework-only. */
803
806
  setRenderFinished: () => void
807
+ /** Framework-only. */
804
808
  cleanup: () => void
805
- onSerializationFinished: (listener: () => void) => void
806
- dehydrate: (opts?: {
807
- requestAssets?: Array<RouterManagedTag>
808
- }) => Promise<void>
809
- takeBufferedScripts: () => RouterManagedTag | undefined
810
809
  /**
811
- * Takes any buffered HTML that was injected.
812
- * Returns the buffered HTML string (which may include multiple script tags) or undefined if empty.
810
+ * Register a listener invoked when the SSR request lifecycle ends (success,
811
+ * error, abort, or stream lifetime expiry). Use to tear down per-request
812
+ * resources whose references would otherwise pin the router (e.g. query
813
+ * cache subscriptions, gcTime timers, abort controllers).
814
+ *
815
+ * Listeners run synchronously and exactly once. Errors are caught and logged.
813
816
  */
817
+ onCleanup: (listener: () => void) => void
818
+ /** Framework-only. */
819
+ onSerializationFinished: (listener: () => void) => () => void
820
+ /** Framework-only. */
821
+ dehydrate: (opts?: { requestAssets?: ManifestRouteAssets }) => Promise<void>
822
+ /** Framework-only. */
823
+ takeBufferedScripts: () => RouterManagedTag | undefined
824
+ /** Framework-only: takes buffered router-owned HTML. */
814
825
  takeBufferedHtml: () => string | undefined
826
+ /** Framework-only. */
815
827
  liftScriptBarrier: () => void
816
828
  }
817
829
 
830
+ export interface RouterSsrLifecycle {
831
+ onServerSsrAttach?: Array<(serverSsr: ServerSsr) => void>
832
+ }
833
+
818
834
  export type AnyRouterWithContext<TContext> = RouterCore<
819
835
  AnyRouteWithContext<TContext>,
820
836
  any,
@@ -2975,6 +2991,8 @@ export class RouterCore<
2975
2991
 
2976
2992
  serverSsr?: ServerSsr
2977
2993
 
2994
+ serverSsrLifecycle?: RouterSsrLifecycle
2995
+
2978
2996
  hasNotFoundMatch = () => {
2979
2997
  return this.stores.matches
2980
2998
  .get()
@@ -5,10 +5,11 @@ import {
5
5
  getNormalizedURL,
6
6
  getOrigin,
7
7
  } from './ssr-server'
8
+ import { normalizeSsrResponse } from './handlerCallback'
8
9
  import type { HandlerCallback } from './handlerCallback'
9
10
  import type { AnyHeaders } from './headers'
10
11
  import type { AnyRouter } from '../router'
11
- import type { Manifest } from '../manifest'
12
+ import type { ServerManifest } from '../manifest'
12
13
 
13
14
  export type RequestHandler<TRouter extends AnyRouter> = (
14
15
  cb: HandlerCallback<TRouter>,
@@ -21,12 +22,11 @@ export function createRequestHandler<TRouter extends AnyRouter>({
21
22
  }: {
22
23
  createRouter: () => TRouter
23
24
  request: Request
24
- getRouterManifest?: () => Manifest | Promise<Manifest>
25
+ getRouterManifest?: () => ServerManifest | Promise<ServerManifest>
25
26
  }): RequestHandler<TRouter> {
26
27
  return async (cb) => {
27
28
  const router = createRouter()
28
- // Track whether the callback will handle cleanup
29
- let cbWillCleanup = false
29
+ let responseOwnsCleanup = false
30
30
 
31
31
  try {
32
32
  attachRouterServerSsrUtils({
@@ -58,19 +58,19 @@ export function createRequestHandler<TRouter extends AnyRouter>({
58
58
  router,
59
59
  })
60
60
 
61
- // Mark that the callback will handle cleanup
62
- cbWillCleanup = true
63
- return cb({
61
+ const response = await cb({
64
62
  request,
65
63
  router,
66
64
  responseHeaders,
67
65
  })
66
+ const ssrResponse = normalizeSsrResponse(response)
67
+ responseOwnsCleanup = ssrResponse.serverSsrCleanup === 'stream'
68
+ return ssrResponse.response
68
69
  } finally {
69
- if (!cbWillCleanup) {
70
+ if (!responseOwnsCleanup) {
70
71
  // Clean up router SSR state if the callback won't handle it
71
72
  // (e.g., if an error occurred before the callback was invoked).
72
- // When the callback runs, it handles cleanup (either via transformStreamWithRouter
73
- // for streaming, or directly in renderRouterToString for non-streaming).
73
+ // Transformed streaming response bodies clean up when consumed/cancelled.
74
74
  router.serverSsr?.cleanup()
75
75
  }
76
76
  }
@@ -78,9 +78,10 @@ export function createRequestHandler<TRouter extends AnyRouter>({
78
78
  }
79
79
 
80
80
  function getRequestHeaders(opts: { router: AnyRouter }): Headers {
81
- const matchHeaders = opts.router.stores.matches
82
- .get()
83
- .map<AnyHeaders>((match) => match.headers)
81
+ const matchHeaders: Array<AnyHeaders> = []
82
+ for (const match of opts.router.stores.matches.get()) {
83
+ matchHeaders.push(match.headers)
84
+ }
84
85
 
85
86
  // Handle Redirects
86
87
  const redirect = opts.router.stores.redirect.get()
@@ -1,11 +1,94 @@
1
1
  import type { AnyRouter } from '../router'
2
2
 
3
+ export type SsrResponse =
4
+ | {
5
+ response: Response
6
+ serverSsrCleanup: 'none'
7
+ }
8
+ | {
9
+ response: Response
10
+ serverSsrCleanup: 'stream'
11
+ dispose: (reason?: unknown) => Promise<void>
12
+ }
13
+
14
+ export type HandlerCallbackResult = Response | SsrResponse
15
+
16
+ export function isSsrResponse(value: unknown): value is SsrResponse {
17
+ return (
18
+ typeof value === 'object' &&
19
+ value !== null &&
20
+ 'response' in value &&
21
+ 'serverSsrCleanup' in value
22
+ )
23
+ }
24
+
25
+ export function normalizeSsrResponse(
26
+ result: HandlerCallbackResult,
27
+ ): SsrResponse {
28
+ return isSsrResponse(result)
29
+ ? result
30
+ : { response: result, serverSsrCleanup: 'none' }
31
+ }
32
+
33
+ export function createSsrStreamResponse<TRouter extends AnyRouter>(
34
+ router: TRouter,
35
+ response: Response,
36
+ ): SsrResponse {
37
+ if (!response.body) {
38
+ throw new Error('Invariant failed: SSR stream response requires a body')
39
+ }
40
+
41
+ let disposed = false
42
+ return {
43
+ response,
44
+ serverSsrCleanup: 'stream',
45
+ async dispose(reason?: unknown) {
46
+ if (disposed) return
47
+ disposed = true
48
+
49
+ try {
50
+ await response.body!.cancel(reason)
51
+ } catch {
52
+ // ignore; fallback cleanup below still releases router SSR state
53
+ }
54
+
55
+ router.serverSsr?.cleanup()
56
+ },
57
+ }
58
+ }
59
+
60
+ export async function replaceSsrResponse(
61
+ result: HandlerCallbackResult,
62
+ response: Response,
63
+ reason?: unknown,
64
+ ): Promise<SsrResponse> {
65
+ const ssrResponse = normalizeSsrResponse(result)
66
+ if (ssrResponse.serverSsrCleanup === 'stream') {
67
+ await ssrResponse.dispose(reason)
68
+ }
69
+ return { response, serverSsrCleanup: 'none' }
70
+ }
71
+
72
+ export async function stripSsrResponseBody(
73
+ result: HandlerCallbackResult,
74
+ reason?: unknown,
75
+ ): Promise<SsrResponse> {
76
+ const ssrResponse = normalizeSsrResponse(result)
77
+ if (ssrResponse.serverSsrCleanup === 'stream') {
78
+ await ssrResponse.dispose(reason)
79
+ }
80
+ return {
81
+ response: new Response(null, ssrResponse.response),
82
+ serverSsrCleanup: 'none',
83
+ }
84
+ }
85
+
3
86
  export interface HandlerCallback<TRouter extends AnyRouter> {
4
87
  (ctx: {
5
88
  request: Request
6
89
  router: TRouter
7
90
  responseHeaders: Headers
8
- }): Response | Promise<Response>
91
+ }): HandlerCallbackResult | Promise<HandlerCallbackResult>
9
92
  }
10
93
 
11
94
  export function defineHandlerCallback<TRouter extends AnyRouter>(
package/src/ssr/server.ts CHANGED
@@ -1,12 +1,24 @@
1
1
  export { createRequestHandler } from './createRequestHandler'
2
2
  export type { RequestHandler } from './createRequestHandler'
3
- export { defineHandlerCallback } from './handlerCallback'
4
- export type { HandlerCallback } from './handlerCallback'
3
+ export {
4
+ createSsrStreamResponse,
5
+ defineHandlerCallback,
6
+ isSsrResponse,
7
+ normalizeSsrResponse,
8
+ replaceSsrResponse,
9
+ stripSsrResponseBody,
10
+ } from './handlerCallback'
11
+ export type {
12
+ HandlerCallback,
13
+ HandlerCallbackResult,
14
+ SsrResponse,
15
+ } from './handlerCallback'
5
16
  export {
6
17
  transformPipeableStreamWithRouter,
7
18
  transformStreamWithRouter,
8
19
  transformReadableStreamWithRouter,
9
20
  } from './transformStreamWithRouter'
21
+ export type { TransformStreamWithRouterOptions } from './transformStreamWithRouter'
10
22
  export {
11
23
  attachRouterServerSsrUtils,
12
24
  getNormalizedURL,