UNPKG

@swizzyweb/swizzy-web-service

Version:

Web service framework for swizzy dyn serve

195 lines (194 loc) 7.18 kB
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() }); } }