@tryghost/job-manager 3.0.3 → 3.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2013-2026 Ghost Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,6 +7,7 @@ A manager for background jobs in Ghost, supporting one-off tasks and recurring j
7
7
  Background job orchestration for Ghost, supporting one-off and recurring jobs in-process or offloaded workers.
8
8
 
9
9
  ## Table of Contents
10
+
10
11
  - [Quick Start](#quick-start)
11
12
  - [Job Types](#job-types)
12
13
  - [Background Job Requirements](#background-job-requirements)
@@ -17,20 +18,20 @@ Background job orchestration for Ghost, supporting one-off and recurring jobs in
17
18
 
18
19
  ```js
19
20
  const JobManager = require('@tryghost/job-manager');
20
- const jobManager = new JobManager({JobModel: models.Job});
21
+ const jobManager = new JobManager({ JobModel: models.Job });
21
22
 
22
23
  // Simple one-off job
23
24
  jobManager.addJob({
24
25
  name: 'hello-world',
25
26
  job: () => console.log('Hello World'),
26
- offloaded: false
27
+ offloaded: false,
27
28
  });
28
29
 
29
30
  // Recurring job every 5 minutes
30
31
  jobManager.addJob({
31
32
  name: 'check-emails',
32
33
  at: 'every 5 minutes',
33
- job: './jobs/check-emails.js'
34
+ job: './jobs/check-emails.js',
34
35
  });
35
36
  ```
36
37
 
@@ -39,14 +40,14 @@ jobManager.addJob({
39
40
  Ghost supports two types of jobs:
40
41
 
41
42
  1. **Inline Jobs**
42
- - Run in the main Ghost process
43
- - Best for quick, simple tasks
44
- - Cannot be scheduled or recurring
43
+ - Run in the main Ghost process
44
+ - Best for quick, simple tasks
45
+ - Cannot be scheduled or recurring
45
46
 
46
47
  2. **Offloaded Jobs**
47
- - Run in a separate process
48
- - Good for CPU-intensive tasks
49
- - Can be scheduled or recurring
48
+ - Run in a separate process
49
+ - Good for CPU-intensive tasks
50
+ - Can be scheduled or recurring
50
51
 
51
52
  ## Background Job Requirements
52
53
 
@@ -61,30 +62,31 @@ Offloaded jobs must:
61
62
  ## Advanced Usage
62
63
 
63
64
  Below is a sample code to wire up job manger and initialize jobs. This is the simplest way to interact with the job manager - these jobs do not persist after reboot:
65
+
64
66
  ```js
65
67
  const JobManager = require('@tryghost/job-manager');
66
68
 
67
- const jobManager = new JobManager({JobModel: models.Job});
69
+ const jobManager = new JobManager({ JobModel: models.Job });
68
70
 
69
71
  // register a job "function" with queued execution in current event loop
70
72
  jobManager.addJob({
71
73
  job: (word) => console.log(word),
72
74
  name: 'hello',
73
- offloaded: false
75
+ offloaded: false,
74
76
  });
75
77
 
76
78
  // register a job "module" with queued execution in current even loop
77
79
  jobManager.addJob({
78
- job:'./path/to/email-module.js',
79
- data: {email: 'send@here.com'},
80
- offloaded: false
80
+ job: './path/to/email-module.js',
81
+ data: { email: 'send@here.com' },
82
+ offloaded: false,
81
83
  });
82
84
 
83
85
  // register recurring job which needs execution outside of current event loop
84
86
  jobManager.addJob({
85
87
  at: 'every 5 minutes',
86
88
  job: './path/to/jobs/check-emails.js',
87
- name: 'email-checker'
89
+ name: 'email-checker',
88
90
  });
89
91
 
90
92
  // register recurring job with cron syntax running every 5 minutes
@@ -93,20 +95,20 @@ jobManager.addJob({
93
95
  jobManager.addJob({
94
96
  at: '0 1/5 * * * *',
95
97
  job: './path/to/jobs/check-emails.js',
96
- name: 'email-checker-cron'
98
+ name: 'email-checker-cron',
97
99
  });
98
100
 
99
101
  // register a job to run immediately running outside of current even loop
100
102
  jobManager.addJob({
101
103
  job: './path/to/jobs/check-emails.js',
102
- name: 'email-checker-now'
104
+ name: 'email-checker-now',
103
105
  });
104
106
 
105
107
  // register a one-off job to be executed immediately within current event loop
106
108
  jobManager.addOneOffJob({
107
109
  name: 'members-migrations',
108
110
  offloaded: false,
109
- job: stripeService.migrations.execute.bind(stripeService.migrations)
111
+ job: stripeService.migrations.execute.bind(stripeService.migrations),
110
112
  });
111
113
 
112
114
  // register a one-off job to be executed immediately outside of current event loop
@@ -115,12 +117,12 @@ jobManager.addOneOffJob({
115
117
  job: './path/to/jobs/backup.js',
116
118
  });
117
119
 
118
- // optionally await completion of the one-off job in case
120
+ // optionally await completion of the one-off job in case
119
121
  // there are state changes expected to execute the rest of the process
120
122
  // NOTE: if multiple jobs are submitted using the same name, the first completion will resolve
121
123
  await jobManager.awaitOneOffCompletion('members-migrations');
122
124
 
123
- // check if previously registered one-off job has been executed
125
+ // check if previously registered one-off job has been executed
124
126
  // successfully - it exists and doesn't have a "failed" state.
125
127
  // NOTE: this is stored in memory and cleared on reboot
126
128
  const backupSuccessful = await jobManager.hasExecutedSuccessfully('generate-backup-2022-09-15');
@@ -139,16 +141,20 @@ For more examples of JobManager initialization check [test/examples](https://git
139
141
  ## Technical Details
140
142
 
141
143
  ### Process Isolation
144
+
142
145
  Background jobs run in separate processes, meaning:
146
+
143
147
  - No shared memory with Ghost
144
148
  - Must import own dependencies
145
149
  - Should avoid heavy dependencies
146
150
  - Should handle graceful shutdown
147
151
 
148
152
  ### Job Lifecycle
153
+
149
154
  Offloaded jobs are running on dedicated worker threads which makes their lifecycle a bit different from inline jobs:
155
+
150
156
  1. When **starting** a job it's only sharing ENV variables with its parent process. The job itself is run on an independent JavaScript execution thread. The script has to re-initialize any modules it will use. For example, it should take care of: model layer initialization, cache initialization, etc.
151
- 2. When **finishing** work in a job prefer to signal successful termination by sending 'done' message to the parent thread: `parentPort.postMessage('done')` ([example use](https://github.com/TryGhost/Utils/blob/0e423f6c5c69b08d81d470f49de95654d8cc90e3/packages/job-manager/test/jobs/graceful.js#L33-L37)). Finishing work this way terminates the thread through [worker.terminate()]((https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html#worker_threads_worker_terminate)), which logs termination in parent process and flushes any pipes opened in thread.
157
+ 2. When **finishing** work in a job prefer to signal successful termination by sending 'done' message to the parent thread: `parentPort.postMessage('done')` ([example use](https://github.com/TryGhost/Utils/blob/0e423f6c5c69b08d81d470f49de95654d8cc90e3/packages/job-manager/test/jobs/graceful.js#L33-L37)). Finishing work this way terminates the thread through [worker.terminate()](<(https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html#worker_threads_worker_terminate)>), which logs termination in parent process and flushes any pipes opened in thread.
152
158
  3. Jobs that have iterative nature, or need cleanup before interrupting work should allow for **graceful shutdown** by listening on `'cancel'` message coming from parent thread ([example use](https://github.com/TryGhost/Utils/blob/0e423f6c5c69b08d81d470f49de95654d8cc90e3/packages/job-manager/test/jobs/graceful.js#L12-L16)).
153
159
  4. When **exceptions** happen and expected outcome is to terminate current job, leave the exception unhandled allowing it to bubble up to the job manager. Unhandled exceptions [terminate current thread](https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html#worker_threads_event_error) and allow for next scheduled job execution to happen.
154
160
 
package/lib/JobManager.js CHANGED
@@ -4,7 +4,7 @@ const setTimeoutPromise = util.promisify(setTimeout);
4
4
  const fastq = require('fastq');
5
5
  const later = require('@breejs/later');
6
6
  const Bree = require('bree');
7
- const {UnhandledJobError, IncorrectUsageError} = require('@tryghost/errors');
7
+ const { UnhandledJobError, IncorrectUsageError } = require('@tryghost/errors');
8
8
  const logging = require('@tryghost/logging');
9
9
  const isCronExpression = require('./is-cron-expression');
10
10
  const assembleBreeJob = require('./assemble-bree-job');
@@ -23,7 +23,7 @@ const ALL_STATUSES = {
23
23
  started: 'started',
24
24
  finished: 'finished',
25
25
  failed: 'failed',
26
- queued: 'queued'
26
+ queued: 'queued',
27
27
  };
28
28
 
29
29
  /**
@@ -37,9 +37,6 @@ const ALL_STATUSES = {
37
37
  class JobManager {
38
38
  #domainEvents;
39
39
  #completionPromises = new Map();
40
- #config;
41
- #JobModel;
42
- #events;
43
40
 
44
41
  /**
45
42
  * @param {Object} options
@@ -47,30 +44,25 @@ class JobManager {
47
44
  * @param {Function} [options.workerMessageHandler] - custom message handler coming from workers
48
45
  * @param {Object} [options.JobModel] - a model which can persist job data in the storage
49
46
  * @param {Object} [options.domainEvents] - domain events emitter
50
- * @param {Object} [options.config] - config
51
- * @param {Object} [options.events] - events instance (for testing)
52
47
  */
53
- constructor({errorHandler, workerMessageHandler, JobModel, domainEvents, config, events = null}) {
48
+ constructor({ errorHandler, workerMessageHandler, JobModel, domainEvents }) {
54
49
  this.inlineQueue = fastq(this, worker, 3);
55
50
  this._jobMessageHandler = this._jobMessageHandler.bind(this);
56
51
  this._jobErrorHandler = this._jobErrorHandler.bind(this);
57
52
  this.#domainEvents = domainEvents;
58
- this.#config = config;
59
- this.#JobModel = JobModel;
60
- this.#events = events;
61
53
 
62
54
  const combinedMessageHandler = workerMessageHandler
63
- ? ({name, message}) => {
64
- workerMessageHandler({name, message});
65
- this._jobMessageHandler({name, message});
66
- }
55
+ ? ({ name, message }) => {
56
+ workerMessageHandler({ name, message });
57
+ this._jobMessageHandler({ name, message });
58
+ }
67
59
  : this._jobMessageHandler;
68
60
 
69
61
  const combinedErrorHandler = errorHandler
70
62
  ? (error, workerMeta) => {
71
- errorHandler(error, workerMeta);
72
- this._jobErrorHandler(error, workerMeta);
73
- }
63
+ errorHandler(error, workerMeta);
64
+ this._jobErrorHandler(error, workerMeta);
65
+ }
74
66
  : this._jobErrorHandler;
75
67
 
76
68
  this.bree = new Bree({
@@ -79,15 +71,15 @@ class JobManager {
79
71
  outputWorkerMetadata: true,
80
72
  logger: logging,
81
73
  errorHandler: combinedErrorHandler,
82
- workerMessageHandler: combinedMessageHandler
74
+ workerMessageHandler: combinedMessageHandler,
83
75
  });
84
76
 
85
77
  this.bree.on('worker created', (name) => {
86
- this._jobMessageHandler({name, message: ALL_STATUSES.started});
78
+ this._jobMessageHandler({ name, message: ALL_STATUSES.started });
87
79
  });
88
80
 
89
81
  if (JobModel) {
90
- this._jobsRepository = new JobsRepository({JobModel});
82
+ this._jobsRepository = new JobsRepository({ JobModel });
91
83
  }
92
84
  }
93
85
 
@@ -95,12 +87,12 @@ class JobManager {
95
87
  return async (error, result) => {
96
88
  if (error) {
97
89
  await this._jobErrorHandler(error, {
98
- name: jobName
90
+ name: jobName,
99
91
  });
100
92
  } else {
101
93
  await this._jobMessageHandler({
102
94
  name: jobName,
103
- message: 'done'
95
+ message: 'done',
104
96
  });
105
97
  }
106
98
 
@@ -109,7 +101,7 @@ class JobManager {
109
101
  };
110
102
  }
111
103
 
112
- async _jobMessageHandler({name, message}) {
104
+ async _jobMessageHandler({ name, message }) {
113
105
  if (name) {
114
106
  if (message === ALL_STATUSES.started) {
115
107
  if (this._jobsRepository) {
@@ -118,7 +110,7 @@ class JobManager {
118
110
  if (job) {
119
111
  await this._jobsRepository.update(job.id, {
120
112
  status: ALL_STATUSES.started,
121
- started_at: new Date()
113
+ started_at: new Date(),
122
114
  });
123
115
  }
124
116
  }
@@ -129,7 +121,7 @@ class JobManager {
129
121
  if (job) {
130
122
  await this._jobsRepository.update(job.id, {
131
123
  status: ALL_STATUSES.finished,
132
- finished_at: new Date()
124
+ finished_at: new Date(),
133
125
  });
134
126
  }
135
127
  }
@@ -169,7 +161,7 @@ class JobManager {
169
161
 
170
162
  if (job) {
171
163
  await this._jobsRepository.update(job.id, {
172
- status: ALL_STATUSES.failed
164
+ status: ALL_STATUSES.failed,
173
165
  });
174
166
  }
175
167
  }
@@ -205,7 +197,7 @@ class JobManager {
205
197
  * @prop {Object} [GhostJob.data] - data to be passed into the job
206
198
  * @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
207
199
  */
208
- async addJob({name, at, job, data, offloaded = true}) {
200
+ async addJob({ name, at, job, data, offloaded = true }) {
209
201
  if (offloaded) {
210
202
  logging.info('Adding offloaded job to the inline job queue');
211
203
  let schedule;
@@ -215,7 +207,7 @@ class JobManager {
215
207
  name = path.parse(job).name;
216
208
  } else {
217
209
  throw new IncorrectUsageError({
218
- message: 'Name parameter should be present if job is a function'
210
+ message: 'Name parameter should be present if job is a function',
219
211
  });
220
212
  }
221
213
  }
@@ -229,11 +221,13 @@ class JobManager {
229
221
 
230
222
  if ((schedule.error && schedule.error !== -1) || schedule.schedules.length === 0) {
231
223
  throw new IncorrectUsageError({
232
- message: 'Invalid schedule format'
224
+ message: 'Invalid schedule format',
233
225
  });
234
226
  }
235
227
 
236
- logging.info(`Scheduling job ${name} at ${at}. Next run on: ${later.schedule(schedule).next()}`);
228
+ logging.info(
229
+ `Scheduling job ${name} at ${at}. Next run on: ${later.schedule(schedule).next()}`,
230
+ );
237
231
  } else if (at !== undefined) {
238
232
  logging.info(`Scheduling job ${name} at ${at}`);
239
233
  } else {
@@ -244,7 +238,9 @@ class JobManager {
244
238
  await this.bree.add(breeJob);
245
239
  return this.bree.start(name);
246
240
  } else {
247
- logging.info(`Adding one-off job to inlineQueue with current length = ${this.inlineQueue.length()} called '${name || 'anonymous'}'`);
241
+ logging.info(
242
+ `Adding one-off job to inlineQueue with current length = ${this.inlineQueue.length()} called '${name || 'anonymous'}'`,
243
+ );
248
244
 
249
245
  this.inlineQueue.push(async () => {
250
246
  try {
@@ -252,7 +248,7 @@ class JobManager {
252
248
  // distinguish between states when the job fails immediately
253
249
  await this._jobMessageHandler({
254
250
  name: name,
255
- message: ALL_STATUSES.started
251
+ message: ALL_STATUSES.started,
256
252
  });
257
253
 
258
254
  if (typeof job === 'function') {
@@ -263,10 +259,12 @@ class JobManager {
263
259
  } catch (err) {
264
260
  // NOTE: each job should be written in a safe way and handle all errors internally
265
261
  // if the error is caught here jobs implementation should be changed
266
- logging.error(new UnhandledJobError({
267
- context: (typeof job === 'function') ? 'function' : job,
268
- err
269
- }));
262
+ logging.error(
263
+ new UnhandledJobError({
264
+ context: typeof job === 'function' ? 'function' : job,
265
+ err,
266
+ }),
267
+ );
270
268
 
271
269
  throw err;
272
270
  }
@@ -275,39 +273,39 @@ class JobManager {
275
273
  }
276
274
 
277
275
  /**
278
- * Adds a job that could ever be executed once. In case the job fails
279
- * can be "added" again, effectively restarting the failed job.
280
- *
281
- * @param {Object} GhostJob - job options
282
- * @prop {Function | String} GhostJob.job - function or path to a module defining a job
283
- * @prop {String} GhostJob.name - unique job name, if not provided takes function name or job script filename
284
- * @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
285
- * @prop {Object} [GhostJob.data] - data to be passed into the job
286
- * @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
287
- */
288
- async addOneOffJob({name, job, data, offloaded = true}) {
276
+ * Adds a job that could ever be executed once. In case the job fails
277
+ * can be "added" again, effectively restarting the failed job.
278
+ *
279
+ * @param {Object} GhostJob - job options
280
+ * @prop {Function | String} GhostJob.job - function or path to a module defining a job
281
+ * @prop {String} GhostJob.name - unique job name, if not provided takes function name or job script filename
282
+ * @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
283
+ * @prop {Object} [GhostJob.data] - data to be passed into the job
284
+ * @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
285
+ */
286
+ async addOneOffJob({ name, job, data, offloaded = true }) {
289
287
  if (!name) {
290
288
  throw new IncorrectUsageError({
291
- message: `The name parameter is required for a one off job.`
289
+ message: `The name parameter is required for a one off job.`,
292
290
  });
293
291
  }
294
292
 
295
293
  const persistedJob = await this._jobsRepository.read(name);
296
294
 
297
- if (persistedJob && (persistedJob.get('status') !== ALL_STATUSES.failed)) {
295
+ if (persistedJob && persistedJob.get('status') !== ALL_STATUSES.failed) {
298
296
  throw new IncorrectUsageError({
299
- message: `A "${name}" one off job has already been executed.`
297
+ message: `A "${name}" one off job has already been executed.`,
300
298
  });
301
299
  }
302
300
 
303
- if (persistedJob && (persistedJob.get('status') === ALL_STATUSES.failed)) {
301
+ if (persistedJob && persistedJob.get('status') === ALL_STATUSES.failed) {
304
302
  await this._jobsRepository.update(persistedJob.id, {
305
- status: ALL_STATUSES.queued
303
+ status: ALL_STATUSES.queued,
306
304
  });
307
305
  } else {
308
306
  await this._jobsRepository.add({
309
307
  name,
310
- status: ALL_STATUSES.queued
308
+ status: ALL_STATUSES.queued,
311
309
  });
312
310
  }
313
311
 
@@ -316,7 +314,7 @@ class JobManager {
316
314
  // For example, it failed and the process was restarted.
317
315
  // If we want to be able to restart within the same instance,
318
316
  // we'd need to handle job restart/removal in Bree first
319
- this.addJob({name, job, data, offloaded});
317
+ this.addJob({ name, job, data, offloaded });
320
318
  }
321
319
 
322
320
  /**
@@ -330,7 +328,7 @@ class JobManager {
330
328
  if (!persistedJob) {
331
329
  return false;
332
330
  } else {
333
- return (persistedJob.get('status') !== ALL_STATUSES.failed);
331
+ return persistedJob.get('status') !== ALL_STATUSES.failed;
334
332
  }
335
333
  } else {
336
334
  return false;
@@ -346,7 +344,10 @@ class JobManager {
346
344
  async awaitOneOffCompletion(name) {
347
345
  const persistedJob = await this._jobsRepository.read(name);
348
346
 
349
- if (!persistedJob || ![ALL_STATUSES.finished, ALL_STATUSES.failed].includes(persistedJob.get('status'))) {
347
+ if (
348
+ !persistedJob ||
349
+ ![ALL_STATUSES.finished, ALL_STATUSES.failed].includes(persistedJob.get('status'))
350
+ ) {
350
351
  // NOTE: can implement exponential backoff here if that's ever needed
351
352
  await setTimeoutPromise(500);
352
353
 
@@ -365,7 +366,7 @@ class JobManager {
365
366
  const promise = new Promise((resolve, reject) => {
366
367
  this.#completionPromises.set(name, [
367
368
  ...(this.#completionPromises.get(name) ?? []),
368
- {resolve, reject}
369
+ { resolve, reject },
369
370
  ]);
370
371
  });
371
372
 
@@ -386,7 +387,7 @@ class JobManager {
386
387
 
387
388
  this.#completionPromises.set(name, [
388
389
  ...(this.#completionPromises.get(name) ?? []),
389
- {resolve, reject}
390
+ { resolve, reject },
390
391
  ]);
391
392
  });
392
393
  }
@@ -417,7 +418,7 @@ class JobManager {
417
418
 
418
419
  logging.warn('Waiting for busy job in inline job queue');
419
420
 
420
- const {default: pWaitFor} = await import('p-wait-for');
421
+ const { default: pWaitFor } = await import('p-wait-for');
421
422
  await pWaitFor(() => this.inlineQueue.idle() === true, options);
422
423
 
423
424
  logging.warn('Inline job queue finished');
@@ -10,7 +10,7 @@ class JobsRepository {
10
10
  * @param {Object} options - The options object.
11
11
  * @param {Object} options.JobModel - The Job model for database operations.
12
12
  */
13
- constructor({JobModel}) {
13
+ constructor({ JobModel }) {
14
14
  // NOTE: We ought to clean this up. We want to use bookshelf models for all db operations,
15
15
  // but we use knex directly in a few places still largely for performance reasons.
16
16
  this._JobModel = JobModel;
@@ -36,7 +36,7 @@ class JobsRepository {
36
36
  * @returns {Promise<Object|null>} The job object if found, null otherwise.
37
37
  */
38
38
  async read(name) {
39
- const job = await this._JobModel.findOne({name});
39
+ const job = await this._JobModel.findOne({ name });
40
40
  return job;
41
41
  }
42
42
 
@@ -49,7 +49,7 @@ class JobsRepository {
49
49
  * @returns {Promise<void>}
50
50
  */
51
51
  async update(id, data) {
52
- await this._JobModel.edit(data, {id});
52
+ await this._JobModel.edit(data, { id });
53
53
  }
54
54
 
55
55
  /**
@@ -61,11 +61,11 @@ class JobsRepository {
61
61
  */
62
62
  async delete(id) {
63
63
  try {
64
- await this._JobModel.destroy({id});
64
+ await this._JobModel.destroy({ id });
65
65
  } catch (error) {
66
66
  logging.error(`Error deleting job ${id}:`, error);
67
67
  }
68
68
  }
69
69
  }
70
70
 
71
- module.exports = JobsRepository;
71
+ module.exports = JobsRepository;
@@ -12,28 +12,28 @@ const assemble = (at, job, data, name) => {
12
12
  const breeJob = {
13
13
  name: name,
14
14
  // NOTE: both function and path syntaxes work with 'path' parameter
15
- path: job
15
+ path: job,
16
16
  };
17
17
 
18
18
  if (data) {
19
19
  Object.assign(breeJob, {
20
20
  worker: {
21
- workerData: data
22
- }
21
+ workerData: data,
22
+ },
23
23
  });
24
24
  }
25
25
 
26
26
  if (at instanceof Date) {
27
27
  Object.assign(breeJob, {
28
- date: at
28
+ date: at,
29
29
  });
30
30
  } else if (at && isCronExpression(at)) {
31
31
  Object.assign(breeJob, {
32
- cron: at
32
+ cron: at,
33
33
  });
34
34
  } else if (at !== undefined) {
35
35
  Object.assign(breeJob, {
36
- interval: at
36
+ interval: at,
37
37
  });
38
38
  }
39
39
 
@@ -28,8 +28,8 @@ const isCronExpression = (expression) => {
28
28
  let cronResult = cronValidate(expression, {
29
29
  preset: 'default', // second field not supported in default preset
30
30
  override: {
31
- useSeconds: true // override preset option
32
- }
31
+ useSeconds: true, // override preset option
32
+ },
33
33
  });
34
34
 
35
35
  return cronResult.isValid();
package/package.json CHANGED
@@ -1,43 +1,41 @@
1
1
  {
2
2
  "name": "@tryghost/job-manager",
3
- "version": "3.0.3",
4
- "author": "Ghost Foundation",
3
+ "version": "3.1.0",
5
4
  "license": "MIT",
6
- "publishConfig": {
7
- "access": "public"
8
- },
9
- "main": "index.js",
10
- "scripts": {
11
- "dev": "echo \"Implement me!\"",
12
- "test:unit": "NODE_ENV=testing vitest run --coverage",
13
- "test": "yarn test:unit",
14
- "lint:code": "eslint *.js lib/ --ext .js --cache",
15
- "lint": "yarn lint:code && yarn lint:test",
16
- "lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache"
5
+ "author": "Ghost Foundation",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/TryGhost/framework.git",
9
+ "directory": "packages/job-manager"
17
10
  },
18
11
  "files": [
19
12
  "index.js",
20
13
  "lib"
21
14
  ],
22
- "devDependencies": {
23
- "@sinonjs/fake-timers": "15.1.1",
24
- "date-fns": "4.1.0",
25
- "rewire": "9.0.1",
26
- "sinon": "21.0.2"
15
+ "main": "index.js",
16
+ "publishConfig": {
17
+ "access": "public"
27
18
  },
28
19
  "dependencies": {
29
20
  "@breejs/later": "4.2.0",
30
- "@tryghost/errors": "^3.0.3",
31
- "@tryghost/logging": "^4.0.3",
32
21
  "bree": "9.2.9",
33
22
  "cron-validate": "1.5.3",
34
23
  "fastq": "1.20.1",
35
24
  "p-wait-for": "6.0.0",
36
- "workerpool": "10.0.1"
25
+ "workerpool": "10.0.2",
26
+ "@tryghost/errors": "3.1.0",
27
+ "@tryghost/logging": "4.1.0"
37
28
  },
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/TryGhost/framework.git",
41
- "directory": "packages/job-manager"
29
+ "devDependencies": {
30
+ "@sinonjs/fake-timers": "15.3.2",
31
+ "date-fns": "4.1.0",
32
+ "rewire": "9.0.1",
33
+ "sinon": "21.1.2"
34
+ },
35
+ "scripts": {
36
+ "dev": "echo \"Implement me!\"",
37
+ "test:unit": "NODE_ENV=testing vitest run --coverage",
38
+ "test": "pnpm run test:unit",
39
+ "lint": "oxlint -c ../../.oxlintrc.json ."
42
40
  }
43
- }
41
+ }