|
| 1 | +import * as express from 'express'; |
| 2 | + |
| 3 | +export const MiddlewareBrand = Symbol(); |
| 4 | +export type MiddlewareBrand = typeof MiddlewareBrand; |
| 5 | + |
| 6 | +export type MiddlewareFn<T extends {}> = ( |
| 7 | + req: express.Request, |
| 8 | + res: express.Response, |
| 9 | +) => Promise<T>; |
| 10 | + |
| 11 | +type MiddlewareHandler<T extends {} = {}> = { |
| 12 | + (req: express.Request, res: express.Response, next: express.NextFunction): void; |
| 13 | + [MiddlewareBrand]: T; |
| 14 | +}; |
| 15 | + |
| 16 | +export type MiddlewareRequestHandler = express.RequestHandler | MiddlewareHandler; |
| 17 | + |
| 18 | +type MiddlewareOutput<T extends MiddlewareRequestHandler> = T extends { |
| 19 | + [MiddlewareBrand]: infer R; |
| 20 | +} |
| 21 | + ? R |
| 22 | + : {}; |
| 23 | + |
| 24 | +type MiddlewareNextFn = (error?: unknown, middlewareResult?: {}) => void; |
| 25 | + |
| 26 | +/** |
| 27 | + * Creates an express request handler that also adds properties to the incoming decoded request. Any existing properties |
| 28 | + * with conflicting names will be replaced. |
| 29 | + * |
| 30 | + * @param fn - an async function that reads the express req/res and returns the desired additional properties. |
| 31 | + * @returns |
| 32 | + */ |
| 33 | +export function middlewareFn<T extends {}>(fn: MiddlewareFn<T>): MiddlewareHandler<T> { |
| 34 | + const result: express.RequestHandler = (req, res, next: MiddlewareNextFn) => { |
| 35 | + fn(req, res) |
| 36 | + .then((result) => next(undefined, result)) |
| 37 | + .catch((err) => next(err)); |
| 38 | + }; |
| 39 | + return result as MiddlewareHandler<T>; |
| 40 | +} |
| 41 | + |
| 42 | +type MiddlewareResult<Input extends {}, T> = T extends MiddlewareRequestHandler |
| 43 | + ? Omit<Input, keyof MiddlewareOutput<T>> & MiddlewareOutput<T> |
| 44 | + : never; |
| 45 | + |
| 46 | +export type MiddlewareChain = |
| 47 | + | [] |
| 48 | + | [MiddlewareRequestHandler] |
| 49 | + | [MiddlewareRequestHandler, MiddlewareRequestHandler] |
| 50 | + | [MiddlewareRequestHandler, MiddlewareRequestHandler, MiddlewareRequestHandler] |
| 51 | + | [ |
| 52 | + MiddlewareRequestHandler, |
| 53 | + MiddlewareRequestHandler, |
| 54 | + MiddlewareRequestHandler, |
| 55 | + MiddlewareRequestHandler, |
| 56 | + ] |
| 57 | + | [ |
| 58 | + MiddlewareRequestHandler, |
| 59 | + MiddlewareRequestHandler, |
| 60 | + MiddlewareRequestHandler, |
| 61 | + MiddlewareRequestHandler, |
| 62 | + MiddlewareRequestHandler, |
| 63 | + ] |
| 64 | + | [ |
| 65 | + MiddlewareRequestHandler, |
| 66 | + MiddlewareRequestHandler, |
| 67 | + MiddlewareRequestHandler, |
| 68 | + MiddlewareRequestHandler, |
| 69 | + MiddlewareRequestHandler, |
| 70 | + MiddlewareRequestHandler, |
| 71 | + ] |
| 72 | + | [ |
| 73 | + MiddlewareRequestHandler, |
| 74 | + MiddlewareRequestHandler, |
| 75 | + MiddlewareRequestHandler, |
| 76 | + MiddlewareRequestHandler, |
| 77 | + MiddlewareRequestHandler, |
| 78 | + MiddlewareRequestHandler, |
| 79 | + MiddlewareRequestHandler, |
| 80 | + ]; |
| 81 | + |
| 82 | +export type MiddlewareChainOutput< |
| 83 | + Input, |
| 84 | + Chain extends MiddlewareChain, |
| 85 | +> = Chain extends [] |
| 86 | + ? Input |
| 87 | + : Chain extends [infer A] |
| 88 | + ? MiddlewareResult<Input, A> |
| 89 | + : Chain extends [infer A, infer B] |
| 90 | + ? MiddlewareResult<MiddlewareResult<Input, A>, B> |
| 91 | + : Chain extends [infer A, infer B, infer C] |
| 92 | + ? MiddlewareResult<MiddlewareResult<MiddlewareResult<Input, A>, B>, C> |
| 93 | + : Chain extends [infer A, infer B, infer C, infer D] |
| 94 | + ? MiddlewareResult< |
| 95 | + MiddlewareResult<MiddlewareResult<MiddlewareResult<Input, A>, B>, C>, |
| 96 | + D |
| 97 | + > |
| 98 | + : Chain extends [infer A, infer B, infer C, infer D, infer E] |
| 99 | + ? MiddlewareResult< |
| 100 | + MiddlewareResult< |
| 101 | + MiddlewareResult<MiddlewareResult<MiddlewareResult<Input, A>, B>, C>, |
| 102 | + D |
| 103 | + >, |
| 104 | + E |
| 105 | + > |
| 106 | + : Chain extends [infer A, infer B, infer C, infer D, infer E, infer F] |
| 107 | + ? MiddlewareResult< |
| 108 | + MiddlewareResult< |
| 109 | + MiddlewareResult< |
| 110 | + MiddlewareResult<MiddlewareResult<MiddlewareResult<Input, A>, B>, C>, |
| 111 | + D |
| 112 | + >, |
| 113 | + E |
| 114 | + >, |
| 115 | + F |
| 116 | + > |
| 117 | + : Chain extends [infer A, infer B, infer C, infer D, infer E, infer F, infer G] |
| 118 | + ? MiddlewareResult< |
| 119 | + MiddlewareResult< |
| 120 | + MiddlewareResult< |
| 121 | + MiddlewareResult< |
| 122 | + MiddlewareResult<MiddlewareResult<MiddlewareResult<Input, A>, B>, C>, |
| 123 | + D |
| 124 | + >, |
| 125 | + E |
| 126 | + >, |
| 127 | + F |
| 128 | + >, |
| 129 | + G |
| 130 | + > |
| 131 | + : never; |
| 132 | + |
| 133 | +/** |
| 134 | + * Runs a middleware chain, and adds any properties returned by middleware.. |
| 135 | + * |
| 136 | + * @param input - the decoded request properties |
| 137 | + * @param chain - the middleware chain |
| 138 | + * @param req - express request object |
| 139 | + * @param res - express response object |
| 140 | + * @returns `input` with possible additional properties as specified by the middleware chain |
| 141 | + */ |
| 142 | +export async function runMiddlewareChain< |
| 143 | + Input extends {}, |
| 144 | + Chain extends MiddlewareChain, |
| 145 | +>( |
| 146 | + input: Input, |
| 147 | + chain: Chain, |
| 148 | + req: express.Request, |
| 149 | + res: express.Response, |
| 150 | +): Promise<MiddlewareChainOutput<Input, Chain>> { |
| 151 | + let result: {} = input; |
| 152 | + for (const middleware of chain) { |
| 153 | + const middlewareResult: {} = await new Promise((resolve, reject) => { |
| 154 | + const next = (value?: unknown, middlewareResult?: {}) => { |
| 155 | + if (value !== undefined) { |
| 156 | + reject(value); |
| 157 | + } else if (middlewareResult !== undefined) { |
| 158 | + resolve(middlewareResult); |
| 159 | + } else { |
| 160 | + resolve({}); |
| 161 | + } |
| 162 | + }; |
| 163 | + |
| 164 | + try { |
| 165 | + middleware(req, res, next); |
| 166 | + } catch (err) { |
| 167 | + reject(err); |
| 168 | + } |
| 169 | + }); |
| 170 | + |
| 171 | + result = { ...result, ...middlewareResult }; |
| 172 | + } |
| 173 | + |
| 174 | + return result as MiddlewareChainOutput<Input, Chain>; |
| 175 | +} |
| 176 | + |
| 177 | +/** |
| 178 | + * Runs a middleware chain, but does not modify the decoded request (input) with properties from any middleware. |
| 179 | + * This primarily exists to preserve backwards-compatible behavior for RouteHandlers defined without the `routeHandler` function. |
| 180 | + * |
| 181 | + * @param input - the decoded request properties (just passed through) |
| 182 | + * @param chain - the middleware chain |
| 183 | + * @param req - express request object |
| 184 | + * @param res - express response object |
| 185 | + * @returns `input` unmodified |
| 186 | + */ |
| 187 | +export async function runMiddlewareChainIgnoringResults< |
| 188 | + Input, |
| 189 | + Chain extends MiddlewareChain, |
| 190 | +>( |
| 191 | + input: Input, |
| 192 | + chain: Chain, |
| 193 | + req: express.Request, |
| 194 | + res: express.Response, |
| 195 | +): Promise<Input> { |
| 196 | + for (const middleware of chain) { |
| 197 | + await new Promise((resolve, reject) => { |
| 198 | + try { |
| 199 | + middleware(req, res, resolve); |
| 200 | + } catch (err) { |
| 201 | + reject(err); |
| 202 | + } |
| 203 | + }); |
| 204 | + } |
| 205 | + return input; |
| 206 | +} |
0 commit comments