@tanstack/start-server-core 1.20.3-alpha.1
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/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/cjs/createRequestHandler.cjs +50 -0
- package/dist/cjs/createRequestHandler.cjs.map +1 -0
- package/dist/cjs/createRequestHandler.d.cts +8 -0
- package/dist/cjs/createStartHandler.cjs +254 -0
- package/dist/cjs/createStartHandler.cjs.map +1 -0
- package/dist/cjs/createStartHandler.d.cts +10 -0
- package/dist/cjs/h3.cjs +355 -0
- package/dist/cjs/h3.cjs.map +1 -0
- package/dist/cjs/h3.d.cts +109 -0
- package/dist/cjs/handlerCallback.cjs +7 -0
- package/dist/cjs/handlerCallback.cjs.map +1 -0
- package/dist/cjs/handlerCallback.d.cts +9 -0
- package/dist/cjs/index.cjs +245 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +12 -0
- package/dist/cjs/router-manifest.cjs +44 -0
- package/dist/cjs/router-manifest.cjs.map +1 -0
- package/dist/cjs/router-manifest.d.cts +17 -0
- package/dist/cjs/server-functions-handler.cjs +154 -0
- package/dist/cjs/server-functions-handler.cjs.map +1 -0
- package/dist/cjs/server-functions-handler.d.cts +4 -0
- package/dist/cjs/serverRoute.cjs +100 -0
- package/dist/cjs/serverRoute.cjs.map +1 -0
- package/dist/cjs/serverRoute.d.cts +115 -0
- package/dist/cjs/ssr-server.cjs +246 -0
- package/dist/cjs/ssr-server.cjs.map +1 -0
- package/dist/cjs/ssr-server.d.cts +29 -0
- package/dist/cjs/transformStreamWithRouter.cjs +183 -0
- package/dist/cjs/transformStreamWithRouter.cjs.map +1 -0
- package/dist/cjs/transformStreamWithRouter.d.cts +6 -0
- package/dist/cjs/tsrScript.cjs +4 -0
- package/dist/cjs/tsrScript.cjs.map +1 -0
- package/dist/cjs/tsrScript.d.cts +1 -0
- package/dist/cjs/undici.cjs +14 -0
- package/dist/cjs/undici.cjs.map +1 -0
- package/dist/cjs/undici.d.cts +43 -0
- package/dist/esm/createRequestHandler.d.ts +8 -0
- package/dist/esm/createRequestHandler.js +50 -0
- package/dist/esm/createRequestHandler.js.map +1 -0
- package/dist/esm/createStartHandler.d.ts +10 -0
- package/dist/esm/createStartHandler.js +232 -0
- package/dist/esm/createStartHandler.js.map +1 -0
- package/dist/esm/h3.d.ts +109 -0
- package/dist/esm/h3.js +248 -0
- package/dist/esm/h3.js.map +1 -0
- package/dist/esm/handlerCallback.d.ts +9 -0
- package/dist/esm/handlerCallback.js +7 -0
- package/dist/esm/handlerCallback.js.map +1 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +137 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/router-manifest.d.ts +17 -0
- package/dist/esm/router-manifest.js +44 -0
- package/dist/esm/router-manifest.js.map +1 -0
- package/dist/esm/server-functions-handler.d.ts +4 -0
- package/dist/esm/server-functions-handler.js +154 -0
- package/dist/esm/server-functions-handler.js.map +1 -0
- package/dist/esm/serverRoute.d.ts +115 -0
- package/dist/esm/serverRoute.js +100 -0
- package/dist/esm/serverRoute.js.map +1 -0
- package/dist/esm/ssr-server.d.ts +29 -0
- package/dist/esm/ssr-server.js +246 -0
- package/dist/esm/ssr-server.js.map +1 -0
- package/dist/esm/transformStreamWithRouter.d.ts +6 -0
- package/dist/esm/transformStreamWithRouter.js +183 -0
- package/dist/esm/transformStreamWithRouter.js.map +1 -0
- package/dist/esm/tsrScript.d.ts +1 -0
- package/dist/esm/tsrScript.js +5 -0
- package/dist/esm/tsrScript.js.map +1 -0
- package/dist/esm/undici.d.ts +43 -0
- package/dist/esm/undici.js +14 -0
- package/dist/esm/undici.js.map +1 -0
- package/package.json +68 -0
- package/src/createRequestHandler.ts +73 -0
- package/src/createStartHandler.ts +348 -0
- package/src/h3.ts +492 -0
- package/src/handlerCallback.ts +15 -0
- package/src/index.tsx +24 -0
- package/src/router-manifest.ts +79 -0
- package/src/server-functions-handler.ts +273 -0
- package/src/serverRoute.ts +661 -0
- package/src/ssr-server.ts +350 -0
- package/src/tanstack-start.d.ts +5 -0
- package/src/transformStreamWithRouter.ts +258 -0
- package/src/tsrScript.ts +91 -0
- package/src/undici.ts +60 -0
- package/src/vite-env.d.ts +4 -0
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
}
|
|
71
|
+
|
|
72
|
+
return headers
|
|
73
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { createMemoryHistory } from '@tanstack/history'
|
|
3
|
+
import {
|
|
4
|
+
flattenMiddlewares,
|
|
5
|
+
json,
|
|
6
|
+
mergeHeaders,
|
|
7
|
+
} from '@tanstack/start-client-core'
|
|
8
|
+
import {
|
|
9
|
+
getMatchedRoutes,
|
|
10
|
+
isRedirect,
|
|
11
|
+
processRouteTree,
|
|
12
|
+
rootRouteId,
|
|
13
|
+
tsrRedirectHeaderKey,
|
|
14
|
+
} from '@tanstack/router-core'
|
|
15
|
+
import { getResponseHeaders, requestHandler } from './h3'
|
|
16
|
+
import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server'
|
|
17
|
+
import { getStartManifest } from './router-manifest'
|
|
18
|
+
import { handleServerAction } from './server-functions-handler'
|
|
19
|
+
import type { AnyServerRoute, AnyServerRouteWithTypes } from './serverRoute'
|
|
20
|
+
import type { RequestHandler } from './h3'
|
|
21
|
+
import type { AnyRouter } from '@tanstack/router-core'
|
|
22
|
+
import type { HandlerCallback } from './handlerCallback'
|
|
23
|
+
|
|
24
|
+
type TODO = any
|
|
25
|
+
|
|
26
|
+
export type CustomizeStartHandler<TRouter extends AnyRouter> = (
|
|
27
|
+
cb: HandlerCallback<TRouter>,
|
|
28
|
+
) => RequestHandler
|
|
29
|
+
|
|
30
|
+
export function getStartResponseHeaders(opts: { router: AnyRouter }) {
|
|
31
|
+
let headers = mergeHeaders(
|
|
32
|
+
getResponseHeaders(),
|
|
33
|
+
{
|
|
34
|
+
'Content-Type': 'text/html; charset=UTF-8',
|
|
35
|
+
},
|
|
36
|
+
...opts.router.state.matches.map((match) => {
|
|
37
|
+
return match.headers
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
// Handle Redirects
|
|
41
|
+
const { redirect } = opts.router.state
|
|
42
|
+
|
|
43
|
+
if (redirect) {
|
|
44
|
+
headers = mergeHeaders(headers, redirect.headers)
|
|
45
|
+
}
|
|
46
|
+
return headers
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createStartHandler<TRouter extends AnyRouter>({
|
|
50
|
+
createRouter,
|
|
51
|
+
}: {
|
|
52
|
+
createRouter: () => TRouter
|
|
53
|
+
}): CustomizeStartHandler<TRouter> {
|
|
54
|
+
return (cb) => {
|
|
55
|
+
return requestHandler(async ({ request }) => {
|
|
56
|
+
const url = new URL(request.url)
|
|
57
|
+
const href = url.href.replace(url.origin, '')
|
|
58
|
+
|
|
59
|
+
// Create a history for the client-side router
|
|
60
|
+
const history = createMemoryHistory({
|
|
61
|
+
initialEntries: [href],
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Create the client-side router
|
|
65
|
+
const router = createRouter()
|
|
66
|
+
|
|
67
|
+
// Attach the server-side SSR utils to the client-side router
|
|
68
|
+
attachRouterServerSsrUtils(router, getStartManifest())
|
|
69
|
+
|
|
70
|
+
// Update the client-side router with the history and context
|
|
71
|
+
router.update({
|
|
72
|
+
history,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const response = await (async () => {
|
|
76
|
+
try {
|
|
77
|
+
if (!process.env.TSS_SERVER_FN_BASE) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'tanstack/start-server-core: TSS_SERVER_FN_BASE must be defined in your environment for createStartHandler()',
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// First, let's attempt to handle server functions
|
|
84
|
+
if (
|
|
85
|
+
href.startsWith(path.join('/', process.env.TSS_SERVER_FN_BASE, '/'))
|
|
86
|
+
) {
|
|
87
|
+
return await handleServerAction({ request })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Then move on to attempting to load server routes
|
|
91
|
+
const serverRouteTreeModule = await (async () => {
|
|
92
|
+
try {
|
|
93
|
+
// @ts-expect-error
|
|
94
|
+
return (await import('tanstack:server-routes')) as {
|
|
95
|
+
routeTree: AnyServerRoute
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.log(e)
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
})()
|
|
102
|
+
|
|
103
|
+
// If we have a server route tree, then we try matching to see if we have a
|
|
104
|
+
// server route that matches the request.
|
|
105
|
+
if (serverRouteTreeModule) {
|
|
106
|
+
const [matchedRoutes, response] = await handleServerRoutes({
|
|
107
|
+
routeTree: serverRouteTreeModule.routeTree,
|
|
108
|
+
request,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if (response) return response
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const requestAcceptHeader = request.headers.get('Accept') || '*/*'
|
|
115
|
+
const splitRequestAcceptHeader = requestAcceptHeader.split(',')
|
|
116
|
+
|
|
117
|
+
const supportedMimeTypes = ['*/*', 'text/html']
|
|
118
|
+
const isRouterAcceptSupported = supportedMimeTypes.some((mimeType) =>
|
|
119
|
+
splitRequestAcceptHeader.some((acceptedMimeType) =>
|
|
120
|
+
acceptedMimeType.trim().startsWith(mimeType),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if (!isRouterAcceptSupported) {
|
|
125
|
+
return json(
|
|
126
|
+
{
|
|
127
|
+
error: 'Only HTML requests are supported here',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
status: 500,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If no Server Routes were found, so fallback to normal SSR matching using
|
|
136
|
+
// the router
|
|
137
|
+
|
|
138
|
+
await router.load()
|
|
139
|
+
|
|
140
|
+
// If there was a redirect, skip rendering the page at all
|
|
141
|
+
if (router.state.redirect) return router.state.redirect
|
|
142
|
+
|
|
143
|
+
dehydrateRouter(router)
|
|
144
|
+
|
|
145
|
+
const responseHeaders = getStartResponseHeaders({ router })
|
|
146
|
+
const response = await cb({
|
|
147
|
+
request,
|
|
148
|
+
router,
|
|
149
|
+
responseHeaders,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return response
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err instanceof Response) {
|
|
155
|
+
return err
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw err
|
|
159
|
+
}
|
|
160
|
+
})()
|
|
161
|
+
|
|
162
|
+
if (isRedirect(response)) {
|
|
163
|
+
if (
|
|
164
|
+
response.options.to &&
|
|
165
|
+
typeof response.options.to === 'string' &&
|
|
166
|
+
!response.options.to.startsWith('/')
|
|
167
|
+
) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Server side redirects must use absolute paths via the 'href' or 'to' options. Received: ${JSON.stringify(response.options)}`,
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
['params', 'search', 'hash'].some(
|
|
175
|
+
(d) => typeof (response.options as any)[d] === 'function',
|
|
176
|
+
)
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(
|
|
180
|
+
response.options,
|
|
181
|
+
)
|
|
182
|
+
.filter((d) => typeof (response.options as any)[d] === 'function')
|
|
183
|
+
.map((d) => `"${d}"`)
|
|
184
|
+
.join(', ')}`,
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const redirect = router.resolveRedirect(response)
|
|
189
|
+
|
|
190
|
+
if (request.headers.get('x-tsr-redirect') === 'manual') {
|
|
191
|
+
return json(
|
|
192
|
+
{
|
|
193
|
+
...response.options,
|
|
194
|
+
isSerializedRedirect: true,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
headers: redirect.headers,
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return redirect
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
response.headers.append(
|
|
206
|
+
'Access-Control-Expose-Headers',
|
|
207
|
+
tsrRedirectHeaderKey,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return response
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function handleServerRoutes({
|
|
216
|
+
routeTree,
|
|
217
|
+
request,
|
|
218
|
+
}: {
|
|
219
|
+
routeTree: AnyServerRouteWithTypes
|
|
220
|
+
request: Request
|
|
221
|
+
}) {
|
|
222
|
+
const { flatRoutes, routesById, routesByPath } = processRouteTree({
|
|
223
|
+
routeTree,
|
|
224
|
+
initRoute: (route, i) => {
|
|
225
|
+
route.init({
|
|
226
|
+
originalIndex: i,
|
|
227
|
+
})
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const url = new URL(request.url)
|
|
232
|
+
const pathname = url.pathname
|
|
233
|
+
|
|
234
|
+
const history = createMemoryHistory({
|
|
235
|
+
initialEntries: [pathname],
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const { matchedRoutes, foundRoute, routeParams } =
|
|
239
|
+
getMatchedRoutes<AnyServerRouteWithTypes>({
|
|
240
|
+
pathname: history.location.pathname,
|
|
241
|
+
basepath: '/',
|
|
242
|
+
caseSensitive: true,
|
|
243
|
+
routesByPath,
|
|
244
|
+
routesById,
|
|
245
|
+
flatRoutes,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
let response: Response | undefined
|
|
249
|
+
|
|
250
|
+
if (foundRoute && foundRoute.id !== rootRouteId) {
|
|
251
|
+
// We've found a server route that matches the request, so we can call it.
|
|
252
|
+
// TODO: Get the input type-signature correct
|
|
253
|
+
// TODO: Perform the middlewares?
|
|
254
|
+
// TODO: Error handling? What happens when its `throw redirect()` vs `throw new Error()`?
|
|
255
|
+
|
|
256
|
+
const method = Object.keys(foundRoute.options.methods).find(
|
|
257
|
+
(method) => method.toLowerCase() === request.method.toLowerCase(),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if (method) {
|
|
261
|
+
const handler = foundRoute.options.methods[method]
|
|
262
|
+
|
|
263
|
+
if (handler) {
|
|
264
|
+
const middlewares = flattenMiddlewares(
|
|
265
|
+
matchedRoutes.flatMap((r) => r.options.middleware).filter(Boolean),
|
|
266
|
+
).map((d) => d.options.server)
|
|
267
|
+
|
|
268
|
+
middlewares.push(handlerToMiddleware(handler) as TODO)
|
|
269
|
+
|
|
270
|
+
// TODO: This is starting to feel too much like a server function
|
|
271
|
+
// Do generalize the existing middleware execution? Or do we need to
|
|
272
|
+
// build a new middleware execution system for server routes?
|
|
273
|
+
const ctx = await executeMiddleware(middlewares, {
|
|
274
|
+
request,
|
|
275
|
+
context: {},
|
|
276
|
+
params: routeParams,
|
|
277
|
+
pathname: history.location.pathname,
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
response = ctx.response
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// We return the matched routes too so if
|
|
286
|
+
// the app router happens to match the same path,
|
|
287
|
+
// it can use any request middleware from server routes
|
|
288
|
+
return [matchedRoutes, response] as const
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function handlerToMiddleware(
|
|
292
|
+
handler: AnyServerRouteWithTypes['options']['methods'][string],
|
|
293
|
+
) {
|
|
294
|
+
return async ({ next, ...rest }: TODO) => ({
|
|
295
|
+
response: await handler(rest),
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function executeMiddleware(middlewares: TODO, ctx: TODO) {
|
|
300
|
+
let index = -1
|
|
301
|
+
|
|
302
|
+
const next = async (ctx: TODO) => {
|
|
303
|
+
index++
|
|
304
|
+
const middleware = middlewares[index]
|
|
305
|
+
if (!middleware) return ctx
|
|
306
|
+
|
|
307
|
+
const result = await middleware({
|
|
308
|
+
...ctx,
|
|
309
|
+
// Allow the middleware to call the next middleware in the chain
|
|
310
|
+
next: async (nextCtx: TODO) => {
|
|
311
|
+
// Allow the caller to extend the context for the next middleware
|
|
312
|
+
const nextResult = await next({ ...ctx, ...nextCtx })
|
|
313
|
+
|
|
314
|
+
// Merge the result into the context\
|
|
315
|
+
return Object.assign(ctx, handleCtxResult(nextResult))
|
|
316
|
+
},
|
|
317
|
+
// Allow the middleware result to extend the return context
|
|
318
|
+
}).catch((err: TODO) => {
|
|
319
|
+
if (isSpecialResponse(err)) {
|
|
320
|
+
return {
|
|
321
|
+
response: err,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
throw err
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// Merge the middleware result into the context, just in case it
|
|
329
|
+
// returns a partial context
|
|
330
|
+
return Object.assign(ctx, handleCtxResult(result))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return handleCtxResult(next(ctx))
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function handleCtxResult(result: TODO) {
|
|
337
|
+
if (isSpecialResponse(result)) {
|
|
338
|
+
return {
|
|
339
|
+
response: result,
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return result
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function isSpecialResponse(err: TODO) {
|
|
347
|
+
return err instanceof Response || isRedirect(err)
|
|
348
|
+
}
|