ModuleManager.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import BaseModule, { ModuleStatus } from "@/BaseModule";
  2. import JobQueue from "@/JobQueue";
  3. export class ModuleManager {
  4. private _modules?: Record<string, BaseModule>;
  5. /**
  6. * getStatus - Get status of modules
  7. *
  8. * @returns Module statuses
  9. */
  10. public getStatus() {
  11. const status: Record<string, ModuleStatus> = {};
  12. Object.entries(this._modules || {}).forEach(([name, module]) => {
  13. status[name] = module.getStatus();
  14. });
  15. return status;
  16. }
  17. /**
  18. * Gets a module
  19. *
  20. */
  21. public getModule<ModuleType extends BaseModule>(
  22. moduleName: string
  23. ): ModuleType | undefined {
  24. return (this._modules && this._modules[moduleName]) as
  25. | ModuleType
  26. | undefined;
  27. }
  28. /**
  29. * Gets modules
  30. */
  31. public getModules() {
  32. return this._modules;
  33. }
  34. /**
  35. * loadModule - Load and initialize module
  36. *
  37. * @param moduleName - Name of the module
  38. * @returns Module
  39. */
  40. private async _loadModule<ModuleType extends BaseModule>(
  41. moduleName: string
  42. ): Promise<ModuleType> {
  43. const mapper: Record<string, string> = {
  44. cache: "CacheModule",
  45. data: "DataModule",
  46. events: "EventsModule",
  47. stations: "StationsModule",
  48. websocket: "WebSocketModule"
  49. };
  50. const { default: Module }: { default: ModuleType } = await import(
  51. `./modules/${mapper[moduleName]}`
  52. );
  53. return Module;
  54. }
  55. /**
  56. * loadModules - Load and initialize all modules
  57. *
  58. * @returns Promise
  59. */
  60. private async _loadModules() {
  61. this._modules = {
  62. cache: await this._loadModule("cache"),
  63. data: await this._loadModule("data"),
  64. events: await this._loadModule("events"),
  65. stations: await this._loadModule("stations"),
  66. websocket: await this._loadModule("websocket")
  67. };
  68. }
  69. /**
  70. * startModule - Start module
  71. */
  72. private async _startModule(module: BaseModule) {
  73. switch (module.getStatus()) {
  74. case ModuleStatus.STARTING:
  75. case ModuleStatus.STARTED:
  76. return;
  77. case ModuleStatus.ERROR:
  78. throw new Error("Dependent module failed to start");
  79. case ModuleStatus.STOPPING:
  80. case ModuleStatus.STOPPED:
  81. case ModuleStatus.DISABLED:
  82. throw new Error("Dependent module is unavailable");
  83. default:
  84. break;
  85. }
  86. for (const name of module.getDependentModules()) {
  87. const dependency = this.getModule(name);
  88. if (!dependency) throw new Error("Dependent module not found");
  89. // eslint-disable-next-line no-await-in-loop
  90. await this._startModule(dependency);
  91. }
  92. await module.startup().catch(async err => {
  93. module.setStatus(ModuleStatus.ERROR);
  94. throw err;
  95. });
  96. }
  97. /**
  98. * getJobs - Get jobs for all modules
  99. */
  100. public getJobs() {
  101. if (!this._modules) return [];
  102. return Object.fromEntries(
  103. Object.entries(this._modules).map(([name, module]) => [
  104. name,
  105. Object.fromEntries(
  106. Object.entries(module.getJobs()).map(([jobName, Job]) => [
  107. jobName,
  108. { api: Job.isApiEnabled() }
  109. ])
  110. )
  111. ])
  112. );
  113. }
  114. /**
  115. * startup - Handle startup
  116. */
  117. public async startup() {
  118. try {
  119. await this._loadModules();
  120. if (!this._modules) throw new Error("No modules were loaded");
  121. for (const module of Object.values(this._modules)) {
  122. // eslint-disable-next-line no-await-in-loop
  123. await this._startModule(module);
  124. }
  125. JobQueue.resume();
  126. } catch (err) {
  127. await this.shutdown();
  128. throw err;
  129. }
  130. }
  131. /**
  132. * shutdown - Handle shutdown
  133. */
  134. public async shutdown() {
  135. if (this._modules) {
  136. const modules = Object.entries(this._modules).filter(([, module]) =>
  137. [
  138. ModuleStatus.STARTED,
  139. ModuleStatus.STARTING,
  140. ModuleStatus.ERROR
  141. ].includes(module.getStatus())
  142. );
  143. const shutdownOrder: string[] = [];
  144. for (const [name, module] of modules) {
  145. if (!shutdownOrder.includes(name)) shutdownOrder.push(name);
  146. const dependencies = module.getDependentModules();
  147. dependencies
  148. .filter(dependency => shutdownOrder.includes(dependency))
  149. .forEach(dependency => {
  150. shutdownOrder.splice(
  151. shutdownOrder.indexOf(dependency),
  152. 1
  153. );
  154. });
  155. shutdownOrder.push(...dependencies);
  156. }
  157. for (const moduleName of shutdownOrder) {
  158. // eslint-disable-next-line no-await-in-loop
  159. await this.getModule(moduleName)?.shutdown();
  160. }
  161. }
  162. }
  163. }
  164. export default new ModuleManager();