diff --git a/e2e/react-start/server-functions/src/routeTree.gen.ts b/e2e/react-start/server-functions/src/routeTree.gen.ts index b5692de059..73ad1c262f 100644 --- a/e2e/react-start/server-functions/src/routeTree.gen.ts +++ b/e2e/react-start/server-functions/src/routeTree.gen.ts @@ -27,6 +27,7 @@ import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index' import { Route as FactoryIndexRouteImport } from './routes/factory/index' import { Route as CookiesIndexRouteImport } from './routes/cookies/index' +import { Route as MiddlewareUnhandledExceptionRouteImport } from './routes/middleware/unhandled-exception' import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' @@ -123,6 +124,12 @@ const CookiesIndexRoute = CookiesIndexRouteImport.update({ path: '/cookies/', getParentRoute: () => rootRouteImport, } as any) +const MiddlewareUnhandledExceptionRoute = + MiddlewareUnhandledExceptionRouteImport.update({ + id: '/middleware/unhandled-exception', + path: '/middleware/unhandled-exception', + getParentRoute: () => rootRouteImport, + } as any) const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ id: '/middleware/send-serverFn', path: '/middleware/send-serverFn', @@ -170,6 +177,7 @@ export interface FileRoutesByFullPath { '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/request-middleware': typeof MiddlewareRequestMiddlewareRoute '/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute + '/middleware/unhandled-exception': typeof MiddlewareUnhandledExceptionRoute '/cookies': typeof CookiesIndexRoute '/factory': typeof FactoryIndexRoute '/formdata-redirect': typeof FormdataRedirectIndexRoute @@ -195,6 +203,7 @@ export interface FileRoutesByTo { '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/request-middleware': typeof MiddlewareRequestMiddlewareRoute '/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute + '/middleware/unhandled-exception': typeof MiddlewareUnhandledExceptionRoute '/cookies': typeof CookiesIndexRoute '/factory': typeof FactoryIndexRoute '/formdata-redirect': typeof FormdataRedirectIndexRoute @@ -221,6 +230,7 @@ export interface FileRoutesById { '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/request-middleware': typeof MiddlewareRequestMiddlewareRoute '/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute + '/middleware/unhandled-exception': typeof MiddlewareUnhandledExceptionRoute '/cookies/': typeof CookiesIndexRoute '/factory/': typeof FactoryIndexRoute '/formdata-redirect/': typeof FormdataRedirectIndexRoute @@ -248,6 +258,7 @@ export interface FileRouteTypes { | '/middleware/client-middleware-router' | '/middleware/request-middleware' | '/middleware/send-serverFn' + | '/middleware/unhandled-exception' | '/cookies' | '/factory' | '/formdata-redirect' @@ -273,6 +284,7 @@ export interface FileRouteTypes { | '/middleware/client-middleware-router' | '/middleware/request-middleware' | '/middleware/send-serverFn' + | '/middleware/unhandled-exception' | '/cookies' | '/factory' | '/formdata-redirect' @@ -298,6 +310,7 @@ export interface FileRouteTypes { | '/middleware/client-middleware-router' | '/middleware/request-middleware' | '/middleware/send-serverFn' + | '/middleware/unhandled-exception' | '/cookies/' | '/factory/' | '/formdata-redirect/' @@ -324,6 +337,7 @@ export interface RootRouteChildren { MiddlewareClientMiddlewareRouterRoute: typeof MiddlewareClientMiddlewareRouterRoute MiddlewareRequestMiddlewareRoute: typeof MiddlewareRequestMiddlewareRoute MiddlewareSendServerFnRoute: typeof MiddlewareSendServerFnRoute + MiddlewareUnhandledExceptionRoute: typeof MiddlewareUnhandledExceptionRoute CookiesIndexRoute: typeof CookiesIndexRoute FactoryIndexRoute: typeof FactoryIndexRoute FormdataRedirectIndexRoute: typeof FormdataRedirectIndexRoute @@ -460,6 +474,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CookiesIndexRouteImport parentRoute: typeof rootRouteImport } + '/middleware/unhandled-exception': { + id: '/middleware/unhandled-exception' + path: '/middleware/unhandled-exception' + fullPath: '/middleware/unhandled-exception' + preLoaderRoute: typeof MiddlewareUnhandledExceptionRouteImport + parentRoute: typeof rootRouteImport + } '/middleware/send-serverFn': { id: '/middleware/send-serverFn' path: '/middleware/send-serverFn' @@ -516,6 +537,7 @@ const rootRouteChildren: RootRouteChildren = { MiddlewareClientMiddlewareRouterRoute: MiddlewareClientMiddlewareRouterRoute, MiddlewareRequestMiddlewareRoute: MiddlewareRequestMiddlewareRoute, MiddlewareSendServerFnRoute: MiddlewareSendServerFnRoute, + MiddlewareUnhandledExceptionRoute: MiddlewareUnhandledExceptionRoute, CookiesIndexRoute: CookiesIndexRoute, FactoryIndexRoute: FactoryIndexRoute, FormdataRedirectIndexRoute: FormdataRedirectIndexRoute, diff --git a/e2e/react-start/server-functions/src/routes/index.tsx b/e2e/react-start/server-functions/src/routes/index.tsx index 596b855c09..ca1394015a 100644 --- a/e2e/react-start/server-functions/src/routes/index.tsx +++ b/e2e/react-start/server-functions/src/routes/index.tsx @@ -88,6 +88,11 @@ function Home() {
Hello, {testValues.name}!
diff --git a/packages/start-client-core/src/client-rpc/serverFnFetcher.ts b/packages/start-client-core/src/client-rpc/serverFnFetcher.ts
index 5511bdc517..b1b781d7d0 100644
--- a/packages/start-client-core/src/client-rpc/serverFnFetcher.ts
+++ b/packages/start-client-core/src/client-rpc/serverFnFetcher.ts
@@ -17,6 +17,16 @@ import type { Plugin as SerovalPlugin } from 'seroval'
let serovalPlugins: Array> | null = null
+// caller =>
+// serverFnFetcher =>
+// client =>
+// server =>
+// fn =>
+// seroval =>
+// client middleware =>
+// serverFnFetcher =>
+// caller
+
export async function serverFnFetcher(
url: string,
args: Array,
@@ -37,7 +47,8 @@ export async function serverFnFetcher(
// Arrange the headers
const headers = new Headers({
- 'x-tsr-redirect': 'manual',
+ 'x-tsr-serverFn': 'true',
+ 'x-tsr-createServerFn': 'true',
...(first.headers instanceof Headers
? Object.fromEntries(first.headers.entries())
: first.headers),
@@ -65,12 +76,6 @@ export async function serverFnFetcher(
}
}
- if (url.includes('?')) {
- url += `&createServerFn`
- } else {
- url += `?createServerFn`
- }
-
let body = undefined
if (first.method === 'POST') {
const fetchBody = await getFetchBody(first)
@@ -97,6 +102,7 @@ export async function serverFnFetcher(
handler(url, {
method: 'POST',
headers: {
+ 'x-tsr-serverFn': 'true',
Accept: 'application/json',
'Content-Type': 'application/json',
},
@@ -165,7 +171,7 @@ async function getFetchBody(
async function getResponse(fn: () => Promise) {
const response = await (async () => {
try {
- return await fn()
+ return await fn() // client => server => fn => server => client
} catch (error) {
if (error instanceof Response) {
return error
@@ -178,22 +184,16 @@ async function getResponse(fn: () => Promise) {
if (response.headers.get(X_TSS_RAW_RESPONSE) === 'true') {
return response
}
+
const contentType = response.headers.get('content-type')
invariant(contentType, 'expected content-type header to be set')
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)
- // If the response is not ok, throw an error
- if (!response.ok) {
- if (serializedByStart && contentType.includes('application/json')) {
- const jsonPayload = await response.json()
- const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
- throw result
- }
-
- throw new Error(await response.text())
- }
+ // If the response is serialized by the start server, we need to process it
+ // differently than a normal response.
if (serializedByStart) {
let result
+ // If it's a stream from the start serializer, process it as such
if (contentType.includes('application/x-ndjson')) {
const refs = new Map()
result = await processServerFnResponse({
@@ -206,17 +206,22 @@ async function getResponse(fn: () => Promise) {
},
})
}
+ // If it's a JSON response, it can be simpler
if (contentType.includes('application/json')) {
const jsonPayload = await response.json()
result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
}
+
invariant(result, 'expected result to be resolved')
if (result instanceof Error) {
throw result
}
+
return result
}
+ // If it wasn't processed by the start serializer, check
+ // if it's JSON
if (contentType.includes('application/json')) {
const jsonPayload = await response.json()
const redirect = parseRedirect(jsonPayload)
@@ -229,6 +234,12 @@ async function getResponse(fn: () => Promise) {
return jsonPayload
}
+ // Othwerwise, if it's not OK, throw the content
+ if (!response.ok) {
+ throw new Error(await response.text())
+ }
+
+ // Or return the response itself
return response
}
diff --git a/packages/start-client-core/src/constants.ts b/packages/start-client-core/src/constants.ts
index 1e541af323..4e0777068d 100644
--- a/packages/start-client-core/src/constants.ts
+++ b/packages/start-client-core/src/constants.ts
@@ -6,4 +6,5 @@ export const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(
export const X_TSS_SERIALIZED = 'x-tss-serialized'
export const X_TSS_RAW_RESPONSE = 'x-tss-raw'
+export const X_TSS_CONTEXT = 'x-tss-context'
export {}
diff --git a/packages/start-client-core/src/createServerFn.ts b/packages/start-client-core/src/createServerFn.ts
index bc95a6b5b9..b472d88d79 100644
--- a/packages/start-client-core/src/createServerFn.ts
+++ b/packages/start-client-core/src/createServerFn.ts
@@ -1,10 +1,9 @@
-import { isNotFound, isRedirect } from '@tanstack/router-core'
import { mergeHeaders } from '@tanstack/router-core/ssr/client'
+import { isRedirect, parseRedirect } from '@tanstack/router-core'
import { TSS_SERVER_FUNCTION_FACTORY } from './constants'
import { getStartOptions } from './getStartOptions'
import { getStartContextServerOnly } from './getStartContextServerOnly'
-import type { TSS_SERVER_FUNCTION } from './constants'
import type {
AnyValidator,
Constrain,
@@ -16,6 +15,7 @@ import type {
ValidateSerializableInput,
Validator,
} from '@tanstack/router-core'
+import type { TSS_SERVER_FUNCTION } from './constants'
import type {
AnyFunctionMiddleware,
AnyRequestMiddleware,
@@ -112,17 +112,22 @@ export const createServerFn: CreateServerFn = (options, __opts) => {
return Object.assign(
async (opts?: CompiledFetcherFnOptions) => {
// Start by executing the client-side middleware chain
- return executeMiddleware(resolvedMiddleware, 'client', {
+ const result = await executeMiddleware(resolvedMiddleware, 'client', {
...extractedFn,
...newOptions,
data: opts?.data as any,
headers: opts?.headers,
signal: opts?.signal,
context: {},
- }).then((d) => {
- if (d.error) throw d.error
- return d.result
})
+
+ const redirect = parseRedirect(result.error)
+ if (redirect) {
+ throw redirect
+ }
+
+ if (result.error) throw result.error
+ return result.result
},
{
// This copies over the URL, function ID
@@ -144,14 +149,18 @@ export const createServerFn: CreateServerFn = (options, __opts) => {
request: startContext.request,
}
- return executeMiddleware(resolvedMiddleware, 'server', ctx).then(
- (d) => ({
- // Only send the result and sendContext back to the client
- result: d.result,
- error: d.error,
- context: d.sendContext,
- }),
- )
+ const result = await executeMiddleware(
+ resolvedMiddleware,
+ 'server',
+ ctx,
+ ).then((d) => ({
+ // Only send the result and sendContext back to the client
+ result: d.result,
+ error: d.error,
+ context: d.sendContext,
+ }))
+
+ return result
},
},
) as any
@@ -180,7 +189,7 @@ export async function executeMiddleware(
...middlewares,
])
- const next: NextFn = async (ctx) => {
+ const callNextMiddleware: NextFn = async (ctx) => {
// Get the next middleware
const nextMiddleware = flattenedMiddlewares.shift()
@@ -189,50 +198,111 @@ export async function executeMiddleware(
return ctx
}
- if (
- 'inputValidator' in nextMiddleware.options &&
- nextMiddleware.options.inputValidator &&
- env === 'server'
- ) {
- // Execute the middleware's input function
- ctx.data = await execValidator(
- nextMiddleware.options.inputValidator,
- ctx.data,
- )
- }
+ // Execute the middleware
+ try {
+ if (
+ 'inputValidator' in nextMiddleware.options &&
+ nextMiddleware.options.inputValidator &&
+ env === 'server'
+ ) {
+ // Execute the middleware's input function
+ ctx.data = await execValidator(
+ nextMiddleware.options.inputValidator,
+ ctx.data,
+ )
+ }
- let middlewareFn: MiddlewareFn | undefined = undefined
- if (env === 'client') {
- if ('client' in nextMiddleware.options) {
- middlewareFn = nextMiddleware.options.client as MiddlewareFn | undefined
+ let middlewareFn: MiddlewareFn | undefined = undefined
+ if (env === 'client') {
+ if ('client' in nextMiddleware.options) {
+ middlewareFn = nextMiddleware.options.client as
+ | MiddlewareFn
+ | undefined
+ }
+ }
+ // env === 'server'
+ else if ('server' in nextMiddleware.options) {
+ middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined
}
- }
- // env === 'server'
- else if ('server' in nextMiddleware.options) {
- middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined
- }
- if (middlewareFn) {
- // Execute the middleware
- return applyMiddleware(middlewareFn, ctx, async (newCtx) => {
- return next(newCtx).catch((error: any) => {
- if (isRedirect(error) || isNotFound(error)) {
+ if (middlewareFn) {
+ const userNext = async (
+ userCtx: ServerFnMiddlewareResult | undefined = {} as any,
+ ) => {
+ // Return the next middleware
+ const nextCtx = {
+ ...ctx,
+ ...userCtx,
+ context: {
+ ...ctx.context,
+ ...userCtx.context,
+ },
+ sendContext: {
+ ...ctx.sendContext,
+ ...(userCtx.sendContext ?? {}),
+ },
+ headers: mergeHeaders(ctx.headers, userCtx.headers),
+ result:
+ userCtx.result !== undefined
+ ? userCtx.result
+ : userCtx instanceof Response
+ ? userCtx
+ : (ctx as any).result,
+ error: userCtx.error ?? (ctx as any).error,
+ }
+
+ try {
+ return await callNextMiddleware(nextCtx)
+ } catch (error: any) {
return {
- ...newCtx,
+ ...nextCtx,
error,
}
}
+ }
- throw error
- })
- })
- }
+ // Execute the middleware
+ const result = await middlewareFn({
+ ...ctx,
+ next: userNext as any,
+ } as any)
+
+ // If result is NOT a ctx object, we need to return it as
+ // the { result }
+ if (isRedirect(result)) {
+ return {
+ ...ctx,
+ error: result,
+ }
+ }
- return next(ctx)
+ if (result instanceof Response) {
+ return {
+ ...ctx,
+ result,
+ }
+ }
+
+ if (!(result as any)) {
+ throw new Error(
+ 'User middleware returned undefined. You must call next() or return a result in your middlewares.',
+ )
+ }
+
+ return result
+ }
+
+ return callNextMiddleware(ctx)
+ } catch (error: any) {
+ return {
+ ...ctx,
+ error,
+ }
+ }
}
// Start the middleware chain
- return next({
+ return callNextMiddleware({
...opts,
headers: opts.headers || {},
sendContext: opts.sendContext || {},
@@ -628,41 +698,6 @@ export type MiddlewareFn = (
},
) => Promise
-export const applyMiddleware = async (
- middlewareFn: MiddlewareFn,
- ctx: ServerFnMiddlewareOptions,
- nextFn: NextFn,
-) => {
- return middlewareFn({
- ...ctx,
- next: (async (
- userCtx: ServerFnMiddlewareResult | undefined = {} as any,
- ) => {
- // Return the next middleware
- return nextFn({
- ...ctx,
- ...userCtx,
- context: {
- ...ctx.context,
- ...userCtx.context,
- },
- sendContext: {
- ...ctx.sendContext,
- ...(userCtx.sendContext ?? {}),
- },
- headers: mergeHeaders(ctx.headers, userCtx.headers),
- result:
- userCtx.result !== undefined
- ? userCtx.result
- : userCtx instanceof Response
- ? userCtx
- : (ctx as any).result,
- error: userCtx.error ?? (ctx as any).error,
- })
- }) as any,
- } as any)
-}
-
export function execValidator(
validator: AnyValidator,
input: unknown,
diff --git a/packages/start-client-core/src/index.tsx b/packages/start-client-core/src/index.tsx
index 70a4692842..10a84f299b 100644
--- a/packages/start-client-core/src/index.tsx
+++ b/packages/start-client-core/src/index.tsx
@@ -74,7 +74,6 @@ export type {
RequiredFetcher,
} from './createServerFn'
export {
- applyMiddleware,
execValidator,
flattenMiddlewares,
executeMiddleware,
@@ -85,6 +84,7 @@ export {
TSS_SERVER_FUNCTION,
X_TSS_SERIALIZED,
X_TSS_RAW_RESPONSE,
+ X_TSS_CONTEXT,
} from './constants'
export type * from './serverRoute'
diff --git a/packages/start-server-core/src/createStartHandler.ts b/packages/start-server-core/src/createStartHandler.ts
index d0fd84e8f4..ff7bd0f445 100644
--- a/packages/start-server-core/src/createStartHandler.ts
+++ b/packages/start-server-core/src/createStartHandler.ts
@@ -268,16 +268,17 @@ export function createStartHandler(
[...middlewares, requestHandlerMiddleware],
{
request,
-
context: requestOpts?.context || {},
},
)
const response: Response = ctx.response
+ console.log('response', response)
+
if (isRedirect(response)) {
if (isResolvedRedirect(response)) {
- if (request.headers.get('x-tsr-redirect') === 'manual') {
+ if (request.headers.get('x-tsr-createServerFn') === 'true') {
return json(
{
...response.options,
@@ -318,7 +319,7 @@ export function createStartHandler(
const router = await getRouter()
const redirect = router.resolveRedirect(response)
- if (request.headers.get('x-tsr-redirect') === 'manual') {
+ if (request.headers.get('x-tsr-createServerFn') === 'true') {
return json(
{
...response.options,
diff --git a/packages/start-server-core/src/server-functions-handler.ts b/packages/start-server-core/src/server-functions-handler.ts
index ec9042ec47..221ca5af00 100644
--- a/packages/start-server-core/src/server-functions-handler.ts
+++ b/packages/start-server-core/src/server-functions-handler.ts
@@ -1,10 +1,11 @@
-import { isNotFound } from '@tanstack/router-core'
+import { isNotFound, isPlainObject } from '@tanstack/router-core'
import invariant from 'tiny-invariant'
import {
TSS_FORMDATA_CONTEXT,
X_TSS_RAW_RESPONSE,
X_TSS_SERIALIZED,
getDefaultSerovalPlugins,
+ json,
} from '@tanstack/start-client-core'
import { fromJSON, toCrossJSONAsync, toCrossJSONStream } from 'seroval'
import { getResponse } from './request-response'
@@ -41,7 +42,9 @@ export const handleServerAction = async ({
createServerFn?: boolean
}
- const isCreateServerFn = 'createServerFn' in search
+ const isServerFn = request.headers.get('x-tsr-serverFn') === 'true'
+ const isCreateServerFn =
+ request.headers.get('x-tsr-createServerFn') === 'true'
if (typeof serverFnId !== 'string') {
throw new Error('Invalid server action param for serverFnId: ' + serverFnId)
@@ -56,6 +59,9 @@ export const handleServerAction = async ({
]
const contentType = request.headers.get('Content-Type')
+ const isFormData = formDataContentTypes.some(
+ (type) => contentType && contentType.includes(type),
+ )
const serovalPlugins = getDefaultSerovalPlugins()
function parsePayload(payload: any) {
@@ -65,13 +71,9 @@ export const handleServerAction = async ({
const response = await (async () => {
try {
- let result = await (async () => {
+ let res = await (async () => {
// FormData
- if (
- formDataContentTypes.some(
- (type) => contentType && contentType.includes(type),
- )
- ) {
+ if (isFormData) {
// We don't support GET requests with FormData payloads... that seems impossible
invariant(
method.toLowerCase() !== 'get',
@@ -135,25 +137,55 @@ export const handleServerAction = async ({
return await action(...jsonPayload)
})()
- // Any time we get a Response back, we should just
- // return it immediately.
- if (result.result instanceof Response) {
- result.result.headers.set(X_TSS_RAW_RESPONSE, 'true')
- return result.result
- }
+ const isCtxResult =
+ isPlainObject(res) &&
+ 'context' in res &&
+ ('result' in res || 'error' in res)
+
+ console.log(
+ {
+ isServerFn,
+ isCreateServerFn,
+ isFormData,
+ isCtxResult,
+ },
+ res,
+ )
- // If this is a non createServerFn request, we need to
- // pull out the result from the result object
- if (!isCreateServerFn) {
- result = result.result
+ function unwrapResultOrError(result: any) {
+ if (
+ isPlainObject(result) &&
+ ('result' in result || 'error' in result)
+ ) {
+ console.log('tanner')
+ return result.result || result.error
+ }
+ return result
+ }
- // The result might again be a response,
- // and if it is, return it.
- if (result instanceof Response) {
- return result
+ // This was not called by the serverFnFetcher, so it's likely a no-JS POST request)
+ if (isCtxResult) {
+ const unwrapped = unwrapResultOrError(res)
+ if (unwrapped instanceof Response) {
+ res = unwrapped
+ } else {
+ res = json(unwrapped)
}
}
+ if (isNotFound(res)) {
+ res = isNotFoundResponse(res)
+ }
+
+ if (!isServerFn) {
+ return res
+ }
+
+ if (res instanceof Response) {
+ res.headers.set(X_TSS_RAW_RESPONSE, 'true')
+ return res
+ }
+
// TODO: RSCs Where are we getting this package?
// if (isValidElement(result)) {
// const { renderToPipeableStream } = await import(
@@ -173,91 +205,91 @@ export const handleServerAction = async ({
// return new Response(null, { status: 200 })
// }
- if (isNotFound(result)) {
- return isNotFoundResponse(result)
- }
-
- const response = getResponse()
- let nonStreamingBody: any = undefined
-
- if (result !== undefined) {
- // first run without the stream in case `result` does not need streaming
- let done = false as boolean
- const callbacks: {
- onParse: (value: any) => void
- onDone: () => void
- onError: (error: any) => void
- } = {
- onParse: (value) => {
- nonStreamingBody = value
- },
- onDone: () => {
- done = true
- },
- onError: (error) => {
- throw error
- },
- }
- toCrossJSONStream(result, {
- refs: new Map(),
- plugins: serovalPlugins,
- onParse(value) {
- callbacks.onParse(value)
- },
- onDone() {
- callbacks.onDone()
- },
- onError: (error) => {
- callbacks.onError(error)
- },
- })
- if (done) {
- return new Response(
- nonStreamingBody ? JSON.stringify(nonStreamingBody) : undefined,
- {
- status: response?.status,
- statusText: response?.statusText,
- headers: {
- 'Content-Type': 'application/json',
- [X_TSS_SERIALIZED]: 'true',
+ return serializeResult(res)
+
+ function serializeResult(res: unknown): Response {
+ let nonStreamingBody: any = undefined
+
+ const alsResponse = getResponse()
+ if (res !== undefined) {
+ // first run without the stream in case `result` does not need streaming
+ let done = false as boolean
+ const callbacks: {
+ onParse: (value: any) => void
+ onDone: () => void
+ onError: (error: any) => void
+ } = {
+ onParse: (value) => {
+ nonStreamingBody = value
+ },
+ onDone: () => {
+ done = true
+ },
+ onError: (error) => {
+ throw error
+ },
+ }
+ toCrossJSONStream(res, {
+ refs: new Map(),
+ plugins: serovalPlugins,
+ onParse(value) {
+ callbacks.onParse(value)
+ },
+ onDone() {
+ callbacks.onDone()
+ },
+ onError: (error) => {
+ callbacks.onError(error)
+ },
+ })
+ if (done) {
+ return new Response(
+ nonStreamingBody ? JSON.stringify(nonStreamingBody) : undefined,
+ {
+ status: alsResponse?.status,
+ statusText: alsResponse?.statusText,
+ headers: {
+ 'Content-Type': 'application/json',
+ [X_TSS_SERIALIZED]: 'true',
+ },
},
+ )
+ }
+
+ // not done yet, we need to stream
+ const stream = new ReadableStream({
+ start(controller) {
+ callbacks.onParse = (value) =>
+ controller.enqueue(JSON.stringify(value) + '\n')
+ callbacks.onDone = () => {
+ try {
+ controller.close()
+ } catch (error) {
+ controller.error(error)
+ }
+ }
+ callbacks.onError = (error) => controller.error(error)
+ // stream the initial body
+ if (nonStreamingBody !== undefined) {
+ callbacks.onParse(nonStreamingBody)
+ }
},
- )
+ })
+ return new Response(stream, {
+ status: alsResponse?.status,
+ statusText: alsResponse?.statusText,
+ headers: {
+ 'Content-Type': 'application/x-ndjson',
+ [X_TSS_SERIALIZED]: 'true',
+ },
+ })
}
- // not done yet, we need to stream
- const stream = new ReadableStream({
- start(controller) {
- callbacks.onParse = (value) =>
- controller.enqueue(JSON.stringify(value) + '\n')
- callbacks.onDone = () => {
- try {
- controller.close()
- } catch (error) {
- controller.error(error)
- }
- }
- callbacks.onError = (error) => controller.error(error)
- // stream the initial body
- if (nonStreamingBody !== undefined) {
- callbacks.onParse(nonStreamingBody)
- }
- },
- })
- return new Response(stream, {
- status: response?.status,
- statusText: response?.statusText,
- headers: {
- 'Content-Type': 'application/x-ndjson',
- [X_TSS_SERIALIZED]: 'true',
- },
+ return new Response(undefined, {
+ status: alsResponse?.status,
+ statusText: alsResponse?.statusText,
})
}
-
- return new Response(undefined, {
- status: response?.status,
- statusText: response?.statusText,
- })
} catch (error: any) {
if (error instanceof Response) {
return error