@azlib/scheduler 1.0.5 → 1.1.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/Dockerfile +14 -0
- package/README.md +40 -0
- package/compose.yaml +28 -0
- package/dist/cli-config-7F2U8qPM.mjs +462 -0
- package/dist/cli-config-7F2U8qPM.mjs.map +1 -0
- package/dist/cli-config-Cz8QP_4I.cjs +512 -0
- package/dist/dashboard/cli.cjs +30 -0
- package/dist/dashboard/cli.d.cts +6 -0
- package/dist/dashboard/cli.d.cts.map +1 -0
- package/dist/dashboard/cli.d.mts +6 -0
- package/dist/dashboard/cli.d.mts.map +1 -0
- package/dist/dashboard/cli.mjs +31 -0
- package/dist/dashboard/cli.mjs.map +1 -0
- package/dist/dashboard/public/assets/index-CbmpG6_w.css +2 -0
- package/dist/dashboard/public/assets/index-eWhp41cY.js +12 -0
- package/dist/dashboard/public/index.html +13 -0
- package/dist/dashboard-types-BHjRLcxJ.d.cts +127 -0
- package/dist/dashboard-types-BHjRLcxJ.d.cts.map +1 -0
- package/dist/dashboard-types-BHjRLcxJ.d.mts +127 -0
- package/dist/dashboard-types-BHjRLcxJ.d.mts.map +1 -0
- package/dist/dashboard.cjs +9 -0
- package/dist/dashboard.d.cts +20 -0
- package/dist/dashboard.d.cts.map +1 -0
- package/dist/dashboard.d.mts +20 -0
- package/dist/dashboard.d.mts.map +1 -0
- package/dist/dashboard.mjs +4 -0
- package/dist/http-server-DN6Lg464.d.cts +19 -0
- package/dist/http-server-DN6Lg464.d.cts.map +1 -0
- package/dist/http-server-DPQ9HIK-.d.mts +19 -0
- package/dist/http-server-DPQ9HIK-.d.mts.map +1 -0
- package/dist/index.cjs +49 -442
- package/dist/index.d.cts +3 -158
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -158
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +44 -439
- package/dist/index.mjs.map +1 -1
- package/dist/queue-dashboard-service-BFXcnfUr.cjs +55 -0
- package/dist/queue-dashboard-service-C0QapgOg.mjs +52 -0
- package/dist/queue-dashboard-service-C0QapgOg.mjs.map +1 -0
- package/dist/store-dashboard-C44d7sdh.d.cts +59 -0
- package/dist/store-dashboard-C44d7sdh.d.cts.map +1 -0
- package/dist/store-dashboard-Cyf_JILa.mjs +515 -0
- package/dist/store-dashboard-Cyf_JILa.mjs.map +1 -0
- package/dist/store-dashboard-D-yIxXxL.d.mts +59 -0
- package/dist/store-dashboard-D-yIxXxL.d.mts.map +1 -0
- package/dist/store-dashboard-Sg4BBWiJ.cjs +554 -0
- package/package.json +55 -7
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_store_dashboard = require("./store-dashboard-Sg4BBWiJ.cjs");
|
|
3
|
+
const require_queue_dashboard_service = require("./queue-dashboard-service-BFXcnfUr.cjs");
|
|
2
4
|
let _azlib_std = require("@azlib/std");
|
|
3
5
|
let node_crypto = require("node:crypto");
|
|
4
6
|
let _azlib_cache = require("@azlib/cache");
|
|
@@ -241,42 +243,6 @@ function createSchedulerCache(namespace = "scheduler-core") {
|
|
|
241
243
|
});
|
|
242
244
|
}
|
|
243
245
|
//#endregion
|
|
244
|
-
//#region src/core/schedule-parser.ts
|
|
245
|
-
const CRON_FIELD_PATTERN = /^\*|\*\/\d+|\d+|\d+-\d+|\d+(?:,\d+)*$/;
|
|
246
|
-
function assertValidTimezone(timezone) {
|
|
247
|
-
try {
|
|
248
|
-
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
|
|
249
|
-
} catch {
|
|
250
|
-
throw new Error(`Invalid timezone: ${timezone}`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
function assertValidCronExpression(expression) {
|
|
254
|
-
const fields = expression.trim().split(/\s+/);
|
|
255
|
-
if (fields.length !== 5) throw new Error("Cron expression must contain 5 fields");
|
|
256
|
-
for (const field of fields) if (!CRON_FIELD_PATTERN.test(field)) throw new Error(`Invalid cron field: ${field}`);
|
|
257
|
-
}
|
|
258
|
-
function assertValidOnceExpression(expression) {
|
|
259
|
-
const date = new Date(expression);
|
|
260
|
-
if (Number.isNaN(date.getTime())) throw new Error("Once schedule expression must be a valid ISO datetime");
|
|
261
|
-
}
|
|
262
|
-
function parseSchedule(scheduleType, expression, timezone) {
|
|
263
|
-
assertValidTimezone(timezone);
|
|
264
|
-
if (scheduleType === "cron") assertValidCronExpression(expression);
|
|
265
|
-
else assertValidOnceExpression(expression);
|
|
266
|
-
return {
|
|
267
|
-
scheduleType,
|
|
268
|
-
expression: expression.trim(),
|
|
269
|
-
timezone
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
function computeNextRunAt(parsed, now = /* @__PURE__ */ new Date()) {
|
|
273
|
-
if (parsed.scheduleType === "once") return new Date(parsed.expression).toISOString();
|
|
274
|
-
const next = new Date(now);
|
|
275
|
-
next.setUTCSeconds(0, 0);
|
|
276
|
-
next.setUTCMinutes(next.getUTCMinutes() + 1);
|
|
277
|
-
return next.toISOString();
|
|
278
|
-
}
|
|
279
|
-
//#endregion
|
|
280
246
|
//#region src/core/scheduler-engine.ts
|
|
281
247
|
function createSchedulerEngine(dependencies) {
|
|
282
248
|
let timer;
|
|
@@ -284,7 +250,7 @@ function createSchedulerEngine(dependencies) {
|
|
|
284
250
|
const cacheKey = `scheduler.nextRun.${job.jobId}.${job.schedule.expression}.${job.schedule.timezone}`;
|
|
285
251
|
const cached = await dependencies.cache?.get(cacheKey);
|
|
286
252
|
if (cached?.status === "hit" && cached.value) return cached.value;
|
|
287
|
-
const nextRunAt = computeNextRunAt(parseSchedule(job.schedule.scheduleType, job.schedule.expression, job.schedule.timezone), now);
|
|
253
|
+
const nextRunAt = require_store_dashboard.computeNextRunAt(require_store_dashboard.parseSchedule(job.schedule.scheduleType, job.schedule.expression, job.schedule.timezone), now);
|
|
288
254
|
await dependencies.cache?.set(cacheKey, nextRunAt);
|
|
289
255
|
await dependencies.cursorStore.set(job.jobId, nextRunAt, now.toISOString());
|
|
290
256
|
return nextRunAt;
|
|
@@ -295,26 +261,16 @@ function createSchedulerEngine(dependencies) {
|
|
|
295
261
|
for (const job of candidates) {
|
|
296
262
|
const nextRunAt = await computeNextRun(job, now);
|
|
297
263
|
if (new Date(nextRunAt).getTime() > now.getTime()) continue;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
264
|
+
await require_store_dashboard.dispatchJobExecution({
|
|
265
|
+
job,
|
|
266
|
+
executionStore: dependencies.executionStore,
|
|
267
|
+
queueService: dependencies.queueService,
|
|
302
268
|
scheduledFor: nextRunAt,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
attemptCount: 0
|
|
269
|
+
attemptCount: 0,
|
|
270
|
+
idempotencyKey: `scheduler:${job.jobId}:${nextRunAt}`
|
|
306
271
|
});
|
|
307
272
|
await dependencies.cursorStore.markTriggered(job.jobId, now.toISOString());
|
|
308
|
-
if (dependencies.queueService) await dependencies.queueService.enqueue({
|
|
309
|
-
idempotencyKey: `scheduler:${job.jobId}:${nextRunAt}`,
|
|
310
|
-
payloadRef: {
|
|
311
|
-
executionId,
|
|
312
|
-
handlerKey: job.handlerKey,
|
|
313
|
-
config: job.config
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
273
|
dependencies.logger.info("scheduler.job.triggered", {
|
|
317
|
-
executionId,
|
|
318
274
|
jobId: job.jobId,
|
|
319
275
|
scheduledFor: nextRunAt
|
|
320
276
|
});
|
|
@@ -371,6 +327,9 @@ function createSchedulerHandlerRegistry() {
|
|
|
371
327
|
},
|
|
372
328
|
has(handlerKey) {
|
|
373
329
|
return handlers.has(handlerKey);
|
|
330
|
+
},
|
|
331
|
+
keys() {
|
|
332
|
+
return Array.from(handlers.keys());
|
|
374
333
|
}
|
|
375
334
|
};
|
|
376
335
|
}
|
|
@@ -463,7 +422,12 @@ function toJobListItem(input) {
|
|
|
463
422
|
name: input.name,
|
|
464
423
|
enabled: input.enabled,
|
|
465
424
|
timezone: input.timezone,
|
|
466
|
-
nextRunAt: input.nextRunAt
|
|
425
|
+
nextRunAt: input.nextRunAt,
|
|
426
|
+
handlerKey: input.handlerKey,
|
|
427
|
+
schedule: input.schedule,
|
|
428
|
+
config: input.config,
|
|
429
|
+
createdAt: input.createdAt,
|
|
430
|
+
updatedAt: input.updatedAt
|
|
467
431
|
};
|
|
468
432
|
}
|
|
469
433
|
function filterExecutions(items, query) {
|
|
@@ -479,364 +443,12 @@ function filterExecutions(items, query) {
|
|
|
479
443
|
}).slice(0, query.limit ?? items.length);
|
|
480
444
|
}
|
|
481
445
|
//#endregion
|
|
482
|
-
//#region src/core/scheduler-service-retry.ts
|
|
483
|
-
async function retryFailedExecutionById(allExecutions, executionStore, executionId) {
|
|
484
|
-
const target = allExecutions.find((item) => item.executionId === executionId);
|
|
485
|
-
if (!target) throw new Error(`Execution not found: ${executionId}`);
|
|
486
|
-
if (target.status !== "failed" && target.status !== "dead-letter") throw new Error("Only failed or dead-letter executions can be retried");
|
|
487
|
-
const retryExecutionId = `exec_${(0, node_crypto.randomUUID)()}`;
|
|
488
|
-
const retry = {
|
|
489
|
-
...target,
|
|
490
|
-
executionId: retryExecutionId,
|
|
491
|
-
status: "queued",
|
|
492
|
-
attemptCount: target.attemptCount + 1,
|
|
493
|
-
triggeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
494
|
-
};
|
|
495
|
-
await executionStore.create(retry);
|
|
496
|
-
return retry;
|
|
497
|
-
}
|
|
498
|
-
//#endregion
|
|
499
|
-
//#region src/core/persistence.ts
|
|
500
|
-
function namespaceFor(config) {
|
|
501
|
-
return config.namespace?.trim() || "azlib";
|
|
502
|
-
}
|
|
503
|
-
function tableNames(namespace) {
|
|
504
|
-
return {
|
|
505
|
-
jobs: `${namespace}__scheduler_jobs`,
|
|
506
|
-
cursors: `${namespace}__scheduler_cursors`,
|
|
507
|
-
executions: `${namespace}__scheduler_executions`
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function quoted(dialect, tableName) {
|
|
511
|
-
return dialect.quoteIdentifier(tableName);
|
|
512
|
-
}
|
|
513
|
-
async function bootstrapSchedulerPersistence(client, dialect, names) {
|
|
514
|
-
await client.transaction(async (tx) => {
|
|
515
|
-
await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.jobs)} (
|
|
516
|
-
job_id TEXT PRIMARY KEY,
|
|
517
|
-
name TEXT NOT NULL,
|
|
518
|
-
handler_key TEXT NOT NULL,
|
|
519
|
-
schedule_type TEXT NOT NULL,
|
|
520
|
-
expression TEXT NOT NULL,
|
|
521
|
-
timezone TEXT NOT NULL,
|
|
522
|
-
overlap_policy TEXT,
|
|
523
|
-
missed_run_policy TEXT,
|
|
524
|
-
config_json TEXT NOT NULL,
|
|
525
|
-
enabled INTEGER NOT NULL,
|
|
526
|
-
created_at TEXT NOT NULL,
|
|
527
|
-
updated_at TEXT NOT NULL
|
|
528
|
-
)`);
|
|
529
|
-
await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.cursors)} (
|
|
530
|
-
job_id TEXT PRIMARY KEY,
|
|
531
|
-
last_evaluated_at TEXT NOT NULL,
|
|
532
|
-
last_triggered_at TEXT,
|
|
533
|
-
next_run_at TEXT NOT NULL,
|
|
534
|
-
version INTEGER NOT NULL
|
|
535
|
-
)`);
|
|
536
|
-
await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.executions)} (
|
|
537
|
-
execution_id TEXT PRIMARY KEY,
|
|
538
|
-
job_id TEXT NOT NULL,
|
|
539
|
-
scheduled_for TEXT NOT NULL,
|
|
540
|
-
triggered_at TEXT NOT NULL,
|
|
541
|
-
status TEXT NOT NULL,
|
|
542
|
-
queue_task_id TEXT,
|
|
543
|
-
attempt_count INTEGER NOT NULL,
|
|
544
|
-
last_error TEXT
|
|
545
|
-
)`);
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
function toJobRow(jobId, definition) {
|
|
549
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
550
|
-
return {
|
|
551
|
-
jobId,
|
|
552
|
-
name: definition.name,
|
|
553
|
-
handlerKey: definition.handlerKey,
|
|
554
|
-
scheduleType: definition.schedule.scheduleType,
|
|
555
|
-
expression: definition.schedule.expression,
|
|
556
|
-
timezone: definition.schedule.timezone,
|
|
557
|
-
overlapPolicy: definition.schedule.overlapPolicy ?? null,
|
|
558
|
-
missedRunPolicy: definition.schedule.missedRunPolicy ?? null,
|
|
559
|
-
configJson: JSON.stringify(definition.config),
|
|
560
|
-
enabled: definition.enabled ?? true ? 1 : 0,
|
|
561
|
-
createdAt: now,
|
|
562
|
-
updatedAt: now
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
function rowToJobRecord(row) {
|
|
566
|
-
return {
|
|
567
|
-
jobId: row.jobId,
|
|
568
|
-
name: row.name,
|
|
569
|
-
handlerKey: row.handlerKey,
|
|
570
|
-
schedule: {
|
|
571
|
-
scheduleType: row.scheduleType,
|
|
572
|
-
expression: row.expression,
|
|
573
|
-
timezone: row.timezone,
|
|
574
|
-
overlapPolicy: row.overlapPolicy === null ? void 0 : row.overlapPolicy,
|
|
575
|
-
missedRunPolicy: row.missedRunPolicy === null ? void 0 : row.missedRunPolicy
|
|
576
|
-
},
|
|
577
|
-
config: JSON.parse(row.configJson),
|
|
578
|
-
enabled: Boolean(row.enabled),
|
|
579
|
-
createdAt: row.createdAt,
|
|
580
|
-
updatedAt: row.updatedAt
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
async function readJobRow(client, dialect, names, jobId) {
|
|
584
|
-
const rows = await client.query(`SELECT job_id AS jobId, name, handler_key AS handlerKey, schedule_type AS scheduleType, expression, timezone, overlap_policy AS overlapPolicy, missed_run_policy AS missedRunPolicy, config_json AS configJson, enabled, created_at AS createdAt, updated_at AS updatedAt
|
|
585
|
-
FROM ${quoted(dialect, names.jobs)} WHERE job_id = ? LIMIT 1`, [jobId]);
|
|
586
|
-
return rows[0] ? rowToJobRecord(rows[0]) : null;
|
|
587
|
-
}
|
|
588
|
-
async function readCursorRow(client, dialect, names, jobId) {
|
|
589
|
-
const row = (await client.query(`SELECT job_id AS jobId, last_evaluated_at AS lastEvaluatedAt, last_triggered_at AS lastTriggeredAt, next_run_at AS nextRunAt, version
|
|
590
|
-
FROM ${quoted(dialect, names.cursors)} WHERE job_id = ? LIMIT 1`, [jobId]))[0];
|
|
591
|
-
return row ? {
|
|
592
|
-
jobId: row.jobId,
|
|
593
|
-
lastEvaluatedAt: row.lastEvaluatedAt,
|
|
594
|
-
lastTriggeredAt: row.lastTriggeredAt ?? void 0,
|
|
595
|
-
nextRunAt: row.nextRunAt,
|
|
596
|
-
version: row.version
|
|
597
|
-
} : null;
|
|
598
|
-
}
|
|
599
|
-
async function readExecutionRow(client, dialect, names, executionId) {
|
|
600
|
-
const row = (await client.query(`SELECT execution_id AS executionId, job_id AS jobId, scheduled_for AS scheduledFor, triggered_at AS triggeredAt, status, queue_task_id AS queueTaskId, attempt_count AS attemptCount, last_error AS lastError
|
|
601
|
-
FROM ${quoted(dialect, names.executions)} WHERE execution_id = ? LIMIT 1`, [executionId]))[0];
|
|
602
|
-
return row ? {
|
|
603
|
-
executionId: row.executionId,
|
|
604
|
-
jobId: row.jobId,
|
|
605
|
-
scheduledFor: row.scheduledFor,
|
|
606
|
-
triggeredAt: row.triggeredAt,
|
|
607
|
-
status: row.status,
|
|
608
|
-
queueTaskId: row.queueTaskId ?? void 0,
|
|
609
|
-
attemptCount: row.attemptCount,
|
|
610
|
-
lastError: row.lastError ?? void 0
|
|
611
|
-
} : null;
|
|
612
|
-
}
|
|
613
|
-
function createSchedulerPersistence(config) {
|
|
614
|
-
const names = tableNames(namespaceFor(config));
|
|
615
|
-
let bootstrapPromise = null;
|
|
616
|
-
async function ensureBootstrapped() {
|
|
617
|
-
if (!bootstrapPromise) bootstrapPromise = bootstrapSchedulerPersistence(config.client, config.dialect, names);
|
|
618
|
-
await bootstrapPromise;
|
|
619
|
-
}
|
|
620
|
-
return {
|
|
621
|
-
jobStore: {
|
|
622
|
-
async create(jobId, definition) {
|
|
623
|
-
await ensureBootstrapped();
|
|
624
|
-
const row = toJobRow(jobId, definition);
|
|
625
|
-
await config.client.transaction(async (tx) => {
|
|
626
|
-
await tx.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
|
|
627
|
-
await tx.execute(`INSERT INTO ${quoted(config.dialect, names.jobs)} (job_id, name, handler_key, schedule_type, expression, timezone, overlap_policy, missed_run_policy, config_json, enabled, created_at, updated_at)
|
|
628
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
629
|
-
row.jobId,
|
|
630
|
-
row.name,
|
|
631
|
-
row.handlerKey,
|
|
632
|
-
row.scheduleType,
|
|
633
|
-
row.expression,
|
|
634
|
-
row.timezone,
|
|
635
|
-
row.overlapPolicy,
|
|
636
|
-
row.missedRunPolicy,
|
|
637
|
-
row.configJson,
|
|
638
|
-
row.enabled,
|
|
639
|
-
row.createdAt,
|
|
640
|
-
row.updatedAt
|
|
641
|
-
]);
|
|
642
|
-
});
|
|
643
|
-
return rowToJobRecord(row);
|
|
644
|
-
},
|
|
645
|
-
async update(jobId, patch) {
|
|
646
|
-
const current = await readJobRow(config.client, config.dialect, names, jobId);
|
|
647
|
-
if (!current) return null;
|
|
648
|
-
const next = {
|
|
649
|
-
...current,
|
|
650
|
-
...patch,
|
|
651
|
-
schedule: {
|
|
652
|
-
...current.schedule,
|
|
653
|
-
...patch.schedule
|
|
654
|
-
},
|
|
655
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
656
|
-
};
|
|
657
|
-
await config.client.transaction(async (tx) => {
|
|
658
|
-
await tx.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
|
|
659
|
-
await tx.execute(`INSERT INTO ${quoted(config.dialect, names.jobs)} (job_id, name, handler_key, schedule_type, expression, timezone, overlap_policy, missed_run_policy, config_json, enabled, created_at, updated_at)
|
|
660
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
661
|
-
next.jobId,
|
|
662
|
-
next.name,
|
|
663
|
-
next.handlerKey,
|
|
664
|
-
next.schedule.scheduleType,
|
|
665
|
-
next.schedule.expression,
|
|
666
|
-
next.schedule.timezone,
|
|
667
|
-
next.schedule.overlapPolicy ?? null,
|
|
668
|
-
next.schedule.missedRunPolicy ?? null,
|
|
669
|
-
JSON.stringify(next.config),
|
|
670
|
-
next.enabled ? 1 : 0,
|
|
671
|
-
next.createdAt,
|
|
672
|
-
next.updatedAt
|
|
673
|
-
]);
|
|
674
|
-
});
|
|
675
|
-
return next;
|
|
676
|
-
},
|
|
677
|
-
async setEnabled(jobId, enabled) {
|
|
678
|
-
const current = await readJobRow(config.client, config.dialect, names, jobId);
|
|
679
|
-
if (!current) return null;
|
|
680
|
-
const next = {
|
|
681
|
-
...current,
|
|
682
|
-
enabled,
|
|
683
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
684
|
-
};
|
|
685
|
-
await config.client.transaction(async (tx) => {
|
|
686
|
-
await tx.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
|
|
687
|
-
await tx.execute(`INSERT INTO ${quoted(config.dialect, names.jobs)} (job_id, name, handler_key, schedule_type, expression, timezone, overlap_policy, missed_run_policy, config_json, enabled, created_at, updated_at)
|
|
688
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
689
|
-
next.jobId,
|
|
690
|
-
next.name,
|
|
691
|
-
next.handlerKey,
|
|
692
|
-
next.schedule.scheduleType,
|
|
693
|
-
next.schedule.expression,
|
|
694
|
-
next.schedule.timezone,
|
|
695
|
-
next.schedule.overlapPolicy ?? null,
|
|
696
|
-
next.schedule.missedRunPolicy ?? null,
|
|
697
|
-
JSON.stringify(next.config),
|
|
698
|
-
next.enabled ? 1 : 0,
|
|
699
|
-
next.createdAt,
|
|
700
|
-
next.updatedAt
|
|
701
|
-
]);
|
|
702
|
-
});
|
|
703
|
-
return next;
|
|
704
|
-
},
|
|
705
|
-
async get(jobId) {
|
|
706
|
-
await ensureBootstrapped();
|
|
707
|
-
return readJobRow(config.client, config.dialect, names, jobId);
|
|
708
|
-
},
|
|
709
|
-
async list() {
|
|
710
|
-
await ensureBootstrapped();
|
|
711
|
-
return (await config.client.query(`SELECT job_id AS jobId, name, handler_key AS handlerKey, schedule_type AS scheduleType, expression, timezone, overlap_policy AS overlapPolicy, missed_run_policy AS missedRunPolicy, config_json AS configJson, enabled, created_at AS createdAt, updated_at AS updatedAt
|
|
712
|
-
FROM ${quoted(config.dialect, names.jobs)} ORDER BY created_at ASC`, [])).map(rowToJobRecord);
|
|
713
|
-
},
|
|
714
|
-
async remove(jobId) {
|
|
715
|
-
await ensureBootstrapped();
|
|
716
|
-
await config.client.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
|
|
717
|
-
return true;
|
|
718
|
-
}
|
|
719
|
-
},
|
|
720
|
-
cursorStore: {
|
|
721
|
-
async set(jobId, nextRunAt, now) {
|
|
722
|
-
await ensureBootstrapped();
|
|
723
|
-
const current = await readCursorRow(config.client, config.dialect, names, jobId);
|
|
724
|
-
const next = {
|
|
725
|
-
jobId,
|
|
726
|
-
lastEvaluatedAt: now,
|
|
727
|
-
lastTriggeredAt: current?.lastTriggeredAt ?? null,
|
|
728
|
-
nextRunAt,
|
|
729
|
-
version: (current?.version ?? 0) + 1
|
|
730
|
-
};
|
|
731
|
-
await config.client.transaction(async (tx) => {
|
|
732
|
-
await tx.execute(`DELETE FROM ${quoted(config.dialect, names.cursors)} WHERE job_id = ?`, [jobId]);
|
|
733
|
-
await tx.execute(`INSERT INTO ${quoted(config.dialect, names.cursors)} (job_id, last_evaluated_at, last_triggered_at, next_run_at, version) VALUES (?, ?, ?, ?, ?)`, [
|
|
734
|
-
next.jobId,
|
|
735
|
-
next.lastEvaluatedAt,
|
|
736
|
-
next.lastTriggeredAt,
|
|
737
|
-
next.nextRunAt,
|
|
738
|
-
next.version
|
|
739
|
-
]);
|
|
740
|
-
});
|
|
741
|
-
return {
|
|
742
|
-
jobId: next.jobId,
|
|
743
|
-
lastEvaluatedAt: next.lastEvaluatedAt,
|
|
744
|
-
lastTriggeredAt: next.lastTriggeredAt ?? void 0,
|
|
745
|
-
nextRunAt: next.nextRunAt,
|
|
746
|
-
version: next.version
|
|
747
|
-
};
|
|
748
|
-
},
|
|
749
|
-
async markTriggered(jobId, triggeredAt) {
|
|
750
|
-
await ensureBootstrapped();
|
|
751
|
-
const current = await readCursorRow(config.client, config.dialect, names, jobId);
|
|
752
|
-
if (!current) return null;
|
|
753
|
-
const next = {
|
|
754
|
-
...current,
|
|
755
|
-
lastTriggeredAt: triggeredAt,
|
|
756
|
-
version: current.version + 1
|
|
757
|
-
};
|
|
758
|
-
await config.client.execute(`UPDATE ${quoted(config.dialect, names.cursors)} SET last_triggered_at = ?, version = ? WHERE job_id = ?`, [
|
|
759
|
-
next.lastTriggeredAt,
|
|
760
|
-
next.version,
|
|
761
|
-
jobId
|
|
762
|
-
]);
|
|
763
|
-
return {
|
|
764
|
-
jobId: next.jobId,
|
|
765
|
-
lastEvaluatedAt: next.lastEvaluatedAt,
|
|
766
|
-
lastTriggeredAt: next.lastTriggeredAt ?? void 0,
|
|
767
|
-
nextRunAt: next.nextRunAt,
|
|
768
|
-
version: next.version
|
|
769
|
-
};
|
|
770
|
-
},
|
|
771
|
-
async get(jobId) {
|
|
772
|
-
await ensureBootstrapped();
|
|
773
|
-
return readCursorRow(config.client, config.dialect, names, jobId);
|
|
774
|
-
},
|
|
775
|
-
async remove(jobId) {
|
|
776
|
-
await ensureBootstrapped();
|
|
777
|
-
await config.client.execute(`DELETE FROM ${quoted(config.dialect, names.cursors)} WHERE job_id = ?`, [jobId]);
|
|
778
|
-
return true;
|
|
779
|
-
}
|
|
780
|
-
},
|
|
781
|
-
executionStore: {
|
|
782
|
-
async create(record) {
|
|
783
|
-
await ensureBootstrapped();
|
|
784
|
-
await config.client.execute(`INSERT INTO ${quoted(config.dialect, names.executions)} (execution_id, job_id, scheduled_for, triggered_at, status, queue_task_id, attempt_count, last_error)
|
|
785
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
786
|
-
record.executionId,
|
|
787
|
-
record.jobId,
|
|
788
|
-
record.scheduledFor,
|
|
789
|
-
record.triggeredAt,
|
|
790
|
-
record.status,
|
|
791
|
-
record.queueTaskId ?? null,
|
|
792
|
-
record.attemptCount,
|
|
793
|
-
record.lastError ?? null
|
|
794
|
-
]);
|
|
795
|
-
return record;
|
|
796
|
-
},
|
|
797
|
-
async update(executionId, patch) {
|
|
798
|
-
const current = await readExecutionRow(config.client, config.dialect, names, executionId);
|
|
799
|
-
if (!current) return null;
|
|
800
|
-
const next = {
|
|
801
|
-
...current,
|
|
802
|
-
...patch
|
|
803
|
-
};
|
|
804
|
-
await config.client.execute(`UPDATE ${quoted(config.dialect, names.executions)} SET job_id = ?, scheduled_for = ?, triggered_at = ?, status = ?, queue_task_id = ?, attempt_count = ?, last_error = ? WHERE execution_id = ?`, [
|
|
805
|
-
next.jobId,
|
|
806
|
-
next.scheduledFor,
|
|
807
|
-
next.triggeredAt,
|
|
808
|
-
next.status,
|
|
809
|
-
next.queueTaskId ?? null,
|
|
810
|
-
next.attemptCount,
|
|
811
|
-
next.lastError ?? null,
|
|
812
|
-
executionId
|
|
813
|
-
]);
|
|
814
|
-
return next;
|
|
815
|
-
},
|
|
816
|
-
async listByJob(jobId) {
|
|
817
|
-
await ensureBootstrapped();
|
|
818
|
-
return (await config.client.query(`SELECT execution_id AS executionId, job_id AS jobId, scheduled_for AS scheduledFor, triggered_at AS triggeredAt, status, queue_task_id AS queueTaskId, attempt_count AS attemptCount, last_error AS lastError
|
|
819
|
-
FROM ${quoted(config.dialect, names.executions)} WHERE job_id = ? ORDER BY triggered_at ASC`, [jobId])).map((row) => ({
|
|
820
|
-
executionId: row.executionId,
|
|
821
|
-
jobId: row.jobId,
|
|
822
|
-
scheduledFor: row.scheduledFor,
|
|
823
|
-
triggeredAt: row.triggeredAt,
|
|
824
|
-
status: row.status,
|
|
825
|
-
queueTaskId: row.queueTaskId ?? void 0,
|
|
826
|
-
attemptCount: row.attemptCount,
|
|
827
|
-
lastError: row.lastError ?? void 0
|
|
828
|
-
}));
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
//#endregion
|
|
834
446
|
//#region src/core/scheduler-service.ts
|
|
835
447
|
function createSchedulerService$1(options) {
|
|
836
448
|
assertRuntimeConfig(options);
|
|
837
449
|
const logger = options.logger ?? createSchedulerLogger();
|
|
838
450
|
const cache = options.cache ?? createSchedulerCache();
|
|
839
|
-
const persistence = options.persistence ? createSchedulerPersistence(options.persistence) : void 0;
|
|
451
|
+
const persistence = options.persistence ? require_store_dashboard.createSchedulerPersistence(options.persistence) : void 0;
|
|
840
452
|
const jobStore = persistence?.jobStore ?? createInMemorySchedulerJobStore();
|
|
841
453
|
const cursorStore = persistence?.cursorStore ?? createInMemoryScheduleCursorStore();
|
|
842
454
|
const executionStore = persistence?.executionStore ?? createInMemoryJobExecutionStore();
|
|
@@ -911,7 +523,12 @@ function createSchedulerService$1(options) {
|
|
|
911
523
|
name: job.name,
|
|
912
524
|
enabled: job.enabled,
|
|
913
525
|
timezone: job.schedule.timezone,
|
|
914
|
-
nextRunAt: cursor?.nextRunAt
|
|
526
|
+
nextRunAt: cursor?.nextRunAt,
|
|
527
|
+
handlerKey: job.handlerKey,
|
|
528
|
+
schedule: job.schedule,
|
|
529
|
+
config: job.config,
|
|
530
|
+
createdAt: job.createdAt,
|
|
531
|
+
updatedAt: job.updatedAt
|
|
915
532
|
});
|
|
916
533
|
}));
|
|
917
534
|
},
|
|
@@ -921,7 +538,7 @@ function createSchedulerService$1(options) {
|
|
|
921
538
|
},
|
|
922
539
|
async retryFailedExecution(executionId) {
|
|
923
540
|
const jobs = await jobStore.list();
|
|
924
|
-
const retry = await retryFailedExecutionById((await Promise.all(jobs.map((job) => executionStore.listByJob(job.jobId)))).flat(), executionStore, executionId);
|
|
541
|
+
const retry = await require_store_dashboard.retryFailedExecutionById((await Promise.all(jobs.map((job) => executionStore.listByJob(job.jobId)))).flat(), jobs, executionStore, queueService, executionId);
|
|
925
542
|
events.emit({
|
|
926
543
|
eventType: "retry",
|
|
927
544
|
jobId: retry.jobId,
|
|
@@ -929,6 +546,25 @@ function createSchedulerService$1(options) {
|
|
|
929
546
|
metadata: { previousExecutionId: executionId }
|
|
930
547
|
});
|
|
931
548
|
},
|
|
549
|
+
async runNow(jobId) {
|
|
550
|
+
const job = await jobStore.get(jobId);
|
|
551
|
+
if (!job) throw new Error(`Job not found: ${jobId}`);
|
|
552
|
+
const scheduledFor = (/* @__PURE__ */ new Date()).toISOString();
|
|
553
|
+
const execution = await require_store_dashboard.dispatchJobExecution({
|
|
554
|
+
job,
|
|
555
|
+
executionStore,
|
|
556
|
+
queueService,
|
|
557
|
+
scheduledFor,
|
|
558
|
+
attemptCount: 0,
|
|
559
|
+
idempotencyKey: `scheduler:${job.jobId}:manual:${scheduledFor}`
|
|
560
|
+
});
|
|
561
|
+
events.emit({
|
|
562
|
+
eventType: "triggered",
|
|
563
|
+
jobId,
|
|
564
|
+
executionId: execution.executionId,
|
|
565
|
+
metadata: { reason: "run-now" }
|
|
566
|
+
});
|
|
567
|
+
},
|
|
932
568
|
async start() {
|
|
933
569
|
logger.info("scheduler.service.start", { mode: options.mode });
|
|
934
570
|
await runtime.start();
|
|
@@ -942,37 +578,6 @@ function createSchedulerService$1(options) {
|
|
|
942
578
|
};
|
|
943
579
|
}
|
|
944
580
|
//#endregion
|
|
945
|
-
//#region src/dashboard/queue-queries.ts
|
|
946
|
-
async function querySchedulerHealth(scheduler) {
|
|
947
|
-
const jobs = await scheduler.listJobs();
|
|
948
|
-
const executions = await scheduler.listExecutions({ status: "failed" });
|
|
949
|
-
return {
|
|
950
|
-
totalJobs: jobs.length,
|
|
951
|
-
enabledJobs: jobs.filter((job) => job.enabled).length,
|
|
952
|
-
pausedJobs: jobs.filter((job) => !job.enabled).length,
|
|
953
|
-
failedExecutions: executions.length
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
async function querySchedulerItems(scheduler) {
|
|
957
|
-
const jobs = await scheduler.listJobs();
|
|
958
|
-
const executions = await scheduler.listExecutions({ limit: 100 });
|
|
959
|
-
return jobs.map((job) => ({
|
|
960
|
-
...job,
|
|
961
|
-
executions: executions.filter((execution) => execution.jobId === job.jobId)
|
|
962
|
-
}));
|
|
963
|
-
}
|
|
964
|
-
//#endregion
|
|
965
|
-
//#region src/dashboard/queue-dashboard-service.ts
|
|
966
|
-
function createSchedulerDashboardService(scheduler) {
|
|
967
|
-
return {
|
|
968
|
-
getHealth: () => querySchedulerHealth(scheduler),
|
|
969
|
-
listItems: () => querySchedulerItems(scheduler),
|
|
970
|
-
pauseJob: (jobId) => scheduler.pauseJob(jobId),
|
|
971
|
-
resumeJob: (jobId) => scheduler.resumeJob(jobId),
|
|
972
|
-
retryExecution: (executionId) => scheduler.retryFailedExecution(executionId)
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
//#endregion
|
|
976
581
|
//#region index.ts
|
|
977
582
|
function createSchedulerService(options) {
|
|
978
583
|
return createSchedulerService$1(options);
|
|
@@ -981,6 +586,8 @@ function createSchedulerService(options) {
|
|
|
981
586
|
exports.CronWeekday = CronWeekday;
|
|
982
587
|
exports.bindSchedulerToHost = bindSchedulerToHost;
|
|
983
588
|
exports.createCronExpression = createCronExpression;
|
|
984
|
-
exports.
|
|
985
|
-
exports.
|
|
589
|
+
exports.createSchedulerDashboardFromPersistence = require_store_dashboard.createSchedulerDashboardFromPersistence;
|
|
590
|
+
exports.createSchedulerDashboardFromStores = require_store_dashboard.createSchedulerDashboardFromStores;
|
|
591
|
+
exports.createSchedulerDashboardService = require_queue_dashboard_service.createSchedulerDashboardService;
|
|
592
|
+
exports.createSchedulerPersistence = require_store_dashboard.createSchedulerPersistence;
|
|
986
593
|
exports.createSchedulerService = createSchedulerService;
|