@azlib/scheduler 1.0.4 → 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.
Files changed (48) hide show
  1. package/Dockerfile +14 -0
  2. package/README.md +40 -0
  3. package/compose.yaml +28 -0
  4. package/dist/cli-config-7F2U8qPM.mjs +462 -0
  5. package/dist/cli-config-7F2U8qPM.mjs.map +1 -0
  6. package/dist/cli-config-Cz8QP_4I.cjs +512 -0
  7. package/dist/dashboard/cli.cjs +30 -0
  8. package/dist/dashboard/cli.d.cts +6 -0
  9. package/dist/dashboard/cli.d.cts.map +1 -0
  10. package/dist/dashboard/cli.d.mts +6 -0
  11. package/dist/dashboard/cli.d.mts.map +1 -0
  12. package/dist/dashboard/cli.mjs +31 -0
  13. package/dist/dashboard/cli.mjs.map +1 -0
  14. package/dist/dashboard/public/assets/index-CbmpG6_w.css +2 -0
  15. package/dist/dashboard/public/assets/index-eWhp41cY.js +12 -0
  16. package/dist/dashboard/public/index.html +13 -0
  17. package/dist/dashboard-types-BHjRLcxJ.d.cts +127 -0
  18. package/dist/dashboard-types-BHjRLcxJ.d.cts.map +1 -0
  19. package/dist/dashboard-types-BHjRLcxJ.d.mts +127 -0
  20. package/dist/dashboard-types-BHjRLcxJ.d.mts.map +1 -0
  21. package/dist/dashboard.cjs +9 -0
  22. package/dist/dashboard.d.cts +20 -0
  23. package/dist/dashboard.d.cts.map +1 -0
  24. package/dist/dashboard.d.mts +20 -0
  25. package/dist/dashboard.d.mts.map +1 -0
  26. package/dist/dashboard.mjs +4 -0
  27. package/dist/http-server-DN6Lg464.d.cts +19 -0
  28. package/dist/http-server-DN6Lg464.d.cts.map +1 -0
  29. package/dist/http-server-DPQ9HIK-.d.mts +19 -0
  30. package/dist/http-server-DPQ9HIK-.d.mts.map +1 -0
  31. package/dist/index.cjs +49 -442
  32. package/dist/index.d.cts +3 -158
  33. package/dist/index.d.cts.map +1 -1
  34. package/dist/index.d.mts +3 -158
  35. package/dist/index.d.mts.map +1 -1
  36. package/dist/index.mjs +44 -439
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/queue-dashboard-service-BFXcnfUr.cjs +55 -0
  39. package/dist/queue-dashboard-service-C0QapgOg.mjs +52 -0
  40. package/dist/queue-dashboard-service-C0QapgOg.mjs.map +1 -0
  41. package/dist/store-dashboard-C44d7sdh.d.cts +59 -0
  42. package/dist/store-dashboard-C44d7sdh.d.cts.map +1 -0
  43. package/dist/store-dashboard-Cyf_JILa.mjs +515 -0
  44. package/dist/store-dashboard-Cyf_JILa.mjs.map +1 -0
  45. package/dist/store-dashboard-D-yIxXxL.d.mts +59 -0
  46. package/dist/store-dashboard-D-yIxXxL.d.mts.map +1 -0
  47. package/dist/store-dashboard-Sg4BBWiJ.cjs +554 -0
  48. package/package.json +56 -8
@@ -0,0 +1,515 @@
1
+ import { randomUUID } from "node:crypto";
2
+ //#region src/core/schedule-parser.ts
3
+ const CRON_FIELD_PATTERN = /^\*|\*\/\d+|\d+|\d+-\d+|\d+(?:,\d+)*$/;
4
+ function assertValidTimezone(timezone) {
5
+ try {
6
+ new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
7
+ } catch {
8
+ throw new Error(`Invalid timezone: ${timezone}`);
9
+ }
10
+ }
11
+ function assertValidCronExpression(expression) {
12
+ const fields = expression.trim().split(/\s+/);
13
+ if (fields.length !== 5) throw new Error("Cron expression must contain 5 fields");
14
+ for (const field of fields) if (!CRON_FIELD_PATTERN.test(field)) throw new Error(`Invalid cron field: ${field}`);
15
+ }
16
+ function assertValidOnceExpression(expression) {
17
+ const date = new Date(expression);
18
+ if (Number.isNaN(date.getTime())) throw new Error("Once schedule expression must be a valid ISO datetime");
19
+ }
20
+ function parseSchedule(scheduleType, expression, timezone) {
21
+ assertValidTimezone(timezone);
22
+ if (scheduleType === "cron") assertValidCronExpression(expression);
23
+ else assertValidOnceExpression(expression);
24
+ return {
25
+ scheduleType,
26
+ expression: expression.trim(),
27
+ timezone
28
+ };
29
+ }
30
+ function computeNextRunAt(parsed, now = /* @__PURE__ */ new Date()) {
31
+ if (parsed.scheduleType === "once") return new Date(parsed.expression).toISOString();
32
+ const next = new Date(now);
33
+ next.setUTCSeconds(0, 0);
34
+ next.setUTCMinutes(next.getUTCMinutes() + 1);
35
+ return next.toISOString();
36
+ }
37
+ //#endregion
38
+ //#region src/core/scheduler-job-trigger.ts
39
+ async function dispatchJobExecution(input) {
40
+ const executionId = `exec_${randomUUID()}`;
41
+ const created = {
42
+ executionId,
43
+ jobId: input.job.jobId,
44
+ scheduledFor: input.scheduledFor,
45
+ triggeredAt: (/* @__PURE__ */ new Date()).toISOString(),
46
+ status: "queued",
47
+ attemptCount: input.attemptCount ?? 0
48
+ };
49
+ await input.executionStore.create(created);
50
+ if (!input.queueService) return created;
51
+ const accepted = await input.queueService.enqueue({
52
+ idempotencyKey: input.idempotencyKey,
53
+ payloadRef: {
54
+ executionId,
55
+ handlerKey: input.job.handlerKey,
56
+ config: input.job.config
57
+ }
58
+ });
59
+ return await input.executionStore.update(executionId, { queueTaskId: accepted.taskId }) ?? {
60
+ ...created,
61
+ queueTaskId: accepted.taskId
62
+ };
63
+ }
64
+ //#endregion
65
+ //#region src/core/scheduler-service-retry.ts
66
+ function findRetryableExecution(allExecutions, executionId) {
67
+ const target = allExecutions.find((item) => item.executionId === executionId);
68
+ if (!target) throw new Error(`Execution not found: ${executionId}`);
69
+ if (target.status !== "failed" && target.status !== "dead-letter") throw new Error("Only failed or dead-letter executions can be retried");
70
+ return target;
71
+ }
72
+ async function retryFailedExecutionById(allExecutions, jobs, executionStore, queueService, executionId) {
73
+ const target = findRetryableExecution(allExecutions, executionId);
74
+ const job = jobs.find((item) => item.jobId === target.jobId);
75
+ if (!job) throw new Error(`Job not found: ${target.jobId}`);
76
+ return dispatchJobExecution({
77
+ job,
78
+ executionStore,
79
+ queueService,
80
+ scheduledFor: target.scheduledFor,
81
+ attemptCount: target.attemptCount + 1,
82
+ idempotencyKey: `scheduler:${job.jobId}:retry:${executionId}`
83
+ });
84
+ }
85
+ //#endregion
86
+ //#region src/core/persistence.ts
87
+ function namespaceFor(config) {
88
+ return config.namespace?.trim() || "azlib";
89
+ }
90
+ function tableNames(namespace) {
91
+ return {
92
+ jobs: `${namespace}__scheduler_jobs`,
93
+ cursors: `${namespace}__scheduler_cursors`,
94
+ executions: `${namespace}__scheduler_executions`
95
+ };
96
+ }
97
+ function quoted(dialect, tableName) {
98
+ return dialect.quoteIdentifier(tableName);
99
+ }
100
+ async function bootstrapSchedulerPersistence(client, dialect, names) {
101
+ await client.transaction(async (tx) => {
102
+ await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.jobs)} (
103
+ job_id TEXT PRIMARY KEY,
104
+ name TEXT NOT NULL,
105
+ handler_key TEXT NOT NULL,
106
+ schedule_type TEXT NOT NULL,
107
+ expression TEXT NOT NULL,
108
+ timezone TEXT NOT NULL,
109
+ overlap_policy TEXT,
110
+ missed_run_policy TEXT,
111
+ config_json TEXT NOT NULL,
112
+ enabled INTEGER NOT NULL,
113
+ created_at TEXT NOT NULL,
114
+ updated_at TEXT NOT NULL
115
+ )`);
116
+ await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.cursors)} (
117
+ job_id TEXT PRIMARY KEY,
118
+ last_evaluated_at TEXT NOT NULL,
119
+ last_triggered_at TEXT,
120
+ next_run_at TEXT NOT NULL,
121
+ version INTEGER NOT NULL
122
+ )`);
123
+ await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.executions)} (
124
+ execution_id TEXT PRIMARY KEY,
125
+ job_id TEXT NOT NULL,
126
+ scheduled_for TEXT NOT NULL,
127
+ triggered_at TEXT NOT NULL,
128
+ status TEXT NOT NULL,
129
+ queue_task_id TEXT,
130
+ attempt_count INTEGER NOT NULL,
131
+ last_error TEXT
132
+ )`);
133
+ });
134
+ }
135
+ function toJobRow(jobId, definition) {
136
+ const now = (/* @__PURE__ */ new Date()).toISOString();
137
+ return {
138
+ jobId,
139
+ name: definition.name,
140
+ handlerKey: definition.handlerKey,
141
+ scheduleType: definition.schedule.scheduleType,
142
+ expression: definition.schedule.expression,
143
+ timezone: definition.schedule.timezone,
144
+ overlapPolicy: definition.schedule.overlapPolicy ?? null,
145
+ missedRunPolicy: definition.schedule.missedRunPolicy ?? null,
146
+ configJson: JSON.stringify(definition.config),
147
+ enabled: definition.enabled ?? true ? 1 : 0,
148
+ createdAt: now,
149
+ updatedAt: now
150
+ };
151
+ }
152
+ function rowToJobRecord(row) {
153
+ return {
154
+ jobId: row.jobId,
155
+ name: row.name,
156
+ handlerKey: row.handlerKey,
157
+ schedule: {
158
+ scheduleType: row.scheduleType,
159
+ expression: row.expression,
160
+ timezone: row.timezone,
161
+ overlapPolicy: row.overlapPolicy === null ? void 0 : row.overlapPolicy,
162
+ missedRunPolicy: row.missedRunPolicy === null ? void 0 : row.missedRunPolicy
163
+ },
164
+ config: JSON.parse(row.configJson),
165
+ enabled: Boolean(row.enabled),
166
+ createdAt: row.createdAt,
167
+ updatedAt: row.updatedAt
168
+ };
169
+ }
170
+ async function readJobRow(client, dialect, names, jobId) {
171
+ 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
172
+ FROM ${quoted(dialect, names.jobs)} WHERE job_id = ? LIMIT 1`, [jobId]);
173
+ return rows[0] ? rowToJobRecord(rows[0]) : null;
174
+ }
175
+ async function readCursorRow(client, dialect, names, jobId) {
176
+ 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
177
+ FROM ${quoted(dialect, names.cursors)} WHERE job_id = ? LIMIT 1`, [jobId]))[0];
178
+ return row ? {
179
+ jobId: row.jobId,
180
+ lastEvaluatedAt: row.lastEvaluatedAt,
181
+ lastTriggeredAt: row.lastTriggeredAt ?? void 0,
182
+ nextRunAt: row.nextRunAt,
183
+ version: row.version
184
+ } : null;
185
+ }
186
+ async function readExecutionRow(client, dialect, names, executionId) {
187
+ 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
188
+ FROM ${quoted(dialect, names.executions)} WHERE execution_id = ? LIMIT 1`, [executionId]))[0];
189
+ return row ? {
190
+ executionId: row.executionId,
191
+ jobId: row.jobId,
192
+ scheduledFor: row.scheduledFor,
193
+ triggeredAt: row.triggeredAt,
194
+ status: row.status,
195
+ queueTaskId: row.queueTaskId ?? void 0,
196
+ attemptCount: row.attemptCount,
197
+ lastError: row.lastError ?? void 0
198
+ } : null;
199
+ }
200
+ function createSchedulerPersistence(config) {
201
+ const names = tableNames(namespaceFor(config));
202
+ let bootstrapPromise = null;
203
+ async function ensureBootstrapped() {
204
+ if (!bootstrapPromise) bootstrapPromise = bootstrapSchedulerPersistence(config.client, config.dialect, names);
205
+ await bootstrapPromise;
206
+ }
207
+ return {
208
+ jobStore: {
209
+ async create(jobId, definition) {
210
+ await ensureBootstrapped();
211
+ const row = toJobRow(jobId, definition);
212
+ await config.client.transaction(async (tx) => {
213
+ await tx.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
214
+ 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)
215
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
216
+ row.jobId,
217
+ row.name,
218
+ row.handlerKey,
219
+ row.scheduleType,
220
+ row.expression,
221
+ row.timezone,
222
+ row.overlapPolicy,
223
+ row.missedRunPolicy,
224
+ row.configJson,
225
+ row.enabled,
226
+ row.createdAt,
227
+ row.updatedAt
228
+ ]);
229
+ });
230
+ return rowToJobRecord(row);
231
+ },
232
+ async update(jobId, patch) {
233
+ const current = await readJobRow(config.client, config.dialect, names, jobId);
234
+ if (!current) return null;
235
+ const next = {
236
+ ...current,
237
+ ...patch,
238
+ schedule: {
239
+ ...current.schedule,
240
+ ...patch.schedule
241
+ },
242
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
243
+ };
244
+ await config.client.transaction(async (tx) => {
245
+ await tx.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
246
+ 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)
247
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
248
+ next.jobId,
249
+ next.name,
250
+ next.handlerKey,
251
+ next.schedule.scheduleType,
252
+ next.schedule.expression,
253
+ next.schedule.timezone,
254
+ next.schedule.overlapPolicy ?? null,
255
+ next.schedule.missedRunPolicy ?? null,
256
+ JSON.stringify(next.config),
257
+ next.enabled ? 1 : 0,
258
+ next.createdAt,
259
+ next.updatedAt
260
+ ]);
261
+ });
262
+ return next;
263
+ },
264
+ async setEnabled(jobId, enabled) {
265
+ const current = await readJobRow(config.client, config.dialect, names, jobId);
266
+ if (!current) return null;
267
+ const next = {
268
+ ...current,
269
+ enabled,
270
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
271
+ };
272
+ await config.client.transaction(async (tx) => {
273
+ await tx.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
274
+ 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)
275
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
276
+ next.jobId,
277
+ next.name,
278
+ next.handlerKey,
279
+ next.schedule.scheduleType,
280
+ next.schedule.expression,
281
+ next.schedule.timezone,
282
+ next.schedule.overlapPolicy ?? null,
283
+ next.schedule.missedRunPolicy ?? null,
284
+ JSON.stringify(next.config),
285
+ next.enabled ? 1 : 0,
286
+ next.createdAt,
287
+ next.updatedAt
288
+ ]);
289
+ });
290
+ return next;
291
+ },
292
+ async get(jobId) {
293
+ await ensureBootstrapped();
294
+ return readJobRow(config.client, config.dialect, names, jobId);
295
+ },
296
+ async list() {
297
+ await ensureBootstrapped();
298
+ 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
299
+ FROM ${quoted(config.dialect, names.jobs)} ORDER BY created_at ASC`, [])).map(rowToJobRecord);
300
+ },
301
+ async remove(jobId) {
302
+ await ensureBootstrapped();
303
+ await config.client.execute(`DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`, [jobId]);
304
+ return true;
305
+ }
306
+ },
307
+ cursorStore: {
308
+ async set(jobId, nextRunAt, now) {
309
+ await ensureBootstrapped();
310
+ const current = await readCursorRow(config.client, config.dialect, names, jobId);
311
+ const next = {
312
+ jobId,
313
+ lastEvaluatedAt: now,
314
+ lastTriggeredAt: current?.lastTriggeredAt ?? null,
315
+ nextRunAt,
316
+ version: (current?.version ?? 0) + 1
317
+ };
318
+ await config.client.transaction(async (tx) => {
319
+ await tx.execute(`DELETE FROM ${quoted(config.dialect, names.cursors)} WHERE job_id = ?`, [jobId]);
320
+ await tx.execute(`INSERT INTO ${quoted(config.dialect, names.cursors)} (job_id, last_evaluated_at, last_triggered_at, next_run_at, version) VALUES (?, ?, ?, ?, ?)`, [
321
+ next.jobId,
322
+ next.lastEvaluatedAt,
323
+ next.lastTriggeredAt,
324
+ next.nextRunAt,
325
+ next.version
326
+ ]);
327
+ });
328
+ return {
329
+ jobId: next.jobId,
330
+ lastEvaluatedAt: next.lastEvaluatedAt,
331
+ lastTriggeredAt: next.lastTriggeredAt ?? void 0,
332
+ nextRunAt: next.nextRunAt,
333
+ version: next.version
334
+ };
335
+ },
336
+ async markTriggered(jobId, triggeredAt) {
337
+ await ensureBootstrapped();
338
+ const current = await readCursorRow(config.client, config.dialect, names, jobId);
339
+ if (!current) return null;
340
+ const next = {
341
+ ...current,
342
+ lastTriggeredAt: triggeredAt,
343
+ version: current.version + 1
344
+ };
345
+ await config.client.execute(`UPDATE ${quoted(config.dialect, names.cursors)} SET last_triggered_at = ?, version = ? WHERE job_id = ?`, [
346
+ next.lastTriggeredAt,
347
+ next.version,
348
+ jobId
349
+ ]);
350
+ return {
351
+ jobId: next.jobId,
352
+ lastEvaluatedAt: next.lastEvaluatedAt,
353
+ lastTriggeredAt: next.lastTriggeredAt ?? void 0,
354
+ nextRunAt: next.nextRunAt,
355
+ version: next.version
356
+ };
357
+ },
358
+ async get(jobId) {
359
+ await ensureBootstrapped();
360
+ return readCursorRow(config.client, config.dialect, names, jobId);
361
+ },
362
+ async remove(jobId) {
363
+ await ensureBootstrapped();
364
+ await config.client.execute(`DELETE FROM ${quoted(config.dialect, names.cursors)} WHERE job_id = ?`, [jobId]);
365
+ return true;
366
+ }
367
+ },
368
+ executionStore: {
369
+ async create(record) {
370
+ await ensureBootstrapped();
371
+ 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)
372
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
373
+ record.executionId,
374
+ record.jobId,
375
+ record.scheduledFor,
376
+ record.triggeredAt,
377
+ record.status,
378
+ record.queueTaskId ?? null,
379
+ record.attemptCount,
380
+ record.lastError ?? null
381
+ ]);
382
+ return record;
383
+ },
384
+ async update(executionId, patch) {
385
+ const current = await readExecutionRow(config.client, config.dialect, names, executionId);
386
+ if (!current) return null;
387
+ const next = {
388
+ ...current,
389
+ ...patch
390
+ };
391
+ 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 = ?`, [
392
+ next.jobId,
393
+ next.scheduledFor,
394
+ next.triggeredAt,
395
+ next.status,
396
+ next.queueTaskId ?? null,
397
+ next.attemptCount,
398
+ next.lastError ?? null,
399
+ executionId
400
+ ]);
401
+ return next;
402
+ },
403
+ async listByJob(jobId) {
404
+ await ensureBootstrapped();
405
+ 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
406
+ FROM ${quoted(config.dialect, names.executions)} WHERE job_id = ? ORDER BY triggered_at ASC`, [jobId])).map((row) => ({
407
+ executionId: row.executionId,
408
+ jobId: row.jobId,
409
+ scheduledFor: row.scheduledFor,
410
+ triggeredAt: row.triggeredAt,
411
+ status: row.status,
412
+ queueTaskId: row.queueTaskId ?? void 0,
413
+ attemptCount: row.attemptCount,
414
+ lastError: row.lastError ?? void 0
415
+ }));
416
+ }
417
+ }
418
+ };
419
+ }
420
+ //#endregion
421
+ //#region src/dashboard/store-dashboard.ts
422
+ function nextRunForJob(job, now = /* @__PURE__ */ new Date()) {
423
+ return computeNextRunAt(parseSchedule(job.schedule.scheduleType, job.schedule.expression, job.schedule.timezone), now);
424
+ }
425
+ async function toDashboardItem(jobStore, cursorStore, executionStore, jobId) {
426
+ const job = await jobStore.get(jobId);
427
+ if (!job) return null;
428
+ const cursor = await cursorStore.get(jobId);
429
+ const executions = await executionStore.listByJob(jobId);
430
+ return {
431
+ ...job,
432
+ nextRunAt: cursor?.nextRunAt,
433
+ executions
434
+ };
435
+ }
436
+ function createSchedulerDashboardFromStores(stores, options) {
437
+ const { jobStore, cursorStore, executionStore } = stores;
438
+ const handlerKeys = [...options?.handlerKeys ?? []];
439
+ return {
440
+ async getHealth() {
441
+ const jobs = await jobStore.list();
442
+ const failed = (await Promise.all(jobs.map((job) => executionStore.listByJob(job.jobId)))).flat().filter((item) => item.status === "failed" || item.status === "dead-letter");
443
+ return {
444
+ totalJobs: jobs.length,
445
+ enabledJobs: jobs.filter((job) => job.enabled).length,
446
+ pausedJobs: jobs.filter((job) => !job.enabled).length,
447
+ failedExecutions: failed.length
448
+ };
449
+ },
450
+ async listItems() {
451
+ const jobs = await jobStore.list();
452
+ return Promise.all(jobs.map(async (job) => {
453
+ return await toDashboardItem(jobStore, cursorStore, executionStore, job.jobId);
454
+ }));
455
+ },
456
+ async getJob(jobId) {
457
+ return toDashboardItem(jobStore, cursorStore, executionStore, jobId);
458
+ },
459
+ async createJob(job) {
460
+ parseSchedule(job.schedule.scheduleType, job.schedule.expression, job.schedule.timezone);
461
+ const jobId = `job_${randomUUID()}`;
462
+ const nextRunAt = nextRunForJob(await jobStore.create(jobId, job));
463
+ await cursorStore.set(jobId, nextRunAt, (/* @__PURE__ */ new Date()).toISOString());
464
+ return { jobId };
465
+ },
466
+ async updateJob(jobId, patch) {
467
+ const updated = await jobStore.update(jobId, patch);
468
+ if (!updated) throw new Error(`Job not found: ${jobId}`);
469
+ const nextRunAt = nextRunForJob(updated);
470
+ await cursorStore.set(jobId, nextRunAt, (/* @__PURE__ */ new Date()).toISOString());
471
+ },
472
+ async pauseJob(jobId) {
473
+ if (!await jobStore.setEnabled(jobId, false)) throw new Error(`Job not found: ${jobId}`);
474
+ },
475
+ async resumeJob(jobId) {
476
+ if (!await jobStore.setEnabled(jobId, true)) throw new Error(`Job not found: ${jobId}`);
477
+ },
478
+ async deleteJob(jobId) {
479
+ const removed = await jobStore.remove(jobId);
480
+ await cursorStore.remove(jobId);
481
+ if (!removed) throw new Error(`Job not found: ${jobId}`);
482
+ },
483
+ async runJob(jobId) {
484
+ const job = await jobStore.get(jobId);
485
+ if (!job) throw new Error(`Job not found: ${jobId}`);
486
+ const scheduledFor = (/* @__PURE__ */ new Date()).toISOString();
487
+ await dispatchJobExecution({
488
+ job,
489
+ executionStore,
490
+ queueService: options?.queueService,
491
+ scheduledFor,
492
+ attemptCount: 0,
493
+ idempotencyKey: `scheduler:${job.jobId}:manual:${scheduledFor}`
494
+ });
495
+ },
496
+ async listExecutions(jobId) {
497
+ if (!await jobStore.get(jobId)) throw new Error(`Job not found: ${jobId}`);
498
+ return executionStore.listByJob(jobId);
499
+ },
500
+ async retryExecution(executionId) {
501
+ const jobs = await jobStore.list();
502
+ await retryFailedExecutionById((await Promise.all(jobs.map((job) => executionStore.listByJob(job.jobId)))).flat(), jobs, executionStore, options?.queueService, executionId);
503
+ },
504
+ listHandlerKeys() {
505
+ return handlerKeys;
506
+ }
507
+ };
508
+ }
509
+ function createSchedulerDashboardFromPersistence(persistence, options) {
510
+ return createSchedulerDashboardFromStores(createSchedulerPersistence(persistence), options);
511
+ }
512
+ //#endregion
513
+ export { dispatchJobExecution as a, retryFailedExecutionById as i, createSchedulerDashboardFromStores as n, computeNextRunAt as o, createSchedulerPersistence as r, parseSchedule as s, createSchedulerDashboardFromPersistence as t };
514
+
515
+ //# sourceMappingURL=store-dashboard-Cyf_JILa.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-dashboard-Cyf_JILa.mjs","names":[],"sources":["../src/core/schedule-parser.ts","../src/core/scheduler-job-trigger.ts","../src/core/scheduler-service-retry.ts","../src/core/persistence.ts","../src/dashboard/store-dashboard.ts"],"sourcesContent":["import type { ScheduleType } from \"../contracts/scheduler-types.js\";\n\nexport interface ParsedSchedule {\n scheduleType: ScheduleType;\n expression: string;\n timezone: string;\n}\n\nconst CRON_FIELD_PATTERN = /^\\*|\\*\\/\\d+|\\d+|\\d+-\\d+|\\d+(?:,\\d+)*$/;\n\nexport function assertValidTimezone(timezone: string): void {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: timezone }).format(new Date());\n } catch {\n throw new Error(`Invalid timezone: ${timezone}`);\n }\n}\n\nfunction assertValidCronExpression(expression: string): void {\n const fields = expression.trim().split(/\\s+/);\n if (fields.length !== 5) {\n throw new Error(\"Cron expression must contain 5 fields\");\n }\n\n for (const field of fields) {\n if (!CRON_FIELD_PATTERN.test(field)) {\n throw new Error(`Invalid cron field: ${field}`);\n }\n }\n}\n\nfunction assertValidOnceExpression(expression: string): void {\n const date = new Date(expression);\n if (Number.isNaN(date.getTime())) {\n throw new Error(\"Once schedule expression must be a valid ISO datetime\");\n }\n}\n\nexport function parseSchedule(\n scheduleType: ScheduleType,\n expression: string,\n timezone: string,\n): ParsedSchedule {\n assertValidTimezone(timezone);\n\n if (scheduleType === \"cron\") {\n assertValidCronExpression(expression);\n } else {\n assertValidOnceExpression(expression);\n }\n\n return {\n scheduleType,\n expression: expression.trim(),\n timezone,\n };\n}\n\nexport function computeNextRunAt(\n parsed: ParsedSchedule,\n now = new Date(),\n): string {\n if (parsed.scheduleType === \"once\") {\n return new Date(parsed.expression).toISOString();\n }\n\n const next = new Date(now);\n next.setUTCSeconds(0, 0);\n next.setUTCMinutes(next.getUTCMinutes() + 1);\n return next.toISOString();\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport type { QueueService } from \"@azlib/queue\";\n\nimport type {\n SchedulerExecutionRecord,\n SchedulerJobRecord,\n} from \"../contracts/scheduler-types.js\";\nimport type { JobExecutionStore } from \"./job-execution-store.js\";\n\nexport async function dispatchJobExecution(input: {\n job: SchedulerJobRecord;\n executionStore: JobExecutionStore;\n queueService?: QueueService;\n scheduledFor: string;\n attemptCount?: number;\n idempotencyKey: string;\n}): Promise<SchedulerExecutionRecord> {\n const executionId = `exec_${randomUUID()}`;\n const created: SchedulerExecutionRecord = {\n executionId,\n jobId: input.job.jobId,\n scheduledFor: input.scheduledFor,\n triggeredAt: new Date().toISOString(),\n status: \"queued\",\n attemptCount: input.attemptCount ?? 0,\n };\n await input.executionStore.create(created);\n\n if (!input.queueService) {\n return created;\n }\n\n const accepted = await input.queueService.enqueue({\n idempotencyKey: input.idempotencyKey,\n payloadRef: {\n executionId,\n handlerKey: input.job.handlerKey,\n config: input.job.config,\n },\n });\n const withTask = await input.executionStore.update(executionId, {\n queueTaskId: accepted.taskId,\n });\n return withTask ?? { ...created, queueTaskId: accepted.taskId };\n}\n","import type { QueueService } from \"@azlib/queue\";\n\nimport type {\n SchedulerExecutionRecord,\n SchedulerJobRecord,\n} from \"../contracts/scheduler-types.js\";\nimport type { JobExecutionStore } from \"./job-execution-store.js\";\nimport { dispatchJobExecution } from \"./scheduler-job-trigger.js\";\n\nexport function findRetryableExecution(\n allExecutions: SchedulerExecutionRecord[],\n executionId: string,\n): SchedulerExecutionRecord {\n const target = allExecutions.find((item) => item.executionId === executionId);\n if (!target) {\n throw new Error(`Execution not found: ${executionId}`);\n }\n if (target.status !== \"failed\" && target.status !== \"dead-letter\") {\n throw new Error(\"Only failed or dead-letter executions can be retried\");\n }\n return target;\n}\n\nexport async function retryFailedExecutionById(\n allExecutions: SchedulerExecutionRecord[],\n jobs: readonly SchedulerJobRecord[],\n executionStore: JobExecutionStore,\n queueService: QueueService | undefined,\n executionId: string,\n): Promise<SchedulerExecutionRecord> {\n const target = findRetryableExecution(allExecutions, executionId);\n const job = jobs.find((item) => item.jobId === target.jobId);\n if (!job) {\n throw new Error(`Job not found: ${target.jobId}`);\n }\n\n return dispatchJobExecution({\n job,\n executionStore,\n queueService,\n scheduledFor: target.scheduledFor,\n attemptCount: target.attemptCount + 1,\n idempotencyKey: `scheduler:${job.jobId}:retry:${executionId}`,\n });\n}\n","import type { SqlClientAdapter, SqlDialectAdapter } from \"@azlib/persistence\";\n\nimport type { SchedulerPersistenceConfig } from \"../contracts/scheduler-types.js\";\nimport type { ScheduleCursorRecord } from \"./schedule-cursor-store.js\";\nimport type { JobExecutionStore } from \"./job-execution-store.js\";\nimport type { ScheduleCursorStore } from \"./schedule-cursor-store.js\";\nimport type { SchedulerJobStore } from \"./scheduler-job-store.js\";\n\ninterface JobRow {\n jobId: string;\n name: string;\n handlerKey: string;\n scheduleType: \"once\" | \"cron\";\n expression: string;\n timezone: string;\n overlapPolicy: string | null;\n missedRunPolicy: string | null;\n configJson: string;\n enabled: number;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface CursorRow {\n jobId: string;\n lastEvaluatedAt: string;\n lastTriggeredAt: string | null;\n nextRunAt: string;\n version: number;\n}\n\ninterface ExecutionRow {\n executionId: string;\n jobId: string;\n scheduledFor: string;\n triggeredAt: string;\n status: string;\n queueTaskId: string | null;\n attemptCount: number;\n lastError: string | null;\n}\n\nfunction namespaceFor(config: SchedulerPersistenceConfig): string {\n return config.namespace?.trim() || \"azlib\";\n}\n\nfunction tableNames(namespace: string) {\n return {\n jobs: `${namespace}__scheduler_jobs`,\n cursors: `${namespace}__scheduler_cursors`,\n executions: `${namespace}__scheduler_executions`,\n };\n}\n\nfunction quoted(dialect: SqlDialectAdapter, tableName: string): string {\n return dialect.quoteIdentifier(tableName);\n}\n\nasync function bootstrapSchedulerPersistence(\n client: SqlClientAdapter,\n dialect: SqlDialectAdapter,\n names: ReturnType<typeof tableNames>,\n): Promise<void> {\n await client.transaction(async (tx) => {\n await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.jobs)} (\n job_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n handler_key TEXT NOT NULL,\n schedule_type TEXT NOT NULL,\n expression TEXT NOT NULL,\n timezone TEXT NOT NULL,\n overlap_policy TEXT,\n missed_run_policy TEXT,\n config_json TEXT NOT NULL,\n enabled INTEGER NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )`);\n await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.cursors)} (\n job_id TEXT PRIMARY KEY,\n last_evaluated_at TEXT NOT NULL,\n last_triggered_at TEXT,\n next_run_at TEXT NOT NULL,\n version INTEGER NOT NULL\n )`);\n await tx.execute(`CREATE TABLE IF NOT EXISTS ${quoted(dialect, names.executions)} (\n execution_id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL,\n scheduled_for TEXT NOT NULL,\n triggered_at TEXT NOT NULL,\n status TEXT NOT NULL,\n queue_task_id TEXT,\n attempt_count INTEGER NOT NULL,\n last_error TEXT\n )`);\n });\n}\n\nfunction toJobRow<TConfig>(\n jobId: string,\n definition: import(\"../contracts/scheduler-types.js\").SchedulerJobDefinition<TConfig>,\n): JobRow {\n const now = new Date().toISOString();\n return {\n jobId,\n name: definition.name,\n handlerKey: definition.handlerKey,\n scheduleType: definition.schedule.scheduleType,\n expression: definition.schedule.expression,\n timezone: definition.schedule.timezone,\n overlapPolicy: definition.schedule.overlapPolicy ?? null,\n missedRunPolicy: definition.schedule.missedRunPolicy ?? null,\n configJson: JSON.stringify(definition.config),\n enabled: (definition.enabled ?? true) ? 1 : 0,\n createdAt: now,\n updatedAt: now,\n };\n}\n\nfunction rowToJobRecord<TConfig>(\n row: JobRow,\n): import(\"../contracts/scheduler-types.js\").SchedulerJobRecord<TConfig> {\n return {\n jobId: row.jobId,\n name: row.name,\n handlerKey: row.handlerKey,\n schedule: {\n scheduleType: row.scheduleType,\n expression: row.expression,\n timezone: row.timezone,\n overlapPolicy:\n row.overlapPolicy === null ? undefined : (row.overlapPolicy as any),\n missedRunPolicy:\n row.missedRunPolicy === null ? undefined : (row.missedRunPolicy as any),\n },\n config: JSON.parse(row.configJson) as TConfig,\n enabled: Boolean(row.enabled),\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nasync function readJobRow(\n client: SqlClientAdapter,\n dialect: SqlDialectAdapter,\n names: ReturnType<typeof tableNames>,\n jobId: string,\n) {\n const rows = await client.query<JobRow>(\n `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\n FROM ${quoted(dialect, names.jobs)} WHERE job_id = ? LIMIT 1`,\n [jobId],\n );\n return rows[0] ? rowToJobRecord(rows[0]) : null;\n}\n\nasync function readCursorRow(\n client: SqlClientAdapter,\n dialect: SqlDialectAdapter,\n names: ReturnType<typeof tableNames>,\n jobId: string,\n): Promise<ScheduleCursorRecord | null> {\n const rows = await client.query<CursorRow>(\n `SELECT job_id AS jobId, last_evaluated_at AS lastEvaluatedAt, last_triggered_at AS lastTriggeredAt, next_run_at AS nextRunAt, version\n FROM ${quoted(dialect, names.cursors)} WHERE job_id = ? LIMIT 1`,\n [jobId],\n );\n const row = rows[0];\n return row\n ? {\n jobId: row.jobId,\n lastEvaluatedAt: row.lastEvaluatedAt,\n lastTriggeredAt: row.lastTriggeredAt ?? undefined,\n nextRunAt: row.nextRunAt,\n version: row.version,\n }\n : null;\n}\n\nasync function readExecutionRow(\n client: SqlClientAdapter,\n dialect: SqlDialectAdapter,\n names: ReturnType<typeof tableNames>,\n executionId: string,\n) {\n const rows = await client.query<ExecutionRow>(\n `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\n FROM ${quoted(dialect, names.executions)} WHERE execution_id = ? LIMIT 1`,\n [executionId],\n );\n const row = rows[0];\n return row\n ? {\n executionId: row.executionId,\n jobId: row.jobId,\n scheduledFor: row.scheduledFor,\n triggeredAt: row.triggeredAt,\n status: row.status as any,\n queueTaskId: row.queueTaskId ?? undefined,\n attemptCount: row.attemptCount,\n lastError: row.lastError ?? undefined,\n }\n : null;\n}\n\nexport interface SchedulerPersistence {\n jobStore: SchedulerJobStore;\n cursorStore: ScheduleCursorStore;\n executionStore: JobExecutionStore;\n}\n\nexport function createSchedulerPersistence(\n config: SchedulerPersistenceConfig,\n): SchedulerPersistence {\n const namespace = namespaceFor(config);\n const names = tableNames(namespace);\n let bootstrapPromise: Promise<void> | null = null;\n\n async function ensureBootstrapped() {\n if (!bootstrapPromise) {\n bootstrapPromise = bootstrapSchedulerPersistence(\n config.client,\n config.dialect,\n names,\n );\n }\n await bootstrapPromise;\n }\n\n return {\n jobStore: {\n async create(jobId, definition) {\n await ensureBootstrapped();\n const row = toJobRow(jobId, definition);\n await config.client.transaction(async (tx) => {\n await tx.execute(\n `DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`,\n [jobId],\n );\n await tx.execute(\n `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)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n row.jobId,\n row.name,\n row.handlerKey,\n row.scheduleType,\n row.expression,\n row.timezone,\n row.overlapPolicy,\n row.missedRunPolicy,\n row.configJson,\n row.enabled,\n row.createdAt,\n row.updatedAt,\n ],\n );\n });\n return rowToJobRecord(row);\n },\n async update(jobId, patch) {\n const current = await readJobRow(\n config.client,\n config.dialect,\n names,\n jobId,\n );\n if (!current) {\n return null;\n }\n const next: import(\"../contracts/scheduler-types.js\").SchedulerJobRecord =\n {\n ...current,\n ...patch,\n schedule: {\n ...current.schedule,\n ...patch.schedule,\n },\n updatedAt: new Date().toISOString(),\n };\n await config.client.transaction(async (tx) => {\n await tx.execute(\n `DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`,\n [jobId],\n );\n await tx.execute(\n `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)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n next.jobId,\n next.name,\n next.handlerKey,\n next.schedule.scheduleType,\n next.schedule.expression,\n next.schedule.timezone,\n next.schedule.overlapPolicy ?? null,\n next.schedule.missedRunPolicy ?? null,\n JSON.stringify(next.config),\n next.enabled ? 1 : 0,\n next.createdAt,\n next.updatedAt,\n ],\n );\n });\n return next;\n },\n async setEnabled(jobId, enabled) {\n const current = await readJobRow(\n config.client,\n config.dialect,\n names,\n jobId,\n );\n if (!current) {\n return null;\n }\n const next = {\n ...current,\n enabled,\n updatedAt: new Date().toISOString(),\n };\n await config.client.transaction(async (tx) => {\n await tx.execute(\n `DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`,\n [jobId],\n );\n await tx.execute(\n `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)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n next.jobId,\n next.name,\n next.handlerKey,\n next.schedule.scheduleType,\n next.schedule.expression,\n next.schedule.timezone,\n next.schedule.overlapPolicy ?? null,\n next.schedule.missedRunPolicy ?? null,\n JSON.stringify(next.config),\n next.enabled ? 1 : 0,\n next.createdAt,\n next.updatedAt,\n ],\n );\n });\n return next;\n },\n async get(jobId) {\n await ensureBootstrapped();\n return readJobRow(config.client, config.dialect, names, jobId);\n },\n async list() {\n await ensureBootstrapped();\n const rows = await config.client.query<JobRow>(\n `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\n FROM ${quoted(config.dialect, names.jobs)} ORDER BY created_at ASC`,\n [],\n );\n return rows.map(rowToJobRecord);\n },\n async remove(jobId) {\n await ensureBootstrapped();\n await config.client.execute(\n `DELETE FROM ${quoted(config.dialect, names.jobs)} WHERE job_id = ?`,\n [jobId],\n );\n return true;\n },\n },\n cursorStore: {\n async set(jobId, nextRunAt, now) {\n await ensureBootstrapped();\n const current = await readCursorRow(\n config.client,\n config.dialect,\n names,\n jobId,\n );\n const next: CursorRow = {\n jobId,\n lastEvaluatedAt: now,\n lastTriggeredAt: current?.lastTriggeredAt ?? null,\n nextRunAt,\n version: (current?.version ?? 0) + 1,\n };\n await config.client.transaction(async (tx) => {\n await tx.execute(\n `DELETE FROM ${quoted(config.dialect, names.cursors)} WHERE job_id = ?`,\n [jobId],\n );\n await tx.execute(\n `INSERT INTO ${quoted(config.dialect, names.cursors)} (job_id, last_evaluated_at, last_triggered_at, next_run_at, version) VALUES (?, ?, ?, ?, ?)`,\n [\n next.jobId,\n next.lastEvaluatedAt,\n next.lastTriggeredAt,\n next.nextRunAt,\n next.version,\n ],\n );\n });\n return {\n jobId: next.jobId,\n lastEvaluatedAt: next.lastEvaluatedAt,\n lastTriggeredAt: next.lastTriggeredAt ?? undefined,\n nextRunAt: next.nextRunAt,\n version: next.version,\n };\n },\n async markTriggered(jobId, triggeredAt) {\n await ensureBootstrapped();\n const current = await readCursorRow(\n config.client,\n config.dialect,\n names,\n jobId,\n );\n if (!current) {\n return null;\n }\n const next: CursorRow = {\n ...current,\n lastTriggeredAt: triggeredAt,\n version: current.version + 1,\n };\n await config.client.execute(\n `UPDATE ${quoted(config.dialect, names.cursors)} SET last_triggered_at = ?, version = ? WHERE job_id = ?`,\n [next.lastTriggeredAt, next.version, jobId],\n );\n return {\n jobId: next.jobId,\n lastEvaluatedAt: next.lastEvaluatedAt,\n lastTriggeredAt: next.lastTriggeredAt ?? undefined,\n nextRunAt: next.nextRunAt,\n version: next.version,\n };\n },\n async get(jobId) {\n await ensureBootstrapped();\n return readCursorRow(config.client, config.dialect, names, jobId);\n },\n async remove(jobId) {\n await ensureBootstrapped();\n await config.client.execute(\n `DELETE FROM ${quoted(config.dialect, names.cursors)} WHERE job_id = ?`,\n [jobId],\n );\n return true;\n },\n },\n executionStore: {\n async create(record) {\n await ensureBootstrapped();\n await config.client.execute(\n `INSERT INTO ${quoted(config.dialect, names.executions)} (execution_id, job_id, scheduled_for, triggered_at, status, queue_task_id, attempt_count, last_error)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n record.executionId,\n record.jobId,\n record.scheduledFor,\n record.triggeredAt,\n record.status,\n record.queueTaskId ?? null,\n record.attemptCount,\n record.lastError ?? null,\n ],\n );\n return record;\n },\n async update(executionId, patch) {\n const current = await readExecutionRow(\n config.client,\n config.dialect,\n names,\n executionId,\n );\n if (!current) {\n return null;\n }\n const next = {\n ...current,\n ...patch,\n };\n await config.client.execute(\n `UPDATE ${quoted(config.dialect, names.executions)} SET job_id = ?, scheduled_for = ?, triggered_at = ?, status = ?, queue_task_id = ?, attempt_count = ?, last_error = ? WHERE execution_id = ?`,\n [\n next.jobId,\n next.scheduledFor,\n next.triggeredAt,\n next.status,\n next.queueTaskId ?? null,\n next.attemptCount,\n next.lastError ?? null,\n executionId,\n ],\n );\n return next;\n },\n async listByJob(jobId) {\n await ensureBootstrapped();\n const rows = await config.client.query<ExecutionRow>(\n `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\n FROM ${quoted(config.dialect, names.executions)} WHERE job_id = ? ORDER BY triggered_at ASC`,\n [jobId],\n );\n return rows.map((row) => ({\n executionId: row.executionId,\n jobId: row.jobId,\n scheduledFor: row.scheduledFor,\n triggeredAt: row.triggeredAt,\n status: row.status as any,\n queueTaskId: row.queueTaskId ?? undefined,\n attemptCount: row.attemptCount,\n lastError: row.lastError ?? undefined,\n }));\n },\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport type { QueueService } from \"@azlib/queue\";\n\nimport type {\n SchedulerJobDefinition,\n SchedulerPersistenceConfig,\n} from \"../contracts/scheduler-types.js\";\nimport type { JobExecutionStore } from \"../core/job-execution-store.js\";\nimport {\n createSchedulerPersistence,\n type SchedulerPersistence,\n} from \"../core/persistence.js\";\nimport { computeNextRunAt, parseSchedule } from \"../core/schedule-parser.js\";\nimport type { ScheduleCursorStore } from \"../core/schedule-cursor-store.js\";\nimport type { SchedulerJobStore } from \"../core/scheduler-job-store.js\";\nimport { dispatchJobExecution } from \"../core/scheduler-job-trigger.js\";\nimport { retryFailedExecutionById } from \"../core/scheduler-service-retry.js\";\nimport type {\n SchedulerDashboardItem,\n SchedulerDashboardService,\n} from \"./dashboard-types.js\";\n\nfunction nextRunForJob(\n job: {\n schedule: {\n scheduleType: \"once\" | \"cron\";\n expression: string;\n timezone: string;\n };\n },\n now = new Date(),\n): string {\n const parsed = parseSchedule(\n job.schedule.scheduleType,\n job.schedule.expression,\n job.schedule.timezone,\n );\n return computeNextRunAt(parsed, now);\n}\n\nasync function toDashboardItem(\n jobStore: SchedulerJobStore,\n cursorStore: ScheduleCursorStore,\n executionStore: JobExecutionStore,\n jobId: string,\n): Promise<SchedulerDashboardItem | null> {\n const job = await jobStore.get(jobId);\n if (!job) {\n return null;\n }\n const cursor = await cursorStore.get(jobId);\n const executions = await executionStore.listByJob(jobId);\n return {\n ...job,\n nextRunAt: cursor?.nextRunAt,\n executions,\n };\n}\n\nexport function createSchedulerDashboardFromStores(\n stores: SchedulerPersistence,\n options?: {\n queueService?: QueueService;\n handlerKeys?: readonly string[];\n },\n): SchedulerDashboardService {\n const { jobStore, cursorStore, executionStore } = stores;\n const handlerKeys = [...(options?.handlerKeys ?? [])];\n\n return {\n async getHealth() {\n const jobs = await jobStore.list();\n const executions = (\n await Promise.all(jobs.map((job) => executionStore.listByJob(job.jobId)))\n ).flat();\n const failed = executions.filter(\n (item) => item.status === \"failed\" || item.status === \"dead-letter\",\n );\n return {\n totalJobs: jobs.length,\n enabledJobs: jobs.filter((job) => job.enabled).length,\n pausedJobs: jobs.filter((job) => !job.enabled).length,\n failedExecutions: failed.length,\n };\n },\n\n async listItems() {\n const jobs = await jobStore.list();\n return Promise.all(\n jobs.map(async (job) => {\n const item = await toDashboardItem(\n jobStore,\n cursorStore,\n executionStore,\n job.jobId,\n );\n return item as SchedulerDashboardItem;\n }),\n );\n },\n\n async getJob(jobId) {\n return toDashboardItem(jobStore, cursorStore, executionStore, jobId);\n },\n\n async createJob<TConfig>(job: SchedulerJobDefinition<TConfig>) {\n parseSchedule(\n job.schedule.scheduleType,\n job.schedule.expression,\n job.schedule.timezone,\n );\n const jobId = `job_${randomUUID()}`;\n const created = await jobStore.create(jobId, job);\n const nextRunAt = nextRunForJob(created);\n await cursorStore.set(jobId, nextRunAt, new Date().toISOString());\n return { jobId };\n },\n\n async updateJob<TConfig>(\n jobId: string,\n patch: Partial<SchedulerJobDefinition<TConfig>>,\n ) {\n const updated = await jobStore.update(\n jobId,\n patch as Partial<SchedulerJobDefinition>,\n );\n if (!updated) {\n throw new Error(`Job not found: ${jobId}`);\n }\n const nextRunAt = nextRunForJob(updated);\n await cursorStore.set(jobId, nextRunAt, new Date().toISOString());\n },\n\n async pauseJob(jobId) {\n const updated = await jobStore.setEnabled(jobId, false);\n if (!updated) {\n throw new Error(`Job not found: ${jobId}`);\n }\n },\n\n async resumeJob(jobId) {\n const updated = await jobStore.setEnabled(jobId, true);\n if (!updated) {\n throw new Error(`Job not found: ${jobId}`);\n }\n },\n\n async deleteJob(jobId) {\n const removed = await jobStore.remove(jobId);\n await cursorStore.remove(jobId);\n if (!removed) {\n throw new Error(`Job not found: ${jobId}`);\n }\n },\n\n async runJob(jobId) {\n const job = await jobStore.get(jobId);\n if (!job) {\n throw new Error(`Job not found: ${jobId}`);\n }\n const scheduledFor = new Date().toISOString();\n await dispatchJobExecution({\n job,\n executionStore,\n queueService: options?.queueService,\n scheduledFor,\n attemptCount: 0,\n idempotencyKey: `scheduler:${job.jobId}:manual:${scheduledFor}`,\n });\n },\n\n async listExecutions(jobId) {\n const job = await jobStore.get(jobId);\n if (!job) {\n throw new Error(`Job not found: ${jobId}`);\n }\n return executionStore.listByJob(jobId);\n },\n\n async retryExecution(executionId) {\n const jobs = await jobStore.list();\n const all = (\n await Promise.all(jobs.map((job) => executionStore.listByJob(job.jobId)))\n ).flat();\n await retryFailedExecutionById(\n all,\n jobs,\n executionStore,\n options?.queueService,\n executionId,\n );\n },\n\n listHandlerKeys() {\n return handlerKeys;\n },\n };\n}\n\nexport function createSchedulerDashboardFromPersistence(\n persistence: SchedulerPersistenceConfig,\n options?: {\n queueService?: QueueService;\n handlerKeys?: readonly string[];\n },\n): SchedulerDashboardService {\n return createSchedulerDashboardFromStores(\n createSchedulerPersistence(persistence),\n options,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { a as SchedulerHandlerRegistry, d as SchedulerPersistenceConfig, i as SchedulerExecutionRecord, o as SchedulerJobDefinition, p as SchedulerService, r as SchedulerDashboardService, s as SchedulerJobRecord } from "./dashboard-types-BHjRLcxJ.mjs";
2
+ import { QueueService } from "@azlib/queue";
3
+ //#region src/dashboard/queue-dashboard-service.d.ts
4
+ declare function createSchedulerDashboardService(scheduler: SchedulerService, options?: {
5
+ handlers?: Pick<SchedulerHandlerRegistry, "keys">;
6
+ }): SchedulerDashboardService;
7
+ //#endregion
8
+ //#region src/core/job-execution-store.d.ts
9
+ interface JobExecutionStore {
10
+ create(record: SchedulerExecutionRecord): Promise<SchedulerExecutionRecord>;
11
+ update(executionId: string, patch: Partial<Omit<SchedulerExecutionRecord, "executionId">>): Promise<SchedulerExecutionRecord | null>;
12
+ listByJob(jobId: string): Promise<SchedulerExecutionRecord[]>;
13
+ }
14
+ //#endregion
15
+ //#region src/core/schedule-cursor-store.d.ts
16
+ interface ScheduleCursorRecord {
17
+ jobId: string;
18
+ lastEvaluatedAt: string;
19
+ lastTriggeredAt?: string;
20
+ nextRunAt: string;
21
+ version: number;
22
+ }
23
+ interface ScheduleCursorStore {
24
+ set(jobId: string, nextRunAt: string, now: string): Promise<ScheduleCursorRecord>;
25
+ markTriggered(jobId: string, triggeredAt: string): Promise<ScheduleCursorRecord | null>;
26
+ get(jobId: string): Promise<ScheduleCursorRecord | null>;
27
+ remove(jobId: string): Promise<boolean>;
28
+ }
29
+ //#endregion
30
+ //#region src/core/scheduler-job-store.d.ts
31
+ interface SchedulerJobStore {
32
+ create(jobId: string, definition: SchedulerJobDefinition): Promise<SchedulerJobRecord>;
33
+ update(jobId: string, patch: Partial<SchedulerJobDefinition>): Promise<SchedulerJobRecord | null>;
34
+ setEnabled(jobId: string, enabled: boolean): Promise<SchedulerJobRecord | null>;
35
+ get(jobId: string): Promise<SchedulerJobRecord | null>;
36
+ list(): Promise<SchedulerJobRecord[]>;
37
+ remove(jobId: string): Promise<boolean>;
38
+ }
39
+ //#endregion
40
+ //#region src/core/persistence.d.ts
41
+ interface SchedulerPersistence {
42
+ jobStore: SchedulerJobStore;
43
+ cursorStore: ScheduleCursorStore;
44
+ executionStore: JobExecutionStore;
45
+ }
46
+ declare function createSchedulerPersistence(config: SchedulerPersistenceConfig): SchedulerPersistence;
47
+ //#endregion
48
+ //#region src/dashboard/store-dashboard.d.ts
49
+ declare function createSchedulerDashboardFromStores(stores: SchedulerPersistence, options?: {
50
+ queueService?: QueueService;
51
+ handlerKeys?: readonly string[];
52
+ }): SchedulerDashboardService;
53
+ declare function createSchedulerDashboardFromPersistence(persistence: SchedulerPersistenceConfig, options?: {
54
+ queueService?: QueueService;
55
+ handlerKeys?: readonly string[];
56
+ }): SchedulerDashboardService;
57
+ //#endregion
58
+ export { createSchedulerDashboardService as i, createSchedulerDashboardFromStores as n, createSchedulerPersistence as r, createSchedulerDashboardFromPersistence as t };
59
+ //# sourceMappingURL=store-dashboard-D-yIxXxL.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-dashboard-D-yIxXxL.d.mts","names":[],"sources":["../src/dashboard/queue-dashboard-service.ts","../src/core/job-execution-store.ts","../src/core/schedule-cursor-store.ts","../src/core/scheduler-job-store.ts","../src/core/persistence.ts","../src/dashboard/store-dashboard.ts"],"mappings":";;;iBAKgB,gCACd,WAAW,kBACX;EAAY,WAAW,KAAK;IAC3B;;;UCNc;EACf,OAAO,QAAQ,2BAA2B,QAAQ;EAClD,OACE,qBACA,OAAO,QAAQ,KAAK,4CACnB,QAAQ;EACX,UAAU,gBAAgB,QAAQ;;;;UCRnB;EACf;EACA;EACA;EACA;EACA;;UAGe;EACf,IACE,eACA,mBACA,cACC,QAAQ;EACX,cACE,eACA,sBACC,QAAQ;EACX,IAAI,gBAAgB,QAAQ;EAC5B,OAAO,gBAAgB;;;;UCdR;EACf,OACE,eACA,YAAY,yBACX,QAAQ;EACX,OACE,eACA,OAAO,QAAQ,0BACd,QAAQ;EACX,WACE,eACA,mBACC,QAAQ;EACX,IAAI,gBAAgB,QAAQ;EAC5B,QAAQ,QAAQ;EAChB,OAAO,gBAAgB;;;;UCyLR;EACf,UAAU;EACV,aAAa;EACb,gBAAgB;;iBAGF,2BACd,QAAQ,6BACP;;;iBCzJa,mCACd,QAAQ,sBACR;EACE,eAAe;EACf;IAED;iBAsIa,wCACd,aAAa,4BACb;EACE,eAAe;EACf;IAED"}