-
Notifications
You must be signed in to change notification settings - Fork 0
/
schedule.js
128 lines (120 loc) · 5.16 KB
/
schedule.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* eslint-disable no-loop-func */
const path = require('path');
const lt = require('long-timeout');
const { get: getStack } = require('method-stack');
const { getNext, getTime } = require('./expression');
let clients = null;
let pending = [];
const timeouts = {};
const callFunc = (func, data, modules) => {
data = data instanceof Map ? Object.fromEntries(data) : data;
modules = modules instanceof Map ? Object.fromEntries(modules) : modules;
const keys = Object.keys(modules);
for (let k = 0; k < keys.length; k += 1) modules[keys[k]] = require(modules[keys[k]]);
if (typeof func === 'string') return eval(`(${func})`)(data, modules);
func(data, modules);
};
const defineTimeout = (id, func, time, data, modules, Client) => {
const after = () => {
delete timeouts[id];
callFunc(func, data, modules);
clients[Client].delete({ id });
};
if (time <= 0) return after();
timeouts[id] = lt.setTimeout(after, time);
};
module.exports = (clientPromises, SetClients) => {
/**
* Specifies the client to store data on
* @typedef {object} On
* @property {Function} On.on - 'redis' or 'mongodb'
*/
/**
* Schedules a function for being executed once after a specified time
* @param {object} options - Schedule options
* @param {Function} options.func - Function to be called after the time specified
* @param {object} options.expression - Time expression: it can be a cron, a date or an object where the keys are the units of time.
* @param {Number} options.expression.millisecond - Millisecond unit.
* @param {Number} options.expression.milliseconds - Millisecond unit.
* @param {Number} options.expression.second - Second unit.
* @param {Number} options.expression.seconds - Second unit.
* @param {Number} options.expression.minute - Minute unit.
* @param {Number} options.expression.minutes - Minute unit.
* @param {Number} options.expression.hour - Hour unit.
* @param {Number} options.expression.hours - Hour unit.
* @param {Number} options.expression.day - Day unit.
* @param {Number} options.expression.days - Day unit.
* @param {Number} options.expression.week - Week unit.
* @param {Number} options.expression.weeks - Week unit.
* @param {Number} options.expression.month - Month unit.
* @param {Number} options.expression.months - Month unit.
* @param {Number} options.expression.quarter - Quarter unit.
* @param {Number} options.expression.quarters - Quarter unit.
* @param {Number} options.expression.year - Year unit.
* @param {Number} options.expression.years - Year unit.
* @param {object} options.data - Store data for using when the function is called.
* @param {object} options.modules - Object where values are package names or absolute paths that will be required on execution.
* @returns {On}
*/
const schedule = (options = {}) => {
const callerPath = getStack()[1].file;
const { id, func, expression, data } = options;
let { modules } = options;
const on = Client => {
const typeOfFunc = typeof func;
if (typeOfFunc !== 'function') {
console.error("Expected 'func' param to be of type function, but is", typeOfFunc);
return false;
}
const typeOfExpression = typeof expression;
if (!['string', 'object'].includes(typeOfExpression)) {
let errorString =
"PersistentScheduler: expected 'expression' param to be of type object or string, but is ";
errorString += typeOfExpression;
console.error(`\n${new Error(errorString).stack}`);
return false;
}
const afterConnect = () => {
const client = clients[Client];
if (!client) {
console.error(`Client ${client} was not configured.`);
return null;
}
if (timeouts[id]) {
console.error(`Timeout with id '${id}' already exists.`);
return null;
}
modules = modules || {};
const mKeys = Object.keys(modules);
for (let k = 0; k < mKeys.length; k += 1) {
const key = mKeys[k];
if (modules[key][0] === '.') modules[key] = path.join(callerPath, '..', modules[key]);
}
defineTimeout(id, func, getTime(expression), data, modules, Client);
return client.save(id, func, getNext(expression), data, modules, Client);
};
if (!clients) {
return new Promise(resolve => {
pending.push({ ...options, callback: () => resolve(afterConnect()) });
});
}
return afterConnect();
};
return { on };
};
(async () => {
clients = SetClients(await Promise.all(clientPromises));
const names = Object.keys(clients);
let clientTimeouts = [];
for (let k = 0; k < names.length; k += 1) clientTimeouts.push(clients[names[k]].get({}));
clientTimeouts = await Promise.all(clientTimeouts);
clientTimeouts = [].concat(...clientTimeouts);
for (let k = 0; k < clientTimeouts.length; k += 1) {
const { id, func, nextExecution, data, modules, Client } = clientTimeouts[k];
defineTimeout(id, func, getTime(nextExecution), data, modules, Client);
}
for (let k = 0; k < pending.length; k += 1) pending[k].callback();
pending = [];
})();
return { schedule };
};