@cronwatch/sdk 0.12.5 → 0.13.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/dist/anthropic.d.cts +1 -1
- package/dist/anthropic.d.ts +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-CFJUFEOg.d.cts → client-DAJH7YiG.d.ts} +28 -7
- package/dist/{client-k5UVIH38.d.ts → client-z1LdR_m2.d.cts} +28 -7
- package/dist/d1.d.cts +1 -1
- package/dist/d1.d.ts +1 -1
- package/dist/datadog.cjs +7 -0
- 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 +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 +1 -1
- package/dist/discord.d.ts +1 -1
- package/dist/discord.js +25 -1
- package/dist/discord.js.map +1 -1
- package/dist/{email-U0hYRnPa.d.cts → email-CCUzlLe9.d.cts} +1 -1
- package/dist/{email-CPoxD8--.d.ts → email-CFtDqlvZ.d.ts} +1 -1
- 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 +191 -47
- 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 +191 -47
- 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/pg-cron.cjs.map +1 -1
- package/dist/pg-cron.d.cts +2 -2
- package/dist/pg-cron.d.ts +2 -2
- package/dist/pg-cron.js.map +1 -1
- package/dist/postgres.cjs +7 -2
- 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 +7 -2
- 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 +1 -1
- package/dist/sentry.d.ts +1 -1
- 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.d.cts +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/twilio.cjs +7 -0
- 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 +7 -0
- package/dist/twilio.js.map +1 -1
- package/dist/{types-V8SN54o9.d.cts → types-D6t0Z846.d.cts} +8 -0
- package/dist/{types-V8SN54o9.d.ts → types-D6t0Z846.d.ts} +8 -0
- package/dist/webhook.cjs +11 -1
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +3 -2
- package/dist/webhook.d.ts +3 -2
- package/dist/webhook.js +11 -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 { 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.1";
|
|
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.1";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* "15m" -> 900000. Accepts a plain number of milliseconds, and compound
|
package/dist/index.js
CHANGED
|
@@ -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") {
|
|
@@ -727,10 +760,10 @@ function json(body, status = 200, headers = {}) {
|
|
|
727
760
|
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", ...headers }
|
|
728
761
|
});
|
|
729
762
|
}
|
|
730
|
-
function constantTimeEqual(
|
|
731
|
-
if (
|
|
732
|
-
let diff =
|
|
733
|
-
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);
|
|
734
767
|
return diff === 0;
|
|
735
768
|
}
|
|
736
769
|
|
|
@@ -1043,7 +1076,7 @@ function staticAsset(path, base) {
|
|
|
1043
1076
|
}
|
|
1044
1077
|
|
|
1045
1078
|
// src/version.ts
|
|
1046
|
-
var VERSION = "0.
|
|
1079
|
+
var VERSION = "0.13.1";
|
|
1047
1080
|
var API_VERSION = 1;
|
|
1048
1081
|
|
|
1049
1082
|
// src/routes/timeline.ts
|
|
@@ -1830,23 +1863,63 @@ function stripBase(pathname, base) {
|
|
|
1830
1863
|
if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
|
|
1831
1864
|
return path;
|
|
1832
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
|
+
}
|
|
1833
1898
|
async function readBody(request) {
|
|
1834
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 });
|
|
1835
1911
|
try {
|
|
1836
|
-
if (
|
|
1837
|
-
const data = await
|
|
1912
|
+
if (json2) {
|
|
1913
|
+
const data = await copy.json();
|
|
1838
1914
|
return typeof data === "object" && data !== null ? Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])) : {};
|
|
1839
1915
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
return out;
|
|
1845
|
-
}
|
|
1916
|
+
const fields = await copy.formData();
|
|
1917
|
+
const out = {};
|
|
1918
|
+
for (const [k, v] of fields.entries()) out[k] = String(v);
|
|
1919
|
+
return out;
|
|
1846
1920
|
} catch {
|
|
1847
1921
|
return {};
|
|
1848
1922
|
}
|
|
1849
|
-
return {};
|
|
1850
1923
|
}
|
|
1851
1924
|
function crossSite(request, publicOrigin) {
|
|
1852
1925
|
const origin = request.headers.get("origin");
|
|
@@ -1910,7 +1983,8 @@ function buildRoutes(cw, options = {}) {
|
|
|
1910
1983
|
};
|
|
1911
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);
|
|
1912
1985
|
if (token && method === "POST" && path === "/signin") {
|
|
1913
|
-
const
|
|
1986
|
+
const body = await readBody(request);
|
|
1987
|
+
const sent = body === TOO_LARGE ? void 0 : body.token;
|
|
1914
1988
|
if (sent === void 0 || !constantTimeEqual(sent, token)) return signInPage();
|
|
1915
1989
|
return redirect(signInReturn(request.headers.get("referer"), publicOrigin, base), { "set-cookie": await signInCookie() });
|
|
1916
1990
|
}
|
|
@@ -1926,7 +2000,7 @@ function buildRoutes(cw, options = {}) {
|
|
|
1926
2000
|
}
|
|
1927
2001
|
if (query !== null) {
|
|
1928
2002
|
url.searchParams.delete("token");
|
|
1929
|
-
return redirect(url.pathname + (url.search || ""), { "set-cookie": await signInCookie() });
|
|
2003
|
+
return redirect(url.pathname.replace(/^\/+/, "/") + (url.search || ""), { "set-cookie": await signInCookie() });
|
|
1930
2004
|
}
|
|
1931
2005
|
}
|
|
1932
2006
|
const redirectBack = () => {
|
|
@@ -1965,9 +2039,11 @@ function buildRoutes(cw, options = {}) {
|
|
|
1965
2039
|
if (parts[2] !== "silence" && parts[2] !== "unsilence") return html(messagePage("Not found", path, base), 404);
|
|
1966
2040
|
if (!await cw.jobSummary(name)) return html(messagePage("No such job", `${name} is not in the store.`, base), 404);
|
|
1967
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);
|
|
1968
2044
|
let duration;
|
|
1969
2045
|
try {
|
|
1970
|
-
duration = silenceDuration(
|
|
2046
|
+
duration = silenceDuration(body.for);
|
|
1971
2047
|
} catch (e) {
|
|
1972
2048
|
return html(messagePage("Not silenced", e.message, base), 400);
|
|
1973
2049
|
}
|
|
@@ -2001,6 +2077,7 @@ function buildRoutes(cw, options = {}) {
|
|
|
2001
2077
|
if (!await cw.jobSummary(name)) return api({ ok: false, error: "No such job" }, 404);
|
|
2002
2078
|
if (rest[2] === "silence") {
|
|
2003
2079
|
const body = await readBody(request);
|
|
2080
|
+
if (body === TOO_LARGE) return api({ ok: false, error: "Request body too large" }, 413);
|
|
2004
2081
|
let duration;
|
|
2005
2082
|
try {
|
|
2006
2083
|
duration = silenceDuration(body.for ?? url.searchParams.get("for"));
|
|
@@ -2250,6 +2327,11 @@ var Cronwatch = class {
|
|
|
2250
2327
|
deferDelivery;
|
|
2251
2328
|
defaults;
|
|
2252
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();
|
|
2253
2335
|
synced = /* @__PURE__ */ new Set();
|
|
2254
2336
|
/** The tail of each job's queue of state updates. See serial(). */
|
|
2255
2337
|
queues = /* @__PURE__ */ new Map();
|
|
@@ -2304,13 +2386,26 @@ var Cronwatch = class {
|
|
|
2304
2386
|
const definition = { ...this.defaults, ...options, name };
|
|
2305
2387
|
validateDefinition(definition);
|
|
2306
2388
|
this.definitions.set(name, definition);
|
|
2389
|
+
this.forgotten.delete(name);
|
|
2307
2390
|
this.synced.delete(name);
|
|
2308
2391
|
return this.handle(definition);
|
|
2309
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
|
+
}
|
|
2310
2404
|
run(name, optionsOrFn, maybeFn) {
|
|
2311
2405
|
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
2312
2406
|
const options = typeof optionsOrFn === "function" ? void 0 : optionsOrFn;
|
|
2313
|
-
const
|
|
2407
|
+
const declared = options ? void 0 : this.declared(name);
|
|
2408
|
+
const handle = declared ? this.handle(declared) : this.job(name, options);
|
|
2314
2409
|
return handle.run(fn);
|
|
2315
2410
|
}
|
|
2316
2411
|
/** The definitions declared in this process. */
|
|
@@ -2319,7 +2414,7 @@ var Cronwatch = class {
|
|
|
2319
2414
|
}
|
|
2320
2415
|
/** A handle on a run started elsewhere, as job(name).resume(runId). The job must be declared in this process. */
|
|
2321
2416
|
resumeRun(name, runId) {
|
|
2322
|
-
const definition = this.
|
|
2417
|
+
const definition = this.declared(name);
|
|
2323
2418
|
if (!definition) return Promise.reject(new Error(`resumeRun: job "${name}" is not declared; call job() first`));
|
|
2324
2419
|
return this.resumeHandle(definition, runId);
|
|
2325
2420
|
}
|
|
@@ -2535,7 +2630,7 @@ var Cronwatch = class {
|
|
|
2535
2630
|
} catch (e) {
|
|
2536
2631
|
this.onError(e, `recording ${name}`);
|
|
2537
2632
|
}
|
|
2538
|
-
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(() => {
|
|
2539
2634
|
}, (e) => this.onError(e, `starting ${name}`)) : Promise.resolve();
|
|
2540
2635
|
const recorder = createRecorder(run);
|
|
2541
2636
|
const clearTimer = after(timeoutMs(definition), () => recorder.abort());
|
|
@@ -2691,7 +2786,7 @@ ${id}`;
|
|
|
2691
2786
|
this.onError(e, `recording ${name}`);
|
|
2692
2787
|
}
|
|
2693
2788
|
if (recorded) {
|
|
2694
|
-
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}`));
|
|
2695
2790
|
}
|
|
2696
2791
|
return this.runHandle(definition, run.id, run, recorded, null);
|
|
2697
2792
|
}
|
|
@@ -2904,7 +2999,7 @@ ${id}`;
|
|
|
2904
2999
|
throw e;
|
|
2905
3000
|
}
|
|
2906
3001
|
if (!evaluate) return [];
|
|
2907
|
-
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 }));
|
|
2908
3003
|
if (run.status === "running") return [];
|
|
2909
3004
|
return this.finishRun(definition, run, this.now());
|
|
2910
3005
|
}
|
|
@@ -2920,7 +3015,11 @@ ${id}`;
|
|
|
2920
3015
|
this.report(new Error(`run ${run.id} of ${run.job} ${claim.ignored}; ignored`), `recording ${run.job}`);
|
|
2921
3016
|
return [];
|
|
2922
3017
|
}
|
|
2923
|
-
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 [];
|
|
2924
3023
|
return this.finishRun(definition, run, this.now());
|
|
2925
3024
|
}
|
|
2926
3025
|
/**
|
|
@@ -2934,7 +3033,8 @@ ${id}`;
|
|
|
2934
3033
|
let history = null;
|
|
2935
3034
|
({ result: held } = await this.updateState(run.job, async (previous) => {
|
|
2936
3035
|
history ??= await this.history(run);
|
|
2937
|
-
|
|
3036
|
+
const before = releaseRun(previous, run.id);
|
|
3037
|
+
return this.outbox(applySilence(before, onRunFinish(definition, run, before, history, now), now), definition, now);
|
|
2938
3038
|
}));
|
|
2939
3039
|
} catch (e) {
|
|
2940
3040
|
this.onError(e, `evaluating ${run.job}`);
|
|
@@ -3039,6 +3139,7 @@ ${id}`;
|
|
|
3039
3139
|
this.reportDropped(stored.name, held.dropped);
|
|
3040
3140
|
alerts.push(...await this.retryUndelivered(stored.name, state, now, retries));
|
|
3041
3141
|
alerts.push(...await this.dispatch(stored.name, held.alerts, now));
|
|
3142
|
+
alerts.push(...await this.evaluatePending(stored, recent, state, now));
|
|
3042
3143
|
jobs.push(summarize(stored, recent, state, nextExpectedAt, now));
|
|
3043
3144
|
} catch (e) {
|
|
3044
3145
|
this.onError(e, `checking ${stored.name}`);
|
|
@@ -3056,6 +3157,32 @@ ${id}`;
|
|
|
3056
3157
|
}
|
|
3057
3158
|
return { checkedAt: now, jobs, alerts, pruned };
|
|
3058
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
|
+
}
|
|
3059
3186
|
/** A job's summary and its newest runs, without alerting. A job that cannot be evaluated is reported and shown as failing. */
|
|
3060
3187
|
async snapshot(stored, now, runs) {
|
|
3061
3188
|
let recent = [];
|
|
@@ -3134,11 +3261,15 @@ ${id}`;
|
|
|
3134
3261
|
}
|
|
3135
3262
|
/**
|
|
3136
3263
|
* Remove a job and its runs from the store. A job still declared in code
|
|
3137
|
-
* comes back: here on its next run,
|
|
3138
|
-
*
|
|
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.
|
|
3139
3268
|
*/
|
|
3140
3269
|
async forget(name) {
|
|
3141
3270
|
await this.ensureReady();
|
|
3271
|
+
const definition = this.definitions.get(name);
|
|
3272
|
+
if (definition) this.forgotten.set(name, definition);
|
|
3142
3273
|
this.definitions.delete(name);
|
|
3143
3274
|
this.synced.delete(name);
|
|
3144
3275
|
await this.store.deleteJob(name);
|
|
@@ -3223,27 +3354,40 @@ ${id}`;
|
|
|
3223
3354
|
}
|
|
3224
3355
|
/**
|
|
3225
3356
|
* Send the alerts that no channel accepted last time, once each, oldest
|
|
3226
|
-
* first.
|
|
3227
|
-
*
|
|
3228
|
-
*
|
|
3229
|
-
*
|
|
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.
|
|
3230
3363
|
*/
|
|
3231
3364
|
async retryUndelivered(name, state, now, budget) {
|
|
3232
|
-
|
|
3233
|
-
|
|
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 [];
|
|
3234
3377
|
const delivered = [];
|
|
3235
3378
|
const failed = [];
|
|
3236
|
-
const
|
|
3237
|
-
for (const alert of pending) {
|
|
3238
|
-
if (dropped.includes(alert)) continue;
|
|
3379
|
+
for (const alert of claimed) {
|
|
3239
3380
|
const left = RETRY_BUDGET_MS - budget.spentMs;
|
|
3240
|
-
if (left <= 0)
|
|
3381
|
+
if (left <= 0) {
|
|
3382
|
+
failed.push(alert);
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3241
3385
|
const started = Date.now();
|
|
3242
3386
|
if (this.triage && alert.type !== "recovered" && alert.triage === void 0) await this.addTriage(alert, Math.min(TRIAGE_TIMEOUT_MS, left));
|
|
3243
3387
|
(await this.deliver(alert) ? delivered : failed).push(alert);
|
|
3244
3388
|
budget.spentMs += Math.max(0, Date.now() - started);
|
|
3245
3389
|
}
|
|
3246
|
-
await this.recordDelivery(name, delivered, failed,
|
|
3390
|
+
await this.recordDelivery(name, delivered, failed, [], now);
|
|
3247
3391
|
return delivered;
|
|
3248
3392
|
}
|
|
3249
3393
|
/**
|