@poppinss/middleware
Version:
Implementation of the chain of responsibility design pattern
179 lines (177 loc) • 4.42 kB
JavaScript
// 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
};