Job.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import BaseModule from "./BaseModule";
  2. import JobContext from "./JobContext";
  3. import JobStatistics from "./JobStatistics";
  4. import LogBook, { Log } from "./LogBook";
  5. import ModuleManager from "./ModuleManager";
  6. import { JobOptions } from "./types/JobOptions";
  7. import { Modules } from "./types/Modules";
  8. export enum JobStatus {
  9. QUEUED = "QUEUED",
  10. ACTIVE = "ACTIVE",
  11. PAUSED = "PAUSED",
  12. COMPLETED = "COMPLETED"
  13. }
  14. export default class Job {
  15. protected name: string;
  16. protected module: Modules[keyof Modules];
  17. protected jobFunction: any;
  18. protected payload: any;
  19. protected context: JobContext;
  20. protected priority: number;
  21. protected longJob?: {
  22. title: string;
  23. progress?: {
  24. data: unknown;
  25. time: Date;
  26. timeout?: NodeJS.Timeout;
  27. };
  28. };
  29. protected uuid: string;
  30. protected status: JobStatus;
  31. protected createdAt: number;
  32. protected startedAt?: number;
  33. protected completedAt?: number;
  34. protected logBook: LogBook;
  35. protected jobStatistics: JobStatistics;
  36. /**
  37. * Job
  38. *
  39. * @param name - Job name
  40. * @param module - Job module
  41. * @param callback - Job callback
  42. * @param options - Job options
  43. */
  44. public constructor(
  45. name: string,
  46. moduleName: keyof Modules,
  47. payload: any,
  48. options?: Omit<JobOptions, "runDirectly">
  49. ) {
  50. this.name = name;
  51. this.priority = 1;
  52. const module = ModuleManager.getPrimaryInstance().getModule(moduleName);
  53. if (!module) throw new Error("Module not found.");
  54. this.module = module;
  55. // eslint-disable-next-line
  56. // @ts-ignore
  57. const jobFunction = this.module[this.name];
  58. if (!jobFunction || typeof jobFunction !== "function")
  59. throw new Error("Job not found.");
  60. if (Object.prototype.hasOwnProperty.call(BaseModule, this.name))
  61. throw new Error("Illegal job function.");
  62. this.jobFunction = jobFunction;
  63. this.payload = payload;
  64. this.context = new JobContext(this);
  65. this.logBook = LogBook.getPrimaryInstance();
  66. this.jobStatistics = JobStatistics.getPrimaryInstance();
  67. this.jobStatistics.updateStats(this.getName(), "added");
  68. if (options) {
  69. const { priority, longJob } = options;
  70. if (priority) this.priority = priority;
  71. if (longJob)
  72. this.longJob = {
  73. title: longJob
  74. };
  75. }
  76. /* eslint-disable no-bitwise, eqeqeq */
  77. this.uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
  78. /[xy]/g,
  79. c => {
  80. const r = (Math.random() * 16) | 0;
  81. const v = c == "x" ? r : (r & 0x3) | 0x8;
  82. return v.toString(16);
  83. }
  84. );
  85. this.status = JobStatus.QUEUED;
  86. this.createdAt = Date.now();
  87. }
  88. /**
  89. * getName - Get module and job name in a dot format, e.g. module.jobName
  90. *
  91. * @returns module.name
  92. */
  93. public getName() {
  94. return `${this.module.getName()}.${this.name}`;
  95. }
  96. /**
  97. * getPriority - Get job priority
  98. *
  99. * @returns priority
  100. */
  101. public getPriority() {
  102. return this.priority;
  103. }
  104. /**
  105. * getUuid - Get job UUID
  106. *
  107. * @returns UUID
  108. */
  109. public getUuid() {
  110. return this.uuid;
  111. }
  112. /**
  113. * getStatus - Get job status
  114. *
  115. * @returns status
  116. */
  117. public getStatus() {
  118. return this.status;
  119. }
  120. /**
  121. * setStatus - Set job status
  122. *
  123. * @param status - Job status
  124. */
  125. protected setStatus(status: JobStatus) {
  126. this.status = status;
  127. }
  128. /**
  129. * getModule - Get module
  130. *
  131. * @returns module
  132. */
  133. public getModule() {
  134. return this.module;
  135. }
  136. /**
  137. * execute - Execute job
  138. *
  139. * @returns Promise
  140. */
  141. public async execute() {
  142. if (this.startedAt) throw new Error("Job has already been executed.");
  143. this.setStatus(JobStatus.ACTIVE);
  144. this.startedAt = Date.now();
  145. return (
  146. this.jobFunction
  147. .apply(this.module, [this.context, this.payload])
  148. // eslint-disable-next-line
  149. // @ts-ignore
  150. .then(response => {
  151. this.log({
  152. message: "Job completed successfully",
  153. type: "success"
  154. });
  155. this.jobStatistics.updateStats(
  156. this.getName(),
  157. "successful"
  158. );
  159. return response;
  160. })
  161. .catch((err: any) => {
  162. this.log({
  163. message: `Job failed with error "${err}"`,
  164. type: "error"
  165. });
  166. this.jobStatistics.updateStats(this.getName(), "failed");
  167. throw err;
  168. })
  169. .finally(() => {
  170. this.completedAt = Date.now();
  171. this.jobStatistics.updateStats(this.getName(), "total");
  172. if (this.startedAt)
  173. this.jobStatistics.updateStats(
  174. this.getName(),
  175. "averageTime",
  176. this.completedAt - this.startedAt
  177. );
  178. this.setStatus(JobStatus.COMPLETED);
  179. })
  180. );
  181. }
  182. /**
  183. * Log a message in the context of the current job, which automatically sets the category and data
  184. *
  185. * @param log - Log message or object
  186. */
  187. public log(log: string | Omit<Log, "timestamp" | "category">) {
  188. const {
  189. message,
  190. type = undefined,
  191. data = {}
  192. } = {
  193. ...(typeof log === "string" ? { message: log } : log)
  194. };
  195. this.logBook.log({
  196. message,
  197. type,
  198. category: this.getName(),
  199. data: {
  200. ...this.toJSON(),
  201. ...data
  202. }
  203. });
  204. }
  205. /**
  206. * Serialize job info
  207. *
  208. * @returns json
  209. */
  210. public toJSON() {
  211. return {
  212. uuid: this.getUuid(),
  213. priority: this.getPriority(),
  214. name: this.getName(),
  215. status: this.getStatus(),
  216. moduleStatus: this.module.getStatus(),
  217. createdAt: this.createdAt,
  218. startedAt: this.startedAt,
  219. completedAt: this.completedAt
  220. };
  221. }
  222. }