node-schedule 1.3.3 → 2.0.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/lib/schedule.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  'use strict';
3
2
 
4
3
  /*
@@ -6,278 +5,7 @@
6
5
  A cron-like and not-cron-like job scheduler for Node.
7
6
  */
8
7
 
9
- var events = require('events'),
10
- util = require('util'),
11
- cronParser = require('cron-parser'),
12
- CronDate = require('cron-parser/lib/date'),
13
- lt = require('long-timeout'),
14
- sorted = require('sorted-array-functions'),
15
- nodeVersionUtils = require('./nodeVersionUtils')
16
-
17
- /* Job object */
18
- var anonJobCounter = 0;
19
- var scheduledJobs = {};
20
- var promisesSupported = nodeVersionUtils.arePromisesSupported(nodeVersionUtils.getNodeVersion(nodeVersionUtils.nodeVersionString))
21
-
22
- function isValidDate(date) {
23
- // Taken from http://stackoverflow.com/a/12372720/1562178
24
- // If getTime() returns NaN it'll return false anyway
25
- return date.getTime() === date.getTime();
26
- }
27
-
28
- function Job(name, job, callback) {
29
- // setup a private pendingInvocations variable
30
- var pendingInvocations = [];
31
-
32
- //setup a private number of invocations variable
33
- var triggeredJobs = 0;
34
-
35
- // Set scope vars
36
- var jobName = name && typeof name === 'string' ? name : '<Anonymous Job ' + (++anonJobCounter) + '>';
37
- this.job = name && typeof name === 'function' ? name : job;
38
-
39
- // Make sure callback is actually a callback
40
- if (this.job === name) {
41
- // Name wasn't provided and maybe a callback is there
42
- this.callback = typeof job === 'function' ? job : false;
43
- } else {
44
- // Name was provided, and maybe a callback is there
45
- this.callback = typeof callback === 'function' ? callback : false;
46
- }
47
-
48
- // Check for generator
49
- if (typeof this.job === 'function' &&
50
- this.job.prototype &&
51
- this.job.prototype.next) {
52
- this.job = function() {
53
- return this.next().value;
54
- }.bind(this.job.call(this));
55
- }
56
-
57
- // define properties
58
- Object.defineProperty(this, 'name', {
59
- value: jobName,
60
- writable: false,
61
- enumerable: true
62
- });
63
-
64
- // method that require private access
65
- this.trackInvocation = function(invocation) {
66
- // add to our invocation list
67
- sorted.add(pendingInvocations, invocation, sorter);
68
- return true;
69
- };
70
- this.stopTrackingInvocation = function(invocation) {
71
- var invIdx = pendingInvocations.indexOf(invocation);
72
- if (invIdx > -1) {
73
- pendingInvocations.splice(invIdx, 1);
74
- return true;
75
- }
76
-
77
- return false;
78
- };
79
- this.triggeredJobs = function() {
80
- return triggeredJobs;
81
- };
82
- this.setTriggeredJobs = function(triggeredJob) {
83
- triggeredJobs = triggeredJob;
84
- };
85
- this.cancel = function(reschedule) {
86
- reschedule = (typeof reschedule == 'boolean') ? reschedule : false;
87
-
88
- var inv, newInv;
89
- var newInvs = [];
90
- for (var j = 0; j < pendingInvocations.length; j++) {
91
- inv = pendingInvocations[j];
92
-
93
- cancelInvocation(inv);
94
-
95
- if (reschedule && (inv.recurrenceRule.recurs || inv.recurrenceRule.next)) {
96
- newInv = scheduleNextRecurrence(inv.recurrenceRule, this, inv.fireDate, inv.endDate);
97
- if (newInv !== null) {
98
- newInvs.push(newInv);
99
- }
100
- }
101
- }
102
-
103
- pendingInvocations = [];
104
-
105
- for (var k = 0; k < newInvs.length; k++) {
106
- this.trackInvocation(newInvs[k]);
107
- }
108
-
109
- // remove from scheduledJobs if reschedule === false
110
- if (!reschedule) {
111
- if (this.name) {
112
- delete scheduledJobs[this.name];
113
- }
114
- }
115
-
116
- return true;
117
- };
118
- this.cancelNext = function(reschedule) {
119
- reschedule = (typeof reschedule == 'boolean') ? reschedule : true;
120
-
121
- if (!pendingInvocations.length) {
122
- return false;
123
- }
124
-
125
- var newInv;
126
- var nextInv = pendingInvocations.shift();
127
-
128
- cancelInvocation(nextInv);
129
-
130
- if (reschedule && (nextInv.recurrenceRule.recurs || nextInv.recurrenceRule.next)) {
131
- newInv = scheduleNextRecurrence(nextInv.recurrenceRule, this, nextInv.fireDate, nextInv.endDate);
132
- if (newInv !== null) {
133
- this.trackInvocation(newInv);
134
- }
135
- }
136
-
137
- return true;
138
- };
139
- this.reschedule = function(spec) {
140
- var inv;
141
- var cInvs = pendingInvocations.slice();
142
-
143
- for (var j = 0; j < cInvs.length; j++) {
144
- inv = cInvs[j];
145
-
146
- cancelInvocation(inv);
147
- }
148
-
149
- pendingInvocations = [];
150
-
151
- if (this.schedule(spec)) {
152
- this.setTriggeredJobs(0);
153
- return true;
154
- } else {
155
- pendingInvocations = cInvs;
156
- return false;
157
- }
158
- };
159
- this.nextInvocation = function() {
160
- if (!pendingInvocations.length) {
161
- return null;
162
- }
163
- return pendingInvocations[0].fireDate;
164
- };
165
- this.pendingInvocations = function() {
166
- return pendingInvocations;
167
- };
168
- }
169
-
170
- util.inherits(Job, events.EventEmitter);
171
-
172
- Job.prototype.invoke = function(fireDate) {
173
- if (typeof this.job == 'function') {
174
- this.setTriggeredJobs(this.triggeredJobs() + 1);
175
- return this.job(fireDate);
176
- } else {
177
- this.job.execute(fireDate);
178
- }
179
- };
180
-
181
- Job.prototype.runOnDate = function(date) {
182
- return this.schedule(date);
183
- };
184
-
185
- Job.prototype.schedule = function(spec) {
186
- var self = this;
187
- var success = false;
188
- var inv;
189
- var start;
190
- var end;
191
- var tz;
192
-
193
- // save passed-in value before 'spec' is replaced
194
- if (typeof spec === 'object' && 'tz' in spec) {
195
- tz = spec.tz;
196
- }
197
-
198
- if (typeof spec === 'object' && spec.rule) {
199
- start = spec.start || undefined;
200
- end = spec.end || undefined;
201
- spec = spec.rule;
202
-
203
- if (start) {
204
- if (!(start instanceof Date)) {
205
- start = new Date(start);
206
- }
207
-
208
- start = new CronDate(start, tz);
209
- if (!isValidDate(start) || start.getTime() < Date.now()) {
210
- start = undefined;
211
- }
212
- }
213
-
214
- if (end && !(end instanceof Date) && !isValidDate(end = new Date(end))) {
215
- end = undefined;
216
- }
217
-
218
- if (end) {
219
- end = new CronDate(end, tz);
220
- }
221
- }
222
-
223
- try {
224
- var res = cronParser.parseExpression(spec, { currentDate: start, tz: tz });
225
- inv = scheduleNextRecurrence(res, self, start, end);
226
- if (inv !== null) {
227
- success = self.trackInvocation(inv);
228
- }
229
- } catch (err) {
230
- var type = typeof spec;
231
- if ((type === 'string') || (type === 'number')) {
232
- spec = new Date(spec);
233
- }
234
-
235
- if ((spec instanceof Date) && (isValidDate(spec))) {
236
- spec = new CronDate(spec);
237
- if (spec.getTime() >= Date.now()) {
238
- inv = new Invocation(self, spec);
239
- scheduleInvocation(inv);
240
- success = self.trackInvocation(inv);
241
- }
242
- } else if (type === 'object') {
243
- if (!(spec instanceof RecurrenceRule)) {
244
- var r = new RecurrenceRule();
245
- if ('year' in spec) {
246
- r.year = spec.year;
247
- }
248
- if ('month' in spec) {
249
- r.month = spec.month;
250
- }
251
- if ('date' in spec) {
252
- r.date = spec.date;
253
- }
254
- if ('dayOfWeek' in spec) {
255
- r.dayOfWeek = spec.dayOfWeek;
256
- }
257
- if ('hour' in spec) {
258
- r.hour = spec.hour;
259
- }
260
- if ('minute' in spec) {
261
- r.minute = spec.minute;
262
- }
263
- if ('second' in spec) {
264
- r.second = spec.second;
265
- }
266
-
267
- spec = r;
268
- }
269
-
270
- spec.tz = tz;
271
- inv = scheduleNextRecurrence(spec, self, start, end);
272
- if (inv !== null) {
273
- success = self.trackInvocation(inv);
274
- }
275
- }
276
- }
277
-
278
- scheduledJobs[this.name] = this;
279
- return success;
280
- };
8
+ const { Job, scheduledJobs } = require('./Job')
281
9
 
282
10
  /* API
283
11
  invoke()
@@ -291,337 +19,22 @@ Job.prototype.schedule = function(spec) {
291
19
  job: readwrite
292
20
  */
293
21
 
294
- /* DoesntRecur rule */
295
- var DoesntRecur = new RecurrenceRule();
296
- DoesntRecur.recurs = false;
297
-
298
- /* Invocation object */
299
- function Invocation(job, fireDate, recurrenceRule, endDate) {
300
- this.job = job;
301
- this.fireDate = fireDate;
302
- this.endDate = endDate;
303
- this.recurrenceRule = recurrenceRule || DoesntRecur;
304
-
305
- this.timerID = null;
306
- }
307
-
308
- function sorter(a, b) {
309
- return (a.fireDate.getTime() - b.fireDate.getTime());
310
- }
311
-
312
- /* Range object */
313
- function Range(start, end, step) {
314
- this.start = start || 0;
315
- this.end = end || 60;
316
- this.step = step || 1;
317
- }
318
-
319
- Range.prototype.contains = function(val) {
320
- if (this.step === null || this.step === 1) {
321
- return (val >= this.start && val <= this.end);
322
- } else {
323
- for (var i = this.start; i < this.end; i += this.step) {
324
- if (i === val) {
325
- return true;
326
- }
327
- }
328
-
329
- return false;
330
- }
331
- };
332
-
333
- /* RecurrenceRule object */
334
- /*
335
- Interpreting each property:
336
- null - any value is valid
337
- number - fixed value
338
- Range - value must fall in range
339
- array - value must validate against any item in list
340
-
341
- NOTE: Cron months are 1-based, but RecurrenceRule months are 0-based.
342
- */
343
- function RecurrenceRule(year, month, date, dayOfWeek, hour, minute, second) {
344
- this.recurs = true;
345
-
346
- this.year = (year == null) ? null : year;
347
- this.month = (month == null) ? null : month;
348
- this.date = (date == null) ? null : date;
349
- this.dayOfWeek = (dayOfWeek == null) ? null : dayOfWeek;
350
- this.hour = (hour == null) ? null : hour;
351
- this.minute = (minute == null) ? null : minute;
352
- this.second = (second == null) ? 0 : second;
353
- }
354
-
355
- RecurrenceRule.prototype.isValid = function() {
356
- function isValidType(num) {
357
- if (Array.isArray(num) || (num instanceof Array)) {
358
- return num.every(function(e) {
359
- return isValidType(e);
360
- });
361
- }
362
- return !(Number.isNaN(Number(num)) && !(num instanceof Range));
363
- }
364
- if (this.month !== null && (this.month < 0 || this.month > 11 || !isValidType(this.month))) {
365
- return false;
366
- }
367
- if (this.dayOfWeek !== null && (this.dayOfWeek < 0 || this.dayOfWeek > 6 || !isValidType(this.dayOfWeek))) {
368
- return false;
369
- }
370
- if (this.hour !== null && (this.hour < 0 || this.hour > 23 || !isValidType(this.hour))) {
371
- return false;
372
- }
373
- if (this.minute !== null && (this.minute < 0 || this.minute > 59 || !isValidType(this.minute))) {
374
- return false;
375
- }
376
- if (this.second !== null && (this.second < 0 || this.second > 59 || !isValidType(this.second))) {
377
- return false;
378
- }
379
- if (this.date !== null) {
380
- if(!isValidType(this.date)) {
381
- return false;
382
- }
383
- switch (this.month) {
384
- case 3:
385
- case 5:
386
- case 8:
387
- case 10:
388
- if (this.date < 1 || this. date > 30) {
389
- return false;
390
- }
391
- break;
392
- case 1:
393
- if (this.date < 1 || this. date > 29) {
394
- return false;
395
- }
396
- break;
397
- default:
398
- if (this.date < 1 || this. date > 31) {
399
- return false;
400
- }
401
- }
402
- }
403
- return true;
404
- };
405
-
406
- RecurrenceRule.prototype.nextInvocationDate = function(base) {
407
- var next = this._nextInvocationDate(base);
408
- return next ? next.toDate() : null;
409
- };
410
-
411
- RecurrenceRule.prototype._nextInvocationDate = function(base) {
412
- base = ((base instanceof CronDate) || (base instanceof Date)) ? base : (new Date());
413
- if (!this.recurs) {
414
- return null;
415
- }
416
-
417
- if(!this.isValid()) {
418
- return null;
419
- }
420
-
421
- var now = new CronDate(Date.now(), this.tz);
422
- var fullYear = now.getFullYear();
423
- if ((this.year !== null) &&
424
- (typeof this.year == 'number') &&
425
- (this.year < fullYear)) {
426
- return null;
427
- }
428
-
429
- var next = new CronDate(base.getTime(), this.tz);
430
- next.addSecond();
431
-
432
- while (true) {
433
- if (this.year !== null) {
434
- fullYear = next.getFullYear();
435
- if ((typeof this.year == 'number') && (this.year < fullYear)) {
436
- next = null;
437
- break;
438
- }
439
-
440
- if (!recurMatch(fullYear, this.year)) {
441
- next.addYear();
442
- next.setMonth(0);
443
- next.setDate(1);
444
- next.setHours(0);
445
- next.setMinutes(0);
446
- next.setSeconds(0);
447
- continue;
448
- }
449
- }
450
- if (this.month != null && !recurMatch(next.getMonth(), this.month)) {
451
- next.addMonth();
452
- continue;
453
- }
454
- if (this.date != null && !recurMatch(next.getDate(), this.date)) {
455
- next.addDay();
456
- continue;
457
- }
458
- if (this.dayOfWeek != null && !recurMatch(next.getDay(), this.dayOfWeek)) {
459
- next.addDay();
460
- continue;
461
- }
462
- if (this.hour != null && !recurMatch(next.getHours(), this.hour)) {
463
- next.addHour();
464
- continue;
465
- }
466
- if (this.minute != null && !recurMatch(next.getMinutes(), this.minute)) {
467
- next.addMinute();
468
- continue;
469
- }
470
- if (this.second != null && !recurMatch(next.getSeconds(), this.second)) {
471
- next.addSecond();
472
- continue;
473
- }
474
-
475
- break;
476
- }
477
-
478
- return next;
479
- };
480
-
481
- function recurMatch(val, matcher) {
482
- if (matcher == null) {
483
- return true;
484
- }
485
-
486
- if (typeof matcher === 'number') {
487
- return (val === matcher);
488
- } else if(typeof matcher === 'string') {
489
- return (val === Number(matcher));
490
- } else if (matcher instanceof Range) {
491
- return matcher.contains(val);
492
- } else if (Array.isArray(matcher) || (matcher instanceof Array)) {
493
- for (var i = 0; i < matcher.length; i++) {
494
- if (recurMatch(val, matcher[i])) {
495
- return true;
496
- }
497
- }
498
- }
499
-
500
- return false;
501
- }
502
-
503
- /* Date-based scheduler */
504
- function runOnDate(date, job) {
505
- var now = Date.now();
506
- var then = date.getTime();
507
-
508
- return lt.setTimeout(function() {
509
- if (then > Date.now())
510
- runOnDate(date, job);
511
- else
512
- job();
513
- }, (then < now ? 0 : then - now));
514
- }
515
-
516
- var invocations = [];
517
- var currentInvocation = null;
518
-
519
- function scheduleInvocation(invocation) {
520
- sorted.add(invocations, invocation, sorter);
521
- prepareNextInvocation();
522
- var date = invocation.fireDate instanceof CronDate ? invocation.fireDate.toDate() : invocation.fireDate;
523
- invocation.job.emit('scheduled', date);
524
- }
525
-
526
- function prepareNextInvocation() {
527
- if (invocations.length > 0 && currentInvocation !== invocations[0]) {
528
- if (currentInvocation !== null) {
529
- lt.clearTimeout(currentInvocation.timerID);
530
- currentInvocation.timerID = null;
531
- currentInvocation = null;
532
- }
533
-
534
- currentInvocation = invocations[0];
535
-
536
- var job = currentInvocation.job;
537
- var cinv = currentInvocation;
538
- currentInvocation.timerID = runOnDate(currentInvocation.fireDate, function() {
539
- currentInvocationFinished();
540
-
541
- if (job.callback) {
542
- job.callback();
543
- }
544
-
545
- if (cinv.recurrenceRule.recurs || cinv.recurrenceRule._endDate === null) {
546
- var inv = scheduleNextRecurrence(cinv.recurrenceRule, cinv.job, cinv.fireDate, cinv.endDate);
547
- if (inv !== null) {
548
- inv.job.trackInvocation(inv);
549
- }
550
- }
551
-
552
- job.stopTrackingInvocation(cinv);
553
-
554
- try {
555
- var result = job.invoke(cinv.fireDate instanceof CronDate ? cinv.fireDate.toDate() : cinv.fireDate);
556
- job.emit('run');
557
-
558
- if (promisesSupported && result instanceof Promise) {
559
- result.catch(function (err) {
560
- job.emit('error', err);
561
- });
562
- }
563
- } catch (err) {
564
- job.emit('error', err);
565
- }
566
- });
567
- }
568
- }
569
-
570
- function currentInvocationFinished() {
571
- invocations.shift();
572
- currentInvocation = null;
573
- prepareNextInvocation();
574
- }
575
-
576
- function cancelInvocation(invocation) {
577
- var idx = invocations.indexOf(invocation);
578
- if (idx > -1) {
579
- invocations.splice(idx, 1);
580
- if (invocation.timerID !== null) {
581
- lt.clearTimeout(invocation.timerID);
582
- }
583
-
584
- if (currentInvocation === invocation) {
585
- currentInvocation = null;
586
- }
587
-
588
- invocation.job.emit('canceled', invocation.fireDate);
589
- prepareNextInvocation();
590
- }
591
- }
592
-
593
- /* Recurrence scheduler */
594
- function scheduleNextRecurrence(rule, job, prevDate, endDate) {
595
-
596
- prevDate = (prevDate instanceof CronDate) ? prevDate : new CronDate();
597
-
598
- var date = (rule instanceof RecurrenceRule) ? rule._nextInvocationDate(prevDate) : rule.next();
599
- if (date === null) {
600
- return null;
601
- }
602
-
603
- if ((endDate instanceof CronDate) && date.getTime() > endDate.getTime()) {
604
- return null;
605
- }
606
-
607
- var inv = new Invocation(job, date, rule, endDate);
608
- scheduleInvocation(inv);
609
-
610
- return inv;
611
- }
612
-
613
22
  /* Convenience methods */
614
23
  function scheduleJob() {
615
24
  if (arguments.length < 2) {
616
- return null;
25
+ throw new RangeError('Invalid number of arguments');
617
26
  }
618
27
 
619
- var name = (arguments.length >= 3 && typeof arguments[0] === 'string') ? arguments[0] : null;
620
- var spec = name ? arguments[1] : arguments[0];
621
- var method = name ? arguments[2] : arguments[1];
622
- var callback = name ? arguments[3] : arguments[2];
28
+ const name = (arguments.length >= 3 && typeof arguments[0] === 'string') ? arguments[0] : null;
29
+ const spec = name ? arguments[1] : arguments[0];
30
+ const method = name ? arguments[2] : arguments[1];
31
+ const callback = name ? arguments[3] : arguments[2];
623
32
 
624
- var job = new Job(name, method, callback);
33
+ if (typeof method !== 'function') {
34
+ throw new RangeError('The job method must be a function.');
35
+ }
36
+
37
+ const job = new Job(name, method, callback);
625
38
 
626
39
  if (job.schedule(spec)) {
627
40
  return job;
@@ -635,18 +48,20 @@ function rescheduleJob(job, spec) {
635
48
  if (job.reschedule(spec)) {
636
49
  return job;
637
50
  }
638
- } else if (typeof job == 'string' || job instanceof String) {
639
- if (job in scheduledJobs && scheduledJobs.hasOwnProperty(job)) {
51
+ } else if (typeof job === 'string') {
52
+ if (scheduledJobs.hasOwnProperty(job)) {
640
53
  if (scheduledJobs[job].reschedule(spec)) {
641
54
  return scheduledJobs[job];
642
55
  }
56
+ } else {
57
+ throw new Error('Cannot reschedule one-off job by name, pass job reference instead')
643
58
  }
644
59
  }
645
60
  return null;
646
61
  }
647
62
 
648
63
  function cancelJob(job) {
649
- var success = false;
64
+ let success = false;
650
65
  if (job instanceof Job) {
651
66
  success = job.cancel();
652
67
  } else if (typeof job == 'string' || job instanceof String) {
@@ -659,11 +74,9 @@ function cancelJob(job) {
659
74
  }
660
75
 
661
76
  /* Public API */
662
- module.exports.Job = Job;
663
- module.exports.Range = Range;
664
- module.exports.RecurrenceRule = RecurrenceRule;
665
- module.exports.Invocation = Invocation;
666
- module.exports.scheduleJob = scheduleJob;
667
- module.exports.rescheduleJob = rescheduleJob;
668
- module.exports.scheduledJobs = scheduledJobs;
669
- module.exports.cancelJob = cancelJob;
77
+ module.exports = {
78
+ scheduleJob,
79
+ rescheduleJob,
80
+ scheduledJobs,
81
+ cancelJob
82
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ function isValidDate(date) {
4
+ // Taken from http://stackoverflow.com/a/12372720/1562178
5
+ // If getTime() returns NaN it'll return false anyway
6
+ return date.getTime() === date.getTime();
7
+ }
8
+
9
+ module.exports = {
10
+ isValidDate
11
+ }