@azlib/scheduler 0.2.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/dist/index.cjs ADDED
@@ -0,0 +1,645 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_crypto = require("node:crypto");
3
+ let _azlib_cache = require("@azlib/cache");
4
+ let _azlib_logger = require("@azlib/logger");
5
+ //#region src/core/scheduler-host-binding.ts
6
+ function bindSchedulerToHost(scheduler, host) {
7
+ host.onStart(() => scheduler.start());
8
+ host.onStop(() => scheduler.stop());
9
+ }
10
+ //#endregion
11
+ //#region src/core/cron-expression-builder.ts
12
+ const CronWeekday = {
13
+ Sunday: 0,
14
+ Monday: 1,
15
+ Tuesday: 2,
16
+ Wednesday: 3,
17
+ Thursday: 4,
18
+ Friday: 5,
19
+ Saturday: 6
20
+ };
21
+ function assertIntegerInRange(label, value, min, max) {
22
+ if (!Number.isInteger(value) || value < min || value > max) throw new Error(`${label} must be an integer between ${min} and ${max}`);
23
+ }
24
+ function assertPositiveInteger(label, value) {
25
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
26
+ }
27
+ function assertStepInRange(label, value, max) {
28
+ assertPositiveInteger(label, value);
29
+ if (value > max) throw new Error(`${label} must be less than or equal to ${max}`);
30
+ }
31
+ function toSortedCsv(values) {
32
+ return [...new Set(values)].sort((left, right) => left - right).join(",");
33
+ }
34
+ var CronExpressionBuilder = class {
35
+ minute = "*";
36
+ hour = "*";
37
+ dayOfMonth = "*";
38
+ month = "*";
39
+ dayOfWeek = "*";
40
+ everyMinute() {
41
+ this.minute = "*";
42
+ return this;
43
+ }
44
+ everyNMinutes(interval) {
45
+ assertStepInRange("minute interval", interval, 59);
46
+ this.minute = `*/${interval}`;
47
+ return this;
48
+ }
49
+ atMinute(minute) {
50
+ assertIntegerInRange("minute", minute, 0, 59);
51
+ this.minute = String(minute);
52
+ return this;
53
+ }
54
+ atMinutes(minutes) {
55
+ if (minutes.length === 0) throw new Error("minutes must contain at least one value");
56
+ minutes.forEach((minute) => assertIntegerInRange("minute", minute, 0, 59));
57
+ this.minute = toSortedCsv(minutes);
58
+ return this;
59
+ }
60
+ everyHour() {
61
+ this.hour = "*";
62
+ return this;
63
+ }
64
+ everyNHours(interval) {
65
+ assertStepInRange("hour interval", interval, 23);
66
+ this.hour = `*/${interval}`;
67
+ return this;
68
+ }
69
+ atHour(hour) {
70
+ assertIntegerInRange("hour", hour, 0, 23);
71
+ this.hour = String(hour);
72
+ return this;
73
+ }
74
+ atHours(hours) {
75
+ if (hours.length === 0) throw new Error("hours must contain at least one value");
76
+ hours.forEach((hour) => assertIntegerInRange("hour", hour, 0, 23));
77
+ this.hour = toSortedCsv(hours);
78
+ return this;
79
+ }
80
+ onDayOfMonth(day) {
81
+ assertIntegerInRange("day of month", day, 1, 31);
82
+ this.dayOfMonth = String(day);
83
+ return this;
84
+ }
85
+ onDaysOfMonth(days) {
86
+ if (days.length === 0) throw new Error("days must contain at least one value");
87
+ days.forEach((day) => assertIntegerInRange("day of month", day, 1, 31));
88
+ this.dayOfMonth = toSortedCsv(days);
89
+ return this;
90
+ }
91
+ everyNDaysOfMonth(interval) {
92
+ assertStepInRange("day-of-month interval", interval, 31);
93
+ this.dayOfMonth = `*/${interval}`;
94
+ return this;
95
+ }
96
+ everyMonth() {
97
+ this.month = "*";
98
+ return this;
99
+ }
100
+ onMonth(month) {
101
+ assertIntegerInRange("month", month, 1, 12);
102
+ this.month = String(month);
103
+ return this;
104
+ }
105
+ onMonths(months) {
106
+ if (months.length === 0) throw new Error("months must contain at least one value");
107
+ months.forEach((month) => assertIntegerInRange("month", month, 1, 12));
108
+ this.month = toSortedCsv(months);
109
+ return this;
110
+ }
111
+ everyNMonths(interval) {
112
+ assertStepInRange("month interval", interval, 12);
113
+ this.month = `*/${interval}`;
114
+ return this;
115
+ }
116
+ onWeekday(weekday) {
117
+ assertIntegerInRange("weekday", weekday, 0, 6);
118
+ this.dayOfWeek = String(weekday);
119
+ return this;
120
+ }
121
+ onWeekdays(weekdays) {
122
+ if (weekdays.length === 0) throw new Error("weekdays must contain at least one value");
123
+ weekdays.forEach((weekday) => assertIntegerInRange("weekday", weekday, 0, 6));
124
+ this.dayOfWeek = toSortedCsv(weekdays);
125
+ return this;
126
+ }
127
+ everyNWeekdays(interval) {
128
+ assertStepInRange("weekday interval", interval, 6);
129
+ this.dayOfWeek = `*/${interval}`;
130
+ return this;
131
+ }
132
+ weekdays() {
133
+ this.dayOfWeek = "1-5";
134
+ return this;
135
+ }
136
+ weekends() {
137
+ this.dayOfWeek = "0,6";
138
+ return this;
139
+ }
140
+ dailyAt(hour, minute = 0) {
141
+ this.atHour(hour);
142
+ this.atMinute(minute);
143
+ this.dayOfMonth = "*";
144
+ this.month = "*";
145
+ this.dayOfWeek = "*";
146
+ return this;
147
+ }
148
+ weeklyOn(weekday, hour = 0, minute = 0) {
149
+ this.atHour(hour);
150
+ this.atMinute(minute);
151
+ this.dayOfWeek = String(weekday);
152
+ this.dayOfMonth = "*";
153
+ this.month = "*";
154
+ return this;
155
+ }
156
+ monthlyOn(dayOfMonth, hour = 0, minute = 0) {
157
+ this.atHour(hour);
158
+ this.atMinute(minute);
159
+ this.onDayOfMonth(dayOfMonth);
160
+ this.month = "*";
161
+ this.dayOfWeek = "*";
162
+ return this;
163
+ }
164
+ build() {
165
+ return `${this.minute} ${this.hour} ${this.dayOfMonth} ${this.month} ${this.dayOfWeek}`;
166
+ }
167
+ toString() {
168
+ return this.build();
169
+ }
170
+ };
171
+ function createCronExpression() {
172
+ return new CronExpressionBuilder();
173
+ }
174
+ //#endregion
175
+ //#region src/core/job-execution-store.ts
176
+ function createInMemoryJobExecutionStore() {
177
+ const byExecution = /* @__PURE__ */ new Map();
178
+ return {
179
+ create(record) {
180
+ byExecution.set(record.executionId, record);
181
+ return record;
182
+ },
183
+ update(executionId, patch) {
184
+ const current = byExecution.get(executionId);
185
+ if (!current) return null;
186
+ const next = {
187
+ ...current,
188
+ ...patch
189
+ };
190
+ byExecution.set(executionId, next);
191
+ return next;
192
+ },
193
+ listByJob(jobId) {
194
+ return Array.from(byExecution.values()).filter((item) => item.jobId === jobId);
195
+ }
196
+ };
197
+ }
198
+ //#endregion
199
+ //#region src/core/schedule-cursor-store.ts
200
+ function createInMemoryScheduleCursorStore() {
201
+ const cursors = /* @__PURE__ */ new Map();
202
+ return {
203
+ set(jobId, nextRunAt, now) {
204
+ const previous = cursors.get(jobId);
205
+ const next = {
206
+ jobId,
207
+ lastEvaluatedAt: now,
208
+ lastTriggeredAt: previous?.lastTriggeredAt,
209
+ nextRunAt,
210
+ version: (previous?.version ?? 0) + 1
211
+ };
212
+ cursors.set(jobId, next);
213
+ return next;
214
+ },
215
+ markTriggered(jobId, triggeredAt) {
216
+ const current = cursors.get(jobId);
217
+ if (!current) return null;
218
+ const next = {
219
+ ...current,
220
+ lastTriggeredAt: triggeredAt,
221
+ version: current.version + 1
222
+ };
223
+ cursors.set(jobId, next);
224
+ return next;
225
+ },
226
+ get(jobId) {
227
+ return cursors.get(jobId) ?? null;
228
+ },
229
+ remove(jobId) {
230
+ return cursors.delete(jobId);
231
+ }
232
+ };
233
+ }
234
+ //#endregion
235
+ //#region src/core/scheduler-cache.ts
236
+ function createSchedulerCache(namespace = "scheduler-core") {
237
+ return (0, _azlib_cache.createCache)({
238
+ mode: "memory",
239
+ namespace
240
+ });
241
+ }
242
+ //#endregion
243
+ //#region src/core/schedule-parser.ts
244
+ const CRON_FIELD_PATTERN = /^\*|\*\/\d+|\d+|\d+-\d+|\d+(?:,\d+)*$/;
245
+ function assertValidTimezone(timezone) {
246
+ try {
247
+ new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
248
+ } catch {
249
+ throw new Error(`Invalid timezone: ${timezone}`);
250
+ }
251
+ }
252
+ function assertValidCronExpression(expression) {
253
+ const fields = expression.trim().split(/\s+/);
254
+ if (fields.length !== 5) throw new Error("Cron expression must contain 5 fields");
255
+ for (const field of fields) if (!CRON_FIELD_PATTERN.test(field)) throw new Error(`Invalid cron field: ${field}`);
256
+ }
257
+ function assertValidOnceExpression(expression) {
258
+ const date = new Date(expression);
259
+ if (Number.isNaN(date.getTime())) throw new Error("Once schedule expression must be a valid ISO datetime");
260
+ }
261
+ function parseSchedule(scheduleType, expression, timezone) {
262
+ assertValidTimezone(timezone);
263
+ if (scheduleType === "cron") assertValidCronExpression(expression);
264
+ else assertValidOnceExpression(expression);
265
+ return {
266
+ scheduleType,
267
+ expression: expression.trim(),
268
+ timezone
269
+ };
270
+ }
271
+ function computeNextRunAt(parsed, now = /* @__PURE__ */ new Date()) {
272
+ if (parsed.scheduleType === "once") return new Date(parsed.expression).toISOString();
273
+ const next = new Date(now);
274
+ next.setUTCSeconds(0, 0);
275
+ next.setUTCMinutes(next.getUTCMinutes() + 1);
276
+ return next.toISOString();
277
+ }
278
+ //#endregion
279
+ //#region src/core/scheduler-engine.ts
280
+ function createSchedulerEngine(dependencies) {
281
+ let timer;
282
+ async function computeNextRun(job, now = /* @__PURE__ */ new Date()) {
283
+ const cacheKey = `scheduler.nextRun.${job.jobId}.${job.schedule.expression}.${job.schedule.timezone}`;
284
+ const cached = await dependencies.cache?.get(cacheKey);
285
+ if (cached?.status === "hit" && cached.value) return cached.value;
286
+ const nextRunAt = computeNextRunAt(parseSchedule(job.schedule.scheduleType, job.schedule.expression, job.schedule.timezone), now);
287
+ await dependencies.cache?.set(cacheKey, nextRunAt);
288
+ dependencies.cursorStore.set(job.jobId, nextRunAt, now.toISOString());
289
+ return nextRunAt;
290
+ }
291
+ async function evaluateTick() {
292
+ const now = /* @__PURE__ */ new Date();
293
+ const candidates = dependencies.jobStore.list().filter((job) => job.enabled).slice(0, dependencies.maxDueJobsPerTick);
294
+ for (const job of candidates) {
295
+ const nextRunAt = await computeNextRun(job, now);
296
+ if (new Date(nextRunAt).getTime() > now.getTime()) continue;
297
+ const executionId = `exec_${(0, node_crypto.randomUUID)()}`;
298
+ dependencies.executionStore.create({
299
+ executionId,
300
+ jobId: job.jobId,
301
+ scheduledFor: nextRunAt,
302
+ triggeredAt: now.toISOString(),
303
+ status: "queued",
304
+ attemptCount: 0
305
+ });
306
+ dependencies.cursorStore.markTriggered(job.jobId, now.toISOString());
307
+ if (dependencies.queueService) await dependencies.queueService.enqueue({
308
+ idempotencyKey: `scheduler:${job.jobId}:${nextRunAt}`,
309
+ payloadRef: {
310
+ executionId,
311
+ handlerKey: job.handlerKey,
312
+ config: job.config
313
+ }
314
+ });
315
+ dependencies.logger.info("scheduler.job.triggered", {
316
+ executionId,
317
+ jobId: job.jobId,
318
+ scheduledFor: nextRunAt
319
+ });
320
+ }
321
+ }
322
+ return {
323
+ async start() {
324
+ if (timer) return;
325
+ dependencies.logger.info("scheduler.runtime.started", { tickIntervalMs: dependencies.tickIntervalMs });
326
+ timer = setInterval(() => {
327
+ evaluateTick().catch((error) => {
328
+ dependencies.logger.error("scheduler.tick.failed", { error: error instanceof Error ? error.message : "Unknown scheduler error" });
329
+ });
330
+ }, dependencies.tickIntervalMs);
331
+ },
332
+ async stop() {
333
+ if (!timer) return;
334
+ clearInterval(timer);
335
+ timer = void 0;
336
+ dependencies.logger.info("scheduler.runtime.stopped");
337
+ },
338
+ computeNextRun
339
+ };
340
+ }
341
+ //#endregion
342
+ //#region src/core/scheduler-events.ts
343
+ function createInMemorySchedulerEventBus() {
344
+ const events = [];
345
+ return {
346
+ emit(event) {
347
+ const record = {
348
+ eventId: `evt_${Math.random().toString(36).slice(2, 12)}`,
349
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
350
+ ...event
351
+ };
352
+ events.push(record);
353
+ return record;
354
+ },
355
+ list() {
356
+ return [...events];
357
+ }
358
+ };
359
+ }
360
+ //#endregion
361
+ //#region src/core/scheduler-handler-registry.ts
362
+ function createSchedulerHandlerRegistry() {
363
+ const handlers = /* @__PURE__ */ new Map();
364
+ return {
365
+ register(handlerKey, handler) {
366
+ handlers.set(handlerKey, handler);
367
+ },
368
+ resolve(handlerKey) {
369
+ return handlers.get(handlerKey) ?? null;
370
+ },
371
+ has(handlerKey) {
372
+ return handlers.has(handlerKey);
373
+ }
374
+ };
375
+ }
376
+ //#endregion
377
+ //#region src/core/scheduler-job-store.ts
378
+ function createInMemorySchedulerJobStore() {
379
+ const jobs = /* @__PURE__ */ new Map();
380
+ return {
381
+ create(jobId, definition) {
382
+ const now = (/* @__PURE__ */ new Date()).toISOString();
383
+ const created = {
384
+ ...definition,
385
+ jobId,
386
+ enabled: definition.enabled ?? true,
387
+ createdAt: now,
388
+ updatedAt: now
389
+ };
390
+ jobs.set(jobId, created);
391
+ return created;
392
+ },
393
+ update(jobId, patch) {
394
+ const current = jobs.get(jobId);
395
+ if (!current) return null;
396
+ const updated = {
397
+ ...current,
398
+ ...patch,
399
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
400
+ };
401
+ jobs.set(jobId, updated);
402
+ return updated;
403
+ },
404
+ setEnabled(jobId, enabled) {
405
+ const current = jobs.get(jobId);
406
+ if (!current) return null;
407
+ const updated = {
408
+ ...current,
409
+ enabled,
410
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
411
+ };
412
+ jobs.set(jobId, updated);
413
+ return updated;
414
+ },
415
+ get(jobId) {
416
+ return jobs.get(jobId) ?? null;
417
+ },
418
+ list() {
419
+ return Array.from(jobs.values());
420
+ },
421
+ remove(jobId) {
422
+ return jobs.delete(jobId);
423
+ }
424
+ };
425
+ }
426
+ //#endregion
427
+ //#region src/core/scheduler-logger.ts
428
+ function createSchedulerLogger() {
429
+ return (0, _azlib_logger.createLogger)({
430
+ level: "info",
431
+ transports: [(0, _azlib_logger.createConsoleTransport)()]
432
+ });
433
+ }
434
+ //#endregion
435
+ //#region src/core/scheduler-runtime.ts
436
+ function createSchedulerRuntime(engine) {
437
+ let started = false;
438
+ return {
439
+ async start() {
440
+ if (started) return;
441
+ started = true;
442
+ await engine.start();
443
+ },
444
+ async stop() {
445
+ if (!started) return;
446
+ started = false;
447
+ await engine.stop();
448
+ }
449
+ };
450
+ }
451
+ //#endregion
452
+ //#region src/core/scheduler-runtime-config.ts
453
+ function assertRuntimeConfig(options) {
454
+ if (options.tickIntervalMs !== void 0 && options.tickIntervalMs <= 0) throw new Error("tickIntervalMs must be greater than 0");
455
+ if (options.maxDueJobsPerTick !== void 0 && options.maxDueJobsPerTick < 1) throw new Error("maxDueJobsPerTick must be at least 1");
456
+ }
457
+ //#endregion
458
+ //#region src/core/scheduler-service-query.ts
459
+ function toJobListItem(input) {
460
+ return {
461
+ jobId: input.jobId,
462
+ name: input.name,
463
+ enabled: input.enabled,
464
+ timezone: input.timezone,
465
+ nextRunAt: input.nextRunAt
466
+ };
467
+ }
468
+ function filterExecutions(items, query) {
469
+ if (!query) return items;
470
+ const fromTime = query.from ? new Date(query.from).getTime() : void 0;
471
+ const toTime = query.to ? new Date(query.to).getTime() : void 0;
472
+ return items.filter((item) => query.jobId ? item.jobId === query.jobId : true).filter((item) => query.status ? item.status === query.status : true).filter((item) => {
473
+ if (fromTime === void 0) return true;
474
+ return new Date(item.triggeredAt).getTime() >= fromTime;
475
+ }).filter((item) => {
476
+ if (toTime === void 0) return true;
477
+ return new Date(item.triggeredAt).getTime() <= toTime;
478
+ }).slice(0, query.limit ?? items.length);
479
+ }
480
+ //#endregion
481
+ //#region src/core/scheduler-service-retry.ts
482
+ function retryFailedExecutionById(allExecutions, executionStore, executionId) {
483
+ const target = allExecutions.find((item) => item.executionId === executionId);
484
+ if (!target) throw new Error(`Execution not found: ${executionId}`);
485
+ if (target.status !== "failed" && target.status !== "dead-letter") throw new Error("Only failed or dead-letter executions can be retried");
486
+ const retryExecutionId = `exec_${(0, node_crypto.randomUUID)()}`;
487
+ const retry = {
488
+ ...target,
489
+ executionId: retryExecutionId,
490
+ status: "queued",
491
+ attemptCount: target.attemptCount + 1,
492
+ triggeredAt: (/* @__PURE__ */ new Date()).toISOString()
493
+ };
494
+ executionStore.create(retry);
495
+ return retry;
496
+ }
497
+ //#endregion
498
+ //#region src/core/scheduler-service.ts
499
+ function createSchedulerService$1(options) {
500
+ assertRuntimeConfig(options);
501
+ const logger = options.logger ?? createSchedulerLogger();
502
+ const cache = options.cache ?? createSchedulerCache();
503
+ const jobStore = createInMemorySchedulerJobStore();
504
+ const cursorStore = createInMemoryScheduleCursorStore();
505
+ const executionStore = createInMemoryJobExecutionStore();
506
+ const events = createInMemorySchedulerEventBus();
507
+ const handlers = createSchedulerHandlerRegistry();
508
+ const queueService = options.queueService;
509
+ const engine = createSchedulerEngine({
510
+ queueService,
511
+ cache,
512
+ logger,
513
+ jobStore,
514
+ cursorStore,
515
+ executionStore,
516
+ tickIntervalMs: options.tickIntervalMs ?? 1e3,
517
+ maxDueJobsPerTick: options.maxDueJobsPerTick ?? 200
518
+ });
519
+ const runtime = createSchedulerRuntime(engine);
520
+ return {
521
+ service: {
522
+ async registerJob(job) {
523
+ if (!handlers.has(job.handlerKey)) throw new Error(`Unknown handlerKey: ${job.handlerKey}`);
524
+ const jobId = `job_${(0, node_crypto.randomUUID)()}`;
525
+ const created = jobStore.create(jobId, job);
526
+ const nextRunAt = await engine.computeNextRun(created);
527
+ cursorStore.set(jobId, nextRunAt, (/* @__PURE__ */ new Date()).toISOString());
528
+ events.emit({
529
+ eventType: "job-created",
530
+ jobId,
531
+ metadata: { name: job.name }
532
+ });
533
+ return { jobId };
534
+ },
535
+ async updateJob(jobId, patch) {
536
+ const updated = jobStore.update(jobId, patch);
537
+ if (!updated) throw new Error(`Job not found: ${jobId}`);
538
+ const nextRunAt = await engine.computeNextRun(updated);
539
+ cursorStore.set(jobId, nextRunAt, (/* @__PURE__ */ new Date()).toISOString());
540
+ events.emit({
541
+ eventType: "job-updated",
542
+ jobId
543
+ });
544
+ },
545
+ async pauseJob(jobId) {
546
+ if (!jobStore.setEnabled(jobId, false)) throw new Error(`Job not found: ${jobId}`);
547
+ events.emit({
548
+ eventType: "job-paused",
549
+ jobId
550
+ });
551
+ },
552
+ async resumeJob(jobId) {
553
+ if (!jobStore.setEnabled(jobId, true)) throw new Error(`Job not found: ${jobId}`);
554
+ events.emit({
555
+ eventType: "job-resumed",
556
+ jobId
557
+ });
558
+ },
559
+ async deleteJob(jobId) {
560
+ const removed = jobStore.remove(jobId);
561
+ cursorStore.remove(jobId);
562
+ if (!removed) throw new Error(`Job not found: ${jobId}`);
563
+ events.emit({
564
+ eventType: "job-deleted",
565
+ jobId
566
+ });
567
+ },
568
+ async listJobs() {
569
+ return jobStore.list().map((job) => {
570
+ const cursor = cursorStore.get(job.jobId);
571
+ return toJobListItem({
572
+ jobId: job.jobId,
573
+ name: job.name,
574
+ enabled: job.enabled,
575
+ timezone: job.schedule.timezone,
576
+ nextRunAt: cursor?.nextRunAt
577
+ });
578
+ });
579
+ },
580
+ async listExecutions(query) {
581
+ return filterExecutions(jobStore.list().flatMap((job) => executionStore.listByJob(job.jobId)), query);
582
+ },
583
+ async retryFailedExecution(executionId) {
584
+ const retry = retryFailedExecutionById(jobStore.list().flatMap((job) => executionStore.listByJob(job.jobId)), executionStore, executionId);
585
+ events.emit({
586
+ eventType: "retry",
587
+ jobId: retry.jobId,
588
+ executionId: retry.executionId,
589
+ metadata: { previousExecutionId: executionId }
590
+ });
591
+ },
592
+ async start() {
593
+ logger.info("scheduler.service.start", { mode: options.mode });
594
+ await runtime.start();
595
+ },
596
+ async stop() {
597
+ logger.info("scheduler.service.stop", { mode: options.mode });
598
+ await runtime.stop();
599
+ }
600
+ },
601
+ handlers
602
+ };
603
+ }
604
+ //#endregion
605
+ //#region src/dashboard/queue-queries.ts
606
+ async function querySchedulerHealth(scheduler) {
607
+ const jobs = await scheduler.listJobs();
608
+ const executions = await scheduler.listExecutions({ status: "failed" });
609
+ return {
610
+ totalJobs: jobs.length,
611
+ enabledJobs: jobs.filter((job) => job.enabled).length,
612
+ pausedJobs: jobs.filter((job) => !job.enabled).length,
613
+ failedExecutions: executions.length
614
+ };
615
+ }
616
+ async function querySchedulerItems(scheduler) {
617
+ const jobs = await scheduler.listJobs();
618
+ const executions = await scheduler.listExecutions({ limit: 100 });
619
+ return jobs.map((job) => ({
620
+ ...job,
621
+ executions: executions.filter((execution) => execution.jobId === job.jobId)
622
+ }));
623
+ }
624
+ //#endregion
625
+ //#region src/dashboard/queue-dashboard-service.ts
626
+ function createSchedulerDashboardService(scheduler) {
627
+ return {
628
+ getHealth: () => querySchedulerHealth(scheduler),
629
+ listItems: () => querySchedulerItems(scheduler),
630
+ pauseJob: (jobId) => scheduler.pauseJob(jobId),
631
+ resumeJob: (jobId) => scheduler.resumeJob(jobId),
632
+ retryExecution: (executionId) => scheduler.retryFailedExecution(executionId)
633
+ };
634
+ }
635
+ //#endregion
636
+ //#region index.ts
637
+ function createSchedulerService(options) {
638
+ return createSchedulerService$1(options);
639
+ }
640
+ //#endregion
641
+ exports.CronWeekday = CronWeekday;
642
+ exports.bindSchedulerToHost = bindSchedulerToHost;
643
+ exports.createCronExpression = createCronExpression;
644
+ exports.createSchedulerDashboardService = createSchedulerDashboardService;
645
+ exports.createSchedulerService = createSchedulerService;