BaseModule.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import JobContext from "@/JobContext";
  2. import LogBook, { Log } from "@/LogBook";
  3. import ModuleManager from "@/ModuleManager";
  4. import { Modules } from "@/types/Modules";
  5. export enum ModuleStatus {
  6. LOADED = "LOADED",
  7. STARTING = "STARTING",
  8. STARTED = "STARTED",
  9. STOPPED = "STOPPED",
  10. STOPPING = "STOPPING",
  11. ERROR = "ERROR",
  12. DISABLED = "DISABLED"
  13. }
  14. export default abstract class BaseModule {
  15. protected _name: string;
  16. protected _status: ModuleStatus;
  17. protected _dependentModules: (keyof Modules)[];
  18. protected _jobApiDefault: boolean;
  19. protected _jobConfig: Record<
  20. string,
  21. | "disabled"
  22. | boolean
  23. | ((context: JobContext, payload?: any) => Promise<any>)
  24. | {
  25. api?: boolean;
  26. method?: (context: JobContext, payload?: any) => Promise<any>;
  27. }
  28. >;
  29. protected _jobs: Record<
  30. string,
  31. {
  32. api: boolean;
  33. method: (context: JobContext, payload?: any) => Promise<any>;
  34. }
  35. >;
  36. /**
  37. * Base Module
  38. *
  39. * @param name - Module name
  40. */
  41. public constructor(name: string) {
  42. this._name = name;
  43. this._status = ModuleStatus.LOADED;
  44. this._dependentModules = [];
  45. this._jobApiDefault = true;
  46. this._jobConfig = {};
  47. this._jobs = {};
  48. this.log(`Module (${this._name}) loaded`);
  49. }
  50. /**
  51. * getName - Get module name
  52. *
  53. * @returns name
  54. */
  55. public getName() {
  56. return this._name;
  57. }
  58. /**
  59. * getStatus - Get module status
  60. *
  61. * @returns status
  62. */
  63. public getStatus() {
  64. return this._status;
  65. }
  66. /**
  67. * setStatus - Set module status
  68. *
  69. * @param status - Module status
  70. */
  71. public setStatus(status: ModuleStatus) {
  72. this._status = status;
  73. }
  74. /**
  75. * getDependentModules - Get module dependencies
  76. */
  77. public getDependentModules() {
  78. return this._dependentModules;
  79. }
  80. /**
  81. * _loadJobs - Load jobs available via api module
  82. */
  83. private async _loadJobs() {
  84. this._jobs = {};
  85. const module = Object.getPrototypeOf(this);
  86. await Promise.all(
  87. Object.getOwnPropertyNames(module).map(async property => {
  88. if (
  89. typeof module[property] !== "function" ||
  90. Object.prototype.hasOwnProperty.call(
  91. BaseModule.prototype,
  92. property
  93. ) ||
  94. property.startsWith("_")
  95. )
  96. return;
  97. const options = this._jobConfig[property];
  98. let api = this._jobApiDefault;
  99. if (
  100. typeof options === "object" &&
  101. typeof options.api === "boolean"
  102. )
  103. api = options.api;
  104. else if (typeof options === "boolean") api = options;
  105. this._jobs[property] = {
  106. api,
  107. method: module[property]
  108. };
  109. })
  110. );
  111. await Promise.all(
  112. Object.entries(this._jobConfig).map(async ([name, options]) => {
  113. if (options === "disabled") {
  114. if (this._jobs[name]) delete this._jobs[name];
  115. return;
  116. }
  117. if (
  118. typeof options === "boolean" ||
  119. (typeof options === "object" &&
  120. typeof options.method !== "function")
  121. )
  122. return;
  123. if (this._jobs[name])
  124. throw new Error(`Job "${name}" is already defined`);
  125. let api = this._jobApiDefault;
  126. if (
  127. typeof options === "object" &&
  128. typeof options.api === "boolean"
  129. )
  130. api = options.api;
  131. let method = options;
  132. if (
  133. typeof method === "object" &&
  134. typeof method.method === "function"
  135. )
  136. method = method.method;
  137. if (typeof method !== "function")
  138. throw new Error(
  139. `Job "${name}" has no function method defined`
  140. );
  141. this._jobs[name] = {
  142. api,
  143. method
  144. };
  145. })
  146. );
  147. }
  148. /**
  149. * getJob - Get module job
  150. */
  151. public getJob(name: string) {
  152. if (!this._jobs[name]) throw new Error(`Job "${name}" not found.`);
  153. return this._jobs[name];
  154. }
  155. /**
  156. * getJobs - Get module jobs
  157. */
  158. public getJobs() {
  159. return this._jobs;
  160. }
  161. /**
  162. * canRunJobs - Determine if module can run jobs
  163. */
  164. public canRunJobs() {
  165. return this.getDependentModules().reduce(
  166. (canRunJobs: boolean, moduleName: keyof Modules): boolean => {
  167. if (canRunJobs === false) return false;
  168. return !!ModuleManager.getModule(moduleName)?.canRunJobs();
  169. },
  170. this.getStatus() === ModuleStatus.STARTED
  171. );
  172. }
  173. /**
  174. * startup - Startup module
  175. */
  176. public async startup() {
  177. this.log(`Module (${this._name}) starting`);
  178. this.setStatus(ModuleStatus.STARTING);
  179. }
  180. /**
  181. * started - called with the module has started
  182. */
  183. protected async _started() {
  184. await this._loadJobs();
  185. this.log(`Module (${this._name}) started`);
  186. this.setStatus(ModuleStatus.STARTED);
  187. }
  188. /**
  189. * shutdown - Shutdown module
  190. */
  191. public async shutdown() {
  192. this.log(`Module (${this._name}) stopping`);
  193. this.setStatus(ModuleStatus.STOPPING);
  194. }
  195. /**
  196. * stopped - called when the module has stopped
  197. */
  198. protected async _stopped() {
  199. this.log(`Module (${this._name}) stopped`);
  200. this.setStatus(ModuleStatus.STOPPED);
  201. }
  202. /**
  203. * log - Add log to logbook
  204. *
  205. * @param log - Log message or object
  206. */
  207. public log(log: string | Omit<Log, "timestamp" | "category">) {
  208. const {
  209. message,
  210. type = undefined,
  211. data = {}
  212. } = {
  213. ...(typeof log === "string" ? { message: log } : log)
  214. };
  215. LogBook.log({
  216. message,
  217. type,
  218. category: `modules.${this.getName()}`,
  219. data: {
  220. moduleName: this._name,
  221. ...data
  222. }
  223. });
  224. }
  225. }