Skip to content

Commit

Permalink
fix: add registerGlobalMiddleware
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/start/src/client/createServerFn.ts
  • Loading branch information
tannerlinsley committed Dec 7, 2024
1 parent 3508300 commit 8950ab2
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 3 deletions.
74 changes: 73 additions & 1 deletion docs/framework/react/start/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,83 @@ const authMiddleware = createMiddleware().client(async ({ next }) => {
})
```

## Using Middleware

Middleware can be used in two different ways:

- **Global Middleware**: Middleware that should be executed for every request.
- **Server Function Middleware**: Middleware that should be executed for a specific server function.

## Global Middleware

Global middleware is registered using the `registerGlobalMiddleware` function. This function receives an array of middleware to be appended to the global middleware array. There is currently no way to remove global middleware once it has been registered. If you need this functionality, please let us know by opening an issue on GitHub.

Here's an example of registering global middleware:

```tsx
import { registerGlobalMiddleware } from '@tanstack/start'

registerGlobalMiddleware({
middleware: [authMiddleware, loggingMiddleware],
})
```

### Global Middleware Type Safety

Global middleware types are inherently **detached** from server functions themselves. This means that if a global middleware supplies additional context to server functions or other server function specific middleware, the types will not be automatically passed through to the server function or other server function specific middleware.

```tsx
// globalMiddleware.ts
registerGlobalMiddleware({
middleware: [authMiddleware],
})

// serverFunction.ts
const authMiddleware = createMiddleware().server(({ next, context }) => {
console.log(context.user) // <-- This will not be typed!
// ...
})
```

To solve this, add the global middleware you are trying to reference to the server function's middleware array. **The global middleware will be deduped to a single entry (the global instance), and your server function will receive the correct types.**

Here's an example of how this works:

```tsx
import { authMiddleware } from './authMiddleware'

const fn = createServerFn().middleware([authMiddleware]).handler(async ({ context }) => {
console.log(context.user)
// ...
})
```

## Middleware Execution Order

Middleware is executed dependency-first, so the following example will execute `a`, `b`, `c` and `d` in that order:
Middleware is executed dependency-first, starting with global middleware, followed by server function middleware. The following example will log the following in this order:

- `globalMiddleware1`
- `globalMiddleware2`
- `a`
- `b`
- `c`
- `d`

```tsx
const globalMiddleware1 = createMiddleware().server(async ({ next }) => {
console.log('globalMiddleware1')
return next()
})

const globalMiddleware2 = createMiddleware().server(async ({ next }) => {
console.log('globalMiddleware2')
return next()
})

registerGlobalMiddleware({
middleware: [globalMiddleware1, globalMiddleware2],
})

const a = createMiddleware().server(async ({ next }) => {
console.log('a')
return next()
Expand Down
13 changes: 11 additions & 2 deletions packages/start/src/client/createServerFn.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import invariant from 'tiny-invariant'
import { defaultTransformer } from '@tanstack/react-router'
import { mergeHeaders } from './headers'
import { globalMiddleware } from './registerGlobalMiddleware'
import type {
AnyValidator,
Constrain,
Expand Down Expand Up @@ -292,14 +293,19 @@ function extractFormDataContext(formData: FormData) {
function flattenMiddlewares(
middlewares: Array<AnyMiddleware>,
): Array<AnyMiddleware> {
const seen = new Set<AnyMiddleware>()
const flattened: Array<AnyMiddleware> = []

const recurse = (middleware: Array<AnyMiddleware>) => {
middleware.forEach((m) => {
if (m.options.middleware) {
recurse(m.options.middleware)
}
flattened.push(m)

if (!seen.has(m)) {
seen.add(m)
flattened.push(m)
}
})
}

Expand Down Expand Up @@ -398,7 +404,10 @@ async function executeMiddleware(
env: 'client' | 'server',
opts: MiddlewareOptions,
): Promise<MiddlewareResult> {
const flattenedMiddlewares = flattenMiddlewares(middlewares)
const flattenedMiddlewares = flattenMiddlewares([
...globalMiddleware,
...middlewares,
])

const next = async (ctx: MiddlewareOptions): Promise<MiddlewareResult> => {
// Get the next middleware
Expand Down
4 changes: 4 additions & 0 deletions packages/start/src/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export {
type MiddlewareAfterServer,
type Middleware,
} from './createMiddleware'
export {
registerGlobalMiddleware,
globalMiddleware,
} from './registerGlobalMiddleware'
export { serverOnly } from './serverOnly'
export { DehydrateRouter } from './DehydrateRouter'
export { json } from './json'
Expand Down
9 changes: 9 additions & 0 deletions packages/start/src/client/registerGlobalMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { AnyMiddleware } from './createMiddleware'

export const globalMiddleware: Array<AnyMiddleware> = []

export function registerGlobalMiddleware(options: {
middleware: Array<AnyMiddleware>
}) {
globalMiddleware.push(...options.middleware)
}

0 comments on commit 8950ab2

Please sign in to comment.