@warlock.js/scheduler 4.0.174 → 4.1.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.
Files changed (69) hide show
  1. package/README.md +60 -1
  2. package/cjs/index.cjs +1132 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/cron-parser.d.mts +129 -0
  5. package/esm/cron-parser.d.mts.map +1 -0
  6. package/esm/cron-parser.mjs +210 -0
  7. package/esm/cron-parser.mjs.map +1 -0
  8. package/esm/index.d.mts +5 -0
  9. package/esm/index.mjs +5 -0
  10. package/esm/job.d.mts +386 -0
  11. package/esm/job.d.mts.map +1 -0
  12. package/esm/job.mjs +633 -0
  13. package/esm/job.mjs.map +1 -0
  14. package/esm/scheduler.d.mts +193 -0
  15. package/esm/scheduler.d.mts.map +1 -0
  16. package/esm/scheduler.mjs +261 -0
  17. package/esm/scheduler.mjs.map +1 -0
  18. package/esm/types.d.mts +70 -0
  19. package/esm/types.d.mts.map +1 -0
  20. package/llms-full.txt +888 -0
  21. package/llms.txt +15 -0
  22. package/package.json +40 -28
  23. package/skills/configure-retry-and-overlap/SKILL.md +137 -0
  24. package/skills/observe-scheduler/SKILL.md +153 -0
  25. package/skills/overview/SKILL.md +92 -0
  26. package/skills/pin-schedule-timezone/SKILL.md +114 -0
  27. package/skills/schedule-fluently/SKILL.md +141 -0
  28. package/skills/schedule-with-cron/SKILL.md +123 -0
  29. package/skills/scheduler-basics/SKILL.md +94 -0
  30. package/cjs/cron-parser.d.ts +0 -98
  31. package/cjs/cron-parser.d.ts.map +0 -1
  32. package/cjs/cron-parser.js +0 -193
  33. package/cjs/cron-parser.js.map +0 -1
  34. package/cjs/index.d.ts +0 -44
  35. package/cjs/index.d.ts.map +0 -1
  36. package/cjs/index.js +0 -1
  37. package/cjs/index.js.map +0 -1
  38. package/cjs/job.d.ts +0 -332
  39. package/cjs/job.d.ts.map +0 -1
  40. package/cjs/job.js +0 -616
  41. package/cjs/job.js.map +0 -1
  42. package/cjs/scheduler.d.ts +0 -182
  43. package/cjs/scheduler.d.ts.map +0 -1
  44. package/cjs/scheduler.js +0 -316
  45. package/cjs/scheduler.js.map +0 -1
  46. package/cjs/types.d.ts +0 -63
  47. package/cjs/types.d.ts.map +0 -1
  48. package/cjs/utils.d.ts +0 -3
  49. package/cjs/utils.d.ts.map +0 -1
  50. package/esm/cron-parser.d.ts +0 -98
  51. package/esm/cron-parser.d.ts.map +0 -1
  52. package/esm/cron-parser.js +0 -193
  53. package/esm/cron-parser.js.map +0 -1
  54. package/esm/index.d.ts +0 -44
  55. package/esm/index.d.ts.map +0 -1
  56. package/esm/index.js +0 -1
  57. package/esm/index.js.map +0 -1
  58. package/esm/job.d.ts +0 -332
  59. package/esm/job.d.ts.map +0 -1
  60. package/esm/job.js +0 -616
  61. package/esm/job.js.map +0 -1
  62. package/esm/scheduler.d.ts +0 -182
  63. package/esm/scheduler.d.ts.map +0 -1
  64. package/esm/scheduler.js +0 -316
  65. package/esm/scheduler.js.map +0 -1
  66. package/esm/types.d.ts +0 -63
  67. package/esm/types.d.ts.map +0 -1
  68. package/esm/utils.d.ts +0 -3
  69. package/esm/utils.d.ts.map +0 -1
package/esm/job.mjs ADDED
@@ -0,0 +1,633 @@
1
+ import { CronParser } from "./cron-parser.mjs";
2
+ import dayjs from "dayjs";
3
+ import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
4
+ import timezone from "dayjs/plugin/timezone.js";
5
+ import utc from "dayjs/plugin/utc.js";
6
+
7
+ //#region ../../@warlock.js/scheduler/src/job.ts
8
+ dayjs.extend(utc);
9
+ dayjs.extend(timezone);
10
+ dayjs.extend(isSameOrAfter);
11
+ /**
12
+ * Days of week mapping (lowercase for consistency with Day type)
13
+ */
14
+ const DAYS_OF_WEEK = [
15
+ "sunday",
16
+ "monday",
17
+ "tuesday",
18
+ "wednesday",
19
+ "thursday",
20
+ "friday",
21
+ "saturday"
22
+ ];
23
+ /**
24
+ * Validate a `HH:mm` or `HH:mm:ss` string and return its parts.
25
+ * Throws when the format is malformed or any part is out of range.
26
+ */
27
+ function parseTimeString(time) {
28
+ if (!/^\d{1,2}:\d{2}(:\d{2})?$/.test(time)) throw new Error("Invalid time format. Use HH:mm or HH:mm:ss.");
29
+ const [hour, minute, second = 0] = time.split(":").map(Number);
30
+ if (hour < 0 || hour > 23) throw new Error(`Invalid hour in time "${time}". Must be between 0 and 23.`);
31
+ if (minute < 0 || minute > 59) throw new Error(`Invalid minute in time "${time}". Must be between 0 and 59.`);
32
+ if (second < 0 || second > 59) throw new Error(`Invalid second in time "${time}". Must be between 0 and 59.`);
33
+ return {
34
+ hour,
35
+ minute,
36
+ second
37
+ };
38
+ }
39
+ /**
40
+ * Job class represents a scheduled task with configurable timing and execution options.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const job = new Job("cleanup", async () => {
45
+ * await cleanupOldFiles();
46
+ * })
47
+ * .everyDay()
48
+ * .at("03:00")
49
+ * .inTimezone("America/New_York")
50
+ * .preventOverlap()
51
+ * .retry(3, 1000);
52
+ * ```
53
+ */
54
+ var Job = class {
55
+ /**
56
+ * Creates a new Job instance
57
+ *
58
+ * @param name - Unique identifier for the job
59
+ * @param callback - Function to execute when the job runs
60
+ */
61
+ constructor(name, _callback) {
62
+ this.name = name;
63
+ this._callback = _callback;
64
+ this._intervals = {};
65
+ this._lastRun = null;
66
+ this._isRunning = false;
67
+ this._skipIfRunning = false;
68
+ this._retryConfig = null;
69
+ this._timezone = "UTC";
70
+ this._cronParser = null;
71
+ this._completionResolvers = [];
72
+ this.nextRun = null;
73
+ }
74
+ /**
75
+ * Returns true if the job is currently executing
76
+ */
77
+ get isRunning() {
78
+ return this._isRunning;
79
+ }
80
+ /**
81
+ * Returns the last execution timestamp (success OR failure).
82
+ */
83
+ get lastRun() {
84
+ return this._lastRun;
85
+ }
86
+ /**
87
+ * Returns the current interval configuration (readonly)
88
+ */
89
+ get intervals() {
90
+ return this._intervals;
91
+ }
92
+ /**
93
+ * Set a custom interval for job execution
94
+ *
95
+ * @param value - Number of time units (must be > 0)
96
+ * @param timeType - Type of time unit
97
+ * @returns this for chaining
98
+ * @throws Error if `value` is not a positive finite number
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * job.every(5, "minute"); // Run every 5 minutes
103
+ * job.every(2, "hour"); // Run every 2 hours
104
+ * ```
105
+ */
106
+ every(value, timeType) {
107
+ if (!Number.isFinite(value) || value <= 0) throw new Error(`Invalid interval value: ${value}. Must be a positive finite number.`);
108
+ this._intervals.every = {
109
+ type: timeType,
110
+ value
111
+ };
112
+ this._determineNextRun();
113
+ return this;
114
+ }
115
+ /**
116
+ * Run job every second (use with caution - high frequency)
117
+ */
118
+ everySecond() {
119
+ return this.every(1, "second");
120
+ }
121
+ /**
122
+ * Run job every specified number of seconds
123
+ */
124
+ everySeconds(seconds) {
125
+ return this.every(seconds, "second");
126
+ }
127
+ /**
128
+ * Run job every minute
129
+ */
130
+ everyMinute() {
131
+ return this.every(1, "minute");
132
+ }
133
+ /**
134
+ * Run job every specified number of minutes
135
+ */
136
+ everyMinutes(minutes) {
137
+ return this.every(minutes, "minute");
138
+ }
139
+ /**
140
+ * Run job every hour
141
+ */
142
+ everyHour() {
143
+ return this.every(1, "hour");
144
+ }
145
+ /**
146
+ * Run job every specified number of hours
147
+ */
148
+ everyHours(hours) {
149
+ return this.every(hours, "hour");
150
+ }
151
+ /**
152
+ * Run job every day at midnight
153
+ */
154
+ everyDay() {
155
+ return this.every(1, "day");
156
+ }
157
+ /**
158
+ * Alias for everyDay()
159
+ */
160
+ daily() {
161
+ return this.everyDay();
162
+ }
163
+ /**
164
+ * Run job twice a day (every 12 hours)
165
+ */
166
+ twiceDaily() {
167
+ return this.every(12, "hour");
168
+ }
169
+ /**
170
+ * Run job every week
171
+ */
172
+ everyWeek() {
173
+ return this.every(1, "week");
174
+ }
175
+ /**
176
+ * Alias for everyWeek()
177
+ */
178
+ weekly() {
179
+ return this.everyWeek();
180
+ }
181
+ /**
182
+ * Run job every month
183
+ */
184
+ everyMonth() {
185
+ return this.every(1, "month");
186
+ }
187
+ /**
188
+ * Alias for everyMonth()
189
+ */
190
+ monthly() {
191
+ return this.everyMonth();
192
+ }
193
+ /**
194
+ * Run job every year
195
+ */
196
+ everyYear() {
197
+ return this.every(1, "year");
198
+ }
199
+ /**
200
+ * Alias for everyYear()
201
+ */
202
+ yearly() {
203
+ return this.everyYear();
204
+ }
205
+ /**
206
+ * Alias for everyMinute() - job runs continuously every minute
207
+ */
208
+ always() {
209
+ return this.everyMinute();
210
+ }
211
+ /**
212
+ * Schedule job using a cron expression
213
+ *
214
+ * Supports standard 5-field cron syntax:
215
+ * ```
216
+ * ┌───────────── minute (0-59)
217
+ * │ ┌───────────── hour (0-23)
218
+ * │ │ ┌───────────── day of month (1-31)
219
+ * │ │ │ ┌───────────── month (1-12)
220
+ * │ │ │ │ ┌───────────── day of week (0-6, Sunday = 0)
221
+ * │ │ │ │ │
222
+ * * * * * *
223
+ * ```
224
+ *
225
+ * Supports:
226
+ * - '*' - any value
227
+ * - '5' - specific value
228
+ * - '1,3,5' - list of values
229
+ * - '1-5' - range of values
230
+ * - '*‍/5' - step values (every 5)
231
+ * - '1-10/2' - range with step
232
+ *
233
+ * @param expression - Standard 5-field cron expression
234
+ * @returns this for chaining
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * job.cron("0 9 * * 1-5"); // 9 AM weekdays
239
+ * job.cron("*‍/5 * * * *"); // Every 5 minutes
240
+ * job.cron("0 0 1 * *"); // First day of month at midnight
241
+ * job.cron("0 *‍/2 * * *"); // Every 2 hours
242
+ * ```
243
+ */
244
+ cron(expression) {
245
+ this._cronParser = new CronParser(expression);
246
+ this._intervals = {};
247
+ this._determineNextRun();
248
+ return this;
249
+ }
250
+ /**
251
+ * Get the cron expression if one is set
252
+ */
253
+ get cronExpression() {
254
+ return this._cronParser?.expression ?? null;
255
+ }
256
+ /**
257
+ * Schedule job on a specific day
258
+ *
259
+ * @param day - Day of week (string) or day of month (number 1-31)
260
+ * @returns this for chaining
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * job.on("monday"); // Run on Mondays
265
+ * job.on(15); // Run on the 15th of each month
266
+ * ```
267
+ */
268
+ on(day) {
269
+ if (typeof day === "number" && (day < 1 || day > 31)) throw new Error("Invalid day of the month. Must be between 1 and 31.");
270
+ this._intervals.day = day;
271
+ if (typeof day === "number") this._intervals.dayOfMonthMode = "specific";
272
+ this._determineNextRun();
273
+ return this;
274
+ }
275
+ /**
276
+ * Schedule job at a specific time
277
+ *
278
+ * @param time - Time in HH:mm or HH:mm:ss format
279
+ * @returns this for chaining
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * job.daily().at("09:00"); // Run daily at 9 AM
284
+ * job.weekly().at("14:30"); // Run weekly at 2:30 PM
285
+ * ```
286
+ */
287
+ at(time) {
288
+ parseTimeString(time);
289
+ this._intervals.time = time;
290
+ this._determineNextRun();
291
+ return this;
292
+ }
293
+ /**
294
+ * Run task at the beginning of the specified time period.
295
+ *
296
+ * - `"day"` → 00:00 every day
297
+ * - `"month"` → 1st of every month at 00:00
298
+ * - `"year"` → January 1st at 00:00 every year
299
+ *
300
+ * @param type - Time type (day, month, year)
301
+ */
302
+ beginOf(type) {
303
+ switch (type) {
304
+ case "day":
305
+ this._intervals.every = {
306
+ type: "day",
307
+ value: 1
308
+ };
309
+ break;
310
+ case "month":
311
+ this._intervals.day = 1;
312
+ this._intervals.dayOfMonthMode = "specific";
313
+ this._intervals.every = {
314
+ type: "month",
315
+ value: 1
316
+ };
317
+ break;
318
+ case "year":
319
+ this._intervals.month = 1;
320
+ this._intervals.day = 1;
321
+ this._intervals.dayOfMonthMode = "specific";
322
+ this._intervals.every = {
323
+ type: "year",
324
+ value: 1
325
+ };
326
+ break;
327
+ default: throw new Error(`Unsupported type for beginOf: ${type}`);
328
+ }
329
+ return this.at("00:00");
330
+ }
331
+ /**
332
+ * Run task at the end of the specified time period.
333
+ *
334
+ * - `"day"` → 23:59 every day
335
+ * - `"month"` → last day of every month at 23:59 (recomputed each cycle —
336
+ * correct in February vs. March)
337
+ * - `"year"` → December 31st at 23:59 every year
338
+ *
339
+ * @param type - Time type (day, month, year)
340
+ */
341
+ endOf(type) {
342
+ switch (type) {
343
+ case "day":
344
+ this._intervals.every = {
345
+ type: "day",
346
+ value: 1
347
+ };
348
+ break;
349
+ case "month":
350
+ this._intervals.dayOfMonthMode = "last";
351
+ this._intervals.every = {
352
+ type: "month",
353
+ value: 1
354
+ };
355
+ break;
356
+ case "year":
357
+ this._intervals.month = 12;
358
+ this._intervals.day = 31;
359
+ this._intervals.dayOfMonthMode = "specific";
360
+ this._intervals.every = {
361
+ type: "year",
362
+ value: 1
363
+ };
364
+ break;
365
+ default: throw new Error(`Unsupported type for endOf: ${type}`);
366
+ }
367
+ return this.at("23:59");
368
+ }
369
+ /**
370
+ * Set the timezone for this job's scheduling
371
+ *
372
+ * @param tz - IANA timezone string (e.g., "America/New_York", "Europe/London")
373
+ * @returns this for chaining
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * job.daily().at("09:00").inTimezone("America/New_York");
378
+ * ```
379
+ */
380
+ inTimezone(tz) {
381
+ this._timezone = tz;
382
+ this._determineNextRun();
383
+ return this;
384
+ }
385
+ /**
386
+ * Prevent overlapping executions of this job.
387
+ *
388
+ * When enabled, if the job is already running when it's scheduled to run again,
389
+ * the new execution will be skipped.
390
+ *
391
+ * @param skip - Whether to skip if already running (default: true)
392
+ * @returns this for chaining
393
+ */
394
+ preventOverlap(skip = true) {
395
+ this._skipIfRunning = skip;
396
+ return this;
397
+ }
398
+ /**
399
+ * Configure automatic retry on failure
400
+ *
401
+ * @param maxRetries - Maximum number of retry attempts (must be ≥ 0)
402
+ * @param delay - Delay between retries in milliseconds (must be ≥ 0)
403
+ * @param backoffMultiplier - Optional multiplier for exponential backoff (must be > 0)
404
+ * @returns this for chaining
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * job.retry(3, 1000); // Retry 3 times with 1s delay
409
+ * job.retry(5, 1000, 2); // Exponential backoff: 1s, 2s, 4s, 8s, 16s
410
+ * ```
411
+ */
412
+ retry(maxRetries, delay = 1e3, backoffMultiplier) {
413
+ if (!Number.isFinite(maxRetries) || maxRetries < 0) throw new Error(`Invalid maxRetries: ${maxRetries}. Must be ≥ 0.`);
414
+ if (!Number.isFinite(delay) || delay < 0) throw new Error(`Invalid retry delay: ${delay}. Must be ≥ 0.`);
415
+ if (backoffMultiplier !== void 0 && (!Number.isFinite(backoffMultiplier) || backoffMultiplier <= 0)) throw new Error(`Invalid backoffMultiplier: ${backoffMultiplier}. Must be > 0.`);
416
+ this._retryConfig = {
417
+ maxRetries,
418
+ delay,
419
+ backoffMultiplier
420
+ };
421
+ return this;
422
+ }
423
+ /**
424
+ * Terminate the job and clear all scheduling data
425
+ */
426
+ terminate() {
427
+ this._intervals = {};
428
+ this._cronParser = null;
429
+ this.nextRun = null;
430
+ this._lastRun = null;
431
+ this._isRunning = false;
432
+ return this;
433
+ }
434
+ /**
435
+ * Prepare the job by calculating the next run time
436
+ * Called by the scheduler when starting
437
+ */
438
+ prepare() {
439
+ this._determineNextRun();
440
+ }
441
+ /**
442
+ * Returns true if the job's `nextRun` has arrived, regardless of whether
443
+ * it is currently running. Used by the scheduler to decide whether a
444
+ * tick is "due" before checking overlap state.
445
+ */
446
+ isDue() {
447
+ return this.nextRun !== null && this._now().isSameOrAfter(this.nextRun);
448
+ }
449
+ /**
450
+ * Determine if the job should run now.
451
+ *
452
+ * Combines `isDue()` with the overlap-prevention rule: when
453
+ * `preventOverlap()` is on and the job is already running, this returns
454
+ * false even if the next-run time has arrived.
455
+ *
456
+ * @returns true if the job should execute
457
+ */
458
+ shouldRun() {
459
+ if (this._skipIfRunning && this._isRunning) return false;
460
+ return this.isDue();
461
+ }
462
+ /**
463
+ * Execute the job once.
464
+ *
465
+ * Always advances `lastRun` and recalculates `nextRun` (success OR failure)
466
+ * so a permanently failing job does not re-fire on every scheduler tick.
467
+ *
468
+ * @returns Promise resolving to the job result
469
+ */
470
+ async run() {
471
+ const startTime = Date.now();
472
+ this._isRunning = true;
473
+ let result;
474
+ let executedRetries = 0;
475
+ try {
476
+ executedRetries = (await this._executeWithRetry()).retries;
477
+ result = {
478
+ success: true,
479
+ duration: Date.now() - startTime,
480
+ retries: executedRetries
481
+ };
482
+ } catch (error) {
483
+ result = {
484
+ success: false,
485
+ duration: Date.now() - startTime,
486
+ error,
487
+ retries: this._retryConfig?.maxRetries ?? 0
488
+ };
489
+ } finally {
490
+ this._lastRun = this._now();
491
+ this._determineNextRun();
492
+ this._isRunning = false;
493
+ const resolvers = this._completionResolvers.splice(0);
494
+ for (const resolve of resolvers) resolve();
495
+ }
496
+ return result;
497
+ }
498
+ /**
499
+ * Wait for the currently executing run to complete.
500
+ *
501
+ * Multiple concurrent waiters are all resolved when the run finishes.
502
+ * Useful for graceful shutdown.
503
+ *
504
+ * @returns Promise that resolves when the job completes
505
+ */
506
+ waitForCompletion() {
507
+ if (!this._isRunning) return Promise.resolve();
508
+ return new Promise((resolve) => {
509
+ this._completionResolvers.push(resolve);
510
+ });
511
+ }
512
+ /**
513
+ * Get current time, respecting the configured timezone.
514
+ */
515
+ _now() {
516
+ return dayjs().tz(this._timezone);
517
+ }
518
+ /**
519
+ * Execute the callback with retry logic
520
+ */
521
+ async _executeWithRetry() {
522
+ let lastError;
523
+ let attempts = 0;
524
+ const maxAttempts = (this._retryConfig?.maxRetries ?? 0) + 1;
525
+ while (attempts < maxAttempts) try {
526
+ await this._callback(this);
527
+ return { retries: attempts };
528
+ } catch (error) {
529
+ lastError = error;
530
+ attempts++;
531
+ if (attempts < maxAttempts && this._retryConfig) {
532
+ const delay = this._calculateRetryDelay(attempts);
533
+ await this._sleep(delay);
534
+ }
535
+ }
536
+ throw lastError;
537
+ }
538
+ /**
539
+ * Calculate retry delay with optional exponential backoff
540
+ */
541
+ _calculateRetryDelay(attempt) {
542
+ if (!this._retryConfig) return 0;
543
+ const { delay, backoffMultiplier } = this._retryConfig;
544
+ if (backoffMultiplier) return delay * Math.pow(backoffMultiplier, attempt - 1);
545
+ return delay;
546
+ }
547
+ /**
548
+ * Sleep for specified milliseconds
549
+ */
550
+ _sleep(ms) {
551
+ return new Promise((resolve) => setTimeout(resolve, ms));
552
+ }
553
+ /**
554
+ * Apply month / day-of-month / day-of-week / time constraints to a Dayjs.
555
+ *
556
+ * Re-applied after every interval advance inside `_determineNextRun` so
557
+ * dynamic constraints (`dayOfMonthMode: "last"` recomputed per month, month
558
+ * lock for `beginOf/endOf("year")`) stay correct as the candidate moves
559
+ * forward.
560
+ */
561
+ _applyConstraints(date) {
562
+ let result = date;
563
+ if (this._intervals.month !== void 0) result = result.month(this._intervals.month - 1);
564
+ if (this._intervals.dayOfMonthMode === "last") result = result.date(result.daysInMonth());
565
+ else if (this._intervals.day !== void 0) if (typeof this._intervals.day === "number") result = result.date(this._intervals.day);
566
+ else {
567
+ const targetDay = DAYS_OF_WEEK.indexOf(this._intervals.day);
568
+ if (targetDay !== -1) result = result.day(targetDay);
569
+ }
570
+ if (this._intervals.time) {
571
+ const { hour, minute, second } = parseTimeString(this._intervals.time);
572
+ result = result.hour(hour).minute(minute).second(second).millisecond(0);
573
+ }
574
+ return result;
575
+ }
576
+ /**
577
+ * Calculate the next run time based on interval or cron configuration.
578
+ *
579
+ * Strategy: apply all constraints (month, day, time) ONCE before the
580
+ * advance loop, then advance by `every` until the candidate is in the
581
+ * future. Static constraints (numeric day, fixed month, fixed time)
582
+ * survive `dayjs.add()` automatically. The only dynamic constraint —
583
+ * `dayOfMonthMode: "last"` — is re-applied inside the loop so the
584
+ * candidate always lands on the *new* month's last day after each
585
+ * advance.
586
+ *
587
+ * Re-applying time/day inside the loop would deadlock: with
588
+ * `twiceDaily().at("06:00")` we'd advance 06:00 → 18:00 → snap back to
589
+ * 06:00 → 18:00 → ... forever.
590
+ */
591
+ _determineNextRun() {
592
+ if (this._cronParser) {
593
+ const now = this._now();
594
+ this.nextRun = this._cronParser.nextRun(now);
595
+ return;
596
+ }
597
+ const intervalValue = this._intervals.every?.value;
598
+ const intervalType = this._intervals.every?.type;
599
+ const hasInterval = !!(intervalValue && intervalType);
600
+ let date;
601
+ if (this._lastRun && hasInterval) date = this._lastRun.add(intervalValue, intervalType);
602
+ else if (this._lastRun) date = this._lastRun.add(1, "second");
603
+ else date = this._now();
604
+ date = this._applyConstraints(date);
605
+ while (date.isBefore(this._now())) {
606
+ if (hasInterval) date = date.add(intervalValue, intervalType);
607
+ else date = date.add(1, "day");
608
+ if (this._intervals.dayOfMonthMode === "last") date = date.date(date.daysInMonth());
609
+ }
610
+ this.nextRun = date;
611
+ }
612
+ };
613
+ /**
614
+ * Factory function to create a new Job instance
615
+ *
616
+ * @param name - Unique identifier for the job
617
+ * @param callback - Function to execute when the job runs
618
+ * @returns New Job instance
619
+ *
620
+ * @example
621
+ * ```typescript
622
+ * const cleanupJob = job("cleanup", async () => {
623
+ * await db.deleteExpiredTokens();
624
+ * }).daily().at("03:00");
625
+ * ```
626
+ */
627
+ function job(name, callback) {
628
+ return new Job(name, callback);
629
+ }
630
+
631
+ //#endregion
632
+ export { Job, job };
633
+ //# sourceMappingURL=job.mjs.map