laravel-workers 0.4.0 → 0.4.1
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 +21 -21
- package/bin/laravel-workers.mjs +141 -141
- package/build.mjs +2599 -2599
- package/compiler/bin/controller.mjs +92 -92
- package/kernel/cache.mjs +405 -405
- package/kernel/db.mjs +703 -703
- package/kernel/encrypter.mjs +1 -1
- package/kernel/hash.mjs +113 -113
- package/kernel/multipart.mjs +161 -161
- package/kernel/router.mjs +276 -276
- package/kernel/scheduler.mjs +236 -236
- package/kernel/str.mjs +445 -445
- package/kernel/tests/cache.test.mjs +290 -290
- package/kernel/tests/db.test.mjs +1068 -1068
- package/kernel/tests/encrypter.test.mjs +1 -1
- package/kernel/tests/hash.test.mjs +149 -149
- package/kernel/tests/http.test.mjs +1182 -1182
- package/kernel/tests/php-json.test.mjs +263 -263
- package/kernel/tests/router.test.mjs +281 -281
- package/kernel/tests/services.test.mjs +474 -474
- package/kernel/tests/str.test.mjs +83 -83
- package/kernel/tests/validation.test.mjs +340 -340
- package/kernel/validation.mjs +123 -123
- package/package.json +69 -69
- package/tools/schema-dump.php +158 -158
package/kernel/scheduler.mjs
CHANGED
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
// kernel/scheduler.mjs — Scheduler (UTC cron, optional per-job/per-schedule timezone)
|
|
2
|
-
const MONTHS = { jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12 };
|
|
3
|
-
const DAYS = { sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6, sunday:0,monday:1,tuesday:2,wednesday:3,thursday:4,friday:5,saturday:6 };
|
|
4
|
-
const WD = { Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6 };
|
|
5
|
-
|
|
6
|
-
// The alias table belongs to the field (the month names are not weekday
|
|
7
|
-
// names): the cron library keeps them apart and an unknown name makes the
|
|
8
|
-
// expression invalid.
|
|
9
|
-
function parsePart(part, min, max, aliases) {
|
|
10
|
-
if (part === '*') return null; // any
|
|
11
|
-
const set = new Set();
|
|
12
|
-
for (const seg of part.split(',')) {
|
|
13
|
-
let step = 1;
|
|
14
|
-
let range = seg;
|
|
15
|
-
if (seg.includes('/')) { const [r, s] = seg.split('/'); range = r; step = Number(s) || 1; }
|
|
16
|
-
let lo, hi;
|
|
17
|
-
if (range === '*') { lo = min; hi = max; }
|
|
18
|
-
else if (range.includes('-')) {
|
|
19
|
-
const [a,b] = range.split('-');
|
|
20
|
-
lo = mapToken(a, min, max, aliases); hi = mapToken(b, min, max, aliases);
|
|
21
|
-
} else if (range === '') { continue; }
|
|
22
|
-
else { lo = hi = mapToken(range, min, max, aliases); }
|
|
23
|
-
for (let v = lo; v <= hi; v += step) set.add(v);
|
|
24
|
-
}
|
|
25
|
-
return set;
|
|
26
|
-
}
|
|
27
|
-
function mapToken(tok, min, max, aliases) {
|
|
28
|
-
const low = String(tok).toLowerCase();
|
|
29
|
-
if (aliases && aliases[low] !== undefined) return aliases[low];
|
|
30
|
-
const n = Number(low);
|
|
31
|
-
if (!Number.isNaN(n)) {
|
|
32
|
-
if (n === 7 && min === 0 && max === 7) return 0; // Sunday 7 -> 0 (dow only)
|
|
33
|
-
return n;
|
|
34
|
-
}
|
|
35
|
-
// An unknown token is an invalid expression (the cron library throws).
|
|
36
|
-
throw new Error('invalid CRON field value ' + tok);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// The day-of-month part: the set plus the 'L' form (the last day of the
|
|
40
|
-
// month) - the cron library's DayOfMonthField.
|
|
41
|
-
function parseDayOfMonth(part) {
|
|
42
|
-
if (part === '*' || part === '?') return null; // any
|
|
43
|
-
const set = new Set();
|
|
44
|
-
let last = false;
|
|
45
|
-
for (const seg of part.split(',')) {
|
|
46
|
-
const t = seg.trim();
|
|
47
|
-
if (/^L$/i.test(t)) { last = true; continue; }
|
|
48
|
-
const s = parsePart(t, 1, 31, null);
|
|
49
|
-
if (s) for (const v of s) set.add(v);
|
|
50
|
-
}
|
|
51
|
-
return { set, last };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// The day-of-week part: the set plus the 'L' form (the last <weekday> of the
|
|
55
|
-
// month) and the '#' form (the n-th <weekday>) - the cron library's
|
|
56
|
-
// DayOfWeekField.
|
|
57
|
-
function parseDayOfWeek(part) {
|
|
58
|
-
if (part === '*' || part === '?') return null; // any
|
|
59
|
-
const set = new Set();
|
|
60
|
-
const lastOf = new Set();
|
|
61
|
-
const nth = [];
|
|
62
|
-
for (const seg of part.split(',')) {
|
|
63
|
-
const t = seg.trim();
|
|
64
|
-
const m = /^(\d+)#(\d+)$/.exec(t);
|
|
65
|
-
if (m) { nth.push({ dow: Number(m[1]) % 7, n: Number(m[2]) }); continue; }
|
|
66
|
-
const lm = /^(\d+)L$/i.exec(t);
|
|
67
|
-
if (lm) { lastOf.add(Number(lm[1]) % 7); continue; }
|
|
68
|
-
const s = parsePart(t, 0, 7, DAYS);
|
|
69
|
-
if (s) for (const v of s) set.add(v);
|
|
70
|
-
}
|
|
71
|
-
return { set, lastOf, nth };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// The number of days in a month (the 'L' forms need it).
|
|
75
|
-
function daysInMonth(year, month) {
|
|
76
|
-
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// The day of the month of the last <weekday> of the month.
|
|
80
|
-
function lastWeekdayOfMonth(year, month, weekday) {
|
|
81
|
-
const last = daysInMonth(year, month);
|
|
82
|
-
for (let d = last; d > last - 7; d--) {
|
|
83
|
-
if (new Date(Date.UTC(year, month - 1, d)).getUTCDay() === weekday) return d;
|
|
84
|
-
}
|
|
85
|
-
return -1;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// The day of the month of the n-th <weekday> of the month.
|
|
89
|
-
function nthWeekdayOfMonth(year, month, weekday, n) {
|
|
90
|
-
const last = daysInMonth(year, month);
|
|
91
|
-
for (let d = 1; d <= 7 && d <= last; d++) {
|
|
92
|
-
if (new Date(Date.UTC(year, month - 1, d)).getUTCDay() === weekday) {
|
|
93
|
-
const day = d + (n - 1) * 7;
|
|
94
|
-
return day <= last ? day : -1;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return -1;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function wallParts(date, timeZone) {
|
|
101
|
-
if (!timeZone) {
|
|
102
|
-
return { minute: date.getUTCMinutes(), hour: date.getUTCHours(), day: date.getUTCDate(), month: date.getUTCMonth() + 1, year: date.getUTCFullYear(), weekday: date.getUTCDay() };
|
|
103
|
-
}
|
|
104
|
-
let fmt;
|
|
105
|
-
try {
|
|
106
|
-
fmt = new Intl.DateTimeFormat('en-US', { timeZone, hour12: false, weekday: 'short', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
|
|
107
|
-
} catch (e) {
|
|
108
|
-
throw new Error('Invalid timezone: ' + timeZone);
|
|
109
|
-
}
|
|
110
|
-
const parts = fmt.formatToParts(date);
|
|
111
|
-
const get = (type) => parts.find(p => p.type === type)?.value;
|
|
112
|
-
const weekday = WD[get('weekday')];
|
|
113
|
-
return {
|
|
114
|
-
minute: Number(get('minute')),
|
|
115
|
-
hour: Number(get('hour')),
|
|
116
|
-
day: Number(get('day')),
|
|
117
|
-
month: Number(get('month')),
|
|
118
|
-
year: Number(get('year')),
|
|
119
|
-
weekday: weekday === undefined ? 0 : weekday,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function match(date, expr, timeZone) {
|
|
124
|
-
const parts = expr.trim().split(/\s+/);
|
|
125
|
-
if (parts.length !== 5) return false;
|
|
126
|
-
const [mS, hS, domS, monS, dowS] = parts;
|
|
127
|
-
const sets = [
|
|
128
|
-
parsePart(mS, 0, 59, null),
|
|
129
|
-
parsePart(hS, 0, 23, null),
|
|
130
|
-
null,
|
|
131
|
-
parsePart(monS, 1, 12, MONTHS),
|
|
132
|
-
null,
|
|
133
|
-
];
|
|
134
|
-
const dom = parseDayOfMonth(domS);
|
|
135
|
-
const dow = parseDayOfWeek(dowS);
|
|
136
|
-
const wp = wallParts(date, timeZone);
|
|
137
|
-
const vals = [wp.minute, wp.hour, wp.day, wp.month, wp.weekday];
|
|
138
|
-
for (let i = 0; i < 2; i++) {
|
|
139
|
-
const set = sets[i];
|
|
140
|
-
if (set === null) continue;
|
|
141
|
-
if (!set.has(vals[i])) return false;
|
|
142
|
-
}
|
|
143
|
-
// The month.
|
|
144
|
-
const monSet = sets[3];
|
|
145
|
-
if (monSet !== null && !monSet.has(wp.month)) return false;
|
|
146
|
-
// The day: the cron library ORs the day-of-month and the day-of-week when
|
|
147
|
-
// BOTH are restricted (the classic cron rule) - '0 0 1 * 1' fires on the
|
|
148
|
-
// 1st AND on every Monday.
|
|
149
|
-
const domMatch = dom === null ? true : dayOfMonthMatches(dom, wp);
|
|
150
|
-
const dowMatch = dow === null ? true : dayOfWeekMatches(dow, wp);
|
|
151
|
-
if (dom !== null && dow !== null) return domMatch || dowMatch;
|
|
152
|
-
return domMatch && dowMatch;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function dayOfMonthMatches(dom, wp) {
|
|
156
|
-
if (dom.set.has(wp.day)) return true;
|
|
157
|
-
if (dom.last) {
|
|
158
|
-
const last = daysInMonth(wp.year, wp.month);
|
|
159
|
-
if (wp.day === last) return true;
|
|
160
|
-
}
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function dayOfWeekMatches(dow, wp) {
|
|
165
|
-
if (dow.set.has(wp.weekday) || (wp.weekday === 0 && dow.set.has(7))) return true;
|
|
166
|
-
for (const d of dow.lastOf) {
|
|
167
|
-
if (d !== wp.weekday) continue;
|
|
168
|
-
if (lastWeekdayOfMonth(wp.year, wp.month, d) === wp.day) return true;
|
|
169
|
-
}
|
|
170
|
-
for (const nth of dow.nth) {
|
|
171
|
-
if (nth.dow !== wp.weekday) continue;
|
|
172
|
-
if (nthWeekdayOfMonth(wp.year, wp.month, nth.dow, nth.n) === wp.day) return true;
|
|
173
|
-
}
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function defineSchedule(fn, opts = {}) {
|
|
178
|
-
const defaultTz = opts && opts.timezone;
|
|
179
|
-
if (defaultTz) {
|
|
180
|
-
// validate up front
|
|
181
|
-
wallParts(new Date(), defaultTz);
|
|
182
|
-
}
|
|
183
|
-
const jobs = [];
|
|
184
|
-
const api = {
|
|
185
|
-
call(name, cron, handler, callOpts = {}) {
|
|
186
|
-
const tz = (callOpts && callOpts.timezone) !== undefined ? callOpts.timezone : defaultTz;
|
|
187
|
-
jobs.push({ name, cron, handler, timezone: tz });
|
|
188
|
-
return api;
|
|
189
|
-
},
|
|
190
|
-
run(date = new Date()) {
|
|
191
|
-
const due = [];
|
|
192
|
-
for (const j of jobs) if (match(date, j.cron, j.timezone)) due.push(j);
|
|
193
|
-
return { due, async exec() { for (const j of due) await j.handler(); return due.map(d => d.name); } };
|
|
194
|
-
},
|
|
195
|
-
_match: match,
|
|
196
|
-
};
|
|
197
|
-
if (typeof fn === 'function') fn(api);
|
|
198
|
-
return api;
|
|
199
|
-
}
|
|
200
|
-
export default { defineSchedule };
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* The cron matching the edge uses for the *dumped* Laravel schedule: does
|
|
204
|
-
* `expression` fire at `date` (in `timeZone`, UTC when omitted)?
|
|
205
|
-
*/
|
|
206
|
-
export function cronMatches(expression, date = new Date(), timeZone) {
|
|
207
|
-
return match(date, String(expression), timeZone);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* The events of a dumped Laravel schedule that are due at `date`.
|
|
212
|
-
*
|
|
213
|
-
* A dumped event is `{ name, expression, command, description, timezone }`
|
|
214
|
-
* (the build writes `dist/src/schedule.generated.mjs`). The edge can only
|
|
215
|
-
* *trigger* an event — the command itself still needs the app runtime, so the
|
|
216
|
-
* caller queues it.
|
|
217
|
-
*/
|
|
218
|
-
export function dueEvents(events, date = new Date(), timeZone) {
|
|
219
|
-
const list = Array.isArray(events) ? events : [];
|
|
220
|
-
return list.filter((ev) => {
|
|
221
|
-
const expr = ev && (ev.expression || ev.cron);
|
|
222
|
-
if (!expr) return false;
|
|
223
|
-
const tz = ev.timezone || timeZone;
|
|
224
|
-
try {
|
|
225
|
-
return match(date, String(expr), tz);
|
|
226
|
-
} catch {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1
|
+
// kernel/scheduler.mjs — Scheduler (UTC cron, optional per-job/per-schedule timezone)
|
|
2
|
+
const MONTHS = { jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12 };
|
|
3
|
+
const DAYS = { sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6, sunday:0,monday:1,tuesday:2,wednesday:3,thursday:4,friday:5,saturday:6 };
|
|
4
|
+
const WD = { Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6 };
|
|
5
|
+
|
|
6
|
+
// The alias table belongs to the field (the month names are not weekday
|
|
7
|
+
// names): the cron library keeps them apart and an unknown name makes the
|
|
8
|
+
// expression invalid.
|
|
9
|
+
function parsePart(part, min, max, aliases) {
|
|
10
|
+
if (part === '*') return null; // any
|
|
11
|
+
const set = new Set();
|
|
12
|
+
for (const seg of part.split(',')) {
|
|
13
|
+
let step = 1;
|
|
14
|
+
let range = seg;
|
|
15
|
+
if (seg.includes('/')) { const [r, s] = seg.split('/'); range = r; step = Number(s) || 1; }
|
|
16
|
+
let lo, hi;
|
|
17
|
+
if (range === '*') { lo = min; hi = max; }
|
|
18
|
+
else if (range.includes('-')) {
|
|
19
|
+
const [a,b] = range.split('-');
|
|
20
|
+
lo = mapToken(a, min, max, aliases); hi = mapToken(b, min, max, aliases);
|
|
21
|
+
} else if (range === '') { continue; }
|
|
22
|
+
else { lo = hi = mapToken(range, min, max, aliases); }
|
|
23
|
+
for (let v = lo; v <= hi; v += step) set.add(v);
|
|
24
|
+
}
|
|
25
|
+
return set;
|
|
26
|
+
}
|
|
27
|
+
function mapToken(tok, min, max, aliases) {
|
|
28
|
+
const low = String(tok).toLowerCase();
|
|
29
|
+
if (aliases && aliases[low] !== undefined) return aliases[low];
|
|
30
|
+
const n = Number(low);
|
|
31
|
+
if (!Number.isNaN(n)) {
|
|
32
|
+
if (n === 7 && min === 0 && max === 7) return 0; // Sunday 7 -> 0 (dow only)
|
|
33
|
+
return n;
|
|
34
|
+
}
|
|
35
|
+
// An unknown token is an invalid expression (the cron library throws).
|
|
36
|
+
throw new Error('invalid CRON field value ' + tok);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The day-of-month part: the set plus the 'L' form (the last day of the
|
|
40
|
+
// month) - the cron library's DayOfMonthField.
|
|
41
|
+
function parseDayOfMonth(part) {
|
|
42
|
+
if (part === '*' || part === '?') return null; // any
|
|
43
|
+
const set = new Set();
|
|
44
|
+
let last = false;
|
|
45
|
+
for (const seg of part.split(',')) {
|
|
46
|
+
const t = seg.trim();
|
|
47
|
+
if (/^L$/i.test(t)) { last = true; continue; }
|
|
48
|
+
const s = parsePart(t, 1, 31, null);
|
|
49
|
+
if (s) for (const v of s) set.add(v);
|
|
50
|
+
}
|
|
51
|
+
return { set, last };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The day-of-week part: the set plus the 'L' form (the last <weekday> of the
|
|
55
|
+
// month) and the '#' form (the n-th <weekday>) - the cron library's
|
|
56
|
+
// DayOfWeekField.
|
|
57
|
+
function parseDayOfWeek(part) {
|
|
58
|
+
if (part === '*' || part === '?') return null; // any
|
|
59
|
+
const set = new Set();
|
|
60
|
+
const lastOf = new Set();
|
|
61
|
+
const nth = [];
|
|
62
|
+
for (const seg of part.split(',')) {
|
|
63
|
+
const t = seg.trim();
|
|
64
|
+
const m = /^(\d+)#(\d+)$/.exec(t);
|
|
65
|
+
if (m) { nth.push({ dow: Number(m[1]) % 7, n: Number(m[2]) }); continue; }
|
|
66
|
+
const lm = /^(\d+)L$/i.exec(t);
|
|
67
|
+
if (lm) { lastOf.add(Number(lm[1]) % 7); continue; }
|
|
68
|
+
const s = parsePart(t, 0, 7, DAYS);
|
|
69
|
+
if (s) for (const v of s) set.add(v);
|
|
70
|
+
}
|
|
71
|
+
return { set, lastOf, nth };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The number of days in a month (the 'L' forms need it).
|
|
75
|
+
function daysInMonth(year, month) {
|
|
76
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The day of the month of the last <weekday> of the month.
|
|
80
|
+
function lastWeekdayOfMonth(year, month, weekday) {
|
|
81
|
+
const last = daysInMonth(year, month);
|
|
82
|
+
for (let d = last; d > last - 7; d--) {
|
|
83
|
+
if (new Date(Date.UTC(year, month - 1, d)).getUTCDay() === weekday) return d;
|
|
84
|
+
}
|
|
85
|
+
return -1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The day of the month of the n-th <weekday> of the month.
|
|
89
|
+
function nthWeekdayOfMonth(year, month, weekday, n) {
|
|
90
|
+
const last = daysInMonth(year, month);
|
|
91
|
+
for (let d = 1; d <= 7 && d <= last; d++) {
|
|
92
|
+
if (new Date(Date.UTC(year, month - 1, d)).getUTCDay() === weekday) {
|
|
93
|
+
const day = d + (n - 1) * 7;
|
|
94
|
+
return day <= last ? day : -1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return -1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function wallParts(date, timeZone) {
|
|
101
|
+
if (!timeZone) {
|
|
102
|
+
return { minute: date.getUTCMinutes(), hour: date.getUTCHours(), day: date.getUTCDate(), month: date.getUTCMonth() + 1, year: date.getUTCFullYear(), weekday: date.getUTCDay() };
|
|
103
|
+
}
|
|
104
|
+
let fmt;
|
|
105
|
+
try {
|
|
106
|
+
fmt = new Intl.DateTimeFormat('en-US', { timeZone, hour12: false, weekday: 'short', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
|
|
107
|
+
} catch (e) {
|
|
108
|
+
throw new Error('Invalid timezone: ' + timeZone);
|
|
109
|
+
}
|
|
110
|
+
const parts = fmt.formatToParts(date);
|
|
111
|
+
const get = (type) => parts.find(p => p.type === type)?.value;
|
|
112
|
+
const weekday = WD[get('weekday')];
|
|
113
|
+
return {
|
|
114
|
+
minute: Number(get('minute')),
|
|
115
|
+
hour: Number(get('hour')),
|
|
116
|
+
day: Number(get('day')),
|
|
117
|
+
month: Number(get('month')),
|
|
118
|
+
year: Number(get('year')),
|
|
119
|
+
weekday: weekday === undefined ? 0 : weekday,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function match(date, expr, timeZone) {
|
|
124
|
+
const parts = expr.trim().split(/\s+/);
|
|
125
|
+
if (parts.length !== 5) return false;
|
|
126
|
+
const [mS, hS, domS, monS, dowS] = parts;
|
|
127
|
+
const sets = [
|
|
128
|
+
parsePart(mS, 0, 59, null),
|
|
129
|
+
parsePart(hS, 0, 23, null),
|
|
130
|
+
null,
|
|
131
|
+
parsePart(monS, 1, 12, MONTHS),
|
|
132
|
+
null,
|
|
133
|
+
];
|
|
134
|
+
const dom = parseDayOfMonth(domS);
|
|
135
|
+
const dow = parseDayOfWeek(dowS);
|
|
136
|
+
const wp = wallParts(date, timeZone);
|
|
137
|
+
const vals = [wp.minute, wp.hour, wp.day, wp.month, wp.weekday];
|
|
138
|
+
for (let i = 0; i < 2; i++) {
|
|
139
|
+
const set = sets[i];
|
|
140
|
+
if (set === null) continue;
|
|
141
|
+
if (!set.has(vals[i])) return false;
|
|
142
|
+
}
|
|
143
|
+
// The month.
|
|
144
|
+
const monSet = sets[3];
|
|
145
|
+
if (monSet !== null && !monSet.has(wp.month)) return false;
|
|
146
|
+
// The day: the cron library ORs the day-of-month and the day-of-week when
|
|
147
|
+
// BOTH are restricted (the classic cron rule) - '0 0 1 * 1' fires on the
|
|
148
|
+
// 1st AND on every Monday.
|
|
149
|
+
const domMatch = dom === null ? true : dayOfMonthMatches(dom, wp);
|
|
150
|
+
const dowMatch = dow === null ? true : dayOfWeekMatches(dow, wp);
|
|
151
|
+
if (dom !== null && dow !== null) return domMatch || dowMatch;
|
|
152
|
+
return domMatch && dowMatch;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function dayOfMonthMatches(dom, wp) {
|
|
156
|
+
if (dom.set.has(wp.day)) return true;
|
|
157
|
+
if (dom.last) {
|
|
158
|
+
const last = daysInMonth(wp.year, wp.month);
|
|
159
|
+
if (wp.day === last) return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function dayOfWeekMatches(dow, wp) {
|
|
165
|
+
if (dow.set.has(wp.weekday) || (wp.weekday === 0 && dow.set.has(7))) return true;
|
|
166
|
+
for (const d of dow.lastOf) {
|
|
167
|
+
if (d !== wp.weekday) continue;
|
|
168
|
+
if (lastWeekdayOfMonth(wp.year, wp.month, d) === wp.day) return true;
|
|
169
|
+
}
|
|
170
|
+
for (const nth of dow.nth) {
|
|
171
|
+
if (nth.dow !== wp.weekday) continue;
|
|
172
|
+
if (nthWeekdayOfMonth(wp.year, wp.month, nth.dow, nth.n) === wp.day) return true;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function defineSchedule(fn, opts = {}) {
|
|
178
|
+
const defaultTz = opts && opts.timezone;
|
|
179
|
+
if (defaultTz) {
|
|
180
|
+
// validate up front
|
|
181
|
+
wallParts(new Date(), defaultTz);
|
|
182
|
+
}
|
|
183
|
+
const jobs = [];
|
|
184
|
+
const api = {
|
|
185
|
+
call(name, cron, handler, callOpts = {}) {
|
|
186
|
+
const tz = (callOpts && callOpts.timezone) !== undefined ? callOpts.timezone : defaultTz;
|
|
187
|
+
jobs.push({ name, cron, handler, timezone: tz });
|
|
188
|
+
return api;
|
|
189
|
+
},
|
|
190
|
+
run(date = new Date()) {
|
|
191
|
+
const due = [];
|
|
192
|
+
for (const j of jobs) if (match(date, j.cron, j.timezone)) due.push(j);
|
|
193
|
+
return { due, async exec() { for (const j of due) await j.handler(); return due.map(d => d.name); } };
|
|
194
|
+
},
|
|
195
|
+
_match: match,
|
|
196
|
+
};
|
|
197
|
+
if (typeof fn === 'function') fn(api);
|
|
198
|
+
return api;
|
|
199
|
+
}
|
|
200
|
+
export default { defineSchedule };
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The cron matching the edge uses for the *dumped* Laravel schedule: does
|
|
204
|
+
* `expression` fire at `date` (in `timeZone`, UTC when omitted)?
|
|
205
|
+
*/
|
|
206
|
+
export function cronMatches(expression, date = new Date(), timeZone) {
|
|
207
|
+
return match(date, String(expression), timeZone);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The events of a dumped Laravel schedule that are due at `date`.
|
|
212
|
+
*
|
|
213
|
+
* A dumped event is `{ name, expression, command, description, timezone }`
|
|
214
|
+
* (the build writes `dist/src/schedule.generated.mjs`). The edge can only
|
|
215
|
+
* *trigger* an event — the command itself still needs the app runtime, so the
|
|
216
|
+
* caller queues it.
|
|
217
|
+
*/
|
|
218
|
+
export function dueEvents(events, date = new Date(), timeZone) {
|
|
219
|
+
const list = Array.isArray(events) ? events : [];
|
|
220
|
+
return list.filter((ev) => {
|
|
221
|
+
const expr = ev && (ev.expression || ev.cron);
|
|
222
|
+
if (!expr) return false;
|
|
223
|
+
const tz = ev.timezone || timeZone;
|
|
224
|
+
try {
|
|
225
|
+
return match(date, String(expr), tz);
|
|
226
|
+
} catch {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|