@cronwatch/sdk 0.12.4 → 0.13.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/README.md +3 -3
- package/dist/anthropic.cjs +2 -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 +2 -2
- package/dist/anthropic.js.map +1 -1
- package/dist/bugsnag.cjs +7 -0
- 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 +7 -0
- package/dist/bugsnag.js.map +1 -1
- package/dist/{client-BkPMRkDq.d.cts → client-DAJH7YiG.d.ts} +41 -20
- package/dist/{client-DySsV5T0.d.ts → client-z1LdR_m2.d.cts} +41 -20
- package/dist/d1.cjs +1 -1
- package/dist/d1.cjs.map +1 -1
- package/dist/d1.d.cts +4 -4
- package/dist/d1.d.ts +4 -4
- package/dist/d1.js +1 -1
- package/dist/d1.js.map +1 -1
- package/dist/datadog.cjs +7 -0
- package/dist/datadog.cjs.map +1 -1
- package/dist/datadog.d.cts +2 -2
- package/dist/datadog.d.ts +2 -2
- package/dist/datadog.js +7 -0
- package/dist/datadog.js.map +1 -1
- package/dist/discord.cjs +25 -1
- package/dist/discord.cjs.map +1 -1
- package/dist/discord.d.cts +5 -5
- package/dist/discord.d.ts +5 -5
- package/dist/discord.js +25 -1
- package/dist/discord.js.map +1 -1
- package/dist/{email-CD6NO8CV.d.cts → email-CCUzlLe9.d.cts} +3 -3
- package/dist/{email-Bx3ehK0O.d.ts → email-CFtDqlvZ.d.ts} +3 -3
- package/dist/honeybadger.cjs +7 -0
- 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 +7 -0
- package/dist/honeybadger.js.map +1 -1
- package/dist/index.cjs +206 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +206 -58
- package/dist/index.js.map +1 -1
- package/dist/mailgun.cjs +7 -0
- 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 +7 -0
- package/dist/mailgun.js.map +1 -1
- package/dist/newrelic.cjs +7 -0
- 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 +7 -0
- package/dist/newrelic.js.map +1 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js.map +1 -1
- package/dist/pg-cron.cjs.map +1 -1
- package/dist/pg-cron.d.cts +10 -10
- package/dist/pg-cron.d.ts +10 -10
- package/dist/pg-cron.js.map +1 -1
- package/dist/postgres.cjs +8 -3
- package/dist/postgres.cjs.map +1 -1
- package/dist/postgres.d.cts +3 -3
- package/dist/postgres.d.ts +3 -3
- package/dist/postgres.js +8 -3
- package/dist/postgres.js.map +1 -1
- package/dist/postmark.cjs +7 -0
- 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 +7 -0
- package/dist/postmark.js.map +1 -1
- package/dist/resend.cjs +7 -0
- 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 +7 -0
- package/dist/resend.js.map +1 -1
- package/dist/rollbar.cjs +7 -0
- 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 +7 -0
- package/dist/rollbar.js.map +1 -1
- package/dist/sendgrid.cjs +7 -0
- 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 +7 -0
- package/dist/sendgrid.js.map +1 -1
- package/dist/sentry.cjs +7 -0
- package/dist/sentry.cjs.map +1 -1
- package/dist/sentry.d.cts +2 -2
- package/dist/sentry.d.ts +2 -2
- package/dist/sentry.js +7 -0
- package/dist/sentry.js.map +1 -1
- package/dist/ses.cjs +7 -0
- 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 +7 -0
- package/dist/ses.js.map +1 -1
- package/dist/slack.cjs +32 -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 +32 -1
- package/dist/slack.js.map +1 -1
- package/dist/sqlite.cjs +1 -1
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +2 -2
- package/dist/sqlite.d.ts +2 -2
- package/dist/sqlite.js +1 -1
- package/dist/sqlite.js.map +1 -1
- package/dist/twilio.cjs +7 -0
- package/dist/twilio.cjs.map +1 -1
- package/dist/twilio.d.cts +5 -5
- package/dist/twilio.d.ts +5 -5
- package/dist/twilio.js +7 -0
- package/dist/twilio.js.map +1 -1
- package/dist/{types-Bm972Uqj.d.cts → types-D6t0Z846.d.cts} +15 -7
- package/dist/{types-Bm972Uqj.d.ts → types-D6t0Z846.d.ts} +15 -7
- package/dist/webhook.cjs +11 -1
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +4 -3
- package/dist/webhook.d.ts +4 -3
- package/dist/webhook.js +11 -1
- package/dist/webhook.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as CronwatchOptions, a as Cronwatch } from './client-
|
|
2
|
-
export { b as CronWatch, c as CronWatchOptions, F as FetchHandler, H as HandlerFn, d as HandlerOptions, J as JobContext, e as JobFn, f as JobHandle, R as RecordRunOptions, g as Routes, h as RoutesOptions, i as RunHandle, j as RunOutcome, S as Source, k as SourceHost, l as StartOptions, m as consoleChannel, n as createRoutes, o 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, h as JobDefinition, i as JobHealth, J as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as SendingAlert, n as StoredJob, T as TriageContext, o as TriageFn } from './types-
|
|
1
|
+
import { C as CronwatchOptions, a as Cronwatch } from './client-z1LdR_m2.cjs';
|
|
2
|
+
export { b as CronWatch, c as CronWatchOptions, F as FetchHandler, H as HandlerFn, d as HandlerOptions, J as JobContext, e as JobFn, f as JobHandle, R as RecordRunOptions, g as Routes, h as RoutesOptions, i as RunHandle, j as RunOutcome, S as Source, k as SourceHost, l as StartOptions, m as consoleChannel, n as createRoutes, o as custom } from './client-z1LdR_m2.cjs';
|
|
3
|
+
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-D6t0Z846.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, h as JobDefinition, i as JobHealth, J as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as SendingAlert, n as StoredJob, T as TriageContext, o as TriageFn } from './types-D6t0Z846.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Keeps everything in process memory. The default when no store is given,
|
|
@@ -11,7 +11,7 @@ export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach
|
|
|
11
11
|
declare function memory(): Store;
|
|
12
12
|
|
|
13
13
|
/** This release of @cronwatch/sdk. scripts/release.mjs keeps it in step with package.json. */
|
|
14
|
-
declare const VERSION = "0.
|
|
14
|
+
declare const VERSION = "0.13.0";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* "15m" -> 900000. Accepts a plain number of milliseconds, and compound
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as CronwatchOptions, a as Cronwatch } from './client-
|
|
2
|
-
export { b as CronWatch, c as CronWatchOptions, F as FetchHandler, H as HandlerFn, d as HandlerOptions, J as JobContext, e as JobFn, f as JobHandle, R as RecordRunOptions, g as Routes, h as RoutesOptions, i as RunHandle, j as RunOutcome, S as Source, k as SourceHost, l as StartOptions, m as consoleChannel, n as createRoutes, o 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, h as JobDefinition, i as JobHealth, J as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as SendingAlert, n as StoredJob, T as TriageContext, o as TriageFn } from './types-
|
|
1
|
+
import { C as CronwatchOptions, a as Cronwatch } from './client-DAJH7YiG.js';
|
|
2
|
+
export { b as CronWatch, c as CronWatchOptions, F as FetchHandler, H as HandlerFn, d as HandlerOptions, J as JobContext, e as JobFn, f as JobHandle, R as RecordRunOptions, g as Routes, h as RoutesOptions, i as RunHandle, j as RunOutcome, S as Source, k as SourceHost, l as StartOptions, m as consoleChannel, n as createRoutes, o as custom } from './client-DAJH7YiG.js';
|
|
3
|
+
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-D6t0Z846.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, h as JobDefinition, i as JobHealth, J as JobOptions, j as JobState, k as JobSummary, R as Run, l as RunStatus, m as SendingAlert, n as StoredJob, T as TriageContext, o as TriageFn } from './types-D6t0Z846.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Keeps everything in process memory. The default when no store is given,
|
|
@@ -11,7 +11,7 @@ export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach
|
|
|
11
11
|
declare function memory(): Store;
|
|
12
12
|
|
|
13
13
|
/** This release of @cronwatch/sdk. scripts/release.mjs keeps it in step with package.json. */
|
|
14
|
-
declare const VERSION = "0.
|
|
14
|
+
declare const VERSION = "0.13.0";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* "15m" -> 900000. Accepts a plain number of milliseconds, and compound
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ function parseDuration(value, label = "duration") {
|
|
|
28
28
|
consumed += match[0];
|
|
29
29
|
}
|
|
30
30
|
if (consumed.replace(/\s+/g, "") !== text.replace(/\s+/g, "")) {
|
|
31
|
-
throw new Error(`${label} "${value}" is not a duration like "15m", "1h30m" or "90s"`);
|
|
31
|
+
throw new Error(`${label} "${value}" is not a duration like "15m", "1h30m", or "90s"`);
|
|
32
32
|
}
|
|
33
33
|
return Math.round(total);
|
|
34
34
|
}
|
|
@@ -79,27 +79,31 @@ function readEnv(name) {
|
|
|
79
79
|
return typeof process === "undefined" || !process.env ? void 0 : process.env[name];
|
|
80
80
|
}
|
|
81
81
|
function readSecretEnv(name) {
|
|
82
|
-
const value = readEnv(name);
|
|
83
|
-
return value === void 0 || value
|
|
82
|
+
const value = readEnv(name)?.trim();
|
|
83
|
+
return value === void 0 || value === "" ? void 0 : value;
|
|
84
84
|
}
|
|
85
85
|
function secretOption(value, what) {
|
|
86
86
|
if (value === void 0 || value === null) return value;
|
|
87
87
|
if (typeof value !== "string") {
|
|
88
88
|
throw new TypeError(`${what} must be a string, or null to opt out, not ${Array.isArray(value) ? "an array" : typeof value}`);
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
const trimmed = value.trim();
|
|
91
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
91
92
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
for (const value of [process.env.CRONWATCH_ENV, process.env.APP_ENV, process.env.NODE_ENV]) {
|
|
93
|
+
function environmentOf(values) {
|
|
94
|
+
for (const value of values) {
|
|
95
95
|
const name = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
96
96
|
if (name === "") continue;
|
|
97
97
|
if (name === "prod") return "production";
|
|
98
|
-
if (name === "dev" || name === "local"
|
|
98
|
+
if (name === "dev" || name === "local") return "development";
|
|
99
99
|
return name;
|
|
100
100
|
}
|
|
101
101
|
return "";
|
|
102
102
|
}
|
|
103
|
+
function environment() {
|
|
104
|
+
if (typeof process === "undefined" || !process.env) return "";
|
|
105
|
+
return environmentOf([process.env.CRONWATCH_ENV, process.env.APP_ENV, process.env.NODE_ENV]);
|
|
106
|
+
}
|
|
103
107
|
function isDevelopment() {
|
|
104
108
|
return environment() === "development";
|
|
105
109
|
}
|
|
@@ -326,7 +330,7 @@ function isObject(value) {
|
|
|
326
330
|
}
|
|
327
331
|
function normalizeState(state, job) {
|
|
328
332
|
if (!isObject(state)) return emptyState(job);
|
|
329
|
-
const { sending, underFloor, ...rest } = state;
|
|
333
|
+
const { sending, underFloor, pendingRuns, ...rest } = state;
|
|
330
334
|
const open = isObject(state.open) ? Object.fromEntries(Object.entries(state.open).filter(([, at]) => typeof at === "number")) : {};
|
|
331
335
|
const number = (value) => typeof value === "number" ? value : null;
|
|
332
336
|
const list = (value) => Array.isArray(value) ? value : [];
|
|
@@ -340,7 +344,8 @@ function normalizeState(state, job) {
|
|
|
340
344
|
pendingRecovery: list(state.pendingRecovery).filter((c) => typeof c === "string"),
|
|
341
345
|
undelivered: list(state.undelivered).filter((alert) => isObject(alert)),
|
|
342
346
|
...Array.isArray(sending) && sending.length > 0 ? { sending: [...sending] } : {},
|
|
343
|
-
...Array.isArray(underFloor) && underFloor.some((m) => typeof m === "string") ? { underFloor: underFloor.filter((m) => typeof m === "string") } : {}
|
|
347
|
+
...Array.isArray(underFloor) && underFloor.some((m) => typeof m === "string") ? { underFloor: underFloor.filter((m) => typeof m === "string") } : {},
|
|
348
|
+
...Array.isArray(pendingRuns) && pendingRuns.some((id) => typeof id === "string") ? { pendingRuns: pendingRuns.filter((id) => typeof id === "string") } : {}
|
|
344
349
|
};
|
|
345
350
|
}
|
|
346
351
|
var MAX_UNDELIVERED = 20;
|
|
@@ -390,6 +395,34 @@ function recordSent(state, delivered, failed, stale, now) {
|
|
|
390
395
|
if (delivered.length > 0) next.lastAlertAt = now;
|
|
391
396
|
return queueUndelivered(next, failed);
|
|
392
397
|
}
|
|
398
|
+
function claimUndelivered(state, now, until) {
|
|
399
|
+
const next = cloneState(state);
|
|
400
|
+
const queue = next.undelivered;
|
|
401
|
+
const stale = queue.filter((alert) => staleAlert(alert, next));
|
|
402
|
+
const silenced = isSilenced(next, now);
|
|
403
|
+
const room = Math.max(0, MAX_UNDELIVERED - (next.sending ?? []).length);
|
|
404
|
+
const claimed = queue.filter((alert) => !stale.includes(alert) && (!silenced || alert.type === "recovered")).slice(0, room);
|
|
405
|
+
if (stale.length === 0 && claimed.length === 0) return { state, claimed, stale };
|
|
406
|
+
next.undelivered = queue.filter((alert) => !stale.includes(alert) && !claimed.includes(alert));
|
|
407
|
+
if (claimed.length > 0) next.sending = [...next.sending ?? [], ...claimed.map((alert) => ({ until, alert }))];
|
|
408
|
+
return { state: next, claimed, stale };
|
|
409
|
+
}
|
|
410
|
+
function holdRun(state, runId) {
|
|
411
|
+
const next = cloneState(state);
|
|
412
|
+
const held = (next.pendingRuns ?? []).filter((id) => id !== runId);
|
|
413
|
+
held.push(runId);
|
|
414
|
+
next.pendingRuns = held.slice(-100);
|
|
415
|
+
return next;
|
|
416
|
+
}
|
|
417
|
+
function releaseRun(state, runId) {
|
|
418
|
+
const held = state.pendingRuns ?? [];
|
|
419
|
+
if (!held.includes(runId)) return state;
|
|
420
|
+
const next = cloneState(state);
|
|
421
|
+
const left = held.filter((id) => id !== runId);
|
|
422
|
+
if (left.length > 0) next.pendingRuns = left;
|
|
423
|
+
else delete next.pendingRuns;
|
|
424
|
+
return next;
|
|
425
|
+
}
|
|
393
426
|
function cloneState(state) {
|
|
394
427
|
return normalizeState(state, state.job);
|
|
395
428
|
}
|
|
@@ -570,7 +603,7 @@ function isSilenced(state, now) {
|
|
|
570
603
|
}
|
|
571
604
|
function applySilence(previous, evaluation, now) {
|
|
572
605
|
if (!isSilenced(previous, now)) return evaluation;
|
|
573
|
-
return { state: muteOpens(previous, evaluation.state), alerts:
|
|
606
|
+
return { state: muteOpens(previous, evaluation.state), alerts: evaluation.alerts.filter((alert) => alert.type === "recovered") };
|
|
574
607
|
}
|
|
575
608
|
function staleAlert(alert, state) {
|
|
576
609
|
if (alert.type === "recovered") {
|
|
@@ -634,6 +667,10 @@ function tail(text, n) {
|
|
|
634
667
|
const lines = text.trimEnd().split("\n");
|
|
635
668
|
return lines.slice(Math.max(0, lines.length - n)).join("\n");
|
|
636
669
|
}
|
|
670
|
+
function andList(words2) {
|
|
671
|
+
if (words2.length <= 2) return words2.join(" and ");
|
|
672
|
+
return `${words2.slice(0, -1).join(", ")}, and ${words2[words2.length - 1]}`;
|
|
673
|
+
}
|
|
637
674
|
function errorLine(error) {
|
|
638
675
|
const text = firstLines(error, 4);
|
|
639
676
|
return /^[A-Za-z_$][\w$]*: /.test(text) ? text : `Error: ${text}`;
|
|
@@ -707,7 +744,7 @@ ${out}`);
|
|
|
707
744
|
break;
|
|
708
745
|
}
|
|
709
746
|
title = `${name} recovered`;
|
|
710
|
-
const after2 = draft.details.after.map((c) => c.replace("_", " "))
|
|
747
|
+
const after2 = andList(draft.details.after.map((c) => c.replace("_", " ")));
|
|
711
748
|
lines.push(`A run ${run ? when(run.startedAt, now) : "just now"} succeeded${after2 ? ` after: ${after2}` : ""}.`);
|
|
712
749
|
if (run?.durationMs !== null && run?.durationMs !== void 0) lines.push(`Ran ${formatDuration(run.durationMs)}.`);
|
|
713
750
|
break;
|
|
@@ -723,10 +760,10 @@ function json(body, status = 200, headers = {}) {
|
|
|
723
760
|
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", ...headers }
|
|
724
761
|
});
|
|
725
762
|
}
|
|
726
|
-
function constantTimeEqual(
|
|
727
|
-
if (
|
|
728
|
-
let diff =
|
|
729
|
-
for (let i = 0; i <
|
|
763
|
+
function constantTimeEqual(sent, secret) {
|
|
764
|
+
if (secret.length === 0) return sent.length === 0;
|
|
765
|
+
let diff = sent.length ^ secret.length;
|
|
766
|
+
for (let i = 0; i < sent.length; i++) diff |= sent.charCodeAt(i) ^ secret.charCodeAt(i % secret.length);
|
|
730
767
|
return diff === 0;
|
|
731
768
|
}
|
|
732
769
|
|
|
@@ -797,7 +834,7 @@ var SECRET_PATTERNS = [
|
|
|
797
834
|
// Incoming webhook URLs carry their secret in the path.
|
|
798
835
|
[/(\bhooks\.slack\.com\/(?:services|workflows|triggers)\/)[A-Za-z0-9/_-]{1,255}/gi, `$1${REDACTED}`],
|
|
799
836
|
[/(\bdiscord(?:app)?\.com\/api\/(?:v\d{1,2}\/)?webhooks\/)[A-Za-z0-9/_-]{1,255}/gi, `$1${REDACTED}`],
|
|
800
|
-
// Well-known token shapes: AWS, GitHub, Slack, Stripe, Anthropic, OpenAI and Google style keys.
|
|
837
|
+
// Well-known token shapes: AWS, GitHub, Slack, Stripe, Anthropic, OpenAI, and Google style keys.
|
|
801
838
|
[/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, REDACTED],
|
|
802
839
|
[/\b(?:gh[pousr]_[A-Za-z0-9]{30,255}|github_pat_[A-Za-z0-9_]{20,255})\b/g, REDACTED],
|
|
803
840
|
[/\bxox[abposr]-[A-Za-z0-9-]{10,255}/g, REDACTED],
|
|
@@ -935,7 +972,7 @@ function manifest(base) {
|
|
|
935
972
|
id: `${base}/`,
|
|
936
973
|
name: "CronWatch",
|
|
937
974
|
short_name: "CronWatch",
|
|
938
|
-
description: "The scheduled jobs of this app: their health, their last day and their runs.",
|
|
975
|
+
description: "The scheduled jobs of this app: their health, their last day, and their runs.",
|
|
939
976
|
start_url: `${base}/`,
|
|
940
977
|
scope: `${base}/`,
|
|
941
978
|
display: "standalone",
|
|
@@ -1039,7 +1076,7 @@ function staticAsset(path, base) {
|
|
|
1039
1076
|
}
|
|
1040
1077
|
|
|
1041
1078
|
// src/version.ts
|
|
1042
|
-
var VERSION = "0.
|
|
1079
|
+
var VERSION = "0.13.0";
|
|
1043
1080
|
var API_VERSION = 1;
|
|
1044
1081
|
|
|
1045
1082
|
// src/routes/timeline.ts
|
|
@@ -1155,11 +1192,11 @@ function laneNote(job, missed, now) {
|
|
|
1155
1192
|
if (job.open.includes("stuck")) return "stuck";
|
|
1156
1193
|
if (job.open.includes("over_budget") && last) {
|
|
1157
1194
|
const over = overCeilings(job);
|
|
1158
|
-
return `went over budget${over.length ? ` on ${over
|
|
1195
|
+
return `went over budget${over.length ? ` on ${andList(over)}` : ""} at ${when2(last.startedAt, now)}`;
|
|
1159
1196
|
}
|
|
1160
1197
|
if (job.open.includes("under_floor") && last) {
|
|
1161
1198
|
const under = underFloors(job);
|
|
1162
|
-
return `fell short${under.length ? ` on ${under
|
|
1199
|
+
return `fell short${under.length ? ` on ${andList(under)}` : ""} at ${when2(last.startedAt, now)}`;
|
|
1163
1200
|
}
|
|
1164
1201
|
if (job.open.includes("slow") && last?.durationMs != null) return `slow: took ${formatDuration(last.durationMs)}`;
|
|
1165
1202
|
if (job.open.includes("failed")) return "failing";
|
|
@@ -1277,7 +1314,7 @@ function legend() {
|
|
|
1277
1314
|
[box("run ok"), "ran"],
|
|
1278
1315
|
[box("run bad"), "failed"],
|
|
1279
1316
|
[box("run timeout"), "timed out"],
|
|
1280
|
-
[box("run warn"), "over budget, under floor or slow"],
|
|
1317
|
+
[box("run warn"), "over budget, under floor, or slow"],
|
|
1281
1318
|
[box("run running"), "running"],
|
|
1282
1319
|
[box("missed"), "missed"]
|
|
1283
1320
|
];
|
|
@@ -1826,23 +1863,63 @@ function stripBase(pathname, base) {
|
|
|
1826
1863
|
if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
|
|
1827
1864
|
return path;
|
|
1828
1865
|
}
|
|
1866
|
+
var MAX_REQUEST_BODY = 1 << 20;
|
|
1867
|
+
var TOO_LARGE = /* @__PURE__ */ Symbol("too large");
|
|
1868
|
+
async function readLimited(request) {
|
|
1869
|
+
const length = Number(request.headers.get("content-length"));
|
|
1870
|
+
if (Number.isFinite(length) && length > MAX_REQUEST_BODY) return TOO_LARGE;
|
|
1871
|
+
if (!request.body) return new Uint8Array(new ArrayBuffer(0));
|
|
1872
|
+
const reader = request.body.getReader();
|
|
1873
|
+
const chunks = [];
|
|
1874
|
+
let size = 0;
|
|
1875
|
+
try {
|
|
1876
|
+
for (; ; ) {
|
|
1877
|
+
const { done, value } = await reader.read();
|
|
1878
|
+
if (done) break;
|
|
1879
|
+
size += value.byteLength;
|
|
1880
|
+
if (size > MAX_REQUEST_BODY) {
|
|
1881
|
+
await reader.cancel().catch(() => {
|
|
1882
|
+
});
|
|
1883
|
+
return TOO_LARGE;
|
|
1884
|
+
}
|
|
1885
|
+
chunks.push(value);
|
|
1886
|
+
}
|
|
1887
|
+
} finally {
|
|
1888
|
+
reader.releaseLock();
|
|
1889
|
+
}
|
|
1890
|
+
const out = new Uint8Array(new ArrayBuffer(size));
|
|
1891
|
+
let at = 0;
|
|
1892
|
+
for (const chunk of chunks) {
|
|
1893
|
+
out.set(chunk, at);
|
|
1894
|
+
at += chunk.byteLength;
|
|
1895
|
+
}
|
|
1896
|
+
return out;
|
|
1897
|
+
}
|
|
1829
1898
|
async function readBody(request) {
|
|
1830
1899
|
const type = request.headers.get("content-type") ?? "";
|
|
1900
|
+
const json2 = type.includes("application/json");
|
|
1901
|
+
const form = type.includes("application/x-www-form-urlencoded") || type.includes("multipart/form-data");
|
|
1902
|
+
if (!json2 && !form) return {};
|
|
1903
|
+
let bytes;
|
|
1904
|
+
try {
|
|
1905
|
+
bytes = await readLimited(request);
|
|
1906
|
+
} catch {
|
|
1907
|
+
return {};
|
|
1908
|
+
}
|
|
1909
|
+
if (bytes === TOO_LARGE) return TOO_LARGE;
|
|
1910
|
+
const copy = new Request(request.url, { method: "POST", headers: { "content-type": type }, body: bytes });
|
|
1831
1911
|
try {
|
|
1832
|
-
if (
|
|
1833
|
-
const data = await
|
|
1912
|
+
if (json2) {
|
|
1913
|
+
const data = await copy.json();
|
|
1834
1914
|
return typeof data === "object" && data !== null ? Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])) : {};
|
|
1835
1915
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
return out;
|
|
1841
|
-
}
|
|
1916
|
+
const fields = await copy.formData();
|
|
1917
|
+
const out = {};
|
|
1918
|
+
for (const [k, v] of fields.entries()) out[k] = String(v);
|
|
1919
|
+
return out;
|
|
1842
1920
|
} catch {
|
|
1843
1921
|
return {};
|
|
1844
1922
|
}
|
|
1845
|
-
return {};
|
|
1846
1923
|
}
|
|
1847
1924
|
function crossSite(request, publicOrigin) {
|
|
1848
1925
|
const origin = request.headers.get("origin");
|
|
@@ -1906,7 +1983,8 @@ function buildRoutes(cw, options = {}) {
|
|
|
1906
1983
|
};
|
|
1907
1984
|
const signInPage = () => generated ? html(messagePage("Sign in", "CRONWATCH_TOKEN is not set, so this development server made a token. The sign-in link is in the server log: open it once, or enter the token from it below, and this browser stays signed in.", base, true), 401) : html(messagePage("Sign in", "Enter your CRONWATCH_TOKEN and this browser stays signed in.", base, true), 401);
|
|
1908
1985
|
if (token && method === "POST" && path === "/signin") {
|
|
1909
|
-
const
|
|
1986
|
+
const body = await readBody(request);
|
|
1987
|
+
const sent = body === TOO_LARGE ? void 0 : body.token;
|
|
1910
1988
|
if (sent === void 0 || !constantTimeEqual(sent, token)) return signInPage();
|
|
1911
1989
|
return redirect(signInReturn(request.headers.get("referer"), publicOrigin, base), { "set-cookie": await signInCookie() });
|
|
1912
1990
|
}
|
|
@@ -1922,7 +2000,7 @@ function buildRoutes(cw, options = {}) {
|
|
|
1922
2000
|
}
|
|
1923
2001
|
if (query !== null) {
|
|
1924
2002
|
url.searchParams.delete("token");
|
|
1925
|
-
return redirect(url.pathname + (url.search || ""), { "set-cookie": await signInCookie() });
|
|
2003
|
+
return redirect(url.pathname.replace(/^\/+/, "/") + (url.search || ""), { "set-cookie": await signInCookie() });
|
|
1926
2004
|
}
|
|
1927
2005
|
}
|
|
1928
2006
|
const redirectBack = () => {
|
|
@@ -1961,9 +2039,11 @@ function buildRoutes(cw, options = {}) {
|
|
|
1961
2039
|
if (parts[2] !== "silence" && parts[2] !== "unsilence") return html(messagePage("Not found", path, base), 404);
|
|
1962
2040
|
if (!await cw.jobSummary(name)) return html(messagePage("No such job", `${name} is not in the store.`, base), 404);
|
|
1963
2041
|
if (parts[2] === "silence") {
|
|
2042
|
+
const body = await readBody(request);
|
|
2043
|
+
if (body === TOO_LARGE) return html(messagePage("Not silenced", "The request was too large.", base), 413);
|
|
1964
2044
|
let duration;
|
|
1965
2045
|
try {
|
|
1966
|
-
duration = silenceDuration(
|
|
2046
|
+
duration = silenceDuration(body.for);
|
|
1967
2047
|
} catch (e) {
|
|
1968
2048
|
return html(messagePage("Not silenced", e.message, base), 400);
|
|
1969
2049
|
}
|
|
@@ -1997,6 +2077,7 @@ function buildRoutes(cw, options = {}) {
|
|
|
1997
2077
|
if (!await cw.jobSummary(name)) return api({ ok: false, error: "No such job" }, 404);
|
|
1998
2078
|
if (rest[2] === "silence") {
|
|
1999
2079
|
const body = await readBody(request);
|
|
2080
|
+
if (body === TOO_LARGE) return api({ ok: false, error: "Request body too large" }, 413);
|
|
2000
2081
|
let duration;
|
|
2001
2082
|
try {
|
|
2002
2083
|
duration = silenceDuration(body.for ?? url.searchParams.get("for"));
|
|
@@ -2223,7 +2304,7 @@ function validateDefinition(def) {
|
|
|
2223
2304
|
}
|
|
2224
2305
|
}
|
|
2225
2306
|
if (def.expect !== void 0 && typeof def.expect !== "string" && !(def.expect instanceof RegExp) && typeof def.expect !== "function") {
|
|
2226
|
-
throw new Error(`job "${name}": expect must be a string, a RegExp or a function`);
|
|
2307
|
+
throw new Error(`job "${name}": expect must be a string, a RegExp, or a function`);
|
|
2227
2308
|
}
|
|
2228
2309
|
}
|
|
2229
2310
|
function sameState(a, b) {
|
|
@@ -2246,6 +2327,11 @@ var Cronwatch = class {
|
|
|
2246
2327
|
deferDelivery;
|
|
2247
2328
|
defaults;
|
|
2248
2329
|
definitions = /* @__PURE__ */ new Map();
|
|
2330
|
+
/**
|
|
2331
|
+
* Declarations forgotten here (forget()), kept so that run() or resumeRun()
|
|
2332
|
+
* by name declares the job again as it was written, not bare.
|
|
2333
|
+
*/
|
|
2334
|
+
forgotten = /* @__PURE__ */ new Map();
|
|
2249
2335
|
synced = /* @__PURE__ */ new Set();
|
|
2250
2336
|
/** The tail of each job's queue of state updates. See serial(). */
|
|
2251
2337
|
queues = /* @__PURE__ */ new Map();
|
|
@@ -2295,18 +2381,31 @@ var Cronwatch = class {
|
|
|
2295
2381
|
/** Declare a job. Call it once, at module level, and keep the handle. */
|
|
2296
2382
|
job(name, options = {}) {
|
|
2297
2383
|
if (!NAME_RE.test(name)) {
|
|
2298
|
-
throw new Error(`job name "${name}" must be 1 to 120 characters of letters, digits, ".", "_", ":" or "-"`);
|
|
2384
|
+
throw new Error(`job name "${name}" must be 1 to 120 characters of letters, digits, ".", "_", ":", or "-"`);
|
|
2299
2385
|
}
|
|
2300
2386
|
const definition = { ...this.defaults, ...options, name };
|
|
2301
2387
|
validateDefinition(definition);
|
|
2302
2388
|
this.definitions.set(name, definition);
|
|
2389
|
+
this.forgotten.delete(name);
|
|
2303
2390
|
this.synced.delete(name);
|
|
2304
2391
|
return this.handle(definition);
|
|
2305
2392
|
}
|
|
2393
|
+
/** The declaration of `name`: the standing one, else one forgotten here, declared again as it was. */
|
|
2394
|
+
declared(name) {
|
|
2395
|
+
const standing = this.definitions.get(name);
|
|
2396
|
+
if (standing) return standing;
|
|
2397
|
+
const forgotten = this.forgotten.get(name);
|
|
2398
|
+
if (!forgotten) return void 0;
|
|
2399
|
+
this.forgotten.delete(name);
|
|
2400
|
+
this.definitions.set(name, forgotten);
|
|
2401
|
+
this.synced.delete(name);
|
|
2402
|
+
return forgotten;
|
|
2403
|
+
}
|
|
2306
2404
|
run(name, optionsOrFn, maybeFn) {
|
|
2307
2405
|
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
2308
2406
|
const options = typeof optionsOrFn === "function" ? void 0 : optionsOrFn;
|
|
2309
|
-
const
|
|
2407
|
+
const declared = options ? void 0 : this.declared(name);
|
|
2408
|
+
const handle = declared ? this.handle(declared) : this.job(name, options);
|
|
2310
2409
|
return handle.run(fn);
|
|
2311
2410
|
}
|
|
2312
2411
|
/** The definitions declared in this process. */
|
|
@@ -2315,7 +2414,7 @@ var Cronwatch = class {
|
|
|
2315
2414
|
}
|
|
2316
2415
|
/** A handle on a run started elsewhere, as job(name).resume(runId). The job must be declared in this process. */
|
|
2317
2416
|
resumeRun(name, runId) {
|
|
2318
|
-
const definition = this.
|
|
2417
|
+
const definition = this.declared(name);
|
|
2319
2418
|
if (!definition) return Promise.reject(new Error(`resumeRun: job "${name}" is not declared; call job() first`));
|
|
2320
2419
|
return this.resumeHandle(definition, runId);
|
|
2321
2420
|
}
|
|
@@ -2531,7 +2630,7 @@ var Cronwatch = class {
|
|
|
2531
2630
|
} catch (e) {
|
|
2532
2631
|
this.onError(e, `recording ${name}`);
|
|
2533
2632
|
}
|
|
2534
|
-
const started = recorded ? this.updateState(name, (before) => ({ state: onRunStart(before), result: void 0 })).then(() => {
|
|
2633
|
+
const started = recorded ? this.updateState(name, (before) => ({ state: holdRun(onRunStart(before), run.id), result: void 0 })).then(() => {
|
|
2535
2634
|
}, (e) => this.onError(e, `starting ${name}`)) : Promise.resolve();
|
|
2536
2635
|
const recorder = createRecorder(run);
|
|
2537
2636
|
const clearTimer = after(timeoutMs(definition), () => recorder.abort());
|
|
@@ -2687,7 +2786,7 @@ ${id}`;
|
|
|
2687
2786
|
this.onError(e, `recording ${name}`);
|
|
2688
2787
|
}
|
|
2689
2788
|
if (recorded) {
|
|
2690
|
-
await this.updateState(name, (before) => ({ state: onRunStart(before), result: void 0 })).catch((e) => this.onError(e, `starting ${name}`));
|
|
2789
|
+
await this.updateState(name, (before) => ({ state: holdRun(onRunStart(before), run.id), result: void 0 })).catch((e) => this.onError(e, `starting ${name}`));
|
|
2691
2790
|
}
|
|
2692
2791
|
return this.runHandle(definition, run.id, run, recorded, null);
|
|
2693
2792
|
}
|
|
@@ -2900,7 +2999,7 @@ ${id}`;
|
|
|
2900
2999
|
throw e;
|
|
2901
3000
|
}
|
|
2902
3001
|
if (!evaluate) return [];
|
|
2903
|
-
await this.updateState(run.job, (before) => ({ state: onRunStart(before), result: void 0 }));
|
|
3002
|
+
await this.updateState(run.job, (before) => ({ state: holdRun(onRunStart(before), run.id), result: void 0 }));
|
|
2904
3003
|
if (run.status === "running") return [];
|
|
2905
3004
|
return this.finishRun(definition, run, this.now());
|
|
2906
3005
|
}
|
|
@@ -2916,7 +3015,11 @@ ${id}`;
|
|
|
2916
3015
|
this.report(new Error(`run ${run.id} of ${run.job} ${claim.ignored}; ignored`), `recording ${run.job}`);
|
|
2917
3016
|
return [];
|
|
2918
3017
|
}
|
|
2919
|
-
if (!evaluate
|
|
3018
|
+
if (!evaluate) {
|
|
3019
|
+
await this.updateState(run.job, (current) => ({ state: releaseRun(current, run.id), result: void 0 })).catch((e) => this.onError(e, `recording ${run.job}`));
|
|
3020
|
+
return [];
|
|
3021
|
+
}
|
|
3022
|
+
if (claim.lateAfterTimeout && run.status !== "ok") return [];
|
|
2920
3023
|
return this.finishRun(definition, run, this.now());
|
|
2921
3024
|
}
|
|
2922
3025
|
/**
|
|
@@ -2930,7 +3033,8 @@ ${id}`;
|
|
|
2930
3033
|
let history = null;
|
|
2931
3034
|
({ result: held } = await this.updateState(run.job, async (previous) => {
|
|
2932
3035
|
history ??= await this.history(run);
|
|
2933
|
-
|
|
3036
|
+
const before = releaseRun(previous, run.id);
|
|
3037
|
+
return this.outbox(applySilence(before, onRunFinish(definition, run, before, history, now), now), definition, now);
|
|
2934
3038
|
}));
|
|
2935
3039
|
} catch (e) {
|
|
2936
3040
|
this.onError(e, `evaluating ${run.job}`);
|
|
@@ -3035,6 +3139,7 @@ ${id}`;
|
|
|
3035
3139
|
this.reportDropped(stored.name, held.dropped);
|
|
3036
3140
|
alerts.push(...await this.retryUndelivered(stored.name, state, now, retries));
|
|
3037
3141
|
alerts.push(...await this.dispatch(stored.name, held.alerts, now));
|
|
3142
|
+
alerts.push(...await this.evaluatePending(stored, recent, state, now));
|
|
3038
3143
|
jobs.push(summarize(stored, recent, state, nextExpectedAt, now));
|
|
3039
3144
|
} catch (e) {
|
|
3040
3145
|
this.onError(e, `checking ${stored.name}`);
|
|
@@ -3052,6 +3157,32 @@ ${id}`;
|
|
|
3052
3157
|
}
|
|
3053
3158
|
return { checkedAt: now, jobs, alerts, pruned };
|
|
3054
3159
|
}
|
|
3160
|
+
/**
|
|
3161
|
+
* Runs the job's state still holds as pending (holdRun) whose finish was
|
|
3162
|
+
* written but never evaluated: the process that finished them stopped, or
|
|
3163
|
+
* its state write failed, between the two writes. Each one finished more
|
|
3164
|
+
* than SEND_LEASE_MS ago is evaluated now, as its finish would have been;
|
|
3165
|
+
* one still running is left to the stuck check, and one the store no
|
|
3166
|
+
* longer has (given back, or pruned) is let go. The wait leaves a finish
|
|
3167
|
+
* under way in another process to write its own evaluation.
|
|
3168
|
+
*/
|
|
3169
|
+
async evaluatePending(stored, recent, state, now) {
|
|
3170
|
+
const alerts = [];
|
|
3171
|
+
for (const id of state.pendingRuns ?? []) {
|
|
3172
|
+
try {
|
|
3173
|
+
const run = recent.find((r) => r.id === id) ?? await this.store.getRun(id);
|
|
3174
|
+
if (!run || run.job !== stored.name) {
|
|
3175
|
+
await this.updateState(stored.name, (current) => ({ state: releaseRun(current, id), result: void 0 }));
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
if (run.status === "running" || typeof run.finishedAt !== "number" || run.finishedAt > now - SEND_LEASE_MS) continue;
|
|
3179
|
+
alerts.push(...await this.finishRun(stored.definition, run, now));
|
|
3180
|
+
} catch (e) {
|
|
3181
|
+
this.onError(e, `evaluating run ${id} of ${stored.name}`);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
return alerts;
|
|
3185
|
+
}
|
|
3055
3186
|
/** A job's summary and its newest runs, without alerting. A job that cannot be evaluated is reported and shown as failing. */
|
|
3056
3187
|
async snapshot(stored, now, runs) {
|
|
3057
3188
|
let recent = [];
|
|
@@ -3118,7 +3249,7 @@ ${id}`;
|
|
|
3118
3249
|
state.silencedUntil = null;
|
|
3119
3250
|
});
|
|
3120
3251
|
}
|
|
3121
|
-
/** Read, change and write one job's state, in turn with every other update to it. */
|
|
3252
|
+
/** Read, change, and write one job's state, in turn with every other update to it. */
|
|
3122
3253
|
async patchState(name, change) {
|
|
3123
3254
|
await this.ensureReady();
|
|
3124
3255
|
const { state } = await this.updateState(name, (current) => {
|
|
@@ -3130,11 +3261,15 @@ ${id}`;
|
|
|
3130
3261
|
}
|
|
3131
3262
|
/**
|
|
3132
3263
|
* Remove a job and its runs from the store. A job still declared in code
|
|
3133
|
-
* comes back: here on its next run,
|
|
3134
|
-
*
|
|
3264
|
+
* comes back, with no history: here on its next run, declared as it was
|
|
3265
|
+
* written (cw.run() and cw.resumeRun() by name included), and in any other
|
|
3266
|
+
* process that declares it on its next run there, or at that process's
|
|
3267
|
+
* next check or dashboard read.
|
|
3135
3268
|
*/
|
|
3136
3269
|
async forget(name) {
|
|
3137
3270
|
await this.ensureReady();
|
|
3271
|
+
const definition = this.definitions.get(name);
|
|
3272
|
+
if (definition) this.forgotten.set(name, definition);
|
|
3138
3273
|
this.definitions.delete(name);
|
|
3139
3274
|
this.synced.delete(name);
|
|
3140
3275
|
await this.store.deleteJob(name);
|
|
@@ -3187,7 +3322,7 @@ ${id}`;
|
|
|
3187
3322
|
}
|
|
3188
3323
|
/**
|
|
3189
3324
|
* Stop the interval, wait for a check already under way (bounded by its
|
|
3190
|
-
* own channel, triage and retry timeouts; what it throws was reported to
|
|
3325
|
+
* own channel, triage, and retry timeouts; what it throws was reported to
|
|
3191
3326
|
* whoever started it), then close the store, so that check neither writes
|
|
3192
3327
|
* after the store is closed nor loses the alerts it would queue.
|
|
3193
3328
|
*/
|
|
@@ -3219,27 +3354,40 @@ ${id}`;
|
|
|
3219
3354
|
}
|
|
3220
3355
|
/**
|
|
3221
3356
|
* Send the alerts that no channel accepted last time, once each, oldest
|
|
3222
|
-
* first.
|
|
3223
|
-
*
|
|
3224
|
-
*
|
|
3225
|
-
*
|
|
3357
|
+
* first. They are claimed first (claimUndelivered): moved into `sending`
|
|
3358
|
+
* with a lease, in one conditional write, so when several processes check
|
|
3359
|
+
* at once only the one whose write lands sends them. An alert that no
|
|
3360
|
+
* longer describes the job (staleAlert) is dropped instead. Retries across
|
|
3361
|
+
* a check share RETRY_BUDGET_MS of wall-clock time; claimed alerts it has
|
|
3362
|
+
* no time left for go back to the queue for the next check.
|
|
3226
3363
|
*/
|
|
3227
3364
|
async retryUndelivered(name, state, now, budget) {
|
|
3228
|
-
|
|
3229
|
-
|
|
3365
|
+
if ((state.undelivered ?? []).length === 0 || this.deferDelivery) return [];
|
|
3366
|
+
let claimed;
|
|
3367
|
+
try {
|
|
3368
|
+
({ result: claimed } = await this.updateState(name, (previous) => {
|
|
3369
|
+
const claim = claimUndelivered(previous, now, this.now() + SEND_LEASE_MS);
|
|
3370
|
+
return { state: claim.state, result: claim.claimed };
|
|
3371
|
+
}));
|
|
3372
|
+
} catch (e) {
|
|
3373
|
+
this.onError(e, `claiming undelivered alerts for ${name}`);
|
|
3374
|
+
return [];
|
|
3375
|
+
}
|
|
3376
|
+
if (claimed.length === 0) return [];
|
|
3230
3377
|
const delivered = [];
|
|
3231
3378
|
const failed = [];
|
|
3232
|
-
const
|
|
3233
|
-
for (const alert of pending) {
|
|
3234
|
-
if (dropped.includes(alert)) continue;
|
|
3379
|
+
for (const alert of claimed) {
|
|
3235
3380
|
const left = RETRY_BUDGET_MS - budget.spentMs;
|
|
3236
|
-
if (left <= 0)
|
|
3381
|
+
if (left <= 0) {
|
|
3382
|
+
failed.push(alert);
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3237
3385
|
const started = Date.now();
|
|
3238
3386
|
if (this.triage && alert.type !== "recovered" && alert.triage === void 0) await this.addTriage(alert, Math.min(TRIAGE_TIMEOUT_MS, left));
|
|
3239
3387
|
(await this.deliver(alert) ? delivered : failed).push(alert);
|
|
3240
3388
|
budget.spentMs += Math.max(0, Date.now() - started);
|
|
3241
3389
|
}
|
|
3242
|
-
await this.recordDelivery(name, delivered, failed,
|
|
3390
|
+
await this.recordDelivery(name, delivered, failed, [], now);
|
|
3243
3391
|
return delivered;
|
|
3244
3392
|
}
|
|
3245
3393
|
/**
|