|
@@ -0,0 +1,202 @@
|
|
|
+import BaseModule from "./BaseModule";
|
|
|
+import Job from "./Job";
|
|
|
+import JobQueue from "./JobQueue";
|
|
|
+import { ModuleStatus } from "./types/ModuleStatus";
|
|
|
+import { Jobs, Modules } from "./types/TestModules";
|
|
|
+
|
|
|
+type ModuleClass<Module extends typeof BaseModule> = {
|
|
|
+ // eslint-disable-next-line
|
|
|
+ new (moduleManager: ModuleManager): Module;
|
|
|
+};
|
|
|
+
|
|
|
+export default class ModuleManager {
|
|
|
+ private modules: Modules | null;
|
|
|
+
|
|
|
+ private jobQueue: JobQueue;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Module Manager
|
|
|
+ *
|
|
|
+ */
|
|
|
+ public constructor() {
|
|
|
+ this.modules = null;
|
|
|
+ this.jobQueue = new JobQueue();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * getStatus - Get status of modules
|
|
|
+ *
|
|
|
+ * @returns {object} Module statuses
|
|
|
+ */
|
|
|
+ public getStatus() {
|
|
|
+ if (!this.modules) return {};
|
|
|
+
|
|
|
+ const status: Record<string, ModuleStatus> = {};
|
|
|
+
|
|
|
+ Object.entries(this.modules).forEach(([name, module]) => {
|
|
|
+ status[name] = module.getStatus();
|
|
|
+ });
|
|
|
+
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * getJobsStats - Get statistics of job queue
|
|
|
+ *
|
|
|
+ * @returns {object} Job queue statistics
|
|
|
+ */
|
|
|
+ public getJobsStats() {
|
|
|
+ return this.jobQueue.getStats();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * getJobsStatus - Get status of job queue
|
|
|
+ *
|
|
|
+ * @returns {object} Job queue status
|
|
|
+ */
|
|
|
+ public getJobsStatus() {
|
|
|
+ return this.jobQueue.getStatus();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * getQueueStatus - Get status of queued jobs
|
|
|
+ *
|
|
|
+ * @returns {object} Job statuses
|
|
|
+ */
|
|
|
+ public getQueueStatus() {
|
|
|
+ return this.jobQueue.getQueueStatus();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param {string} moduleName Name of the module
|
|
|
+ * @returns {typeof BaseModule} Module
|
|
|
+ */
|
|
|
+ private getModule<T extends keyof Modules>(
|
|
|
+ moduleName: T
|
|
|
+ ): Promise<Modules[T]> {
|
|
|
+ return new Promise(resolve => {
|
|
|
+ const mapper = {
|
|
|
+ stations: "StationModule",
|
|
|
+ others: "OtherModule"
|
|
|
+ };
|
|
|
+ import(`./modules/${mapper[moduleName]}`).then(response => {
|
|
|
+ const Module: ModuleClass<Modules[T]> = response.default;
|
|
|
+ const module = new Module(this);
|
|
|
+ resolve(module);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * loadModules - Load and initialize all modules
|
|
|
+ *
|
|
|
+ * @returns {Promise} Promise
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * loadModules - Load and initialize all modules
|
|
|
+ *
|
|
|
+ * @returns {Promise} Promise
|
|
|
+ */
|
|
|
+ private loadModules(): Promise<void> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const fetchModules = async () => ({
|
|
|
+ stations: await this.getModule("stations"),
|
|
|
+ others: await this.getModule("others")
|
|
|
+ });
|
|
|
+ fetchModules()
|
|
|
+ .then(modules => {
|
|
|
+ this.modules = modules;
|
|
|
+ resolve();
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ reject(new Error(err));
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * startup - Handle startup
|
|
|
+ */
|
|
|
+ public startup(): void {
|
|
|
+ this.loadModules()
|
|
|
+ .then(() => {
|
|
|
+ if (!this.modules) return;
|
|
|
+ Object.values(this.modules).forEach(module => module.startup());
|
|
|
+ this.jobQueue.resume();
|
|
|
+ })
|
|
|
+ .catch(() => this.shutdown());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * shutdown - Handle shutdown
|
|
|
+ */
|
|
|
+ public shutdown(): void {
|
|
|
+ if (!this.modules) return;
|
|
|
+ console.log(this.modules);
|
|
|
+ Object.values(this.modules).forEach(module => module.shutdown());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * runJob - Run a job
|
|
|
+ *
|
|
|
+ * @param {string} moduleName Module name
|
|
|
+ * @param {string} jobName Job name
|
|
|
+ * @param {[ any, { priority?: number }? ]} params Params
|
|
|
+ */
|
|
|
+ public runJob<
|
|
|
+ ModuleName extends keyof Jobs & keyof Modules,
|
|
|
+ JobName extends keyof Jobs[ModuleName] &
|
|
|
+ keyof Omit<Modules[ModuleName], keyof BaseModule>,
|
|
|
+ Payload extends "payload" extends keyof Jobs[ModuleName][JobName]
|
|
|
+ ? Jobs[ModuleName][JobName]["payload"]
|
|
|
+ : undefined,
|
|
|
+ ReturnType = "returns" extends keyof Jobs[ModuleName][JobName]
|
|
|
+ ? Jobs[ModuleName][JobName]["returns"]
|
|
|
+ : never
|
|
|
+ >(
|
|
|
+ moduleName: ModuleName,
|
|
|
+ jobName: JobName,
|
|
|
+ ...params: Payload extends undefined
|
|
|
+ ? []
|
|
|
+ : [Payload, { priority?: number }?]
|
|
|
+ ): Promise<ReturnType> {
|
|
|
+ const [payload, options] = params;
|
|
|
+ return new Promise<ReturnType>((resolve, reject) => {
|
|
|
+ if (!this.modules) return;
|
|
|
+ const module = this.modules[moduleName];
|
|
|
+ if (!module) reject(new Error("Module not found."));
|
|
|
+ else {
|
|
|
+ const jobFunction = module[jobName];
|
|
|
+ if (!jobFunction || typeof jobFunction !== "function")
|
|
|
+ reject(new Error("Job function not found."));
|
|
|
+ else if (
|
|
|
+ Object.prototype.hasOwnProperty.call(BaseModule, jobName)
|
|
|
+ )
|
|
|
+ reject(new Error("Illegal job function."));
|
|
|
+ else {
|
|
|
+ this.jobQueue.add(
|
|
|
+ new Job(
|
|
|
+ jobName.toString(),
|
|
|
+ moduleName,
|
|
|
+ async (resolveJob, rejectJob) => {
|
|
|
+ jobFunction(payload)
|
|
|
+ .then((response: ReturnType) => {
|
|
|
+ resolveJob();
|
|
|
+ resolve(response);
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ rejectJob();
|
|
|
+ reject();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ {
|
|
|
+ priority: (options && options.priority) || 10
|
|
|
+ }
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|