@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.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as CronWatchOptions, a as CronWatch } from './client-
|
|
2
|
-
export { F as FetchHandler, H as HandlerFn, b as HandlerOptions, J as JobContext, c as JobFn, d as JobHandle, R as RecordRunOptions, e as Routes, f as RoutesOptions, g as RunHandle, h as RunOutcome, S as Source, i as SourceHost, j as StartOptions, k as consoleChannel, l as createRoutes, m as custom } from './client-
|
|
3
|
-
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-
|
|
4
|
-
export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach, C as ChannelContext, f as CheckResult, g as Condition, E as ExpectRule, J as JobDefinition, h as JobHealth, i as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as StoredJob, T as TriageContext, n as TriageFn } from './types-
|
|
1
|
+
import { C as CronWatchOptions, a as CronWatch } from './client-D35cfnNV.cjs';
|
|
2
|
+
export { F as FetchHandler, H as HandlerFn, b as HandlerOptions, J as JobContext, c as JobFn, d as JobHandle, R as RecordRunOptions, e as Routes, f as RoutesOptions, g as RunHandle, h as RunOutcome, S as Source, i as SourceHost, j as StartOptions, k as consoleChannel, l as createRoutes, m as custom } from './client-D35cfnNV.cjs';
|
|
3
|
+
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-Cz47Boks.cjs';
|
|
4
|
+
export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach, C as ChannelContext, f as CheckResult, g as Condition, E as ExpectRule, J as JobDefinition, h as JobHealth, i as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as StoredJob, T as TriageContext, n as TriageFn } from './types-Cz47Boks.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Keeps everything in process memory. The default when no store is given,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as CronWatchOptions, a as CronWatch } from './client-
|
|
2
|
-
export { F as FetchHandler, H as HandlerFn, b as HandlerOptions, J as JobContext, c as JobFn, d as JobHandle, R as RecordRunOptions, e as Routes, f as RoutesOptions, g as RunHandle, h as RunOutcome, S as Source, i as SourceHost, j as StartOptions, k as consoleChannel, l as createRoutes, m as custom } from './client-
|
|
3
|
-
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-
|
|
4
|
-
export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach, C as ChannelContext, f as CheckResult, g as Condition, E as ExpectRule, J as JobDefinition, h as JobHealth, i as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as StoredJob, T as TriageContext, n as TriageFn } from './types-
|
|
1
|
+
import { C as CronWatchOptions, a as CronWatch } from './client-CQm-8-mR.js';
|
|
2
|
+
export { F as FetchHandler, H as HandlerFn, b as HandlerOptions, J as JobContext, c as JobFn, d as JobHandle, R as RecordRunOptions, e as Routes, f as RoutesOptions, g as RunHandle, h as RunOutcome, S as Source, i as SourceHost, j as StartOptions, k as consoleChannel, l as createRoutes, m as custom } from './client-CQm-8-mR.js';
|
|
3
|
+
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-Cz47Boks.js';
|
|
4
|
+
export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach, C as ChannelContext, f as CheckResult, g as Condition, E as ExpectRule, J as JobDefinition, h as JobHealth, i as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as StoredJob, T as TriageContext, n as TriageFn } from './types-Cz47Boks.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Keeps everything in process memory. The default when no store is given,
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,14 @@ function formatRelative(at, now) {
|
|
|
65
65
|
if (abs < 5e3) return text;
|
|
66
66
|
return diff < 0 ? `${text} ago` : `in ${text}`;
|
|
67
67
|
}
|
|
68
|
+
var FIRST_DATE_MS = -621355968e5;
|
|
69
|
+
var LAST_DATE_MS = 253402300799999;
|
|
70
|
+
function isoTime(at) {
|
|
71
|
+
return at >= FIRST_DATE_MS && at <= LAST_DATE_MS ? new Date(at).toISOString() : null;
|
|
72
|
+
}
|
|
73
|
+
function beyondDates(at) {
|
|
74
|
+
return at > LAST_DATE_MS ? "after 9999-12-31 23:59:59 UTC" : "before 0001-01-01 00:00:00 UTC";
|
|
75
|
+
}
|
|
68
76
|
|
|
69
77
|
// src/env.ts
|
|
70
78
|
function readEnv(name) {
|
|
@@ -102,15 +110,36 @@ function parseSchedule(schedule, timezone) {
|
|
|
102
110
|
cache.set(key, parsed);
|
|
103
111
|
return parsed;
|
|
104
112
|
}
|
|
113
|
+
var CYCLE_MS = 146097 * 864e5;
|
|
114
|
+
var CRONER_FIRST_MS = Date.UTC(400, 0, 1);
|
|
115
|
+
var CRONER_LAST_MS = Date.UTC(2800, 0, 1);
|
|
116
|
+
function runsAfter(cron, n, from) {
|
|
117
|
+
let shift = 0;
|
|
118
|
+
if (from < CRONER_FIRST_MS) shift = Math.ceil((CRONER_FIRST_MS - from) / CYCLE_MS) * CYCLE_MS;
|
|
119
|
+
else if (from >= CRONER_LAST_MS) shift = -(Math.floor((from - CRONER_LAST_MS) / CYCLE_MS) + 1) * CYCLE_MS;
|
|
120
|
+
const out = [];
|
|
121
|
+
for (const date of cron.nextRuns(n, new Date(from + shift))) {
|
|
122
|
+
const t = date.getTime() - shift;
|
|
123
|
+
if (t > LAST_DATE_MS) break;
|
|
124
|
+
out.push(t);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
function countFrom(from) {
|
|
129
|
+
if (from >= LAST_DATE_MS) return null;
|
|
130
|
+
return from >= FIRST_DATE_MS ? from : FIRST_DATE_MS - 1;
|
|
131
|
+
}
|
|
105
132
|
function fireAfter(parsed, from) {
|
|
106
133
|
const cron = crons.get(parsed);
|
|
107
134
|
if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
|
|
108
|
-
|
|
135
|
+
const start = countFrom(from);
|
|
136
|
+
if (start === null) return null;
|
|
137
|
+
let probe = start;
|
|
109
138
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
110
|
-
const runs = cron
|
|
139
|
+
const runs = runsAfter(cron, 8, probe);
|
|
111
140
|
if (runs.length === 0) return null;
|
|
112
|
-
const found = runs.find((
|
|
113
|
-
if (found) return found
|
|
141
|
+
const found = runs.find((t) => t > start);
|
|
142
|
+
if (found !== void 0) return found;
|
|
114
143
|
probe += 36e5;
|
|
115
144
|
}
|
|
116
145
|
return null;
|
|
@@ -119,20 +148,21 @@ function firesBetween(parsed, from, to, limit) {
|
|
|
119
148
|
const cron = crons.get(parsed);
|
|
120
149
|
if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
|
|
121
150
|
const out = [];
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
const start = countFrom(from);
|
|
152
|
+
if (start === null) return out;
|
|
153
|
+
let probe = start;
|
|
154
|
+
let last = start;
|
|
124
155
|
for (let guard = 0; guard < 1e3; guard++) {
|
|
125
|
-
const batch = cron
|
|
156
|
+
const batch = runsAfter(cron, Math.min(limit + 1 - out.length, 24), probe);
|
|
126
157
|
if (batch.length === 0) return out;
|
|
127
|
-
for (const
|
|
128
|
-
const t = date.getTime();
|
|
158
|
+
for (const t of batch) {
|
|
129
159
|
if (t <= last) continue;
|
|
130
160
|
if (t > to) return out;
|
|
131
161
|
out.push(t);
|
|
132
162
|
last = t;
|
|
133
163
|
if (out.length > limit) return null;
|
|
134
164
|
}
|
|
135
|
-
const end = batch[batch.length - 1]
|
|
165
|
+
const end = batch[batch.length - 1];
|
|
136
166
|
probe = end > probe ? end : probe + 36e5;
|
|
137
167
|
}
|
|
138
168
|
return out;
|
|
@@ -168,6 +198,7 @@ function runCovers(startedAt, dueAt, followingAt = null) {
|
|
|
168
198
|
}
|
|
169
199
|
function inSpringForwardGap(parsed, startedAt, fireAt) {
|
|
170
200
|
const LOOKBACK = 3 * 36e5;
|
|
201
|
+
if (fireAt - LOOKBACK < FIRST_DATE_MS) return false;
|
|
171
202
|
const after2 = utcOffset(fireAt, parsed.timezone);
|
|
172
203
|
const before = utcOffset(fireAt - LOOKBACK, parsed.timezone);
|
|
173
204
|
const gap = after2 - before;
|
|
@@ -204,8 +235,10 @@ function utcOffset(at, timezone) {
|
|
|
204
235
|
for (const part of format.formatToParts(new Date(at))) {
|
|
205
236
|
if (part.type !== "literal") parts[part.type] = Number(part.value);
|
|
206
237
|
}
|
|
207
|
-
const wall =
|
|
208
|
-
|
|
238
|
+
const wall = /* @__PURE__ */ new Date(0);
|
|
239
|
+
wall.setUTCFullYear(parts.year, parts.month - 1, parts.day);
|
|
240
|
+
wall.setUTCHours(parts.hour, parts.minute, parts.second);
|
|
241
|
+
return wall.getTime() - Math.floor(at / 1e3) * 1e3;
|
|
209
242
|
}
|
|
210
243
|
|
|
211
244
|
// src/stats.ts
|
|
@@ -228,6 +261,20 @@ var DEFAULT_TIMEOUT_MS = 60 * 6e4;
|
|
|
228
261
|
var SLOW_FLOOR_MS = 1e4;
|
|
229
262
|
var BASELINE_MIN_RUNS = 5;
|
|
230
263
|
var BASELINE_WINDOW = 20;
|
|
264
|
+
var MAX_DURATION_MS = Number.MAX_SAFE_INTEGER;
|
|
265
|
+
function runDuration(startedAt, finishedAt) {
|
|
266
|
+
const ms = finishedAt - startedAt;
|
|
267
|
+
return ms > 0 ? Math.min(ms, MAX_DURATION_MS) : 0;
|
|
268
|
+
}
|
|
269
|
+
function stateVersion(state) {
|
|
270
|
+
const version = state?.version;
|
|
271
|
+
return typeof version === "number" && Number.isSafeInteger(version) && version >= 0 ? version + 0 : 0;
|
|
272
|
+
}
|
|
273
|
+
function failureCount(state) {
|
|
274
|
+
const count = state?.consecutiveFailures;
|
|
275
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count <= 0) return 0;
|
|
276
|
+
return Math.min(count, MAX_DURATION_MS);
|
|
277
|
+
}
|
|
231
278
|
function emptyState(job) {
|
|
232
279
|
return { job, open: {}, consecutiveFailures: 0, silencedUntil: null, lastAlertAt: null, pendingRecovery: [], undelivered: [] };
|
|
233
280
|
}
|
|
@@ -237,6 +284,7 @@ function normalizeState(state, job) {
|
|
|
237
284
|
...emptyState(job),
|
|
238
285
|
...state,
|
|
239
286
|
open: { ...state.open },
|
|
287
|
+
consecutiveFailures: failureCount(state),
|
|
240
288
|
pendingRecovery: [...state.pendingRecovery ?? []],
|
|
241
289
|
undelivered: [...state.undelivered ?? []]
|
|
242
290
|
};
|
|
@@ -335,7 +383,7 @@ function onRunFinish(def, run, state, history, now) {
|
|
|
335
383
|
}
|
|
336
384
|
return { state: next, alerts };
|
|
337
385
|
}
|
|
338
|
-
next.consecutiveFailures
|
|
386
|
+
next.consecutiveFailures = Math.min(next.consecutiveFailures + 1, MAX_DURATION_MS);
|
|
339
387
|
closeCondition(next, "missed");
|
|
340
388
|
const threshold = Math.max(1, def.failuresBeforeAlert ?? 1);
|
|
341
389
|
const condition = run.status === "timeout" ? "stuck" : "failed";
|
|
@@ -438,7 +486,9 @@ function summary(stored, recent, state, nextExpectedAt, health) {
|
|
|
438
486
|
// src/format.ts
|
|
439
487
|
function when(at, now) {
|
|
440
488
|
if (at === null || at === void 0) return "never";
|
|
441
|
-
|
|
489
|
+
const iso = isoTime(at);
|
|
490
|
+
if (iso === null) return beyondDates(at);
|
|
491
|
+
return `${iso.replace("T", " ").slice(0, 19)} UTC (${formatRelative(at, now)})`;
|
|
442
492
|
}
|
|
443
493
|
function firstLines(text, n) {
|
|
444
494
|
if (!text) return "";
|
|
@@ -547,6 +597,9 @@ function capOutput(text) {
|
|
|
547
597
|
function stripNul(text) {
|
|
548
598
|
return text.includes("\0") ? text.replace(/\u0000/g, "") : text;
|
|
549
599
|
}
|
|
600
|
+
function stripJsonNul(json2) {
|
|
601
|
+
return json2.includes("\\u0000") ? json2.replace(/\\(u0000|[\s\S])/g, (escape, next) => next === "u0000" ? "" : escape) : json2;
|
|
602
|
+
}
|
|
550
603
|
function errorMessage(error) {
|
|
551
604
|
return capOutput(describeError(error));
|
|
552
605
|
}
|
|
@@ -834,9 +887,13 @@ function dayLabel(t) {
|
|
|
834
887
|
return `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`;
|
|
835
888
|
}
|
|
836
889
|
function when2(t, now) {
|
|
890
|
+
if (t > LAST_DATE_MS) return "after 31 Dec 9999 23:59";
|
|
891
|
+
if (!(t >= FIRST_DATE_MS)) return "before 1 Jan 0001 00:00";
|
|
837
892
|
if (Math.floor(t / DAY) === Math.floor(now / DAY)) return clock(t);
|
|
838
893
|
const d = new Date(t);
|
|
839
|
-
|
|
894
|
+
const year = d.getUTCFullYear();
|
|
895
|
+
const other = year === new Date(now).getUTCFullYear() ? "" : ` ${String(year).padStart(4, "0")}`;
|
|
896
|
+
return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}${other} ${clock(t)}`;
|
|
840
897
|
}
|
|
841
898
|
function parsedSchedule(job) {
|
|
842
899
|
if (!job.definition.schedule) return null;
|
|
@@ -1327,7 +1384,8 @@ function sparkline(runs) {
|
|
|
1327
1384
|
}
|
|
1328
1385
|
function stamp(at, now) {
|
|
1329
1386
|
if (at === null) return `<span class="muted">never</span>`;
|
|
1330
|
-
const iso =
|
|
1387
|
+
const iso = isoTime(at);
|
|
1388
|
+
if (iso === null) return `<span class="nowrap">${beyondDates(at)}</span>`;
|
|
1331
1389
|
return `<time class="nowrap" datetime="${iso}" title="${iso.replace("T", " ").slice(0, 19)} UTC">${h(formatRelative(at, now))}</time>`;
|
|
1332
1390
|
}
|
|
1333
1391
|
function healthFigures(jobs) {
|
|
@@ -1794,13 +1852,21 @@ function memory() {
|
|
|
1794
1852
|
const states = /* @__PURE__ */ new Map();
|
|
1795
1853
|
let seq = 0;
|
|
1796
1854
|
const clone = (v) => JSON.parse(JSON.stringify(v));
|
|
1855
|
+
const kept = (v) => JSON.parse(stripJsonNul(JSON.stringify(v)));
|
|
1856
|
+
const keptRun = (run) => ({
|
|
1857
|
+
...clone(run),
|
|
1858
|
+
trigger: stripNul(run.trigger),
|
|
1859
|
+
output: run.output === null ? null : stripNul(run.output),
|
|
1860
|
+
error: run.error === null ? null : stripNul(run.error),
|
|
1861
|
+
metrics: kept(run.metrics)
|
|
1862
|
+
});
|
|
1797
1863
|
const byName = (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
1798
1864
|
return {
|
|
1799
1865
|
async upsertJob(definition, now) {
|
|
1800
1866
|
const existing = jobs.get(definition.name);
|
|
1801
1867
|
jobs.set(definition.name, {
|
|
1802
1868
|
name: definition.name,
|
|
1803
|
-
definition:
|
|
1869
|
+
definition: kept(definition),
|
|
1804
1870
|
createdAt: existing?.createdAt ?? now,
|
|
1805
1871
|
updatedAt: now
|
|
1806
1872
|
});
|
|
@@ -1822,19 +1888,19 @@ function memory() {
|
|
|
1822
1888
|
},
|
|
1823
1889
|
async insertRun(run) {
|
|
1824
1890
|
if (runs.has(run.id)) throw new Error(`run ${run.id} already exists`);
|
|
1825
|
-
runs.set(run.id,
|
|
1891
|
+
runs.set(run.id, keptRun(run));
|
|
1826
1892
|
order.set(run.id, ++seq);
|
|
1827
1893
|
},
|
|
1828
1894
|
async updateRun(run) {
|
|
1829
1895
|
const existing = runs.get(run.id);
|
|
1830
1896
|
if (!existing) return;
|
|
1831
|
-
const { status, finishedAt, durationMs, error, output, metrics } =
|
|
1897
|
+
const { status, finishedAt, durationMs, error, output, metrics } = keptRun(run);
|
|
1832
1898
|
runs.set(run.id, { ...existing, status, finishedAt, durationMs, error, output, metrics });
|
|
1833
1899
|
},
|
|
1834
1900
|
async updateRunIf(run, fromStatuses) {
|
|
1835
1901
|
const existing = runs.get(run.id);
|
|
1836
1902
|
if (!existing || !fromStatuses.includes(existing.status)) return false;
|
|
1837
|
-
const { status, finishedAt, durationMs, error, output, metrics } =
|
|
1903
|
+
const { status, finishedAt, durationMs, error, output, metrics } = keptRun(run);
|
|
1838
1904
|
runs.set(run.id, { ...existing, status, finishedAt, durationMs, error, output, metrics });
|
|
1839
1905
|
return true;
|
|
1840
1906
|
},
|
|
@@ -1857,11 +1923,11 @@ function memory() {
|
|
|
1857
1923
|
return s ? clone(s) : null;
|
|
1858
1924
|
},
|
|
1859
1925
|
async setState(state) {
|
|
1860
|
-
states.set(state.job,
|
|
1926
|
+
states.set(state.job, kept(state));
|
|
1861
1927
|
},
|
|
1862
1928
|
async compareAndSetState(state, expectedVersion) {
|
|
1863
|
-
if ((states.get(state.job)
|
|
1864
|
-
states.set(state.job,
|
|
1929
|
+
if (stateVersion(states.get(state.job)) !== expectedVersion) return false;
|
|
1930
|
+
states.set(state.job, kept(state));
|
|
1865
1931
|
return true;
|
|
1866
1932
|
},
|
|
1867
1933
|
async prune(before) {
|
|
@@ -1949,6 +2015,8 @@ var CronWatch = class {
|
|
|
1949
2015
|
synced = /* @__PURE__ */ new Set();
|
|
1950
2016
|
/** The tail of each job's queue of state updates. See serial(). */
|
|
1951
2017
|
queues = /* @__PURE__ */ new Map();
|
|
2018
|
+
/** The tail of each job's queue of declaration writes. See sync(). */
|
|
2019
|
+
syncing = /* @__PURE__ */ new Map();
|
|
1952
2020
|
/** start() calls with an id still in flight, so two at once in this process record one run. */
|
|
1953
2021
|
starting = /* @__PURE__ */ new Map();
|
|
1954
2022
|
ready = null;
|
|
@@ -2087,26 +2155,40 @@ var CronWatch = class {
|
|
|
2087
2155
|
}
|
|
2088
2156
|
await this.ready;
|
|
2089
2157
|
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Writes the declaration of `definition`'s name as it stands, unless the
|
|
2160
|
+
* store has it. A handle kept from an earlier declaration writes the one
|
|
2161
|
+
* that replaced it, never its own over it, and one forgotten since writes
|
|
2162
|
+
* its own. The writes of one name take turns, in the order they were asked
|
|
2163
|
+
* for, so one still under way cannot land after a later one; and a name
|
|
2164
|
+
* declared again while its write was under way is still to be written.
|
|
2165
|
+
*/
|
|
2090
2166
|
async sync(definition) {
|
|
2091
2167
|
await this.ensureReady();
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
this.
|
|
2168
|
+
const name = definition.name;
|
|
2169
|
+
if (this.synced.has(name)) return;
|
|
2170
|
+
await this.serial(name, async () => {
|
|
2171
|
+
if (this.synced.has(name)) return;
|
|
2172
|
+
const standing = this.definitions.get(name) ?? definition;
|
|
2173
|
+
await this.store.upsertJob(toStored(standing), this.now());
|
|
2174
|
+
if ((this.definitions.get(name) ?? standing) === standing) this.synced.add(name);
|
|
2175
|
+
}, this.syncing);
|
|
2095
2176
|
}
|
|
2096
2177
|
/**
|
|
2097
2178
|
* Runs `fn` after every earlier state update for the same job has settled,
|
|
2098
2179
|
* so two runs (or a run and a check) in this process never read and write
|
|
2099
2180
|
* the job's state over each other. Other processes are coordinated by
|
|
2100
|
-
* updateState() instead.
|
|
2181
|
+
* updateState() instead. sync() queues a job's declaration writes the same
|
|
2182
|
+
* way, in `syncing`.
|
|
2101
2183
|
*/
|
|
2102
|
-
serial(job, fn) {
|
|
2103
|
-
const result = (
|
|
2184
|
+
serial(job, fn, queues = this.queues) {
|
|
2185
|
+
const result = (queues.get(job) ?? Promise.resolve()).then(fn);
|
|
2104
2186
|
const tail2 = result.then(() => {
|
|
2105
2187
|
}, () => {
|
|
2106
2188
|
});
|
|
2107
|
-
|
|
2189
|
+
queues.set(job, tail2);
|
|
2108
2190
|
void tail2.then(() => {
|
|
2109
|
-
if (
|
|
2191
|
+
if (queues.get(job) === tail2) queues.delete(job);
|
|
2110
2192
|
});
|
|
2111
2193
|
return result;
|
|
2112
2194
|
}
|
|
@@ -2130,7 +2212,7 @@ var CronWatch = class {
|
|
|
2130
2212
|
const current = await this.readState(job);
|
|
2131
2213
|
const { state, result } = await change(current);
|
|
2132
2214
|
if (sameState(state, current)) return { state: current, result };
|
|
2133
|
-
const version = current
|
|
2215
|
+
const version = stateVersion(current);
|
|
2134
2216
|
const next = { ...state, version: version + 1 };
|
|
2135
2217
|
if (await this.writeState(next, version)) return { state: next, result };
|
|
2136
2218
|
if (attempt >= STATE_ATTEMPTS) {
|
|
@@ -2190,7 +2272,7 @@ var CronWatch = class {
|
|
|
2190
2272
|
}
|
|
2191
2273
|
const finishedAt = this.now();
|
|
2192
2274
|
run.finishedAt = finishedAt;
|
|
2193
|
-
run.durationMs =
|
|
2275
|
+
run.durationMs = runDuration(startedAt, finishedAt);
|
|
2194
2276
|
run.metrics = recorder.metrics();
|
|
2195
2277
|
run.output = recorder.output() ?? (typeof result === "string" ? capOutput(result) : null);
|
|
2196
2278
|
this.conclude(definition, run, result, error, threw, recorder.expectText() ?? (typeof result === "string" ? result : null));
|
|
@@ -2424,7 +2506,7 @@ ${id}`;
|
|
|
2424
2506
|
...from,
|
|
2425
2507
|
status: "running",
|
|
2426
2508
|
finishedAt,
|
|
2427
|
-
durationMs:
|
|
2509
|
+
durationMs: runDuration(from.startedAt, finishedAt),
|
|
2428
2510
|
error: null,
|
|
2429
2511
|
output: joinOutput(from.output, added),
|
|
2430
2512
|
metrics: { ...from.metrics, ...recorder.metrics() }
|
|
@@ -2506,6 +2588,7 @@ ${id}`;
|
|
|
2506
2588
|
async recordRun(input, options = {}) {
|
|
2507
2589
|
const declared = this.definitions.get(input.job);
|
|
2508
2590
|
if (!declared) throw new Error(`recordRun: job "${input.job}" is not declared; call job() first`);
|
|
2591
|
+
if (input.id.includes("\0")) throw new Error(`recordRun: run ids cannot contain a NUL character (job "${input.job}")`);
|
|
2509
2592
|
await this.sync(declared);
|
|
2510
2593
|
const run = { ...input, metrics: { ...input.metrics } };
|
|
2511
2594
|
if (run.status === "ok") {
|
|
@@ -2613,7 +2696,7 @@ ${id}`;
|
|
|
2613
2696
|
const timeout = timeoutMs(definition);
|
|
2614
2697
|
run.status = "timeout";
|
|
2615
2698
|
run.finishedAt = now;
|
|
2616
|
-
run.durationMs =
|
|
2699
|
+
run.durationMs = runDuration(run.startedAt, now);
|
|
2617
2700
|
run.error = `Still running after ${formatDuration(timeout)}; marked as timed out`;
|
|
2618
2701
|
if (!await this.writeRunIf(run, ["running"])) continue;
|
|
2619
2702
|
alerts.push(...await this.finishRun(definition, run, now));
|
|
@@ -2876,6 +2959,7 @@ function checkRunId(job, id, method) {
|
|
|
2876
2959
|
if (typeof id !== "string" || id.length === 0 || id.length > 200) {
|
|
2877
2960
|
throw new Error(`job "${job}": ${method}() needs a run id of 1 to 200 characters (got ${typeof id === "string" ? `${id.length} characters` : typeof id})`);
|
|
2878
2961
|
}
|
|
2962
|
+
if (id.includes("\0")) throw new Error(`job "${job}": ${method}() cannot take a run id containing a NUL character`);
|
|
2879
2963
|
if (id.startsWith(RESERVED_RUN_ID_PREFIX)) {
|
|
2880
2964
|
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`);
|
|
2881
2965
|
}
|