@ddd-ts-kit/scheduling 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +424 -2
- package/dist/errors/error-code-of.d.ts +4 -0
- package/dist/errors/error-code-of.d.ts.map +1 -0
- package/dist/errors/error-code-of.js +11 -0
- package/dist/errors/error-code-of.js.map +1 -0
- package/dist/errors/job-aborted-error.d.ts +6 -0
- package/dist/errors/job-aborted-error.d.ts.map +1 -0
- package/dist/errors/job-aborted-error.js +9 -0
- package/dist/errors/job-aborted-error.js.map +1 -0
- package/dist/errors/job-lock-unavailable-error.d.ts +6 -0
- package/dist/errors/job-lock-unavailable-error.d.ts.map +1 -0
- package/dist/errors/job-lock-unavailable-error.js +9 -0
- package/dist/errors/job-lock-unavailable-error.js.map +1 -0
- package/dist/errors/job-timeout-error.d.ts +6 -0
- package/dist/errors/job-timeout-error.d.ts.map +1 -0
- package/dist/errors/job-timeout-error.js +9 -0
- package/dist/errors/job-timeout-error.js.map +1 -0
- package/dist/in-process-job-scheduler.d.ts +66 -0
- package/dist/in-process-job-scheduler.d.ts.map +1 -0
- package/dist/in-process-job-scheduler.js +189 -0
- package/dist/in-process-job-scheduler.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/job-checkpoints.d.ts +11 -0
- package/dist/job-checkpoints.d.ts.map +1 -0
- package/dist/job-checkpoints.js +9 -0
- package/dist/job-checkpoints.js.map +1 -0
- package/dist/job-recorder.d.ts +28 -0
- package/dist/job-recorder.d.ts.map +1 -0
- package/dist/job-recorder.js +114 -0
- package/dist/job-recorder.js.map +1 -0
- package/dist/job-run-observer.d.ts +9 -0
- package/dist/job-run-observer.d.ts.map +1 -0
- package/dist/job-run-observer.js +2 -0
- package/dist/job-run-observer.js.map +1 -0
- package/dist/job-runner.d.ts +29 -0
- package/dist/job-runner.d.ts.map +1 -0
- package/dist/job-runner.js +96 -0
- package/dist/job-runner.js.map +1 -0
- package/dist/job-timers.d.ts +7 -0
- package/dist/job-timers.d.ts.map +1 -0
- package/dist/job-timers.js +5 -0
- package/dist/job-timers.js.map +1 -0
- package/dist/metric-names.d.ts +3 -0
- package/dist/metric-names.d.ts.map +1 -0
- package/dist/metric-names.js +3 -0
- package/dist/metric-names.js.map +1 -0
- package/dist/occurrence.d.ts +6 -0
- package/dist/occurrence.d.ts.map +1 -0
- package/dist/occurrence.js +2 -0
- package/dist/occurrence.js.map +1 -0
- package/dist/occurrences/instants-of-wall-time.d.ts +7 -0
- package/dist/occurrences/instants-of-wall-time.d.ts.map +1 -0
- package/dist/occurrences/instants-of-wall-time.js +15 -0
- package/dist/occurrences/instants-of-wall-time.js.map +1 -0
- package/dist/occurrences/latest-occurrence.d.ts +10 -0
- package/dist/occurrences/latest-occurrence.d.ts.map +1 -0
- package/dist/occurrences/latest-occurrence.js +22 -0
- package/dist/occurrences/latest-occurrence.js.map +1 -0
- package/dist/occurrences/local-day-of.d.ts +4 -0
- package/dist/occurrences/local-day-of.d.ts.map +1 -0
- package/dist/occurrences/local-day-of.js +7 -0
- package/dist/occurrences/local-day-of.js.map +1 -0
- package/dist/occurrences/next-occurrence.d.ts +4 -0
- package/dist/occurrences/next-occurrence.d.ts.map +1 -0
- package/dist/occurrences/next-occurrence.js +20 -0
- package/dist/occurrences/next-occurrence.js.map +1 -0
- package/dist/occurrences/occurrences-of-day.d.ts +4 -0
- package/dist/occurrences/occurrences-of-day.d.ts.map +1 -0
- package/dist/occurrences/occurrences-of-day.js +35 -0
- package/dist/occurrences/occurrences-of-day.js.map +1 -0
- package/dist/occurrences/offset-at.d.ts +3 -0
- package/dist/occurrences/offset-at.d.ts.map +1 -0
- package/dist/occurrences/offset-at.js +32 -0
- package/dist/occurrences/offset-at.js.map +1 -0
- package/dist/registered-job.d.ts +21 -0
- package/dist/registered-job.d.ts.map +1 -0
- package/dist/registered-job.js +27 -0
- package/dist/registered-job.js.map +1 -0
- package/dist/report-failed-runs.d.ts +11 -0
- package/dist/report-failed-runs.d.ts.map +1 -0
- package/dist/report-failed-runs.js +23 -0
- package/dist/report-failed-runs.js.map +1 -0
- package/dist/started-run.d.ts +7 -0
- package/dist/started-run.d.ts.map +1 -0
- package/dist/started-run.js +2 -0
- package/dist/started-run.js.map +1 -0
- package/dist/system-job-timers.d.ts +3 -0
- package/dist/system-job-timers.d.ts.map +1 -0
- package/dist/system-job-timers.js +9 -0
- package/dist/system-job-timers.js.map +1 -0
- package/dist/testing/manual-job-scheduler.d.ts +24 -0
- package/dist/testing/manual-job-scheduler.d.ts.map +1 -0
- package/dist/testing/manual-job-scheduler.js +42 -0
- package/dist/testing/manual-job-scheduler.js.map +1 -0
- package/dist/testing/manual-job-timers.d.ts +23 -0
- package/dist/testing/manual-job-timers.d.ts.map +1 -0
- package/dist/testing/manual-job-timers.js +54 -0
- package/dist/testing/manual-job-timers.js.map +1 -0
- package/dist/testing.d.ts +3 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/package.json +36 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { DuplicateJobError, NoopLogger, NoopMetrics, UnknownJobError, } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { JobAbortedError } from './errors/job-aborted-error.js';
|
|
3
|
+
import { JobRecorder } from './job-recorder.js';
|
|
4
|
+
import { JobRunner } from './job-runner.js';
|
|
5
|
+
import { latestOccurrence } from './occurrences/latest-occurrence.js';
|
|
6
|
+
import { nextOccurrence } from './occurrences/next-occurrence.js';
|
|
7
|
+
import { RegisteredJob } from './registered-job.js';
|
|
8
|
+
import { systemJobTimers } from './system-job-timers.js';
|
|
9
|
+
/*
|
|
10
|
+
* A sleep never lasts longer: the wall clock and the timers of the runtime drift apart, and a
|
|
11
|
+
* timer of the runtime cannot wait more than 24 days. A timer that fires early finds nothing
|
|
12
|
+
* due and sleeps again.
|
|
13
|
+
*/
|
|
14
|
+
const LONGEST_SLEEP_MS = 3_600_000;
|
|
15
|
+
/**
|
|
16
|
+
* The scheduler inside one process. Instants come from the `Clock`, sleeps go through the
|
|
17
|
+
* timers, and nothing is remembered across a restart unless `checkpoints` is given. Several
|
|
18
|
+
* instances of an application each run one: the `Lock` decides which of them runs an occurrence.
|
|
19
|
+
*/
|
|
20
|
+
export class InProcessJobScheduler {
|
|
21
|
+
jobs = new Map();
|
|
22
|
+
ticks = new Set();
|
|
23
|
+
runsInProgress = new Set();
|
|
24
|
+
settings;
|
|
25
|
+
recorder;
|
|
26
|
+
runner;
|
|
27
|
+
started = false;
|
|
28
|
+
stopping = false;
|
|
29
|
+
constructor(options) {
|
|
30
|
+
const { clock, logger = new NoopLogger(), metrics = new NoopMetrics(), timers = systemJobTimers } = options;
|
|
31
|
+
const { lateAfterMs = 60_000, graceMs = 30_000, lockTtlMs = 900_000, random = Math.random, checkpoints, onRun, } = options;
|
|
32
|
+
const { lock, scope, ids } = options;
|
|
33
|
+
this.settings = {
|
|
34
|
+
clock,
|
|
35
|
+
logger,
|
|
36
|
+
metrics,
|
|
37
|
+
timers,
|
|
38
|
+
lateAfterMs,
|
|
39
|
+
graceMs,
|
|
40
|
+
lockTtlMs,
|
|
41
|
+
random,
|
|
42
|
+
...(checkpoints === undefined ? {} : { checkpoints }),
|
|
43
|
+
};
|
|
44
|
+
this.recorder = new JobRecorder({ clock, logger, metrics, ...(onRun === undefined ? {} : { onRun }) });
|
|
45
|
+
this.runner = new JobRunner({ clock, lock, scope, ids, timers, lockTtlMs, lateAfterMs }, this.recorder);
|
|
46
|
+
}
|
|
47
|
+
register(definition) {
|
|
48
|
+
if (this.jobs.has(definition.name)) {
|
|
49
|
+
throw new DuplicateJobError(definition.name);
|
|
50
|
+
}
|
|
51
|
+
const job = new RegisteredJob(definition);
|
|
52
|
+
this.jobs.set(job.name, job);
|
|
53
|
+
if (this.started) {
|
|
54
|
+
void this.follow(job, this.begin(job));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async start() {
|
|
58
|
+
if (this.started) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.started = true;
|
|
62
|
+
await Promise.all([...this.jobs.values()].map((job) => this.follow(job, this.begin(job))));
|
|
63
|
+
}
|
|
64
|
+
async stop() {
|
|
65
|
+
this.started = false;
|
|
66
|
+
this.stopping = true;
|
|
67
|
+
for (const job of this.jobs.values()) {
|
|
68
|
+
job.cancelSleep?.();
|
|
69
|
+
}
|
|
70
|
+
await Promise.all(this.ticks);
|
|
71
|
+
const cancelAbort = this.settings.timers.schedule(this.settings.graceMs, () => {
|
|
72
|
+
for (const job of this.jobs.values()) {
|
|
73
|
+
job.abort?.(new JobAbortedError(job.name));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
await Promise.all(this.runsInProgress);
|
|
77
|
+
cancelAbort();
|
|
78
|
+
this.stopping = false;
|
|
79
|
+
}
|
|
80
|
+
async runNow(name) {
|
|
81
|
+
const job = this.jobs.get(name);
|
|
82
|
+
if (job === undefined) {
|
|
83
|
+
throw new UnknownJobError(name);
|
|
84
|
+
}
|
|
85
|
+
return this.trigger(job, { scheduledAt: this.settings.clock.now(), manual: true });
|
|
86
|
+
}
|
|
87
|
+
describe() {
|
|
88
|
+
return [...this.jobs.values()].map((job) => ({
|
|
89
|
+
name: job.name,
|
|
90
|
+
schedule: job.scheduleText,
|
|
91
|
+
enabled: job.policy.enabled,
|
|
92
|
+
running: job.active !== undefined,
|
|
93
|
+
...(this.started && job.cursor !== undefined ? { nextRunAt: nextOccurrence(job.schedule, job.cursor) } : {}),
|
|
94
|
+
...(job.lastRun === undefined ? {} : { lastRun: job.lastRun }),
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
/** A tick never rejects: what goes wrong while following a job is logged, and the others go on. */
|
|
98
|
+
follow(job, tick) {
|
|
99
|
+
const followed = tick.catch((error) => {
|
|
100
|
+
this.recorder.fault(job, error);
|
|
101
|
+
});
|
|
102
|
+
this.ticks.add(followed);
|
|
103
|
+
return followed.finally(() => this.ticks.delete(followed));
|
|
104
|
+
}
|
|
105
|
+
async begin(job) {
|
|
106
|
+
if (!job.policy.enabled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
job.cursor = (await this.remembered(job)) ?? this.settings.clock.now();
|
|
110
|
+
await this.tick(job);
|
|
111
|
+
}
|
|
112
|
+
/** A checkpoint that cannot be read is no reason to leave the job asleep: it starts from now. */
|
|
113
|
+
async remembered(job) {
|
|
114
|
+
try {
|
|
115
|
+
return await this.settings.checkpoints?.read(job.name);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
this.recorder.fault(job, error);
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async tick(job) {
|
|
123
|
+
try {
|
|
124
|
+
await this.handleDue(job);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
this.sleep(job);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Of the occurrences that became due, the latest is run when it is on time or owed; the others are missed. */
|
|
131
|
+
async handleDue(job) {
|
|
132
|
+
const now = this.settings.clock.now();
|
|
133
|
+
const after = job.cursor ?? now;
|
|
134
|
+
const due = latestOccurrence(job.schedule, { after, notAfter: now });
|
|
135
|
+
if (due === undefined) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
job.cursor = due;
|
|
139
|
+
const earliest = nextOccurrence(job.schedule, after);
|
|
140
|
+
const late = now.getTime() - due.getTime() > this.settings.lateAfterMs + job.policy.jitterMs;
|
|
141
|
+
const runs = !late || job.policy.missed === 'run-once';
|
|
142
|
+
if (!runs || earliest.getTime() < due.getTime()) {
|
|
143
|
+
this.recorder.notRun(job, { scheduledAt: earliest, manual: false }, 'missed');
|
|
144
|
+
}
|
|
145
|
+
if (runs) {
|
|
146
|
+
void this.trigger(job, { scheduledAt: due, manual: false });
|
|
147
|
+
}
|
|
148
|
+
await this.settings.checkpoints?.write(job.name, due);
|
|
149
|
+
}
|
|
150
|
+
sleep(job) {
|
|
151
|
+
if (!this.started || job.cursor === undefined) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const { clock, timers, random } = this.settings;
|
|
155
|
+
const untilNext = nextOccurrence(job.schedule, job.cursor).getTime() - clock.now().getTime();
|
|
156
|
+
const jitter = Math.floor(random() * (job.policy.jitterMs + 1));
|
|
157
|
+
job.cancelSleep = timers.schedule(Math.min(Math.max(0, untilNext), LONGEST_SLEEP_MS) + jitter, () => {
|
|
158
|
+
void this.follow(job, this.tick(job));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
trigger(job, occurrence) {
|
|
162
|
+
const previous = job.active;
|
|
163
|
+
if (previous === undefined) {
|
|
164
|
+
return this.occupy(job, this.runner.run(job, occurrence));
|
|
165
|
+
}
|
|
166
|
+
if (job.policy.overlap === 'skip' || job.waiting) {
|
|
167
|
+
return Promise.resolve(this.recorder.notRun(job, occurrence, 'skipped-overlap'));
|
|
168
|
+
}
|
|
169
|
+
job.waiting = true;
|
|
170
|
+
return this.occupy(job, previous.then(() => this.afterPrevious(job, occurrence)));
|
|
171
|
+
}
|
|
172
|
+
occupy(job, run) {
|
|
173
|
+
const active = run.finally(() => {
|
|
174
|
+
this.runsInProgress.delete(active);
|
|
175
|
+
if (job.active === active) {
|
|
176
|
+
job.active = undefined;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
job.active = active;
|
|
180
|
+
this.runsInProgress.add(active);
|
|
181
|
+
return active;
|
|
182
|
+
}
|
|
183
|
+
/** A run that waited behind another is dropped when the scheduler is stopping. */
|
|
184
|
+
afterPrevious(job, occurrence) {
|
|
185
|
+
job.waiting = false;
|
|
186
|
+
return this.stopping ? this.recorder.notRun(job, occurrence, 'missed') : this.runner.run(job, occurrence);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=in-process-job-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-process-job-scheduler.js","sourceRoot":"","sources":["../src/in-process-job-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,eAAe,GAWhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAiCzD;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAEnC;;;;GAIG;AACH,MAAM,OAAO,qBAAqB;IACf,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAExC,KAAK,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEjC,cAAc,GAAG,IAAI,GAAG,EAAmB,CAAC;IAE5C,QAAQ,CAAW;IAEnB,QAAQ,CAAc;IAEtB,MAAM,CAAY;IAE3B,OAAO,GAAG,KAAK,CAAC;IAEhB,QAAQ,GAAG,KAAK,CAAC;IAEzB,YAAY,OAAqC;QAC/C,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,UAAU,EAAE,EAAE,OAAO,GAAG,IAAI,WAAW,EAAE,EAAE,MAAM,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;QAE5G,MAAM,EACJ,WAAW,GAAG,MAAM,EACpB,OAAO,GAAG,MAAM,EAChB,SAAS,GAAG,OAAO,EACnB,MAAM,GAAG,IAAI,CAAC,MAAM,EACpB,WAAW,EACX,KAAK,GACN,GAAG,OAAO,CAAC;QAEZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QAErC,IAAI,CAAC,QAAQ,GAAG;YACd,KAAK;YACL,MAAM;YACN,OAAO;YACP,MAAM;YACN,WAAW;YACX,OAAO;YACP,SAAS;YACT,MAAM;YACN,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;SACtD,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG,IAAI,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACvG,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1G,CAAC;IAED,QAAQ,CAAC,UAAyB;QAChC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACrC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;QACtB,CAAC;QAED,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE9B,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;YAC5E,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACvC,WAAW,EAAE,CAAC;QACd,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEhC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,GAAG,CAAC,YAAY;YAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO;YAC3B,OAAO,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS;YACjC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5G,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;SAC/D,CAAC,CAAC,CAAC;IACN,CAAC;IAED,mGAAmG;IAC3F,MAAM,CAAC,GAAkB,EAAE,IAAmB;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC7C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAEzB,OAAO,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7D,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,GAAkB;QACpC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,GAAG,CAAC,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACvE,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAED,iGAAiG;IACzF,KAAK,CAAC,UAAU,CAAC,GAAkB;QACzC,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAEhC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,GAAkB;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,+GAA+G;IACvG,KAAK,CAAC,SAAS,CAAC,GAAkB;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QAChC,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QAErE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QAED,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QAEjB,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC7F,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC;QAEvD,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;YAChD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,CAAC;QAChF,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IAEO,KAAK,CAAC,GAAkB;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QAChD,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QAC7F,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;QAEhE,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,gBAAgB,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE;YAClG,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,OAAO,CAAC,GAAkB,EAAE,UAAsB;QACxD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC;QAE5B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YACjD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACnF,CAAC;QAED,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;QAEnB,OAAO,IAAI,CAAC,MAAM,CAChB,GAAG,EACH,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CACzD,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,GAAkB,EAAE,GAAoB;QACrD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9B,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAEnC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC1B,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;QACpB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEhC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kFAAkF;IAC1E,aAAa,CAAC,GAAkB,EAAE,UAAsB;QAC9D,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC;QAEpB,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAC5G,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { InProcessJobScheduler, type InProcessJobSchedulerOptions } from './in-process-job-scheduler.js';
|
|
2
|
+
export { JobCheckpoints } from './job-checkpoints.js';
|
|
3
|
+
export { latestOccurrence, type OccurrenceRange } from './occurrences/latest-occurrence.js';
|
|
4
|
+
export { nextOccurrence } from './occurrences/next-occurrence.js';
|
|
5
|
+
export { JobTimers } from './job-timers.js';
|
|
6
|
+
export { systemJobTimers } from './system-job-timers.js';
|
|
7
|
+
export { JobAbortedError } from './errors/job-aborted-error.js';
|
|
8
|
+
export { JobLockUnavailableError } from './errors/job-lock-unavailable-error.js';
|
|
9
|
+
export { JobTimeoutError } from './errors/job-timeout-error.js';
|
|
10
|
+
export { UNEXPECTED_ERROR } from './errors/error-code-of.js';
|
|
11
|
+
export { DURATION_HISTOGRAM, RUNS_COUNTER } from './metric-names.js';
|
|
12
|
+
export type { JobRunDetail, JobRunObserver } from './job-run-observer.js';
|
|
13
|
+
export { reportFailedRuns } from './report-failed-runs.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,KAAK,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { InProcessJobScheduler } from './in-process-job-scheduler.js';
|
|
2
|
+
export { JobCheckpoints } from './job-checkpoints.js';
|
|
3
|
+
export { latestOccurrence } from './occurrences/latest-occurrence.js';
|
|
4
|
+
export { nextOccurrence } from './occurrences/next-occurrence.js';
|
|
5
|
+
export { JobTimers } from './job-timers.js';
|
|
6
|
+
export { systemJobTimers } from './system-job-timers.js';
|
|
7
|
+
export { JobAbortedError } from './errors/job-aborted-error.js';
|
|
8
|
+
export { JobLockUnavailableError } from './errors/job-lock-unavailable-error.js';
|
|
9
|
+
export { JobTimeoutError } from './errors/job-timeout-error.js';
|
|
10
|
+
export { UNEXPECTED_ERROR } from './errors/error-code-of.js';
|
|
11
|
+
export { DURATION_HISTOGRAM, RUNS_COUNTER } from './metric-names.js';
|
|
12
|
+
export { reportFailedRuns } from './report-failed-runs.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAqC,MAAM,+BAA+B,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAwB,MAAM,oCAAoC,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a scheduler remembers the last occurrence it handled for each job, so that a restart
|
|
3
|
+
* knows what passed while the process was down. Shared by the instances of an application: a
|
|
4
|
+
* table, a key of a store.
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class JobCheckpoints {
|
|
7
|
+
static readonly tokenKey = "scheduling.JobCheckpoints";
|
|
8
|
+
abstract read(job: string): Promise<Date | undefined>;
|
|
9
|
+
abstract write(job: string, handled: Date): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=job-checkpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-checkpoints.d.ts","sourceRoot":"","sources":["../src/job-checkpoints.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,8BAAsB,cAAc;IAClC,MAAM,CAAC,QAAQ,CAAC,QAAQ,+BAA+B;IAEvD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC;IAEtD,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a scheduler remembers the last occurrence it handled for each job, so that a restart
|
|
3
|
+
* knows what passed while the process was down. Shared by the instances of an application: a
|
|
4
|
+
* table, a key of a store.
|
|
5
|
+
*/
|
|
6
|
+
export class JobCheckpoints {
|
|
7
|
+
static tokenKey = 'scheduling.JobCheckpoints';
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=job-checkpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-checkpoints.js","sourceRoot":"","sources":["../src/job-checkpoints.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAgB,cAAc;IAClC,MAAM,CAAU,QAAQ,GAAG,2BAA2B,CAAC;CAKxD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Clock, JobOutcome, JobRun, Logger, Metrics } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { JobRunObserver } from './job-run-observer.js';
|
|
3
|
+
import type { Occurrence } from './occurrence.js';
|
|
4
|
+
import type { RegisteredJob } from './registered-job.js';
|
|
5
|
+
import type { StartedRun } from './started-run.js';
|
|
6
|
+
export interface RecorderOptions {
|
|
7
|
+
readonly clock: Clock;
|
|
8
|
+
readonly logger: Logger;
|
|
9
|
+
readonly metrics: Metrics;
|
|
10
|
+
readonly onRun?: JobRunObserver;
|
|
11
|
+
}
|
|
12
|
+
/** Says what became of each occurrence, to the log, the metrics and the observer. Never the message of an error. */
|
|
13
|
+
export declare class JobRecorder {
|
|
14
|
+
private readonly options;
|
|
15
|
+
constructor(options: RecorderOptions);
|
|
16
|
+
succeeded(job: RegisteredJob, started: StartedRun): JobRun;
|
|
17
|
+
failed(job: RegisteredJob, started: StartedRun, error: unknown): JobRun;
|
|
18
|
+
/** The occurrence could not even be started. */
|
|
19
|
+
refused(job: RegisteredJob, occurrence: Occurrence, error: unknown): JobRun;
|
|
20
|
+
notRun(job: RegisteredJob, occurrence: Occurrence, outcome: Exclude<JobOutcome, 'succeeded' | 'failed'>): JobRun;
|
|
21
|
+
/** The scheduler itself stumbled while following a job: it goes on with the next occurrence. */
|
|
22
|
+
fault(job: RegisteredJob, error: unknown): void;
|
|
23
|
+
private ran;
|
|
24
|
+
private record;
|
|
25
|
+
private measure;
|
|
26
|
+
private log;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=job-recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-recorder.d.ts","sourceRoot":"","sources":["../src/job-recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAuB,MAAM,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE1G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;CACjC;AAOD,oHAAoH;AACpH,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAApC,YAA6B,OAAO,EAAE,eAAe,EAAI;IAEzD,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAEzD;IAED,MAAM,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAItE;IAED,gDAAgD;IAChD,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAS1E;IAED,MAAM,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,WAAW,GAAG,QAAQ,CAAC,GAAG,MAAM,CAE/G;IAED,gGAAgG;IAChG,KAAK,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAW9C;IAED,OAAO,CAAC,GAAG;IAOX,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,GAAG;CAoBZ"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { errorCodeOf } from './errors/error-code-of.js';
|
|
2
|
+
import { DURATION_HISTOGRAM, RUNS_COUNTER } from './metric-names.js';
|
|
3
|
+
/** Says what became of each occurrence, to the log, the metrics and the observer. Never the message of an error. */
|
|
4
|
+
export class JobRecorder {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
succeeded(job, started) {
|
|
10
|
+
return this.record(job, this.ran(job, started, 'succeeded'), { correlationId: started.correlationId });
|
|
11
|
+
}
|
|
12
|
+
failed(job, started, error) {
|
|
13
|
+
const run = { ...this.ran(job, started, 'failed'), errorCode: errorCodeOf(error) };
|
|
14
|
+
return this.record(job, run, { correlationId: started.correlationId, failure: { error } });
|
|
15
|
+
}
|
|
16
|
+
/** The occurrence could not even be started. */
|
|
17
|
+
refused(job, occurrence, error) {
|
|
18
|
+
const run = {
|
|
19
|
+
job: job.name,
|
|
20
|
+
outcome: 'failed',
|
|
21
|
+
scheduledAt: occurrence.scheduledAt,
|
|
22
|
+
errorCode: errorCodeOf(error),
|
|
23
|
+
};
|
|
24
|
+
return this.record(job, run, { failure: { error } });
|
|
25
|
+
}
|
|
26
|
+
notRun(job, occurrence, outcome) {
|
|
27
|
+
return this.record(job, { job: job.name, outcome, scheduledAt: occurrence.scheduledAt }, {});
|
|
28
|
+
}
|
|
29
|
+
/** The scheduler itself stumbled while following a job: it goes on with the next occurrence. */
|
|
30
|
+
fault(job, error) {
|
|
31
|
+
const fields = {
|
|
32
|
+
job: job.name,
|
|
33
|
+
context: contextOf(job.name),
|
|
34
|
+
errorType: typeOf(error),
|
|
35
|
+
errorCode: errorCodeOf(error),
|
|
36
|
+
};
|
|
37
|
+
quietly(() => {
|
|
38
|
+
this.options.logger.error('Scheduled job could not be followed', fields);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
ran(job, started, outcome) {
|
|
42
|
+
const { occurrence, startedAt } = started;
|
|
43
|
+
const durationMs = this.options.clock.now().getTime() - startedAt.getTime();
|
|
44
|
+
return { job: job.name, outcome, scheduledAt: occurrence.scheduledAt, startedAt, durationMs };
|
|
45
|
+
}
|
|
46
|
+
record(job, run, detail) {
|
|
47
|
+
const { correlationId } = detail;
|
|
48
|
+
job.lastRun = run;
|
|
49
|
+
quietly(() => {
|
|
50
|
+
this.measure(run);
|
|
51
|
+
});
|
|
52
|
+
quietly(() => {
|
|
53
|
+
this.log(run, detail);
|
|
54
|
+
});
|
|
55
|
+
quietly(() => {
|
|
56
|
+
this.options.onRun?.(run, detail.failure?.error, correlationId === undefined ? {} : { correlationId });
|
|
57
|
+
});
|
|
58
|
+
return run;
|
|
59
|
+
}
|
|
60
|
+
measure(run) {
|
|
61
|
+
const labels = { job: run.job, context: contextOf(run.job), status: run.outcome, error_code: run.errorCode };
|
|
62
|
+
this.options.metrics.counter(RUNS_COUNTER).increment(labels);
|
|
63
|
+
if (run.durationMs !== undefined) {
|
|
64
|
+
this.options.metrics.histogram(DURATION_HISTOGRAM).record(run.durationMs, labels);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
log(run, { correlationId, failure }) {
|
|
68
|
+
const level = LEVELS[run.outcome];
|
|
69
|
+
if (!this.options.logger.isLevelEnabled(level)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const fields = {
|
|
73
|
+
job: run.job,
|
|
74
|
+
context: contextOf(run.job),
|
|
75
|
+
outcome: run.outcome,
|
|
76
|
+
scheduledAt: run.scheduledAt,
|
|
77
|
+
...(run.durationMs === undefined ? {} : { durationMs: run.durationMs }),
|
|
78
|
+
...(correlationId === undefined ? {} : { correlationId }),
|
|
79
|
+
...(run.errorCode === undefined ? {} : { errorCode: run.errorCode }),
|
|
80
|
+
...(failure === undefined ? {} : { errorType: typeOf(failure.error) }),
|
|
81
|
+
};
|
|
82
|
+
this.options.logger[level](`Scheduled job ${WORDS[run.outcome]}`, fields);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function contextOf(job) {
|
|
86
|
+
return job.split('.', 1).join('');
|
|
87
|
+
}
|
|
88
|
+
function typeOf(error) {
|
|
89
|
+
return error instanceof Error ? error.constructor.name : typeof error;
|
|
90
|
+
}
|
|
91
|
+
/** What records a run must not break it: a logger, a meter or an observer that throws is left alone. */
|
|
92
|
+
function quietly(tell) {
|
|
93
|
+
try {
|
|
94
|
+
tell();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const LEVELS = {
|
|
101
|
+
succeeded: 'info',
|
|
102
|
+
failed: 'error',
|
|
103
|
+
'skipped-overlap': 'warn',
|
|
104
|
+
'skipped-locked': 'debug',
|
|
105
|
+
missed: 'warn',
|
|
106
|
+
};
|
|
107
|
+
const WORDS = {
|
|
108
|
+
succeeded: 'succeeded',
|
|
109
|
+
failed: 'failed',
|
|
110
|
+
'skipped-overlap': 'skipped: the previous run is still in progress',
|
|
111
|
+
'skipped-locked': 'skipped: another instance runs this occurrence',
|
|
112
|
+
missed: 'missed',
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=job-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-recorder.js","sourceRoot":"","sources":["../src/job-recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JobRun } from '@ddd-ts-kit/kernel';
|
|
2
|
+
/** What the run carried that its `JobRun` does not say. */
|
|
3
|
+
export interface JobRunDetail {
|
|
4
|
+
/** The correlation id of the execution context the job ran in. Absent when it never started. */
|
|
5
|
+
readonly correlationId?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Told of every occurrence handled, with what the job threw when it failed: for an error tracker, or a test. */
|
|
8
|
+
export type JobRunObserver = (run: JobRun, error?: unknown, detail?: JobRunDetail) => void;
|
|
9
|
+
//# sourceMappingURL=job-run-observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-run-observer.d.ts","sourceRoot":"","sources":["../src/job-run-observer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC3B,gGAAgG;IAChG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,iHAAiH;AACjH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-run-observer.js","sourceRoot":"","sources":["../src/job-run-observer.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Clock, type ExecutionScope, type IdGenerator, type JobRun, type Lock } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { JobRecorder } from './job-recorder.js';
|
|
3
|
+
import type { JobTimers } from './job-timers.js';
|
|
4
|
+
import type { Occurrence } from './occurrence.js';
|
|
5
|
+
import type { RegisteredJob } from './registered-job.js';
|
|
6
|
+
export interface RunnerOptions {
|
|
7
|
+
readonly clock: Clock;
|
|
8
|
+
readonly lock: Lock;
|
|
9
|
+
readonly scope: ExecutionScope;
|
|
10
|
+
readonly ids: IdGenerator;
|
|
11
|
+
readonly timers: JobTimers;
|
|
12
|
+
/** How long the lock of an occurrence is kept for a job without timeout. */
|
|
13
|
+
readonly lockTtlMs: number;
|
|
14
|
+
/** How long after its instant another instance may still try an occurrence. */
|
|
15
|
+
readonly lateAfterMs: number;
|
|
16
|
+
}
|
|
17
|
+
/** Runs one occurrence: the lock of the occurrence, the execution scope, the timeout. */
|
|
18
|
+
export declare class JobRunner {
|
|
19
|
+
private readonly options;
|
|
20
|
+
private readonly recorder;
|
|
21
|
+
constructor(options: RunnerOptions, recorder: JobRecorder);
|
|
22
|
+
/** Answers what became of the occurrence. Never rejects. */
|
|
23
|
+
run(job: RegisteredJob, occurrence: Occurrence): Promise<JobRun>;
|
|
24
|
+
private holds;
|
|
25
|
+
private execute;
|
|
26
|
+
private armTimeout;
|
|
27
|
+
private inScope;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=job-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-runner.d.ts","sourceRoot":"","sources":["../src/job-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,yFAAyF;AACzF,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAF3B,YACmB,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,WAAW,EACpC;IAEJ,4DAA4D;IACtD,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAQrE;YAOa,KAAK;YAkBL,OAAO;IAsBrB,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,OAAO;CAehB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { executionContextOfJob, } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { JobLockUnavailableError } from './errors/job-lock-unavailable-error.js';
|
|
3
|
+
import { JobTimeoutError } from './errors/job-timeout-error.js';
|
|
4
|
+
/** Runs one occurrence: the lock of the occurrence, the execution scope, the timeout. */
|
|
5
|
+
export class JobRunner {
|
|
6
|
+
options;
|
|
7
|
+
recorder;
|
|
8
|
+
constructor(options, recorder) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.recorder = recorder;
|
|
11
|
+
}
|
|
12
|
+
/** Answers what became of the occurrence. Never rejects. */
|
|
13
|
+
async run(job, occurrence) {
|
|
14
|
+
try {
|
|
15
|
+
const held = await this.holds(job, occurrence);
|
|
16
|
+
return held ? await this.execute(job, occurrence) : this.recorder.notRun(job, occurrence, 'skipped-locked');
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return this.recorder.refused(job, occurrence, error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/*
|
|
23
|
+
* The lock is not released when the run ends: an instance whose timer fires a little later
|
|
24
|
+
* would take it and run the occurrence again. It is kept until this instance handles the next
|
|
25
|
+
* occurrence of the job, or until its time to live has passed.
|
|
26
|
+
*/
|
|
27
|
+
async holds(job, { scheduledAt, manual }) {
|
|
28
|
+
if (!job.policy.singleInstance || manual) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const { timeoutMs, jitterMs } = job.policy;
|
|
32
|
+
const ttlMs = (timeoutMs ?? this.options.lockTtlMs) + jitterMs + this.options.lateAfterMs;
|
|
33
|
+
try {
|
|
34
|
+
await job.held?.release();
|
|
35
|
+
job.held = await this.options.lock.acquire(`job:${job.name}:${scheduledAt.toISOString()}`, { ttlMs });
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new JobLockUnavailableError(job.name, { cause: error });
|
|
39
|
+
}
|
|
40
|
+
return job.held !== undefined;
|
|
41
|
+
}
|
|
42
|
+
async execute(job, occurrence) {
|
|
43
|
+
const started = { occurrence, startedAt: this.options.clock.now(), correlationId: this.options.ids.next() };
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
job.abort = (reason) => {
|
|
46
|
+
controller.abort(reason);
|
|
47
|
+
};
|
|
48
|
+
const disarm = this.armTimeout(job, controller);
|
|
49
|
+
try {
|
|
50
|
+
await unlessAborted(controller.signal, this.inScope(job, started, controller.signal));
|
|
51
|
+
return this.recorder.succeeded(job, started);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return this.recorder.failed(job, started, error);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
disarm();
|
|
58
|
+
job.abort = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
armTimeout(job, controller) {
|
|
62
|
+
const { timeoutMs } = job.policy;
|
|
63
|
+
if (timeoutMs === undefined) {
|
|
64
|
+
return () => undefined;
|
|
65
|
+
}
|
|
66
|
+
return this.options.timers.schedule(timeoutMs, () => {
|
|
67
|
+
controller.abort(new JobTimeoutError(job.name, timeoutMs));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
inScope(job, started, signal) {
|
|
71
|
+
const { occurrence, startedAt, correlationId } = started;
|
|
72
|
+
const { scheduledAt } = occurrence;
|
|
73
|
+
const { timeoutMs } = job.policy;
|
|
74
|
+
const context = executionContextOfJob(job.name, {
|
|
75
|
+
correlationId,
|
|
76
|
+
scheduledAt,
|
|
77
|
+
...(timeoutMs === undefined ? {} : { deadline: new Date(startedAt.getTime() + timeoutMs) }),
|
|
78
|
+
});
|
|
79
|
+
const execution = { scheduledAt, startedAt, attempt: 1, signal };
|
|
80
|
+
return this.options.scope.run(context, async () => job.definition.run(execution));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** A job that ignores its signal is left behind: the run ends when the signal is aborted. */
|
|
84
|
+
function unlessAborted(signal, work) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
signal.addEventListener('abort', () => {
|
|
87
|
+
reject(reasonOf(signal));
|
|
88
|
+
}, { once: true });
|
|
89
|
+
work.then(resolve, reject);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function reasonOf(signal) {
|
|
93
|
+
const reason = signal.reason;
|
|
94
|
+
return reason instanceof Error ? reason : new Error('The run was aborted.');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=job-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-runner.js","sourceRoot":"","sources":["../src/job-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** How the in-process scheduler sleeps. Injected so that a test moves time instead of waiting for it. */
|
|
2
|
+
export declare abstract class JobTimers {
|
|
3
|
+
static readonly tokenKey = "scheduling.JobTimers";
|
|
4
|
+
/** Calls `task` once `delayMs` have passed. The answer cancels it. */
|
|
5
|
+
abstract schedule(delayMs: number, task: () => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=job-timers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-timers.d.ts","sourceRoot":"","sources":["../src/job-timers.ts"],"names":[],"mappings":"AAAA,yGAAyG;AACzG,8BAAsB,SAAS;IAC7B,MAAM,CAAC,QAAQ,CAAC,QAAQ,0BAA0B;IAElD,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAClE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-timers.js","sourceRoot":"","sources":["../src/job-timers.ts"],"names":[],"mappings":"AAAA,yGAAyG;AACzG,MAAM,OAAgB,SAAS;IAC7B,MAAM,CAAU,QAAQ,GAAG,sBAAsB,CAAC;CAInD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-names.d.ts","sourceRoot":"","sources":["../src/metric-names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,6BAA6B,CAAC;AAEvD,eAAO,MAAM,kBAAkB,8BAA8B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-names.js","sourceRoot":"","sources":["../src/metric-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,0BAA0B,CAAC;AAEvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,2BAA2B,CAAC"}
|