@tryghost/job-manager 0.8.23 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ const {UnhandledJobError, IncorrectUsageError} = require('@tryghost/errors');
7
7
  const logging = require('@tryghost/logging');
8
8
  const isCronExpression = require('./is-cron-expression');
9
9
  const assembleBreeJob = require('./assemble-bree-job');
10
+ const JobsRepository = require('./jobs-repository');
10
11
 
11
12
  const worker = async (task, callback) => {
12
13
  try {
@@ -31,18 +32,73 @@ class JobManager {
31
32
  * @param {Object} options
32
33
  * @param {Function} [options.errorHandler] - custom job error handler
33
34
  * @param {Function} [options.workerMessageHandler] - custom message handler coming from workers
35
+ * @param {Object} [options.JobModel] - a model which can persist job data in the storage
34
36
  */
35
- constructor({errorHandler, workerMessageHandler}) {
37
+ constructor({errorHandler, workerMessageHandler, JobModel}) {
36
38
  this.queue = fastq(this, worker, 1);
39
+ this._jobMessageHandler = this._jobMessageHandler.bind(this);
40
+ this._jobErrorHandler = this._jobErrorHandler.bind(this);
41
+
42
+ const combinedMessageHandler = workerMessageHandler
43
+ ? ({name, message}) => {
44
+ workerMessageHandler({name, message});
45
+ this._jobMessageHandler({name, message});
46
+ }
47
+ : this._jobMessageHandler;
48
+
49
+ const combinedErrorHandler = errorHandler
50
+ ? (error, workerMeta) => {
51
+ errorHandler(error, workerMeta);
52
+ this._jobErrorHandler(error, workerMeta);
53
+ }
54
+ : this._jobErrorHandler;
37
55
 
38
56
  this.bree = new Bree({
39
57
  root: false, // set this to `false` to prevent requiring a root directory of jobs
40
58
  hasSeconds: true, // precision is needed to avoid task overlaps after immediate execution
41
59
  outputWorkerMetadata: true,
42
60
  logger: logging,
43
- errorHandler: errorHandler,
44
- workerMessageHandler: workerMessageHandler
61
+ errorHandler: combinedErrorHandler,
62
+ workerMessageHandler: combinedMessageHandler
63
+ });
64
+
65
+ this.bree.on('worker created', (name) => {
66
+ this._jobMessageHandler({name, message: 'started'});
45
67
  });
68
+
69
+ this._jobsRepository = new JobsRepository({JobModel});
70
+ }
71
+
72
+ async _jobMessageHandler({name, message}) {
73
+ if (message === 'started') {
74
+ const job = await this._jobsRepository.read(name);
75
+
76
+ if (job) {
77
+ await this._jobsRepository.update(job.id, {
78
+ status: 'started',
79
+ started_at: new Date()
80
+ });
81
+ }
82
+ } else if (message === 'done') {
83
+ const job = await this._jobsRepository.read(name);
84
+
85
+ if (job) {
86
+ await this._jobsRepository.update(job.id, {
87
+ status: 'finished',
88
+ finished_at: new Date()
89
+ });
90
+ }
91
+ }
92
+ }
93
+
94
+ async _jobErrorHandler(error, workerMeta) {
95
+ const job = await this._jobsRepository.read(workerMeta.name);
96
+
97
+ if (job) {
98
+ await this._jobsRepository.update(job.id, {
99
+ status: 'failed'
100
+ });
101
+ }
46
102
  }
47
103
 
48
104
  /**
@@ -118,6 +174,33 @@ class JobManager {
118
174
  }
119
175
  }
120
176
 
177
+ /**
178
+ * Adds a job that could ever be executed once.
179
+ *
180
+ * @param {Object} GhostJob - job options
181
+ * @prop {Function | String} GhostJob.job - function or path to a module defining a job
182
+ * @prop {String} [GhostJob.name] - unique job name, if not provided takes function name or job script filename
183
+ * @prop {String | Date} [GhostJob.at] - Date, cron or human readable schedule format. Manage will do immediate execution if not specified. Not supported for "inline" jobs
184
+ * @prop {Object} [GhostJob.data] - data to be passed into the job
185
+ * @prop {Boolean} [GhostJob.offloaded] - creates an "offloaded" job running in a worker thread by default. If set to "false" runs an "inline" job on the same event loop
186
+ */
187
+ async addOneOffJob({name, job, data, offloaded = true}) {
188
+ const persistedJob = await this._jobsRepository.read(name);
189
+
190
+ if (persistedJob) {
191
+ throw new IncorrectUsageError({
192
+ message: `A "${name}" one off job has already been executed.`
193
+ });
194
+ }
195
+
196
+ await this._jobsRepository.add({
197
+ name,
198
+ status: 'queued'
199
+ });
200
+
201
+ this.addJob({name, job, data, offloaded});
202
+ }
203
+
121
204
  /**
122
205
  * Removes an "offloaded" job from scheduled jobs queue.
123
206
  * It's NOT yet possible to remove "inline" jobs (will be possible when scheduling is added https://github.com/breejs/bree/issues/68).
@@ -0,0 +1,23 @@
1
+ class JobsRepository {
2
+ constructor({JobModel}) {
3
+ this._JobModel = JobModel;
4
+ }
5
+
6
+ async add(data) {
7
+ const job = await this._JobModel.add(data);
8
+
9
+ return job;
10
+ }
11
+
12
+ async read(name) {
13
+ const job = await this._JobModel.findOne({name});
14
+
15
+ return job;
16
+ }
17
+
18
+ async update(id, data) {
19
+ await this._JobModel.edit(data, {id});
20
+ }
21
+ }
22
+
23
+ module.exports = JobsRepository;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryghost/job-manager",
3
- "version": "0.8.23",
3
+ "version": "0.9.0",
4
4
  "repository": "https://github.com/TryGhost/Utils/tree/main/packages/job-manager",
5
5
  "author": "Ghost Foundation",
6
6
  "license": "MIT",
@@ -20,12 +20,12 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "@sinonjs/fake-timers": "9.1.2",
23
- "c8": "7.11.2",
24
- "date-fns": "2.28.0",
23
+ "c8": "7.12.0",
24
+ "date-fns": "2.29.1",
25
25
  "delay": "5.0.0",
26
26
  "mocha": "10.0.0",
27
27
  "should": "13.2.3",
28
- "sinon": "13.0.2"
28
+ "sinon": "14.0.0"
29
29
  },
30
30
  "dependencies": {
31
31
  "@breejs/later": "^4.0.2",
@@ -35,5 +35,5 @@
35
35
  "fastq": "^1.11.0",
36
36
  "p-wait-for": "^3.2.0"
37
37
  },
38
- "gitHead": "e14f269ad87fee8f5c7c9fa6c2bc69d587f26e38"
38
+ "gitHead": "2ce0a263975cf6e0f34233caf19404fd8ab5e7a5"
39
39
  }