UNPKG

@poppinss/middleware

Version:

Implementation of the chain of responsibility design pattern

179 lines (177 loc) 4.42 kB
// src/runner.ts import { debuglog } from "util"; var debug = debuglog("poppinss:middleware"); function once(scope, callback) { function next() { if (next.called) { return; } next.called = true; debug("next invoked"); return callback(scope); } next.called = false; return next; } var DEFAULT_FINAL_HANDLER = () => Promise.resolve(); var Runner = class { /** * An array of middleware to execute */ #middleware; /** * The active index for the middleware handler */ #currentIndex = 0; /** * Executor is responsible for executing a middleware */ #executor; /** * Final handler to execute */ #finalHandler = DEFAULT_FINAL_HANDLER; /** * Error handler to self handle errors */ #errorHandler; constructor(middleware) { this.#middleware = middleware; } /** * Invoke one middleware at a time. Middleware fns will be executed * recursively until `next` is invoked. * * If one method doesn't call `next`, then the chain will be finished * automatically. */ #invoke(self) { const middleware = self.#middleware[self.#currentIndex++]; debug("running middleware at index", self.#currentIndex); if (!middleware) { return self.#finalHandler(); } return self.#executor(middleware, once(self, self.#invoke)); } /** * Same as invoke, but captures errors */ #invokeWithErrorManagement(self) { const middleware = self.#middleware[self.#currentIndex++]; debug("running middleware at index", self.#currentIndex); if (!middleware) { return self.#finalHandler().catch(self.#errorHandler); } return self.#executor(middleware, once(self, self.#invokeWithErrorManagement)).catch(self.#errorHandler); } /** * Final handler to be executed, when the chain ends successfully. */ finalHandler(finalHandler) { this.#finalHandler = finalHandler; return this; } /** * Specify a custom error handler to use. Defining an error handler * turns will make run method not throw an exception and instead * run the upstream middleware logic */ errorHandler(errorHandler) { this.#errorHandler = errorHandler; return this; } /** * Start the middleware queue and pass params to it. The `params` * array will be passed as spread arguments. */ async run(cb) { this.#executor = cb; debug("starting middleware chain with %d middleware", this.#middleware.length); if (this.#errorHandler) { return this.#invokeWithErrorManagement(this); } return this.#invoke(this); } }; // src/middleware.ts var Middleware = class { #middleware = /* @__PURE__ */ new Set(); #middlewareArray; #isFrozen = false; /** * Get access to all the registered middleware. The return value is * a set of handlers. */ all() { return this.#middleware; } /** * Find if a handler has been registered as a middleware * already. */ has(handler) { return this.#middleware.has(handler); } /** * Add a middleware. Adding the same middleware * twice will result in a noop. */ add(handler) { if (this.#isFrozen) { throw new Error("Middleware stack is frozen. Cannot add new middleware"); } this.#middleware.add(handler); return this; } /** * Remove a specific middleware */ remove(handler) { if (this.#isFrozen) { throw new Error("Middleware stack is frozen. Cannot remove middleware"); } return this.#middleware.delete(handler); } /** * Remove all middleware */ clear() { if (this.#isFrozen) { throw new Error("Middleware stack is frozen. Cannot clear middleware"); } this.#middleware.clear(); } /** * Merge middleware from a existing middleware * instance. The merged middleware are * appended */ merge(hooks) { if (this.#isFrozen) { throw new Error("Middleware stack is frozen. Cannot merge middleware"); } hooks.all().forEach((handler) => { this.add(handler); }); } /** * Freezes the middleware stack for further modifications */ freeze() { if (this.#isFrozen) { return; } this.#isFrozen = true; this.#middlewareArray = [...this.all()]; } /** * Returns an instance of the runner to run hooks */ runner() { this.freeze(); return new Runner(this.#middlewareArray); } }; export { Middleware as default };