@kevboutin/azure-middy-core
Version:
A Node.js middleware engine for Azure functions (core package)
189 lines (173 loc) • 5.96 kB
text/typescript
import {
AzureFunctionRequest,
MiddlewareFunction,
Middleware,
Plugin,
MiddyInstance,
BaseHandler,
MiddyFunction,
} from "./typings";
import { HttpRequest, InvocationContext, HttpResponse } from '@azure/functions';
/**
* Creates a middleware wrapper function.
*
* @param baseHandler - The base handler function.
* @param plugin - The plugin object.
* @returns The middleware wrapper function.
*/
const middy: MiddyFunction = (
baseHandler: BaseHandler = async (_req?: HttpRequest, _context?: InvocationContext) => {
return new HttpResponse({
status: 200,
headers: new Headers(),
});
},
plugin?: Plugin,
) => {
plugin?.beforePrefetch?.();
const beforeMiddlewares: MiddlewareFunction[] = [];
const afterMiddlewares: MiddlewareFunction[] = [];
const onErrorMiddlewares: MiddlewareFunction[] = [];
const instance: MiddyInstance = (req: any = {}, context: any = {}) => {
plugin?.requestStart?.();
const request: AzureFunctionRequest = {
req,
context,
response: undefined,
error: undefined,
internal: {},
};
return runRequest(
request,
[...beforeMiddlewares],
baseHandler,
[...afterMiddlewares],
[...onErrorMiddlewares],
plugin,
);
};
instance.use = (middlewares: Middleware | Middleware[]): MiddyInstance => {
if (Array.isArray(middlewares)) {
for (const middleware of middlewares) {
instance.applyMiddleware(middleware);
}
return instance;
}
return instance.applyMiddleware(middlewares);
};
instance.applyMiddleware = (middleware: Middleware): MiddyInstance => {
const { before, after, onError } = middleware;
if (!before && !after && !onError) {
throw new Error(
'Middleware must be an object containing at least one key among "before", "after", "onError"',
);
}
if (before) instance.before(before);
if (after) instance.after(after);
if (onError) instance.onError(onError);
return instance;
};
// Inline Middlewares
instance.before = (beforeMiddleware: MiddlewareFunction): MiddyInstance => {
beforeMiddlewares.push(beforeMiddleware);
return instance;
};
instance.after = (afterMiddleware: MiddlewareFunction): MiddyInstance => {
afterMiddlewares.unshift(afterMiddleware);
return instance;
};
instance.onError = (
onErrorMiddleware: MiddlewareFunction,
): MiddyInstance => {
onErrorMiddlewares.push(onErrorMiddleware);
return instance;
};
instance.__middlewares = {
before: beforeMiddlewares,
after: afterMiddlewares,
onError: onErrorMiddlewares,
};
return instance;
};
/**
* Executes a request by running the provided middlewares and handler.
*
* @param request - The request object.
* @param beforeMiddlewares - The array of middlewares to run before the handler.
* @param baseHandler - The base handler function.
* @param afterMiddlewares - The array of middlewares to run after the handler.
* @param onErrorMiddlewares - The array of middlewares to run when an error occurs.
* @param plugin - The plugin object.
* @returns A promise that resolves to the response of the request.
*/
const runRequest = async (
request: AzureFunctionRequest,
beforeMiddlewares: MiddlewareFunction[],
baseHandler: BaseHandler,
afterMiddlewares: MiddlewareFunction[],
onErrorMiddlewares: MiddlewareFunction[],
plugin?: Plugin,
): Promise<any> => {
try {
await runMiddlewares(request, beforeMiddlewares, plugin);
// Check if before stack hasn't exit early
if (request.response === undefined) {
plugin?.beforeHandler?.();
request.response = await baseHandler(request.req, request.context) as HttpResponse;
plugin?.afterHandler?.();
await runMiddlewares(request, afterMiddlewares, plugin);
}
} catch (e) {
// Reset response changes made by after stack before error thrown
request.response = undefined;
request.error = e as Error;
try {
await runMiddlewares(request, onErrorMiddlewares, plugin);
// Catch if onError stack hasn't handled the error
if (request.response === undefined) throw request.error;
} catch (e) {
// Save error that wasn't handled
(e as any).originalError = request.error;
request.error = e as Error;
throw request.error;
}
} finally {
await plugin?.requestEnd?.();
}
return request.response;
};
/**
* Executes the provided middlewares for a given request.
*
* @param request - The request object.
* @param middlewares - The array of middlewares to execute.
* @param plugin - The plugin object.
* @returns A promise that resolves to the response of the request.
*/
const runMiddlewares = async (
request: AzureFunctionRequest,
middlewares: MiddlewareFunction[],
plugin?: Plugin,
): Promise<void> => {
for (const nextMiddleware of middlewares) {
plugin?.beforeMiddleware?.(nextMiddleware?.name);
const res = await nextMiddleware?.(request);
plugin?.afterMiddleware?.(nextMiddleware?.name);
// short circuit chaining and respond early
if (res !== undefined && res !== null && typeof res === 'object' && Object.keys(res).length > 0) {
request.response = res as HttpResponse;
return;
}
}
};
export default middy;
export { middy };
export type {
AzureFunctionRequest,
MiddlewareFunction,
Middleware,
Plugin,
MiddyInstance,
BaseHandler,
MiddyFunction,
};