@swizzyweb/swizzy-web-service
Version:
Web service framework for swizzy dyn serve
195 lines (194 loc) • 7.18 kB
JavaScript
import path from "path";
import { assertOrThrow } from "../util/assertion-util.js";
import { middlewaresToJson } from "../util/index.js";
/**
* Base web service class to be implemented.
*/
export class WebService {
name;
instanceId;
port;
packageName;
path;
logger;
state;
_isInstalled;
app;
routerClasses;
installedRouters;
middleware;
/**
* Constructor for WebService base class.
* Note: For subclasses of this, it is recommended to use {@link IWebServiceProps}
* to simplify user implementation usage.
* @param props internal props
*/
constructor(props) {
this.name = props.name;
this.instanceId = crypto.randomUUID();
this._isInstalled = false;
this.logger = props.logger.clone({ ownerName: this.name });
this.routerClasses = props.routerClasses;
this.app = props.app;
this.port = props.port;
this.packageName = props.packageName;
this.state = props.state;
this.installedRouters = [];
this.path = props.path;
this.middleware = props.middleware ?? [];
}
async install(props) {
const logger = this.logger;
logger.debug(`Installing web service ${this.name}`);
this.validateNotInstalled();
try {
logger.debug(`Installing routers for ${this.name}`);
await this.installRouters();
logger.debug(`Installed routers for ${this.name}`);
this._isInstalled = true;
logger.debug(`Installed ${this.name} successfully`);
return {
message: `WebService ${this.name} installed successfully`,
};
}
catch (e) {
logger.error(`Failed to install ${this.name} with error ${JSON.stringify(e)}`);
throw {
message: `WebService ${this.name} failed to install`,
error: e,
};
}
}
validateNotInstalled() {
const logger = this.logger;
logger.debug(`Validating WebService ${this.name} is not installed`);
if (this.isInstalled()) {
logger.error(`Service ${this.name} is already installed`);
throw {
message: `Service ${this.name} is already installed`,
stack: new Error(`Service ${this.name} is already installed`).stack,
};
}
logger.debug(`WebService ${this.name} is not installed as expected`);
}
isInstalled() {
return this._isInstalled;
}
async installRouters() {
let logger = this.logger;
logger.debug(`Installing routers for WebService ${this.name}`);
for (const router of this.routerClasses) {
await this.installRouter(router);
}
logger.debug(`Installed routers for WebService ${this.name}`);
return {
message: `Routers installed for WebService ${this.name}`,
};
}
async installRouter(router) {
const logger = this.logger;
logger.debug(`Installing router ${router.name}`);
const instance = new router({
logger,
});
const state = this.getState();
await instance.initialize({
appState: state,
});
const { expressRouter } = await this.useRouter({
instance,
});
await this.saveInstance({ instance, router: expressRouter });
logger.debug(`Installed router ${router.name}`);
}
getState() {
return this.state;
}
async useRouter(props) {
const logger = this.logger;
const { instance } = props;
const expressRouter = instance.router();
logger.debug(`Calling app.use(router) with ${instance.name}`);
await this.app.use(path.join("/", this.path, "/", instance.path), this.middleware.map((middle) => middle({ logger, state: this.getState() })), expressRouter);
logger.debug(`Called app.use(router) with ${instance.name}`);
return { expressRouter: expressRouter };
}
async saveInstance(props) {
const logger = this.logger;
const { instance, router } = props;
logger.debug("Saving router instance in WebService");
assertOrThrow({
args: { instance: instance.router(), router: router },
errorMessage: "Provided router does not match instance router when attempting to save instance",
assertion: (args) => instance.router() === router,
});
logger.debug(`Pushing instance ${router.name} to installedRouters`);
this.installedRouters.push(instance);
logger.debug(`Pushed instance ${router.name} to installedRouters`);
logger.debug("Saved router instance in WebService");
}
async uninstall(props) {
const logger = this.logger;
try {
logger.debug(`Uninstalling ${this.name}`);
logger.debug(`Uninstalling routers for ${this.name}`);
await this.uninstallRouters();
logger.debug(`Uninstalled routers for ${this.name}`);
this._isInstalled = false;
logger.debug(`Uninstalled ${this.name}`);
return {
message: `WebService ${this.name} uninstalled successfully`,
};
}
catch (e) {
logger.error(`Failed to uninstall ${this.name} with exception ${e}`);
throw {
message: `WebService ${this.name} failed to uninstall`,
error: e,
};
}
}
async uninstallRouters() {
const logger = this.logger;
logger.debug(`Uninstalling routers for WebService ${this.name}`);
while (this.installedRouters.length > 0) {
let router = this.installedRouters.pop();
await this.uninstallRouter({ router });
}
logger.debug(`Uninstalled routers`);
return {
message: `Uninstalled routers for Service ${this.name}`,
};
}
async uninstallRouter(props) {
const logger = this.logger;
const { router } = props;
const app = this.app;
logger.debug(`Uninstalling router ${router.name}`);
const expressRouter = router.router();
logger.debug(`Unusing router ${router.name}`);
await app.unuse(path.join("/", this.path, "/", router.path), expressRouter);
logger.debug(`Unused router ${router.name}`);
logger.debug(`Uninstalled router ${router.name}`);
}
toJson() {
const installedRouters = this.installedRouters.map((installedRouter) => installedRouter.toJson());
const middleware = middlewaresToJson(this.middleware);
const { name, instanceId, _isInstalled, logger, port, packageName, path } = this;
return {
name,
instanceId,
isInstalled: _isInstalled,
port,
packageName,
path,
logger: undefined,
state: undefined,
installedRouters,
middleware,
};
}
toString() {
return JSON.stringify({ service: this.toJson() });
}
}