ModuleManager.ts 4.1 KB

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