import type { NextFunction, Request, RequestHandler, Response } from "express"; /** * An Express route handler whose body is `async` (returns a `Promise`). * Only `ResBody`/`Locals` are made generic (what this codebase actually * varies per-route) — params/request-body/query stay at Express's own * internal defaults, same as an unparameterized `Request`. * * `Locals` is constrained to `Record`, matching Express's own * `Response` exactly (see `@types/express-serve-static- * core`) rather than the stricter `Record`: a plain * `interface` (e.g. `AuthLocals` in `require-auth.ts`) has no index * signature, so under `unknown` it fails this generic's constraint even * though it's assignable to `Response`'s own `Locals` param directly — * `any` is what lets that structural gap close. */ export type AsyncRequestHandler< ResBody = unknown, // biome-ignore lint/suspicious/noExplicitAny: mirrors Express's own Response> constraint (see comment above) — `unknown` here would reject plain interfaces like AuthLocals that Response itself accepts fine. Locals extends Record = Record, > = (req: Request, res: Response, next: NextFunction) => Promise; /** * Wraps an async Express handler so a thrown error or rejected promise is * forwarded to `next(err)` automatically. Without this, an unhandled * rejection inside an `async` route handler never reaches Express's error * middleware — every route ends up needing its own `try { ... } catch (err) * { next(err); }` boilerplate, which this removes. * * @example * router.post("/signup", wrapAsyncHandler(async (req, res) => { * const profile = await signup(req.body); * res.status(201).json(profile); * })); */ export function wrapAsyncHandler< ResBody = unknown, // biome-ignore lint/suspicious/noExplicitAny: same constraint as AsyncRequestHandler above, for the same reason. Locals extends Record = Record, >(handler: AsyncRequestHandler): RequestHandler { return (req, res, next) => { handler(req, res as Response, next).catch(next); }; }