@cronwatch/sdk 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anthropic.cjs +13 -2
- package/dist/anthropic.cjs.map +1 -1
- package/dist/anthropic.d.cts +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/anthropic.js +13 -2
- package/dist/anthropic.js.map +1 -1
- package/dist/bugsnag.cjs +8 -1
- package/dist/bugsnag.cjs.map +1 -1
- package/dist/bugsnag.d.cts +1 -1
- package/dist/bugsnag.d.ts +1 -1
- package/dist/bugsnag.js +8 -1
- package/dist/bugsnag.js.map +1 -1
- package/dist/{client-B5hM4l-D.d.ts → client-CQm-8-mR.d.ts} +13 -2
- package/dist/{client-Di5HAWQz.d.cts → client-D35cfnNV.d.cts} +13 -2
- package/dist/d1.cjs +33 -20
- package/dist/d1.cjs.map +1 -1
- package/dist/d1.d.cts +1 -1
- package/dist/d1.d.ts +1 -1
- package/dist/d1.js +33 -20
- package/dist/d1.js.map +1 -1
- package/dist/datadog.cjs.map +1 -1
- package/dist/datadog.d.cts +1 -1
- package/dist/datadog.d.ts +1 -1
- package/dist/datadog.js.map +1 -1
- package/dist/discord.cjs.map +1 -1
- package/dist/discord.d.cts +1 -1
- package/dist/discord.d.ts +1 -1
- package/dist/discord.js.map +1 -1
- package/dist/{email-Ciq7GRn4.d.ts → email-BxCp9RQV.d.ts} +1 -1
- package/dist/{email-DX6gkd5H.d.cts → email-D1d5niMy.d.cts} +1 -1
- package/dist/honeybadger.cjs +8 -1
- package/dist/honeybadger.cjs.map +1 -1
- package/dist/honeybadger.d.cts +1 -1
- package/dist/honeybadger.d.ts +1 -1
- package/dist/honeybadger.js +8 -1
- package/dist/honeybadger.js.map +1 -1
- package/dist/index.cjs +119 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +119 -35
- package/dist/index.js.map +1 -1
- package/dist/mailgun.cjs.map +1 -1
- package/dist/mailgun.d.cts +2 -2
- package/dist/mailgun.d.ts +2 -2
- package/dist/mailgun.js.map +1 -1
- package/dist/newrelic.cjs.map +1 -1
- package/dist/newrelic.d.cts +1 -1
- package/dist/newrelic.d.ts +1 -1
- package/dist/newrelic.js.map +1 -1
- package/dist/pg-cron.cjs +53 -5
- package/dist/pg-cron.cjs.map +1 -1
- package/dist/pg-cron.d.cts +5 -2
- package/dist/pg-cron.d.ts +5 -2
- package/dist/pg-cron.js +53 -5
- package/dist/pg-cron.js.map +1 -1
- package/dist/postgres.cjs +28 -15
- package/dist/postgres.cjs.map +1 -1
- package/dist/postgres.d.cts +1 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.js +28 -15
- package/dist/postgres.js.map +1 -1
- package/dist/postmark.cjs.map +1 -1
- package/dist/postmark.d.cts +2 -2
- package/dist/postmark.d.ts +2 -2
- package/dist/postmark.js.map +1 -1
- package/dist/resend.cjs.map +1 -1
- package/dist/resend.d.cts +2 -2
- package/dist/resend.d.ts +2 -2
- package/dist/resend.js.map +1 -1
- package/dist/rollbar.cjs +8 -1
- package/dist/rollbar.cjs.map +1 -1
- package/dist/rollbar.d.cts +1 -1
- package/dist/rollbar.d.ts +1 -1
- package/dist/rollbar.js +8 -1
- package/dist/rollbar.js.map +1 -1
- package/dist/sendgrid.cjs.map +1 -1
- package/dist/sendgrid.d.cts +2 -2
- package/dist/sendgrid.d.ts +2 -2
- package/dist/sendgrid.js.map +1 -1
- package/dist/sentry.cjs +8 -1
- package/dist/sentry.cjs.map +1 -1
- package/dist/sentry.d.cts +1 -1
- package/dist/sentry.d.ts +1 -1
- package/dist/sentry.js +8 -1
- package/dist/sentry.js.map +1 -1
- package/dist/ses.cjs.map +1 -1
- package/dist/ses.d.cts +2 -2
- package/dist/ses.d.ts +2 -2
- package/dist/ses.js.map +1 -1
- package/dist/slack.cjs.map +1 -1
- package/dist/slack.d.cts +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js.map +1 -1
- package/dist/sqlite.cjs +30 -19
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.js +30 -19
- package/dist/sqlite.js.map +1 -1
- package/dist/twilio.cjs.map +1 -1
- package/dist/twilio.d.cts +1 -1
- package/dist/twilio.d.ts +1 -1
- package/dist/twilio.js.map +1 -1
- package/dist/{types-BjBbCGeJ.d.cts → types-Cz47Boks.d.cts} +6 -0
- package/dist/{types-BjBbCGeJ.d.ts → types-Cz47Boks.d.ts} +6 -0
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +1 -1
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -67,6 +67,14 @@ function formatRelative(at, now) {
|
|
|
67
67
|
if (abs < 5e3) return text;
|
|
68
68
|
return diff < 0 ? `${text} ago` : `in ${text}`;
|
|
69
69
|
}
|
|
70
|
+
var FIRST_DATE_MS = -621355968e5;
|
|
71
|
+
var LAST_DATE_MS = 253402300799999;
|
|
72
|
+
function isoTime(at) {
|
|
73
|
+
return at >= FIRST_DATE_MS && at <= LAST_DATE_MS ? new Date(at).toISOString() : null;
|
|
74
|
+
}
|
|
75
|
+
function beyondDates(at) {
|
|
76
|
+
return at > LAST_DATE_MS ? "after 9999-12-31 23:59:59 UTC" : "before 0001-01-01 00:00:00 UTC";
|
|
77
|
+
}
|
|
70
78
|
|
|
71
79
|
// src/env.ts
|
|
72
80
|
function readEnv(name) {
|
|
@@ -104,15 +112,36 @@ function parseSchedule(schedule, timezone) {
|
|
|
104
112
|
cache.set(key, parsed);
|
|
105
113
|
return parsed;
|
|
106
114
|
}
|
|
115
|
+
var CYCLE_MS = 146097 * 864e5;
|
|
116
|
+
var CRONER_FIRST_MS = Date.UTC(400, 0, 1);
|
|
117
|
+
var CRONER_LAST_MS = Date.UTC(2800, 0, 1);
|
|
118
|
+
function runsAfter(cron, n, from) {
|
|
119
|
+
let shift = 0;
|
|
120
|
+
if (from < CRONER_FIRST_MS) shift = Math.ceil((CRONER_FIRST_MS - from) / CYCLE_MS) * CYCLE_MS;
|
|
121
|
+
else if (from >= CRONER_LAST_MS) shift = -(Math.floor((from - CRONER_LAST_MS) / CYCLE_MS) + 1) * CYCLE_MS;
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const date of cron.nextRuns(n, new Date(from + shift))) {
|
|
124
|
+
const t = date.getTime() - shift;
|
|
125
|
+
if (t > LAST_DATE_MS) break;
|
|
126
|
+
out.push(t);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function countFrom(from) {
|
|
131
|
+
if (from >= LAST_DATE_MS) return null;
|
|
132
|
+
return from >= FIRST_DATE_MS ? from : FIRST_DATE_MS - 1;
|
|
133
|
+
}
|
|
107
134
|
function fireAfter(parsed, from) {
|
|
108
135
|
const cron = crons.get(parsed);
|
|
109
136
|
if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
|
|
110
|
-
|
|
137
|
+
const start = countFrom(from);
|
|
138
|
+
if (start === null) return null;
|
|
139
|
+
let probe = start;
|
|
111
140
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
112
|
-
const runs = cron
|
|
141
|
+
const runs = runsAfter(cron, 8, probe);
|
|
113
142
|
if (runs.length === 0) return null;
|
|
114
|
-
const found = runs.find((
|
|
115
|
-
if (found) return found
|
|
143
|
+
const found = runs.find((t) => t > start);
|
|
144
|
+
if (found !== void 0) return found;
|
|
116
145
|
probe += 36e5;
|
|
117
146
|
}
|
|
118
147
|
return null;
|
|
@@ -121,20 +150,21 @@ function firesBetween(parsed, from, to, limit) {
|
|
|
121
150
|
const cron = crons.get(parsed);
|
|
122
151
|
if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
|
|
123
152
|
const out = [];
|
|
124
|
-
|
|
125
|
-
|
|
153
|
+
const start = countFrom(from);
|
|
154
|
+
if (start === null) return out;
|
|
155
|
+
let probe = start;
|
|
156
|
+
let last = start;
|
|
126
157
|
for (let guard = 0; guard < 1e3; guard++) {
|
|
127
|
-
const batch = cron
|
|
158
|
+
const batch = runsAfter(cron, Math.min(limit + 1 - out.length, 24), probe);
|
|
128
159
|
if (batch.length === 0) return out;
|
|
129
|
-
for (const
|
|
130
|
-
const t = date.getTime();
|
|
160
|
+
for (const t of batch) {
|
|
131
161
|
if (t <= last) continue;
|
|
132
162
|
if (t > to) return out;
|
|
133
163
|
out.push(t);
|
|
134
164
|
last = t;
|
|
135
165
|
if (out.length > limit) return null;
|
|
136
166
|
}
|
|
137
|
-
const end = batch[batch.length - 1]
|
|
167
|
+
const end = batch[batch.length - 1];
|
|
138
168
|
probe = end > probe ? end : probe + 36e5;
|
|
139
169
|
}
|
|
140
170
|
return out;
|
|
@@ -170,6 +200,7 @@ function runCovers(startedAt, dueAt, followingAt = null) {
|
|
|
170
200
|
}
|
|
171
201
|
function inSpringForwardGap(parsed, startedAt, fireAt) {
|
|
172
202
|
const LOOKBACK = 3 * 36e5;
|
|
203
|
+
if (fireAt - LOOKBACK < FIRST_DATE_MS) return false;
|
|
173
204
|
const after2 = utcOffset(fireAt, parsed.timezone);
|
|
174
205
|
const before = utcOffset(fireAt - LOOKBACK, parsed.timezone);
|
|
175
206
|
const gap = after2 - before;
|
|
@@ -206,8 +237,10 @@ function utcOffset(at, timezone) {
|
|
|
206
237
|
for (const part of format.formatToParts(new Date(at))) {
|
|
207
238
|
if (part.type !== "literal") parts[part.type] = Number(part.value);
|
|
208
239
|
}
|
|
209
|
-
const wall =
|
|
210
|
-
|
|
240
|
+
const wall = /* @__PURE__ */ new Date(0);
|
|
241
|
+
wall.setUTCFullYear(parts.year, parts.month - 1, parts.day);
|
|
242
|
+
wall.setUTCHours(parts.hour, parts.minute, parts.second);
|
|
243
|
+
return wall.getTime() - Math.floor(at / 1e3) * 1e3;
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
// src/stats.ts
|
|
@@ -230,6 +263,20 @@ var DEFAULT_TIMEOUT_MS = 60 * 6e4;
|
|
|
230
263
|
var SLOW_FLOOR_MS = 1e4;
|
|
231
264
|
var BASELINE_MIN_RUNS = 5;
|
|
232
265
|
var BASELINE_WINDOW = 20;
|
|
266
|
+
var MAX_DURATION_MS = Number.MAX_SAFE_INTEGER;
|
|
267
|
+
function runDuration(startedAt, finishedAt) {
|
|
268
|
+
const ms = finishedAt - startedAt;
|
|
269
|
+
return ms > 0 ? Math.min(ms, MAX_DURATION_MS) : 0;
|
|
270
|
+
}
|
|
271
|
+
function stateVersion(state) {
|
|
272
|
+
const version = state?.version;
|
|
273
|
+
return typeof version === "number" && Number.isSafeInteger(version) && version >= 0 ? version + 0 : 0;
|
|
274
|
+
}
|
|
275
|
+
function failureCount(state) {
|
|
276
|
+
const count = state?.consecutiveFailures;
|
|
277
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count <= 0) return 0;
|
|
278
|
+
return Math.min(count, MAX_DURATION_MS);
|
|
279
|
+
}
|
|
233
280
|
function emptyState(job) {
|
|
234
281
|
return { job, open: {}, consecutiveFailures: 0, silencedUntil: null, lastAlertAt: null, pendingRecovery: [], undelivered: [] };
|
|
235
282
|
}
|
|
@@ -239,6 +286,7 @@ function normalizeState(state, job) {
|
|
|
239
286
|
...emptyState(job),
|
|
240
287
|
...state,
|
|
241
288
|
open: { ...state.open },
|
|
289
|
+
consecutiveFailures: failureCount(state),
|
|
242
290
|
pendingRecovery: [...state.pendingRecovery ?? []],
|
|
243
291
|
undelivered: [...state.undelivered ?? []]
|
|
244
292
|
};
|
|
@@ -337,7 +385,7 @@ function onRunFinish(def, run, state, history, now) {
|
|
|
337
385
|
}
|
|
338
386
|
return { state: next, alerts };
|
|
339
387
|
}
|
|
340
|
-
next.consecutiveFailures
|
|
388
|
+
next.consecutiveFailures = Math.min(next.consecutiveFailures + 1, MAX_DURATION_MS);
|
|
341
389
|
closeCondition(next, "missed");
|
|
342
390
|
const threshold = Math.max(1, def.failuresBeforeAlert ?? 1);
|
|
343
391
|
const condition = run.status === "timeout" ? "stuck" : "failed";
|
|
@@ -440,7 +488,9 @@ function summary(stored, recent, state, nextExpectedAt, health) {
|
|
|
440
488
|
// src/format.ts
|
|
441
489
|
function when(at, now) {
|
|
442
490
|
if (at === null || at === void 0) return "never";
|
|
443
|
-
|
|
491
|
+
const iso = isoTime(at);
|
|
492
|
+
if (iso === null) return beyondDates(at);
|
|
493
|
+
return `${iso.replace("T", " ").slice(0, 19)} UTC (${formatRelative(at, now)})`;
|
|
444
494
|
}
|
|
445
495
|
function firstLines(text, n) {
|
|
446
496
|
if (!text) return "";
|
|
@@ -549,6 +599,9 @@ function capOutput(text) {
|
|
|
549
599
|
function stripNul(text) {
|
|
550
600
|
return text.includes("\0") ? text.replace(/\u0000/g, "") : text;
|
|
551
601
|
}
|
|
602
|
+
function stripJsonNul(json2) {
|
|
603
|
+
return json2.includes("\\u0000") ? json2.replace(/\\(u0000|[\s\S])/g, (escape, next) => next === "u0000" ? "" : escape) : json2;
|
|
604
|
+
}
|
|
552
605
|
function errorMessage(error) {
|
|
553
606
|
return capOutput(describeError(error));
|
|
554
607
|
}
|
|
@@ -836,9 +889,13 @@ function dayLabel(t) {
|
|
|
836
889
|
return `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`;
|
|
837
890
|
}
|
|
838
891
|
function when2(t, now) {
|
|
892
|
+
if (t > LAST_DATE_MS) return "after 31 Dec 9999 23:59";
|
|
893
|
+
if (!(t >= FIRST_DATE_MS)) return "before 1 Jan 0001 00:00";
|
|
839
894
|
if (Math.floor(t / DAY) === Math.floor(now / DAY)) return clock(t);
|
|
840
895
|
const d = new Date(t);
|
|
841
|
-
|
|
896
|
+
const year = d.getUTCFullYear();
|
|
897
|
+
const other = year === new Date(now).getUTCFullYear() ? "" : ` ${String(year).padStart(4, "0")}`;
|
|
898
|
+
return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}${other} ${clock(t)}`;
|
|
842
899
|
}
|
|
843
900
|
function parsedSchedule(job) {
|
|
844
901
|
if (!job.definition.schedule) return null;
|
|
@@ -1329,7 +1386,8 @@ function sparkline(runs) {
|
|
|
1329
1386
|
}
|
|
1330
1387
|
function stamp(at, now) {
|
|
1331
1388
|
if (at === null) return `<span class="muted">never</span>`;
|
|
1332
|
-
const iso =
|
|
1389
|
+
const iso = isoTime(at);
|
|
1390
|
+
if (iso === null) return `<span class="nowrap">${beyondDates(at)}</span>`;
|
|
1333
1391
|
return `<time class="nowrap" datetime="${iso}" title="${iso.replace("T", " ").slice(0, 19)} UTC">${h(formatRelative(at, now))}</time>`;
|
|
1334
1392
|
}
|
|
1335
1393
|
function healthFigures(jobs) {
|
|
@@ -1796,13 +1854,21 @@ function memory() {
|
|
|
1796
1854
|
const states = /* @__PURE__ */ new Map();
|
|
1797
1855
|
let seq = 0;
|
|
1798
1856
|
const clone = (v) => JSON.parse(JSON.stringify(v));
|
|
1857
|
+
const kept = (v) => JSON.parse(stripJsonNul(JSON.stringify(v)));
|
|
1858
|
+
const keptRun = (run) => ({
|
|
1859
|
+
...clone(run),
|
|
1860
|
+
trigger: stripNul(run.trigger),
|
|
1861
|
+
output: run.output === null ? null : stripNul(run.output),
|
|
1862
|
+
error: run.error === null ? null : stripNul(run.error),
|
|
1863
|
+
metrics: kept(run.metrics)
|
|
1864
|
+
});
|
|
1799
1865
|
const byName = (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
1800
1866
|
return {
|
|
1801
1867
|
async upsertJob(definition, now) {
|
|
1802
1868
|
const existing = jobs.get(definition.name);
|
|
1803
1869
|
jobs.set(definition.name, {
|
|
1804
1870
|
name: definition.name,
|
|
1805
|
-
definition:
|
|
1871
|
+
definition: kept(definition),
|
|
1806
1872
|
createdAt: existing?.createdAt ?? now,
|
|
1807
1873
|
updatedAt: now
|
|
1808
1874
|
});
|
|
@@ -1824,19 +1890,19 @@ function memory() {
|
|
|
1824
1890
|
},
|
|
1825
1891
|
async insertRun(run) {
|
|
1826
1892
|
if (runs.has(run.id)) throw new Error(`run ${run.id} already exists`);
|
|
1827
|
-
runs.set(run.id,
|
|
1893
|
+
runs.set(run.id, keptRun(run));
|
|
1828
1894
|
order.set(run.id, ++seq);
|
|
1829
1895
|
},
|
|
1830
1896
|
async updateRun(run) {
|
|
1831
1897
|
const existing = runs.get(run.id);
|
|
1832
1898
|
if (!existing) return;
|
|
1833
|
-
const { status, finishedAt, durationMs, error, output, metrics } =
|
|
1899
|
+
const { status, finishedAt, durationMs, error, output, metrics } = keptRun(run);
|
|
1834
1900
|
runs.set(run.id, { ...existing, status, finishedAt, durationMs, error, output, metrics });
|
|
1835
1901
|
},
|
|
1836
1902
|
async updateRunIf(run, fromStatuses) {
|
|
1837
1903
|
const existing = runs.get(run.id);
|
|
1838
1904
|
if (!existing || !fromStatuses.includes(existing.status)) return false;
|
|
1839
|
-
const { status, finishedAt, durationMs, error, output, metrics } =
|
|
1905
|
+
const { status, finishedAt, durationMs, error, output, metrics } = keptRun(run);
|
|
1840
1906
|
runs.set(run.id, { ...existing, status, finishedAt, durationMs, error, output, metrics });
|
|
1841
1907
|
return true;
|
|
1842
1908
|
},
|
|
@@ -1859,11 +1925,11 @@ function memory() {
|
|
|
1859
1925
|
return s ? clone(s) : null;
|
|
1860
1926
|
},
|
|
1861
1927
|
async setState(state) {
|
|
1862
|
-
states.set(state.job,
|
|
1928
|
+
states.set(state.job, kept(state));
|
|
1863
1929
|
},
|
|
1864
1930
|
async compareAndSetState(state, expectedVersion) {
|
|
1865
|
-
if ((states.get(state.job)
|
|
1866
|
-
states.set(state.job,
|
|
1931
|
+
if (stateVersion(states.get(state.job)) !== expectedVersion) return false;
|
|
1932
|
+
states.set(state.job, kept(state));
|
|
1867
1933
|
return true;
|
|
1868
1934
|
},
|
|
1869
1935
|
async prune(before) {
|
|
@@ -1951,6 +2017,8 @@ var CronWatch = class {
|
|
|
1951
2017
|
synced = /* @__PURE__ */ new Set();
|
|
1952
2018
|
/** The tail of each job's queue of state updates. See serial(). */
|
|
1953
2019
|
queues = /* @__PURE__ */ new Map();
|
|
2020
|
+
/** The tail of each job's queue of declaration writes. See sync(). */
|
|
2021
|
+
syncing = /* @__PURE__ */ new Map();
|
|
1954
2022
|
/** start() calls with an id still in flight, so two at once in this process record one run. */
|
|
1955
2023
|
starting = /* @__PURE__ */ new Map();
|
|
1956
2024
|
ready = null;
|
|
@@ -2089,26 +2157,40 @@ var CronWatch = class {
|
|
|
2089
2157
|
}
|
|
2090
2158
|
await this.ready;
|
|
2091
2159
|
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Writes the declaration of `definition`'s name as it stands, unless the
|
|
2162
|
+
* store has it. A handle kept from an earlier declaration writes the one
|
|
2163
|
+
* that replaced it, never its own over it, and one forgotten since writes
|
|
2164
|
+
* its own. The writes of one name take turns, in the order they were asked
|
|
2165
|
+
* for, so one still under way cannot land after a later one; and a name
|
|
2166
|
+
* declared again while its write was under way is still to be written.
|
|
2167
|
+
*/
|
|
2092
2168
|
async sync(definition) {
|
|
2093
2169
|
await this.ensureReady();
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
this.
|
|
2170
|
+
const name = definition.name;
|
|
2171
|
+
if (this.synced.has(name)) return;
|
|
2172
|
+
await this.serial(name, async () => {
|
|
2173
|
+
if (this.synced.has(name)) return;
|
|
2174
|
+
const standing = this.definitions.get(name) ?? definition;
|
|
2175
|
+
await this.store.upsertJob(toStored(standing), this.now());
|
|
2176
|
+
if ((this.definitions.get(name) ?? standing) === standing) this.synced.add(name);
|
|
2177
|
+
}, this.syncing);
|
|
2097
2178
|
}
|
|
2098
2179
|
/**
|
|
2099
2180
|
* Runs `fn` after every earlier state update for the same job has settled,
|
|
2100
2181
|
* so two runs (or a run and a check) in this process never read and write
|
|
2101
2182
|
* the job's state over each other. Other processes are coordinated by
|
|
2102
|
-
* updateState() instead.
|
|
2183
|
+
* updateState() instead. sync() queues a job's declaration writes the same
|
|
2184
|
+
* way, in `syncing`.
|
|
2103
2185
|
*/
|
|
2104
|
-
serial(job, fn) {
|
|
2105
|
-
const result = (
|
|
2186
|
+
serial(job, fn, queues = this.queues) {
|
|
2187
|
+
const result = (queues.get(job) ?? Promise.resolve()).then(fn);
|
|
2106
2188
|
const tail2 = result.then(() => {
|
|
2107
2189
|
}, () => {
|
|
2108
2190
|
});
|
|
2109
|
-
|
|
2191
|
+
queues.set(job, tail2);
|
|
2110
2192
|
void tail2.then(() => {
|
|
2111
|
-
if (
|
|
2193
|
+
if (queues.get(job) === tail2) queues.delete(job);
|
|
2112
2194
|
});
|
|
2113
2195
|
return result;
|
|
2114
2196
|
}
|
|
@@ -2132,7 +2214,7 @@ var CronWatch = class {
|
|
|
2132
2214
|
const current = await this.readState(job);
|
|
2133
2215
|
const { state, result } = await change(current);
|
|
2134
2216
|
if (sameState(state, current)) return { state: current, result };
|
|
2135
|
-
const version = current
|
|
2217
|
+
const version = stateVersion(current);
|
|
2136
2218
|
const next = { ...state, version: version + 1 };
|
|
2137
2219
|
if (await this.writeState(next, version)) return { state: next, result };
|
|
2138
2220
|
if (attempt >= STATE_ATTEMPTS) {
|
|
@@ -2192,7 +2274,7 @@ var CronWatch = class {
|
|
|
2192
2274
|
}
|
|
2193
2275
|
const finishedAt = this.now();
|
|
2194
2276
|
run.finishedAt = finishedAt;
|
|
2195
|
-
run.durationMs =
|
|
2277
|
+
run.durationMs = runDuration(startedAt, finishedAt);
|
|
2196
2278
|
run.metrics = recorder.metrics();
|
|
2197
2279
|
run.output = recorder.output() ?? (typeof result === "string" ? capOutput(result) : null);
|
|
2198
2280
|
this.conclude(definition, run, result, error, threw, recorder.expectText() ?? (typeof result === "string" ? result : null));
|
|
@@ -2426,7 +2508,7 @@ ${id}`;
|
|
|
2426
2508
|
...from,
|
|
2427
2509
|
status: "running",
|
|
2428
2510
|
finishedAt,
|
|
2429
|
-
durationMs:
|
|
2511
|
+
durationMs: runDuration(from.startedAt, finishedAt),
|
|
2430
2512
|
error: null,
|
|
2431
2513
|
output: joinOutput(from.output, added),
|
|
2432
2514
|
metrics: { ...from.metrics, ...recorder.metrics() }
|
|
@@ -2508,6 +2590,7 @@ ${id}`;
|
|
|
2508
2590
|
async recordRun(input, options = {}) {
|
|
2509
2591
|
const declared = this.definitions.get(input.job);
|
|
2510
2592
|
if (!declared) throw new Error(`recordRun: job "${input.job}" is not declared; call job() first`);
|
|
2593
|
+
if (input.id.includes("\0")) throw new Error(`recordRun: run ids cannot contain a NUL character (job "${input.job}")`);
|
|
2511
2594
|
await this.sync(declared);
|
|
2512
2595
|
const run = { ...input, metrics: { ...input.metrics } };
|
|
2513
2596
|
if (run.status === "ok") {
|
|
@@ -2615,7 +2698,7 @@ ${id}`;
|
|
|
2615
2698
|
const timeout = timeoutMs(definition);
|
|
2616
2699
|
run.status = "timeout";
|
|
2617
2700
|
run.finishedAt = now;
|
|
2618
|
-
run.durationMs =
|
|
2701
|
+
run.durationMs = runDuration(run.startedAt, now);
|
|
2619
2702
|
run.error = `Still running after ${formatDuration(timeout)}; marked as timed out`;
|
|
2620
2703
|
if (!await this.writeRunIf(run, ["running"])) continue;
|
|
2621
2704
|
alerts.push(...await this.finishRun(definition, run, now));
|
|
@@ -2878,6 +2961,7 @@ function checkRunId(job, id, method) {
|
|
|
2878
2961
|
if (typeof id !== "string" || id.length === 0 || id.length > 200) {
|
|
2879
2962
|
throw new Error(`job "${job}": ${method}() needs a run id of 1 to 200 characters (got ${typeof id === "string" ? `${id.length} characters` : typeof id})`);
|
|
2880
2963
|
}
|
|
2964
|
+
if (id.includes("\0")) throw new Error(`job "${job}": ${method}() cannot take a run id containing a NUL character`);
|
|
2881
2965
|
if (id.startsWith(RESERVED_RUN_ID_PREFIX)) {
|
|
2882
2966
|
throw new Error(`job "${job}": ${method}() cannot take a run id starting with "${RESERVED_RUN_ID_PREFIX}", which the pg_cron source uses for its runs`);
|
|
2883
2967
|
}
|