Job.ts 4.9 KB

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