@abtnode/queue 1.16.8-beta-186fd5aa → 1.16.8-next-d1e52353

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/lib/index.js CHANGED
@@ -4,28 +4,31 @@ const Queue = require('fastq');
4
4
  const EventEmitter = require('events');
5
5
  const tryWithTimeout = require('@abtnode/util/lib/try-with-timeout');
6
6
  const sleep = require('@abtnode/util/lib/sleep');
7
- const debug = require('debug')('core/queue');
8
7
 
9
- const JobStore = require('./store');
8
+ const NedbStore = require('./store/nedb');
10
9
  const logger = require('./logger');
11
10
 
12
11
  const CANCELLED = '__CANCELLED__';
13
- const MIN_DELAY = 5;
12
+ const MIN_DELAY = process.env.NODE_ENV === 'test' ? 2 : 5;
14
13
 
15
14
  /**
16
15
  *
17
- * @param {Object} Options
18
- * @param {string} Options.file filepath of JobStore
19
- * @param {function} Options.onJob called on receives job
20
- * @param {Object} Options.options options
21
- * @param {string} Options.options.id id of the job
22
- * @param {number} Options.options.concurrency [param=1] number of concurrent jobs
23
- * @param {number} Options.options.maxRetries [param=1] number of max retries, default 1
24
- * @param {number} Options.options.maxTimeout [param=86400000] max timeout, in ms, default 86400000ms(1d)
25
- * @param {number} Options.options.retryDelay [param=0] retry delay, in ms, default 0ms
26
- * @param {boolean} Options.options.enableScheduledJob [param=false] enable scheduled job or not, default is false
16
+ * @param {Object} config
17
+ * @param {string} [config.file] filepath of JobStore
18
+ * @param {object} [config.store] queue store instance
19
+ * @param {function} config.onJob called on receives job
20
+ * @param {Object} [config.options] options
21
+ * @param {string} [config.options.id] id of the job
22
+ * @param {number} [config.options.concurrency] [param=1] number of concurrent jobs
23
+ * @param {number} [config.options.maxRetries] [param=1] number of max retries, default 1
24
+ * @param {number} [config.options.maxTimeout] [param=86400000] max timeout, in ms, default 86400000ms(1d)
25
+ * @param {number} [config.options.retryDelay] [param=0] retry delay, in ms, default 0ms
26
+ * @param {boolean} [config.options.enableScheduledJob] [param=false] enable scheduled job or not, default is false
27
27
  */
28
- module.exports = function createQueue({ file, onJob, options = {} }) {
28
+ module.exports = function createQueue({ file, store, onJob, options = {} }) {
29
+ if (!file && !store) {
30
+ throw new Error('Either nedb file path or store instance must be provided to create a queue');
31
+ }
29
32
  if (typeof onJob !== 'function') {
30
33
  throw new Error('onJob must be a function to create a queue');
31
34
  }
@@ -42,13 +45,16 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
42
45
  const maxTimeout = Math.max(options.maxTimeout || defaults.maxTimeout, 0);
43
46
  const retryDelay = Math.max(options.retryDelay || defaults.retryDelay, 0);
44
47
  const enableScheduledJob = typeof options.enableScheduledJob === 'boolean' ? options.enableScheduledJob : false;
48
+ const queueEvents = new EventEmitter();
45
49
 
46
50
  if (typeof options.maxRetries === 'number' && options.maxRetries >= 0) {
47
51
  maxRetries = options.maxRetries;
48
52
  }
49
53
 
50
- const store = new JobStore(file);
51
- const queueEvents = new EventEmitter();
54
+ if (file) {
55
+ // eslint-disable-next-line no-param-reassign
56
+ store = new NedbStore(file);
57
+ }
52
58
 
53
59
  const getJobId = (jobId, job) =>
54
60
  jobId || (typeof options.id === 'function' ? options.id(job) : uuid.v4()) || uuid.v4();
@@ -77,11 +83,11 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
77
83
 
78
84
  /**
79
85
  * Push job to the queue, the old way of calling `push(job, jobId, persist)` will be deprecated
80
- * @param {object} args
81
- * @param {object} args.job The data of the job
82
- * @param {string} args.jobId Optional, custom jobId
83
- * @param {boolean} args.persist [persist=true] Persisting the job to the database
84
- * @param {number} args.delay Optional, default with no delay, unit is second, for example: 10 will run after 10s
86
+ * @param {object} params
87
+ * @param {object} params.job The data of the job
88
+ * @param {string} params.jobId Optional, custom jobId
89
+ * @param {boolean} params.persist [persist=true] Persisting the job to the database
90
+ * @param {number} params.delay Optional, default with no delay, unit is second, for example: 10 will run after 10s
85
91
  * @returns
86
92
  */
87
93
  const push = (...args) => {
@@ -101,10 +107,10 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
101
107
  [job, jobId, persist = true] = args;
102
108
  }
103
109
 
104
- const jobEvents = new EventEmitter();
110
+ const events = new EventEmitter();
105
111
  const emit = (e, data) => {
106
112
  queueEvents.emit(e, data);
107
- jobEvents.emit(e, data);
113
+ events.emit(e, data);
108
114
  };
109
115
 
110
116
  if (!job) {
@@ -123,41 +129,18 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
123
129
  throw new Error(`minimum delay is ${MIN_DELAY}s`);
124
130
  }
125
131
 
126
- store.addJob(
127
- id,
128
- job,
129
- (err) => {
130
- if (err) {
131
- logger.error('failed to add job', err);
132
- emit('failed', { id, job, error: err });
133
- return;
134
- }
135
-
136
- emit('queued', { id, job });
137
- },
138
- { delay, willRunAt: Date.now() + delay * 1000 }
139
- );
140
-
141
- jobEvents.id = id;
132
+ store.addJob(id, job, { delay, willRunAt: Date.now() + delay * 1000 }).then(() => {
133
+ emit('queued', { id, job });
134
+ });
142
135
 
143
- return jobEvents;
136
+ events.id = id;
137
+ return events;
144
138
  }
145
139
 
146
140
  // eslint-disable-next-line no-shadow
147
- const clearJob = (id, cb) => {
148
- store.deleteJob(id, (e) => {
149
- if (typeof cb === 'function') {
150
- cb(e);
151
- return;
152
- }
153
-
154
- if (e) {
155
- logger.error('Failed to delete job from persistent store', { error: e, id });
156
- }
157
- });
158
- };
141
+ const clearJob = (id) => store.deleteJob(id);
159
142
 
160
- const onJobComplete = (err, result) => {
143
+ const onJobComplete = async (err, result) => {
161
144
  if (result === CANCELLED) {
162
145
  emit('cancelled', { id, job, result });
163
146
  clearJob(id);
@@ -170,125 +153,99 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
170
153
  return;
171
154
  }
172
155
 
173
- store.getJob(id, (e, attrs) => {
174
- if (e) {
175
- logger.error('Failed to get job for retry', { error: e, id });
176
- emit('failed', { id, job, error: err });
177
- return;
178
- }
179
-
180
- if (attrs.retryCount >= maxRetries) {
156
+ try {
157
+ const doc = await store.getJob(id);
158
+ if (doc.retryCount >= maxRetries) {
181
159
  logger.info('fail job', { id });
182
- clearJob(id, () => {
183
- emit('failed', { id, job, error: err });
184
- });
160
+ await clearJob(id);
161
+ emit('failed', { id, job, error: err });
185
162
  return;
186
163
  }
187
164
 
188
- store.updateJob(id, { retryCount: attrs.retryCount + 1 }, (_e) => {
189
- if (_e) {
190
- logger.error('Failed to update job for retry', { error: _e, id });
191
- emit('failed', { id, job, error: _e });
192
- clearJob(id);
193
- return;
194
- }
195
-
196
- logger.info('retry job', { id, count: attrs.retryCount + 1 });
197
- setTimeout(() => {
198
- queue.unshift({ id, job }, onJobComplete);
199
- }, retryDelay);
200
- });
201
- });
165
+ await store.updateJob(id, { retryCount: doc.retryCount + 1 });
166
+ logger.info('retry job', { id, count: doc.retryCount + 1 });
167
+ setTimeout(() => {
168
+ queue.unshift({ id, job }, onJobComplete);
169
+ }, retryDelay);
170
+ // eslint-disable-next-line no-shadow
171
+ } catch (err) {
172
+ console.error(err);
173
+ await clearJob(id);
174
+ emit('failed', { id, job, error: err });
175
+ }
202
176
  };
203
177
 
204
- if (persist) {
205
- store.addJob(id, job, (err) => {
206
- if (err) {
207
- logger.error('failed to add job', err);
208
- emit('failed', { id, job, error: err });
209
- return;
210
- }
211
-
212
- setImmediate(() => {
213
- emit('queued', { id, job });
214
- logger.info('queue job', { id, job, persist });
215
- queue.push({ id, job, persist }, onJobComplete);
216
- });
217
- });
218
- } else {
178
+ const queueJob = () =>
219
179
  setImmediate(() => {
220
180
  emit('queued', { id, job, persist });
221
181
  logger.info('queue job', { id, job });
222
182
  queue.push({ id, job, persist }, onJobComplete);
223
183
  });
224
- }
225
184
 
226
- jobEvents.id = id;
185
+ if (persist) {
186
+ store.addJob(id, job).then(queueJob);
187
+ } else {
188
+ queueJob();
189
+ }
227
190
 
228
- return jobEvents;
191
+ events.id = id;
192
+ return events;
229
193
  };
230
194
 
231
- const cancel = async (id) =>
232
- new Promise((resolve, reject) => {
233
- store.updateJob(id, { cancelled: true }, (e, doc) => {
234
- if (e) {
235
- return reject(e);
236
- }
237
- return resolve(doc ? doc.job : null);
238
- });
239
- });
240
-
241
- const getJob = async (id) =>
242
- new Promise((resolve, reject) => {
243
- store.getJob(id, (e, doc) => {
244
- if (e) {
245
- return reject(e);
246
- }
195
+ const cancel = async (id) => {
196
+ const doc = await store.updateJob(id, { cancelled: true });
197
+ return doc ? doc.job : null;
198
+ };
247
199
 
248
- return resolve(doc ? doc.job : null);
249
- });
250
- });
200
+ const getJob = async (id) => {
201
+ const doc = await store.getJob(id);
202
+ return doc ? doc.job : null;
203
+ };
251
204
 
252
205
  // Populate the queue on startup
253
- store.db.loadDatabase((err) => {
206
+ store.loadDatabase(async (err) => {
254
207
  if (err) {
255
- logger.error('Can not load job database from disk', { error: err });
208
+ logger.error('Can not load job database', { error: err });
256
209
  throw err;
257
210
  }
258
211
 
259
- debug('queue database loaded', { file });
260
-
261
- store.getJobs((e, jobs) => {
262
- if (err) {
263
- logger.error('Can not find old jobs from db', { error: e });
264
- } else {
265
- debug('queue jobs to populate', { count: jobs.length });
266
- jobs.forEach((x) => {
267
- if (x.job && x._id) {
268
- push(x.job, x._id, false);
269
- } else {
270
- logger.info('skip invalid job from db', { job: x });
271
- }
272
- });
273
- }
274
- });
212
+ try {
213
+ const jobs = await store.getJobs();
214
+ logger.info('queue jobs to populate', { count: jobs.length });
215
+ jobs.forEach((x) => {
216
+ if (x.job && x.id) {
217
+ push(x.job, x.id, false);
218
+ } else {
219
+ logger.info('skip invalid job from db', { job: x });
220
+ }
221
+ });
222
+ // eslint-disable-next-line no-shadow
223
+ } catch (err) {
224
+ console.error(err);
225
+ logger.error('Can not load existing jobs', { error: err });
226
+ }
275
227
  });
276
228
 
277
229
  const loop = async () => {
278
230
  if (enableScheduledJob === true) {
279
231
  // eslint-disable-next-line no-constant-condition
280
232
  while (true) {
281
- /* eslint-disable no-await-in-loop */
282
- const jobs = await store.getScheduledJobs();
283
- jobs.forEach((x) => {
284
- if (x.job && x._id) {
285
- push(x.job, x._id, false);
286
- } else {
287
- logger.info('skip invalid job from db', { job: x });
288
- }
289
- });
290
-
291
- await sleep(2000);
233
+ try {
234
+ /* eslint-disable no-await-in-loop */
235
+ const jobs = await store.getScheduledJobs();
236
+ jobs.forEach((x) => {
237
+ if (x.job && x.id) {
238
+ push(x.job, x.id, false);
239
+ } else {
240
+ logger.info('skip invalid job from db', { job: x });
241
+ }
242
+ });
243
+ } catch (err) {
244
+ console.error(err);
245
+ logger.error('Can not load scheduled jobs', { error: err });
246
+ }
247
+
248
+ await sleep(MIN_DELAY / 2);
292
249
  }
293
250
  }
294
251
  };
package/lib/logger.js CHANGED
@@ -1,9 +1,10 @@
1
1
  const debug = require('debug')(require('../package.json').name);
2
2
 
3
- const noop = () => {};
4
- const isTest = !!process.env.NODE_ENV && process.env.NODE_ENV.startsWith('test');
5
-
6
- module.exports = ['error', 'warn', 'info', 'debug', 'verbose', 'silly'].reduce((acc, fn) => {
7
- acc[fn] = isTest ? noop : debug;
8
- return acc;
9
- }, {});
3
+ module.exports = {
4
+ error: debug,
5
+ warn: debug,
6
+ info: debug,
7
+ debug,
8
+ verbose: debug,
9
+ silly: debug,
10
+ };
@@ -0,0 +1,74 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ /* eslint-disable func-names */
3
+ /* eslint-disable consistent-return */
4
+ const { DataStore } = require('@abtnode/db/lib/base');
5
+
6
+ class NedbStore {
7
+ constructor(dbPath) {
8
+ this.dbPath = dbPath;
9
+ this.db = new DataStore({
10
+ filename: this.dbPath,
11
+ autoload: true,
12
+ });
13
+ }
14
+
15
+ async isCancelled(id) {
16
+ const job = await this.db.findOne({ id });
17
+ return !!job && !!job.cancelled;
18
+ }
19
+
20
+ getJob(id) {
21
+ return this.db.findOne({ id });
22
+ }
23
+
24
+ getJobs() {
25
+ return this.db
26
+ .cursor({ delay: { $exists: false } })
27
+ .sort({ createdAt: 1 })
28
+ .exec();
29
+ }
30
+
31
+ getScheduledJobs() {
32
+ return this.db
33
+ .cursor({ delay: { $exists: true }, willRunAt: { $lte: Date.now() } })
34
+ .sort({ createdAt: 1 })
35
+ .exec();
36
+ }
37
+
38
+ async updateJob(id, updates) {
39
+ const job = await this.db.findOne({ id });
40
+ if (!job) {
41
+ throw new Error(`Job ${id} does not exist`);
42
+ }
43
+
44
+ const update = { ...updates, updatedAt: Date.now() };
45
+ await this.db.update({ id }, { $set: update });
46
+ return Object.assign(job, update);
47
+ }
48
+
49
+ async addJob(id, job, attrs = {}) {
50
+ const exist = await this.db.findOne({ id });
51
+ if (exist) {
52
+ throw new Error(`Job ${id} already exist`);
53
+ }
54
+ return this.db.insert({
55
+ id,
56
+ job,
57
+ retryCount: 1,
58
+ cancelled: false,
59
+ ...attrs,
60
+ createdAt: Date.now(),
61
+ updatedAt: Date.now(),
62
+ });
63
+ }
64
+
65
+ async deleteJob(id) {
66
+ return this.db.remove({ id });
67
+ }
68
+
69
+ loadDatabase(cb) {
70
+ this.db.loadDatabase(cb);
71
+ }
72
+ }
73
+
74
+ module.exports = NedbStore;
@@ -0,0 +1,65 @@
1
+ class SequelizeStore {
2
+ constructor(db, queue) {
3
+ this.db = db;
4
+ this.queue = queue;
5
+ }
6
+
7
+ async isCancelled(id) {
8
+ const job = await this.db.findOne({ queue: this.queue, id });
9
+ return !!job && !!job.cancelled;
10
+ }
11
+
12
+ getJob(id) {
13
+ return this.db.findOne({ queue: this.queue, id });
14
+ }
15
+
16
+ getJobs() {
17
+ return this.db.find({ queue: this.queue, delay: { $exists: false } }, {}, { createdAt: 1 });
18
+ }
19
+
20
+ getScheduledJobs() {
21
+ return this.db.find(
22
+ { queue: this.queue, delay: { $exists: true }, willRunAt: { $lte: Date.now() } },
23
+ {},
24
+ { createdAt: 1 }
25
+ );
26
+ }
27
+
28
+ async updateJob(id, updates) {
29
+ const job = await this.db.findOne({ queue: this.queue, id });
30
+ if (!job) {
31
+ throw new Error(`Job ${id} does not exist`);
32
+ }
33
+
34
+ const update = { ...updates, updatedAt: Date.now() };
35
+ await this.db.update({ queue: this.queue, id }, { $set: update });
36
+ return Object.assign(job, update);
37
+ }
38
+
39
+ async addJob(id, job, attrs = {}) {
40
+ const exist = await this.db.findOne({ queue: this.queue, id });
41
+ if (exist) {
42
+ throw new Error(`Job ${id} already exist`);
43
+ }
44
+ return this.db.insert({
45
+ id,
46
+ job,
47
+ queue: this.queue,
48
+ retryCount: 1,
49
+ cancelled: false,
50
+ ...attrs,
51
+ createdAt: Date.now(),
52
+ updatedAt: Date.now(),
53
+ });
54
+ }
55
+
56
+ async deleteJob(id) {
57
+ return this.db.remove({ queue: this.queue, id });
58
+ }
59
+
60
+ loadDatabase(cb) {
61
+ return this.db.loadDatabase(cb);
62
+ }
63
+ }
64
+
65
+ module.exports = SequelizeStore;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.8-beta-186fd5aa",
6
+ "version": "1.16.8-next-d1e52353",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -13,19 +13,19 @@
13
13
  "lint": "eslint tests lib",
14
14
  "lint:fix": "eslint --fix tests lib",
15
15
  "test": "node tools/jest.js",
16
- "coverage": "npm run test -- --coverage"
16
+ "coverage": "npm run test -- --coverage --runInBand"
17
17
  },
18
18
  "keywords": [],
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/db": "1.16.8-beta-186fd5aa",
23
- "@abtnode/util": "1.16.8-beta-186fd5aa",
22
+ "@abtnode/db": "1.16.8-next-d1e52353",
23
+ "@abtnode/util": "1.16.8-next-d1e52353",
24
24
  "debug": "^4.3.4",
25
25
  "fastq": "^1.13.0",
26
- "uuid": "7.0.3"
26
+ "uuid": "^8.3.2"
27
27
  },
28
- "gitHead": "3150c3a5c3e041fe7f5a3902418d9d62adee3173",
28
+ "gitHead": "d357376aa3df9ef789befc7f548629deecb04d96",
29
29
  "devDependencies": {
30
30
  "jest": "^27.5.1"
31
31
  }
package/lib/store.js DELETED
@@ -1,104 +0,0 @@
1
- /* eslint-disable no-underscore-dangle */
2
- /* eslint-disable func-names */
3
- /* eslint-disable consistent-return */
4
- const { DataStore } = require('@abtnode/db/lib/base');
5
-
6
- class JobStore {
7
- constructor(dbPath) {
8
- this.dbPath = dbPath;
9
- this.db = new DataStore({
10
- filename: this.dbPath,
11
- autoload: true,
12
- });
13
- }
14
-
15
- isCancelled(id) {
16
- return new Promise((resolve, reject) => {
17
- this.db.findOne({ _id: id }, (e, job) => {
18
- if (e) return reject(e);
19
- return resolve(!!job && !!job.cancelled);
20
- });
21
- });
22
- }
23
-
24
- getJob(id, cb) {
25
- this.db.findOne({ _id: id }, cb);
26
- }
27
-
28
- getJobs(cb) {
29
- this.db
30
- .cursor({ delay: { $exists: false } })
31
- .sort({ createdAt: 1 })
32
- .exec((err, result) => {
33
- if (err) {
34
- console.error('error find jobs', err);
35
- }
36
- cb(err, result);
37
- });
38
- }
39
-
40
- getScheduledJobs() {
41
- return this.db
42
- .cursor({ delay: { $exists: true }, willRunAt: { $lte: Date.now() } })
43
- .sort({ createdAt: 1 })
44
- .exec();
45
- }
46
-
47
- updateJob(id, updates, cb) {
48
- this.db.findOne({ _id: id }, (err, exist) => {
49
- if (err) return cb(err);
50
- if (!exist) return cb(new Error(`Job ${id} does not exist`));
51
-
52
- const update = {
53
- ...updates,
54
- updatedAt: Date.now(),
55
- };
56
- this.db.update(
57
- {
58
- _id: id,
59
- },
60
- {
61
- $set: update,
62
- },
63
- (e) => {
64
- if (e) {
65
- console.error('error updating job', e);
66
- return cb(e, null);
67
- }
68
- return cb(null, Object.assign(exist, update));
69
- }
70
- );
71
- });
72
- }
73
-
74
- addJob(id, job, cb, attrs = {}) {
75
- this.db.findOne({ _id: id }, (err, exist) => {
76
- if (err) return cb(err);
77
- if (exist) return new Error(`Job ${id} already exist`);
78
- this.db.insert(
79
- {
80
- _id: id,
81
- job,
82
- retryCount: 1,
83
- cancelled: false,
84
- ...attrs,
85
- createdAt: Date.now(),
86
- updatedAt: Date.now(),
87
- },
88
- (e) => {
89
- if (e) console.error('error inserting job', e);
90
- return cb(e);
91
- }
92
- );
93
- });
94
- }
95
-
96
- deleteJob(id, cb) {
97
- this.db.findOne({ _id: id }, (err) => {
98
- if (err) return cb(err);
99
- this.db.remove({ _id: id }, {}, (e) => cb(e));
100
- });
101
- }
102
- }
103
-
104
- module.exports = JobStore;