Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions e2e/react-start/server-functions/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middle
import { Route as MiddlewareMiddlewareFactoryRouteImport } from './routes/middleware/middleware-factory'
import { Route as MiddlewareFunctionMetadataRouteImport } from './routes/middleware/function-metadata'
import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router'
import { Route as MiddlewareCatchHandlerErrorRouteImport } from './routes/middleware/catch-handler-error'
import { Route as CookiesSetRouteImport } from './routes/cookies/set'
import { Route as AbortSignalMethodRouteImport } from './routes/abort-signal/$method'
import { Route as MiddlewareRedirectWithMiddlewareIndexRouteImport } from './routes/middleware/redirect-with-middleware/index'
Expand Down Expand Up @@ -231,6 +232,12 @@ const MiddlewareClientMiddlewareRouterRoute =
path: '/middleware/client-middleware-router',
getParentRoute: () => rootRouteImport,
} as any)
const MiddlewareCatchHandlerErrorRoute =
MiddlewareCatchHandlerErrorRouteImport.update({
id: '/middleware/catch-handler-error',
path: '/middleware/catch-handler-error',
getParentRoute: () => rootRouteImport,
} as any)
const CookiesSetRoute = CookiesSetRouteImport.update({
id: '/cookies/set',
path: '/cookies/set',
Expand Down Expand Up @@ -279,6 +286,7 @@ export interface FileRoutesByFullPath {
'/submit-post-formdata': typeof SubmitPostFormdataRoute
'/abort-signal/$method': typeof AbortSignalMethodRoute
'/cookies/set': typeof CookiesSetRoute
'/middleware/catch-handler-error': typeof MiddlewareCatchHandlerErrorRoute
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
'/middleware/function-metadata': typeof MiddlewareFunctionMetadataRoute
'/middleware/middleware-factory': typeof MiddlewareMiddlewareFactoryRoute
Expand Down Expand Up @@ -321,6 +329,7 @@ export interface FileRoutesByTo {
'/submit-post-formdata': typeof SubmitPostFormdataRoute
'/abort-signal/$method': typeof AbortSignalMethodRoute
'/cookies/set': typeof CookiesSetRoute
'/middleware/catch-handler-error': typeof MiddlewareCatchHandlerErrorRoute
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
'/middleware/function-metadata': typeof MiddlewareFunctionMetadataRoute
'/middleware/middleware-factory': typeof MiddlewareMiddlewareFactoryRoute
Expand Down Expand Up @@ -364,6 +373,7 @@ export interface FileRoutesById {
'/submit-post-formdata': typeof SubmitPostFormdataRoute
'/abort-signal/$method': typeof AbortSignalMethodRoute
'/cookies/set': typeof CookiesSetRoute
'/middleware/catch-handler-error': typeof MiddlewareCatchHandlerErrorRoute
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
'/middleware/function-metadata': typeof MiddlewareFunctionMetadataRoute
'/middleware/middleware-factory': typeof MiddlewareMiddlewareFactoryRoute
Expand Down Expand Up @@ -408,6 +418,7 @@ export interface FileRouteTypes {
| '/submit-post-formdata'
| '/abort-signal/$method'
| '/cookies/set'
| '/middleware/catch-handler-error'
| '/middleware/client-middleware-router'
| '/middleware/function-metadata'
| '/middleware/middleware-factory'
Expand Down Expand Up @@ -450,6 +461,7 @@ export interface FileRouteTypes {
| '/submit-post-formdata'
| '/abort-signal/$method'
| '/cookies/set'
| '/middleware/catch-handler-error'
| '/middleware/client-middleware-router'
| '/middleware/function-metadata'
| '/middleware/middleware-factory'
Expand Down Expand Up @@ -492,6 +504,7 @@ export interface FileRouteTypes {
| '/submit-post-formdata'
| '/abort-signal/$method'
| '/cookies/set'
| '/middleware/catch-handler-error'
| '/middleware/client-middleware-router'
| '/middleware/function-metadata'
| '/middleware/middleware-factory'
Expand Down Expand Up @@ -535,6 +548,7 @@ export interface RootRouteChildren {
SubmitPostFormdataRoute: typeof SubmitPostFormdataRoute
AbortSignalMethodRoute: typeof AbortSignalMethodRoute
CookiesSetRoute: typeof CookiesSetRoute
MiddlewareCatchHandlerErrorRoute: typeof MiddlewareCatchHandlerErrorRoute
MiddlewareClientMiddlewareRouterRoute: typeof MiddlewareClientMiddlewareRouterRoute
MiddlewareFunctionMetadataRoute: typeof MiddlewareFunctionMetadataRoute
MiddlewareMiddlewareFactoryRoute: typeof MiddlewareMiddlewareFactoryRoute
Expand Down Expand Up @@ -806,6 +820,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport
parentRoute: typeof rootRouteImport
}
'/middleware/catch-handler-error': {
id: '/middleware/catch-handler-error'
path: '/middleware/catch-handler-error'
fullPath: '/middleware/catch-handler-error'
preLoaderRoute: typeof MiddlewareCatchHandlerErrorRouteImport
parentRoute: typeof rootRouteImport
}
'/cookies/set': {
id: '/cookies/set'
path: '/cookies/set'
Expand Down Expand Up @@ -863,6 +884,7 @@ const rootRouteChildren: RootRouteChildren = {
SubmitPostFormdataRoute: SubmitPostFormdataRoute,
AbortSignalMethodRoute: AbortSignalMethodRoute,
CookiesSetRoute: CookiesSetRoute,
MiddlewareCatchHandlerErrorRoute: MiddlewareCatchHandlerErrorRoute,
MiddlewareClientMiddlewareRouterRoute: MiddlewareClientMiddlewareRouterRoute,
MiddlewareFunctionMetadataRoute: MiddlewareFunctionMetadataRoute,
MiddlewareMiddlewareFactoryRoute: MiddlewareMiddlewareFactoryRoute,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createFileRoute } from '@tanstack/react-router'
import { createMiddleware, createServerFn } from '@tanstack/react-start'
import { useState } from 'react'

const errorCatchingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
try {
return await next()
} catch (error) {
const originalMessage =
error instanceof Error ? error.message : 'Unknown error'
throw new Error(`Middleware caught and transformed: ${originalMessage}`)
}
},
)

const $serverFnThatThrows = createServerFn({ method: 'GET' })
.middleware([errorCatchingMiddleware])
.handler(() => {
throw new Error('This error should be caught by middleware')
})

export const Route = createFileRoute('/middleware/catch-handler-error')({
component: RouteComponent,
})

function RouteComponent() {
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)

const handleClick = async () => {
setLoading(true)
setError(null)
try {
await $serverFnThatThrows()
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setLoading(false)
}
}

return (
<div className="p-4">
<h1 data-testid="catch-handler-error-title">
Middleware Error Catching Test
</h1>
<p className="mt-2">
This tests that middleware can catch and transform errors thrown by
server function handlers
</p>
<button
data-testid="trigger-error-btn"
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
onClick={handleClick}
disabled={loading}
>
{loading ? 'Loading' : 'Call Server Function That Throws'}
</button>
{error && (
<div data-testid="transformed-error" className="mt-4 text-red-500">
Error: {error}
</div>
)}
</div>
)
}
22 changes: 22 additions & 0 deletions e2e/react-start/server-functions/tests/server-functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,3 +1021,25 @@ test('server function receives serverFnMeta in options', async ({ page }) => {
expect(nestingPostMetadata.inner.post.name.length).toBeGreaterThan(0)
expect(nestingPostMetadata.inner.post.filename.length).toBeGreaterThan(0)
})

test('middleware can catch errors thrown by server function handlers', async ({
page,
}) => {
await page.goto('/middleware/catch-handler-error')

await page.waitForLoadState('networkidle')

await expect(page.getByTestId('catch-handler-error-title')).toBeVisible()

await page.getByTestId('trigger-error-btn').click()

await expect(page.getByTestId('transformed-error')).toBeVisible()

await expect(page.getByTestId('transformed-error')).toContainText(
'Middleware caught and transformed',
)

await expect(page.getByTestId('transformed-error')).toContainText(
'This error should be caught by middleware',
)
})
13 changes: 6 additions & 7 deletions packages/start-client-core/src/createServerFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,13 @@ export async function executeMiddleware(
error: userCtx.error ?? (ctx as any).error,
}

try {
return await callNextMiddleware(nextCtx)
} catch (error: any) {
return {
...nextCtx,
error,
}
const result = await callNextMiddleware(nextCtx)

if (result.error) {
throw result.error
}

return result
}

// Execute the middleware
Expand Down
Loading