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.
@@ -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
+