-
-
Couldn't load subscription status.
- Fork 1.3k
fix: refactor middleware #5517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: refactor middleware #5517
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { createFileRoute } from '@tanstack/react-router' | ||
| import { createMiddleware, createServerFn } from '@tanstack/react-start' | ||
|
|
||
| export const authMiddleware = createMiddleware({ type: 'function' }).server( | ||
| async ({ next, context }) => { | ||
| throw new Error('Unauthorized') | ||
| }, | ||
| ) | ||
|
|
||
| const personServerFn = createServerFn({ method: 'GET' }) | ||
| .middleware([authMiddleware]) | ||
| .inputValidator((d: string) => d) | ||
| .handler(({ data: name }) => { | ||
| return { name, randomNumber: Math.floor(Math.random() * 100) } | ||
| }) | ||
|
|
||
| export const Route = createFileRoute('/middleware/unhandled-exception')({ | ||
| loader: async () => { | ||
| return { | ||
| person: await personServerFn({ data: 'John Doe' }), | ||
| } | ||
| }, | ||
| component: RouteComponent, | ||
| }) | ||
|
|
||
| function RouteComponent() { | ||
| const { person } = Route.useLoaderData() | ||
| return ( | ||
| <div data-testid="regular-person"> | ||
| {person.name} - {person.randomNumber} | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,16 @@ import type { Plugin as SerovalPlugin } from 'seroval' | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let serovalPlugins: Array<SerovalPlugin<any, any>> | null = null | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // caller => | ||||||||||||||||||||||||||||||
| // serverFnFetcher => | ||||||||||||||||||||||||||||||
| // client => | ||||||||||||||||||||||||||||||
| // server => | ||||||||||||||||||||||||||||||
| // fn => | ||||||||||||||||||||||||||||||
| // seroval => | ||||||||||||||||||||||||||||||
| // client middleware => | ||||||||||||||||||||||||||||||
| // serverFnFetcher => | ||||||||||||||||||||||||||||||
| // caller | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export async function serverFnFetcher( | ||||||||||||||||||||||||||||||
| url: string, | ||||||||||||||||||||||||||||||
| args: Array<any>, | ||||||||||||||||||||||||||||||
|
|
@@ -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<Response>) { | ||||||||||||||||||||||||||||||
| 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<Response>) { | |||||||||||||||||||||||||||||
| 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<Response>) { | |||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| // 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 | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
215
to
221
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: falsy results (0, '', false) are incorrectly rejected. invariant(result, ...) rejects valid falsy values; check only undefined. - invariant(result, 'expected result to be resolved')
+ invariant(typeof result !== 'undefined', 'expected result to be resolved')📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 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<Response>) { | |||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle no-content responses; don’t assert content-type.
Server may return undefined/204 with no content-type (e.g., serializeResult(undefined)). Current invariant throws. Treat “no content” as undefined when ok.
Also applies to: 288-294
🤖 Prompt for AI Agents