Last active
November 8, 2025 23:04
-
-
Save lxe/2f16634d84d1b28611f77fac574f732f to your computer and use it in GitHub Desktop.
bun-middleware.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // TODO: somehow enforce ordering at compile time | |
| type WriteOnly<T> = { | |
| -readonly [K in keyof T]: T[K]; // TODO: Typescript can't block setters like this natively :) | |
| }; | |
| type RouteHandler<T extends string> = ( // TODO: Extract from Bun... I think it has this built in | |
| req: Bun.BunRequest<T>, | |
| server: Bun.Server, | |
| ) => Response | Promise<Response>; | |
| // Helper to extract the output context from a middleware | |
| // Note: Handles Middleware<CtxOut>, Middleware<CtxOut, Route>, and Middleware<Deps, CtxOut, Route> | |
| type MiddlewareOutput<M> = | |
| M extends Middleware<infer First, infer Second, any> | |
| ? Second extends undefined | |
| ? First // Single param case: First is the output | |
| : Second extends string | |
| ? First // Two param case with Route: First is the output, Second is the route | |
| : Second // Three param case: Second is the output | |
| : never; | |
| // Helper to accumulate outputs from an array of middleware types | |
| type AccumulateOutputs<T extends readonly any[]> = T extends readonly [infer First, ...infer Rest] | |
| ? MiddlewareOutput<First> & AccumulateOutputs<Rest> | |
| : {}; | |
| // Middleware that mutates context in-place | |
| // Single param: Middleware<CtxOut> - no dependencies, just output | |
| // Two params: Middleware<CtxOut, Route> - output context with specific route | |
| // Three params: Middleware<Deps, CtxOut, Route> - deps as array or type, plus output, with route | |
| type Middleware< | |
| DepsOrCtxOut extends readonly any[] | any = {}, | |
| CtxOutOrRoute extends Record<string, any> | string | undefined = undefined, | |
| Route extends string = any | |
| > = ( | |
| req: Bun.BunRequest< | |
| CtxOutOrRoute extends undefined | |
| ? any // Single param case: any route | |
| : CtxOutOrRoute extends string | |
| ? CtxOutOrRoute // Two param case: CtxOutOrRoute is the route | |
| : Route // Three param case: Route is the route | |
| >, | |
| srv: Bun.Server, | |
| ctx: ( | |
| CtxOutOrRoute extends undefined | |
| ? WriteOnly<DepsOrCtxOut> // Single param: DepsOrCtxOut is the output | |
| : CtxOutOrRoute extends string | |
| ? WriteOnly<DepsOrCtxOut> // Two param case: DepsOrCtxOut is the output, CtxOutOrRoute is the route | |
| : (DepsOrCtxOut extends readonly any[] ? AccumulateOutputs<DepsOrCtxOut> : DepsOrCtxOut) & WriteOnly<CtxOutOrRoute> // Three param case: DepsOrCtxOut is deps, CtxOutOrRoute is output | |
| ) | |
| ) => Response | Promise<Response> | void | Promise<void>; | |
| // Helper to accumulate context types through middleware chain | |
| // Uses MiddlewareOutput to handle both single and two-param forms | |
| type AccumulateContext < | |
| T extends readonly any[], | |
| Acc = {} | |
| > = T extends readonly [infer First, ...infer Rest] | |
| ? AccumulateContext<Rest, Acc & MiddlewareOutput<First>> | |
| : Acc; | |
| function withMiddleware < | |
| Route extends string, | |
| M extends readonly Middleware<any, any, Route>[] | |
| >( | |
| ...middlewares: M | |
| ) { | |
| return <R extends Route = Route>( | |
| handler: (ctx: AccumulateContext<M>) => RouteHandler<R> | |
| ): RouteHandler<R> => { | |
| return async (req, srv) => { | |
| const ctx: any = {}; | |
| // Run middleware in order, short-circuit if any returns a Response | |
| for (const middleware of middlewares) { | |
| const result = await middleware(req, srv, ctx); | |
| if (result instanceof Response) { | |
| return result; | |
| } | |
| } | |
| return handler(ctx)(req, srv); | |
| }; | |
| }; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I asked AI and it SOLVED the dependency ordering. Behold: