@cronwatch/sdk 0.11.0 → 0.12.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 +4 -1
- package/dist/anthropic.d.cts +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/bugsnag.cjs +1 -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 +1 -1
- package/dist/bugsnag.js.map +1 -1
- package/dist/{client-CRMfhylq.d.cts → client-BkPMRkDq.d.cts} +1 -1
- package/dist/{client-D2Xk2600.d.ts → client-DySsV5T0.d.ts} +1 -1
- package/dist/d1.d.cts +1 -1
- package/dist/d1.d.ts +1 -1
- package/dist/datadog.cjs +1 -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 +1 -0
- package/dist/datadog.js.map +1 -1
- package/dist/discord.cjs +1 -0
- 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 +1 -0
- package/dist/discord.js.map +1 -1
- package/dist/{email-BWuUiCkw.d.ts → email-Bx3ehK0O.d.ts} +1 -1
- package/dist/{email-Dcqe3k8n.d.cts → email-CD6NO8CV.d.cts} +1 -1
- package/dist/honeybadger.cjs +1 -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 +1 -0
- package/dist/honeybadger.js.map +1 -1
- package/dist/index.cjs +83 -7
- 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 +83 -7
- 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 +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 +1 -1
- package/dist/newrelic.js.map +1 -1
- package/dist/pg-cron.cjs +2 -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 +2 -1
- package/dist/pg-cron.js.map +1 -1
- package/dist/postgres.d.cts +1 -1
- package/dist/postgres.d.ts +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 +1 -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 +1 -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 +1 -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 +1 -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 +1 -0
- 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 +1 -0
- 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.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-DLnlUie8.d.cts → types-Bm972Uqj.d.cts} +19 -1
- package/dist/{types-DLnlUie8.d.ts → types-Bm972Uqj.d.ts} +19 -1
- 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 { 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-BkPMRkDq.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-BkPMRkDq.cjs';
|
|
3
|
+
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-Bm972Uqj.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-Bm972Uqj.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.12.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-DySsV5T0.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-DySsV5T0.js';
|
|
3
|
+
import { S as Store, D as Duration, b as AlertDraft, c as StoredJobDefinition, A as Alert } from './types-Bm972Uqj.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-Bm972Uqj.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.12.0";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* "15m" -> 900000. Accepts a plain number of milliseconds, and compound
|
package/dist/index.js
CHANGED
|
@@ -326,7 +326,7 @@ function isObject(value) {
|
|
|
326
326
|
}
|
|
327
327
|
function normalizeState(state, job) {
|
|
328
328
|
if (!isObject(state)) return emptyState(job);
|
|
329
|
-
const { sending, ...rest } = state;
|
|
329
|
+
const { sending, underFloor, ...rest } = state;
|
|
330
330
|
const open = isObject(state.open) ? Object.fromEntries(Object.entries(state.open).filter(([, at]) => typeof at === "number")) : {};
|
|
331
331
|
const number = (value) => typeof value === "number" ? value : null;
|
|
332
332
|
const list = (value) => Array.isArray(value) ? value : [];
|
|
@@ -339,7 +339,8 @@ function normalizeState(state, job) {
|
|
|
339
339
|
lastAlertAt: number(state.lastAlertAt),
|
|
340
340
|
pendingRecovery: list(state.pendingRecovery).filter((c) => typeof c === "string"),
|
|
341
341
|
undelivered: list(state.undelivered).filter((alert) => isObject(alert)),
|
|
342
|
-
...Array.isArray(sending) && sending.length > 0 ? { sending: [...sending] } : {}
|
|
342
|
+
...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") } : {}
|
|
343
344
|
};
|
|
344
345
|
}
|
|
345
346
|
var MAX_UNDELIVERED = 20;
|
|
@@ -439,6 +440,26 @@ function budgetBreaches(def, run, history) {
|
|
|
439
440
|
}
|
|
440
441
|
return breaches;
|
|
441
442
|
}
|
|
443
|
+
function floorBreaches(def, run, history, previous = []) {
|
|
444
|
+
const breaches = [];
|
|
445
|
+
for (const [metric, value] of Object.entries(run.metrics)) {
|
|
446
|
+
const floor = def.floor?.[metric];
|
|
447
|
+
if (floor !== void 0) {
|
|
448
|
+
if (value < floor) breaches.push({ metric, value, limit: floor, basis: "floor" });
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (value > 0) continue;
|
|
452
|
+
if (previous.includes(metric)) {
|
|
453
|
+
breaches.push({ metric, value, limit: 0, basis: "0 or less on the run before too" });
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const past = history.filter((r) => r.status === "ok" && typeof r.metrics[metric] === "number").slice(0, BASELINE_WINDOW).map((r) => r.metrics[metric]);
|
|
457
|
+
if (past.length < BASELINE_MIN_RUNS || !past.every((v) => v > 0)) continue;
|
|
458
|
+
const lowest = Math.min(...past);
|
|
459
|
+
breaches.push({ metric, value, limit: lowest, basis: `the last ${past.length} runs all reported more than 0, the lowest ${formatNumber(lowest)}` });
|
|
460
|
+
}
|
|
461
|
+
return breaches;
|
|
462
|
+
}
|
|
442
463
|
function hasFullBaseline(history) {
|
|
443
464
|
return history.filter((r) => r.status === "ok").length >= BASELINE_WINDOW;
|
|
444
465
|
}
|
|
@@ -476,6 +497,16 @@ function onRunFinish(def, run, state, history, now) {
|
|
|
476
497
|
} else {
|
|
477
498
|
closeCondition(next, "over_budget");
|
|
478
499
|
}
|
|
500
|
+
const short = floorBreaches(def, run, history, next.underFloor);
|
|
501
|
+
if (short.length > 0) {
|
|
502
|
+
next.underFloor = short.map((b) => b.metric);
|
|
503
|
+
if (openCondition(next, "under_floor", now)) {
|
|
504
|
+
alerts.push({ type: "under_floor", run, details: { breaches: short } });
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
delete next.underFloor;
|
|
508
|
+
closeCondition(next, "under_floor");
|
|
509
|
+
}
|
|
479
510
|
const pending = next.pendingRecovery ?? [];
|
|
480
511
|
if (pending.length > 0 && openConditions(next).length === 0) {
|
|
481
512
|
alerts.push({ type: "recovered", run, details: { after: [...pending] } });
|
|
@@ -660,6 +691,14 @@ ${out}`);
|
|
|
660
691
|
if (run) lines.push(`Started ${when(run.startedAt, now)}.`);
|
|
661
692
|
break;
|
|
662
693
|
}
|
|
694
|
+
case "under_floor": {
|
|
695
|
+
title = `${name} fell short`;
|
|
696
|
+
for (const b of draft.details.breaches) {
|
|
697
|
+
lines.push(b.basis === "floor" ? `${b.metric}: ${formatNumber(b.value)}, below the floor of ${formatNumber(b.limit)}.` : `${b.metric}: ${formatNumber(b.value)} (${b.basis}).`);
|
|
698
|
+
}
|
|
699
|
+
if (run) lines.push(`Started ${when(run.startedAt, now)}.`);
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
663
702
|
case "recovered": {
|
|
664
703
|
if (draft.details.reason === "unscheduled") {
|
|
665
704
|
title = `${name} is no longer scheduled`;
|
|
@@ -912,6 +951,21 @@ function manifest(base) {
|
|
|
912
951
|
});
|
|
913
952
|
}
|
|
914
953
|
var APP_JS = `"use strict";
|
|
954
|
+
(function () {
|
|
955
|
+
var root = document.documentElement;
|
|
956
|
+
try {
|
|
957
|
+
var stored = localStorage.getItem("cronwatch-theme");
|
|
958
|
+
if (stored === "light" || stored === "dark") root.setAttribute("data-theme", stored);
|
|
959
|
+
} catch (e) {}
|
|
960
|
+
document.addEventListener("keydown", function (event) {
|
|
961
|
+
if (!(event.metaKey || event.ctrlKey) || !event.shiftKey || event.altKey || event.code !== "KeyD") return;
|
|
962
|
+
event.preventDefault();
|
|
963
|
+
var shown = root.getAttribute("data-theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
964
|
+
var next = shown === "dark" ? "light" : "dark";
|
|
965
|
+
root.setAttribute("data-theme", next);
|
|
966
|
+
try { localStorage.setItem("cronwatch-theme", next); } catch (e) {}
|
|
967
|
+
});
|
|
968
|
+
})();
|
|
915
969
|
(function () {
|
|
916
970
|
var script = document.currentScript;
|
|
917
971
|
if (!script || !("serviceWorker" in navigator)) return;
|
|
@@ -1063,7 +1117,7 @@ function toneOf(run, job, now) {
|
|
|
1063
1117
|
if (run.status === "failed") return "bad";
|
|
1064
1118
|
if (run.status === "timeout") return "timeout";
|
|
1065
1119
|
const latest = job.lastRun?.id === run.id;
|
|
1066
|
-
return latest && (job.open.includes("over_budget") || job.open.includes("slow")) ? "warn" : "ok";
|
|
1120
|
+
return latest && (job.open.includes("over_budget") || job.open.includes("under_floor") || job.open.includes("slow")) ? "warn" : "ok";
|
|
1067
1121
|
}
|
|
1068
1122
|
function timeoutText(job) {
|
|
1069
1123
|
return safely(() => formatDuration(timeoutMs(job.definition)), "configured");
|
|
@@ -1073,13 +1127,17 @@ function describeRun(run, tone, job, now) {
|
|
|
1073
1127
|
if (tone === "running") return `running since ${at}, ${formatDuration(now - run.startedAt)} so far`;
|
|
1074
1128
|
if (tone === "stuck") return `running since ${at}, past its ${timeoutText(job)} timeout`;
|
|
1075
1129
|
const took = run.durationMs !== null ? `, took ${formatDuration(run.durationMs)}` : "";
|
|
1076
|
-
const extra = tone === "warn" ? job.open.includes("over_budget") ? ", over budget" : ", slow" : "";
|
|
1130
|
+
const extra = tone === "warn" ? job.open.includes("over_budget") ? ", over budget" : job.open.includes("under_floor") ? ", under floor" : ", slow" : "";
|
|
1077
1131
|
return `${run.status} at ${at}${took}${extra}`;
|
|
1078
1132
|
}
|
|
1079
1133
|
function overCeilings(job) {
|
|
1080
1134
|
const metrics = job.lastRun?.metrics ?? {};
|
|
1081
1135
|
return Object.entries(job.definition.budget ?? {}).filter(([k, limit]) => (metrics[k] ?? -Infinity) > limit).map(([k]) => k);
|
|
1082
1136
|
}
|
|
1137
|
+
function underFloors(job) {
|
|
1138
|
+
const floors = job.definition.floor ?? {};
|
|
1139
|
+
return Object.entries(job.lastRun?.metrics ?? {}).filter(([k, v]) => floors[k] !== void 0 ? v < floors[k] : v <= 0).map(([k]) => k);
|
|
1140
|
+
}
|
|
1083
1141
|
function laneNote(job, missed, now) {
|
|
1084
1142
|
const last = job.lastRun;
|
|
1085
1143
|
if (job.silencedUntil !== null && job.silencedUntil > now) return `silenced until ${when2(job.silencedUntil, now)}`;
|
|
@@ -1095,6 +1153,10 @@ function laneNote(job, missed, now) {
|
|
|
1095
1153
|
const over = overCeilings(job);
|
|
1096
1154
|
return `went over budget${over.length ? ` on ${over.join(" and ")}` : ""} at ${when2(last.startedAt, now)}`;
|
|
1097
1155
|
}
|
|
1156
|
+
if (job.open.includes("under_floor") && last) {
|
|
1157
|
+
const under = underFloors(job);
|
|
1158
|
+
return `fell short${under.length ? ` on ${under.join(" and ")}` : ""} at ${when2(last.startedAt, now)}`;
|
|
1159
|
+
}
|
|
1098
1160
|
if (job.open.includes("slow") && last?.durationMs != null) return `slow: took ${formatDuration(last.durationMs)}`;
|
|
1099
1161
|
if (job.open.includes("failed")) return "failing";
|
|
1100
1162
|
if (!last && job.nextExpectedAt !== null) return `no runs yet, first due ${when2(job.nextExpectedAt, now)}`;
|
|
@@ -1211,7 +1273,7 @@ function legend() {
|
|
|
1211
1273
|
[box("run ok"), "ran"],
|
|
1212
1274
|
[box("run bad"), "failed"],
|
|
1213
1275
|
[box("run timeout"), "timed out"],
|
|
1214
|
-
[box("run warn"), "over budget or slow"],
|
|
1276
|
+
[box("run warn"), "over budget, under floor or slow"],
|
|
1215
1277
|
[box("run running"), "running"],
|
|
1216
1278
|
[box("missed"), "missed"]
|
|
1217
1279
|
];
|
|
@@ -1288,7 +1350,8 @@ function weekRunsLimit(job, now) {
|
|
|
1288
1350
|
var h = escapeHtml;
|
|
1289
1351
|
var CSS = `
|
|
1290
1352
|
:root{color-scheme:light dark;--paper:#f4f4f5;--sheet:#fff;--sunk:#fafafa;--rule:#e4e4e7;--rule-2:#d4d4d8;--tick:#909098;--ink:#000;--body:#18181b;--muted:#71717a;--ok:#15803d;--warn:#a16207;--bad:#b91c1c;--serif:"Newsreader",ui-serif,Georgia,Cambria,"Times New Roman",serif;--mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--who:200px}
|
|
1291
|
-
@media(prefers-color-scheme:dark){:root{--paper:#09090b;--sheet:#111113;--sunk:#18181b;--rule:#27272a;--rule-2:#3f3f46;--tick:#66666f;--ink:#fff;--body:#e4e4e7;--muted:#a1a1aa;--ok:#4ade80;--warn:#fbbf24;--bad:#f87171}}
|
|
1353
|
+
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){--paper:#09090b;--sheet:#111113;--sunk:#18181b;--rule:#27272a;--rule-2:#3f3f46;--tick:#66666f;--ink:#fff;--body:#e4e4e7;--muted:#a1a1aa;--ok:#4ade80;--warn:#fbbf24;--bad:#f87171}}
|
|
1354
|
+
:root[data-theme=light]{color-scheme:light}:root[data-theme=dark]{color-scheme:dark;--paper:#09090b;--sheet:#111113;--sunk:#18181b;--rule:#27272a;--rule-2:#3f3f46;--tick:#66666f;--ink:#fff;--body:#e4e4e7;--muted:#a1a1aa;--ok:#4ade80;--warn:#fbbf24;--bad:#f87171}
|
|
1292
1355
|
*{box-sizing:border-box}html{-webkit-text-size-adjust:100%}
|
|
1293
1356
|
body{margin:0;background:var(--paper);color:var(--ink);font:400 16px/1.55 var(--serif);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
|
|
1294
1357
|
a{color:inherit;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.16em;text-decoration-color:var(--rule-2)}a:hover{text-decoration-color:currentColor}
|
|
@@ -1628,6 +1691,7 @@ function jobPage(job, runs, now, base, complete = true) {
|
|
|
1628
1691
|
<dt>Timeout</dt><dd>${h(d.timeout ?? "1h")}</dd>
|
|
1629
1692
|
${d.maxDuration ? `<dt>Max duration</dt><dd>${h(d.maxDuration)}</dd>` : ""}
|
|
1630
1693
|
${d.budget ? `<dt>Budget</dt><dd>${h(Object.entries(d.budget).map(([k, v]) => `${k} \u2264 ${v}`).join(", "))}</dd>` : ""}
|
|
1694
|
+
${d.floor ? `<dt>Floor</dt><dd>${h(Object.entries(d.floor).map(([k, v]) => `${k} \u2265 ${v}`).join(", "))}</dd>` : ""}
|
|
1631
1695
|
${d.expect ? `<dt>Expect</dt><dd>${h(d.expect)}</dd>` : ""}
|
|
1632
1696
|
${d.failuresBeforeAlert && d.failuresBeforeAlert > 1 ? `<dt>Alert after</dt><dd>${h(d.failuresBeforeAlert)} consecutive failures</dd>` : ""}
|
|
1633
1697
|
${d.tags?.length ? `<dt>Tags</dt><dd>${d.tags.map((t) => h(t)).join(", ")}</dd>` : ""}
|
|
@@ -1645,7 +1709,7 @@ function messagePage(title, message, base, signIn = false) {
|
|
|
1645
1709
|
}
|
|
1646
1710
|
|
|
1647
1711
|
// src/version.ts
|
|
1648
|
-
var VERSION = "0.
|
|
1712
|
+
var VERSION = "0.12.0";
|
|
1649
1713
|
var API_VERSION = 1;
|
|
1650
1714
|
|
|
1651
1715
|
// src/routes/index.ts
|
|
@@ -2139,6 +2203,18 @@ function validateDefinition(def) {
|
|
|
2139
2203
|
}
|
|
2140
2204
|
}
|
|
2141
2205
|
}
|
|
2206
|
+
if (def.floor !== void 0) {
|
|
2207
|
+
if (typeof def.floor !== "object" || def.floor === null) throw new Error(`job "${name}": floor must be an object of { metric: floor }`);
|
|
2208
|
+
for (const [metric, floor] of Object.entries(def.floor)) {
|
|
2209
|
+
if (typeof floor !== "number" || !Number.isFinite(floor)) {
|
|
2210
|
+
throw new Error(`job "${name}": floor.${metric} must be a finite number (got ${String(floor)})`);
|
|
2211
|
+
}
|
|
2212
|
+
const ceiling = def.budget?.[metric];
|
|
2213
|
+
if (ceiling !== void 0 && floor > ceiling) {
|
|
2214
|
+
throw new Error(`job "${name}": floor.${metric} (${floor}) is above budget.${metric} (${ceiling}), so every run would alert`);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2142
2218
|
if (def.expect !== void 0 && typeof def.expect !== "string" && !(def.expect instanceof RegExp) && typeof def.expect !== "function") {
|
|
2143
2219
|
throw new Error(`job "${name}": expect must be a string, a RegExp or a function`);
|
|
2144
2220
|
}
|