Skip to content

Instantly share code, notes, and snippets.

@lxe
Last active November 15, 2025 14:12
Show Gist options
  • Select an option

  • Save lxe/2f16634d84d1b28611f77fac574f732f to your computer and use it in GitHub Desktop.

Select an option

Save lxe/2f16634d84d1b28611f77fac574f732f to your computer and use it in GitHub Desktop.
bun-middleware.ts
// 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);
};
};
}
@freakynit
Copy link

It should be illegal for such type monstrosity to even exist 😡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment