@cronwatch/sdk 0.1.0 → 0.3.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -12
  3. package/dist/anthropic.cjs +19 -5
  4. package/dist/anthropic.cjs.map +1 -1
  5. package/dist/anthropic.d.cts +1 -1
  6. package/dist/anthropic.d.ts +1 -1
  7. package/dist/anthropic.js +19 -5
  8. package/dist/anthropic.js.map +1 -1
  9. package/dist/discord.cjs +14 -2
  10. package/dist/discord.cjs.map +1 -1
  11. package/dist/discord.d.cts +6 -2
  12. package/dist/discord.d.ts +6 -2
  13. package/dist/discord.js +13 -3
  14. package/dist/discord.js.map +1 -1
  15. package/dist/index.cjs +676 -285
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +100 -44
  18. package/dist/index.d.ts +100 -44
  19. package/dist/index.js +677 -283
  20. package/dist/index.js.map +1 -1
  21. package/dist/postgres.cjs +140 -73
  22. package/dist/postgres.cjs.map +1 -1
  23. package/dist/postgres.d.cts +2 -2
  24. package/dist/postgres.d.ts +2 -2
  25. package/dist/postgres.js +140 -73
  26. package/dist/postgres.js.map +1 -1
  27. package/dist/slack.cjs +12 -6
  28. package/dist/slack.cjs.map +1 -1
  29. package/dist/slack.d.cts +1 -1
  30. package/dist/slack.d.ts +1 -1
  31. package/dist/slack.js +12 -6
  32. package/dist/slack.js.map +1 -1
  33. package/dist/sqlite.cjs +142 -71
  34. package/dist/sqlite.cjs.map +1 -1
  35. package/dist/sqlite.d.cts +3 -1
  36. package/dist/sqlite.d.ts +3 -1
  37. package/dist/sqlite.js +143 -72
  38. package/dist/sqlite.js.map +1 -1
  39. package/dist/{types-BQ1P8z55.d.cts → types-C1PyRjI5.d.cts} +63 -5
  40. package/dist/{types-BQ1P8z55.d.ts → types-C1PyRjI5.d.ts} +63 -5
  41. package/dist/webhook.cjs +10 -2
  42. package/dist/webhook.cjs.map +1 -1
  43. package/dist/webhook.d.cts +1 -1
  44. package/dist/webhook.d.ts +1 -1
  45. package/dist/webhook.js +10 -2
  46. package/dist/webhook.js.map +1 -1
  47. package/package.json +95 -32
package/dist/index.d.cts CHANGED
@@ -1,6 +1,5 @@
1
- import { S as Store, a as AlertChannel, T as TriageFn, D as Duration, J as JobOptions, b as JobDefinition, R as Run, C as CheckResult, c as JobSummary, d as JobState, A as Alert, e as Condition, f as StoredJobDefinition } from './types-BQ1P8z55.cjs';
2
- export { g as AlertType, E as ExpectRule, h as JobHealth, i as RunStatus, j as StoredJob, k as TriageContext } from './types-BQ1P8z55.cjs';
3
- import { Cron } from 'croner';
1
+ import { S as Store, a as AlertChannel, T as TriageFn, D as Duration, J as JobOptions, b as JobDefinition, C as CheckResult, c as JobSummary, R as Run, d as JobState, A as Alert, e as AlertDraft, f as StoredJobDefinition } from './types-C1PyRjI5.cjs';
2
+ export { g as AlertDetails, h as AlertType, B as BudgetBreach, i as Condition, E as ExpectRule, j as JobHealth, k as RunStatus, l as StoredJob, m as TriageContext } from './types-C1PyRjI5.cjs';
4
3
 
5
4
  /** What a job function receives. */
6
5
  interface JobContext {
@@ -20,8 +19,10 @@ interface RoutesOptions {
20
19
  /**
21
20
  * Required to reach anything. Send it as `Authorization: Bearer <token>`,
22
21
  * or open the dashboard once with `?token=<token>` and a cookie is set.
23
- * Defaults to process.env.CRONWATCH_TOKEN. With no token at all, the routes
24
- * are open in development and refuse to serve in production.
22
+ * Defaults to process.env.CRONWATCH_TOKEN; an empty string counts as unset.
23
+ * With no token, the routes answer only requests to localhost while NODE_ENV
24
+ * is "development" or "test", and 503 otherwise. Pass `null` to opt out and serve them
25
+ * open everywhere, for example behind your own auth.
25
26
  *
26
27
  * The check endpoint (/api/check) also accepts the client's cronSecret, so
27
28
  * a platform cron that sends `Authorization: Bearer <CRON_SECRET>` can
@@ -52,7 +53,9 @@ interface HandlerOptions {
52
53
  /**
53
54
  * Callers must send `Authorization: Bearer <secret>`. Defaults to the
54
55
  * client's cronSecret, which defaults to process.env.CRON_SECRET (what
55
- * Vercel sends its cron requests with). Pass null to allow anyone.
56
+ * Vercel sends its cron requests with). An empty string counts as unset.
57
+ * With no secret at all the handler answers 503 unless NODE_ENV is
58
+ * "development" or "test". Pass null to allow anyone.
56
59
  */
57
60
  secret?: string | null;
58
61
  }
@@ -71,41 +74,65 @@ interface CronWatchOptions {
71
74
  store?: Store;
72
75
  /** Where alerts go. Defaults to the console. */
73
76
  alerts?: AlertChannel[];
74
- /** Adds a short diagnosis to failure alerts. See @cronwatch/sdk/anthropic. */
77
+ /** Adds a short diagnosis to every alert except recoveries. See @cronwatch/sdk/anthropic. */
75
78
  triage?: TriageFn;
76
- /** Shared secret that handler() requests must carry. Defaults to process.env.CRON_SECRET. */
79
+ /**
80
+ * Shared secret that handler() requests must carry. Defaults to
81
+ * process.env.CRON_SECRET; an empty string counts as unset. Pass null to
82
+ * let handlers run without one.
83
+ */
77
84
  cronSecret?: string | null;
78
85
  /** How long finished runs are kept. Default "30d". */
79
86
  retention?: Duration;
80
87
  /** Applied to every job unless the job sets its own. */
81
88
  defaults?: Pick<JobOptions, "grace" | "timeout" | "timezone" | "failuresBeforeAlert">;
82
- /** Called with anything that goes wrong outside a job: an alert channel failing, a triage timeout. */
89
+ /**
90
+ * Applied to every run's output and error before it is stored, shown or
91
+ * sent to an alert channel or triage. The default blanks values that look
92
+ * like secrets (password=..., URL credentials, bearer tokens, AWS, GitHub,
93
+ * Slack, Stripe and API key formats). Pass your own function, or false to
94
+ * keep output exactly as logged.
95
+ */
96
+ redact?: ((text: string) => string) | false;
97
+ /**
98
+ * "now" (the default) sends alerts from this process. "check" sends nothing
99
+ * from here: each alert is queued in the store and the next check, in a
100
+ * process that delivers now, sends it (with triage). For a process that
101
+ * records runs but cannot reach the network, such as a sandboxed backup job.
102
+ * Its `alerts` and `triage` are not used.
103
+ */
104
+ deliver?: "now" | "check";
105
+ /** Called with anything that goes wrong outside a job: the store failing, an alert channel failing, a triage timeout. */
83
106
  onError?: (error: unknown, where: string) => void;
84
107
  /** The clock. Tests use this. */
85
108
  now?: () => number;
86
109
  }
87
- interface ExecuteResult<T> {
88
- run: Run;
89
- result: T | undefined;
90
- error: unknown;
91
- threw: boolean;
92
- }
93
110
  declare class CronWatch {
94
111
  readonly store: Store;
95
112
  readonly alerts: AlertChannel[];
96
113
  readonly triage: TriageFn | undefined;
114
+ /** The secret handler() requests must carry, or null when none is set. */
97
115
  readonly cronSecret: string | null;
98
116
  readonly retentionMs: number;
99
117
  readonly now: () => number;
100
118
  readonly onError: (error: unknown, where: string) => void;
119
+ /** cronSecret was passed as null: handlers may run without a secret. */
120
+ private readonly secretOptOut;
121
+ private readonly redact;
122
+ /** "check": queue alerts for another process's check instead of sending them. See CronWatchOptions.deliver. */
123
+ private readonly deferDelivery;
101
124
  private readonly defaults;
102
125
  private readonly definitions;
103
126
  private readonly synced;
127
+ /** The tail of each job's queue of state updates. See serial(). */
128
+ private readonly queues;
104
129
  private ready;
105
130
  private checking;
106
131
  private lastPruneAt;
107
132
  private timer;
133
+ private firstTick;
108
134
  private usingDefaultStore;
135
+ private warnedNoSecret;
109
136
  constructor(options?: CronWatchOptions);
110
137
  /** Declare a job. Call it once, at module level, and keep the handle. */
111
138
  job(name: string, options?: JobOptions): JobHandle;
@@ -115,25 +142,61 @@ declare class CronWatch {
115
142
  /** The definitions declared in this process. */
116
143
  definedJobs(): JobDefinition[];
117
144
  private handle;
145
+ private warnNoSecret;
118
146
  private ensureReady;
119
147
  private sync;
120
- /** Runs a function as a recorded run. Never throws for the job's own error; see `threw`. */
121
- execute<T>(definition: JobDefinition, fn: JobFn<T>, trigger: string): Promise<ExecuteResult<T>>;
122
148
  /**
123
- * Look for missed and stuck runs across every job, send alerts, and prune
124
- * old runs. Call it from an interval (start()), a cron hitting the mounted
125
- * routes, or by hand. Concurrent calls share one check.
149
+ * Runs `fn` after every earlier state update for the same job has settled,
150
+ * so two runs (or a run and a check) in this process never read and write
151
+ * the job's state over each other. Other processes are not coordinated.
152
+ */
153
+ private serial;
154
+ private readState;
155
+ /**
156
+ * Runs a function as a recorded run. The function always runs, whatever
157
+ * the store is doing: store errors go to onError, and the result is the
158
+ * function's own outcome. Never throws for the job's own error; see `threw`.
159
+ */
160
+ private execute;
161
+ /** Whether a check already marked this run as timed out, for a failure that finished late. */
162
+ private markedTimedOut;
163
+ /**
164
+ * Record a finished run (ok, failed, or timed out by a check), evaluate it
165
+ * against the job's state and send what that produces. Never throws.
166
+ */
167
+ private finishRun;
168
+ /**
169
+ * The runs before `run`, newest first, with up to BASELINE_WINDOW
170
+ * successful ones when the store has them. One small read normally; a
171
+ * larger one only when failures crowd the successes out of it.
172
+ */
173
+ private history;
174
+ /**
175
+ * Look for missed and stuck runs across every job, send alerts, retry
176
+ * alerts no channel accepted, and prune old runs. Call it from an interval
177
+ * (start()), a cron hitting the mounted routes, or by hand. Concurrent
178
+ * calls share one check.
126
179
  */
127
180
  check(): Promise<CheckResult>;
128
181
  private runCheck;
182
+ /** A job's summary and its newest runs, without alerting. */
183
+ private snapshot;
129
184
  /** Every job the store knows about, with its health. Does not send alerts. */
130
185
  jobs(): Promise<JobSummary[]>;
186
+ /** Every job's summary with its newest `limit` runs, read together. What the dashboard shows. */
187
+ jobsWithRuns(limit?: number): Promise<{
188
+ job: JobSummary;
189
+ runs: Run[];
190
+ }[]>;
131
191
  jobSummary(name: string): Promise<JobSummary | null>;
192
+ /** A job's runs, newest first. `limit` is a whole number from 1 to 500. */
132
193
  runs(name: string, limit?: number): Promise<Run[]>;
133
194
  getRun(id: string): Promise<Run | null>;
134
195
  /** Stop alerts for a job for a while. State keeps updating underneath. */
135
196
  silence(name: string, duration: Duration): Promise<JobState>;
136
197
  unsilence(name: string): Promise<JobState>;
198
+ /** Read, change and write one job's state, in turn with every other update to it. */
199
+ private patchState;
137
200
  /** Remove a job and its runs from the store. A job still declared in code comes back on its next run. */
138
201
  forget(name: string): Promise<void>;
139
202
  /** The dashboard and JSON API as fetch-style handlers. See createRoutes(). */
@@ -142,18 +205,27 @@ declare class CronWatch {
142
205
  start(every?: Duration): void;
143
206
  stop(): void;
144
207
  close(): Promise<void>;
145
- private summarize;
146
- /** Save an evaluation's state, honouring silence, and send its alerts. */
208
+ /** Save an evaluation's state, honouring silence, and return what should be sent. Call inside serial(). */
147
209
  private settle;
210
+ /**
211
+ * Compose, triage and send each draft. The state was saved before this
212
+ * (settle), so a slow channel holds up nothing else; afterwards only the
213
+ * delivery fields are written back, onto a fresh read of the state.
214
+ */
148
215
  private dispatch;
216
+ /** Send the alerts that no channel accepted last time, once each. */
217
+ private retryUndelivered;
218
+ /** Mark delivered alerts done and keep failed ones for the next check. lastAlertAt moves only on a delivery. */
219
+ private recordDelivery;
220
+ /** Send to every channel at once. True when at least one accepted it, or there are none. */
221
+ private deliver;
222
+ private addTriage;
149
223
  }
150
224
  /** Writes alerts to the console. The default channel. */
151
225
  declare function consoleChannel(): AlertChannel;
152
226
  /** Wrap any function as an alert channel. */
153
227
  declare function custom(name: string, send: (alert: Alert) => Promise<void> | void): AlertChannel;
154
228
 
155
- declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
156
-
157
229
  /**
158
230
  * Keeps everything in process memory. The default when no store is given,
159
231
  * good for tests and for trying the library out. State is gone on restart,
@@ -168,15 +240,14 @@ declare function memory(): Store;
168
240
  declare function parseDuration(value: Duration, label?: string): number;
169
241
  /** 90000 -> "1m 30s". For messages, not for parsing back. */
170
242
  declare function formatDuration(ms: number): string;
171
- /** "5 minutes ago", "in 2h". Relative to `now`. */
172
- declare function formatRelative(at: number, now: number): string;
173
243
 
174
244
  interface ParsedSchedule {
175
245
  kind: "cron" | "interval";
176
246
  source: string;
247
+ /** The IANA timezone a cron is read in, when one was given. */
248
+ timezone?: string;
177
249
  /** For intervals, the period in milliseconds. */
178
250
  everyMs?: number;
179
- cron?: Cron;
180
251
  }
181
252
  /**
182
253
  * "0 2 * * *" (cron, five or six fields), "@hourly", or "every 5m".
@@ -187,23 +258,8 @@ interface ParsedSchedule {
187
258
  * timezone: "UTC" for those.
188
259
  */
189
260
  declare function parseSchedule(schedule: string, timezone?: string): ParsedSchedule;
190
- /** The next time the cron fires strictly after `from`. */
261
+ /** The next time the schedule fires strictly after `from`. For an interval, counted from the last run when there is one. */
191
262
  declare function nextFire(parsed: ParsedSchedule, from: number, lastRunAt: number | null): number | null;
192
- /**
193
- * The most recent time the cron fired at or before `now`, or null when the
194
- * expression never fires in the year before `now`.
195
- *
196
- * croner only looks forward, so this searches: widen a window behind `now`
197
- * until a fire time falls inside it, then bisect on the window's start for
198
- * the latest start whose next fire is still at or before `now`.
199
- */
200
- declare function previousFire(parsed: ParsedSchedule, now: number): number | null;
201
-
202
- interface AlertDraft {
203
- type: Condition | "recovered";
204
- run: Run | null;
205
- details: Record<string, unknown>;
206
- }
207
263
 
208
264
  /** Turns a draft into the title and message every channel shows. */
209
265
  declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now: number): Alert;
@@ -216,4 +272,4 @@ declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now:
216
272
  */
217
273
  declare function cronwatch(options?: CronWatchOptions): CronWatch;
218
274
 
219
- export { Alert, AlertChannel, CheckResult, Condition, CronWatch, type CronWatchOptions, Duration, type HandlerFn, type HandlerOptions, type JobContext, JobDefinition, type JobFn, type JobHandle, JobOptions, JobState, JobSummary, type Routes, type RoutesOptions, Run, Store, StoredJobDefinition, TriageFn, composeAlert, consoleChannel, createRoutes, cronwatch, custom, formatDuration, formatRelative, json, memory, nextFire, parseDuration, parseSchedule, previousFire };
275
+ export { Alert, AlertChannel, AlertDraft, CheckResult, CronWatch, type CronWatchOptions, Duration, type FetchHandler, type HandlerFn, type HandlerOptions, type JobContext, JobDefinition, type JobFn, type JobHandle, JobOptions, JobState, JobSummary, type ParsedSchedule, type Routes, type RoutesOptions, Run, Store, StoredJobDefinition, TriageFn, composeAlert, consoleChannel, createRoutes, cronwatch, custom, formatDuration, memory, nextFire, parseDuration, parseSchedule };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { S as Store, a as AlertChannel, T as TriageFn, D as Duration, J as JobOptions, b as JobDefinition, R as Run, C as CheckResult, c as JobSummary, d as JobState, A as Alert, e as Condition, f as StoredJobDefinition } from './types-BQ1P8z55.js';
2
- export { g as AlertType, E as ExpectRule, h as JobHealth, i as RunStatus, j as StoredJob, k as TriageContext } from './types-BQ1P8z55.js';
3
- import { Cron } from 'croner';
1
+ import { S as Store, a as AlertChannel, T as TriageFn, D as Duration, J as JobOptions, b as JobDefinition, C as CheckResult, c as JobSummary, R as Run, d as JobState, A as Alert, e as AlertDraft, f as StoredJobDefinition } from './types-C1PyRjI5.js';
2
+ export { g as AlertDetails, h as AlertType, B as BudgetBreach, i as Condition, E as ExpectRule, j as JobHealth, k as RunStatus, l as StoredJob, m as TriageContext } from './types-C1PyRjI5.js';
4
3
 
5
4
  /** What a job function receives. */
6
5
  interface JobContext {
@@ -20,8 +19,10 @@ interface RoutesOptions {
20
19
  /**
21
20
  * Required to reach anything. Send it as `Authorization: Bearer <token>`,
22
21
  * or open the dashboard once with `?token=<token>` and a cookie is set.
23
- * Defaults to process.env.CRONWATCH_TOKEN. With no token at all, the routes
24
- * are open in development and refuse to serve in production.
22
+ * Defaults to process.env.CRONWATCH_TOKEN; an empty string counts as unset.
23
+ * With no token, the routes answer only requests to localhost while NODE_ENV
24
+ * is "development" or "test", and 503 otherwise. Pass `null` to opt out and serve them
25
+ * open everywhere, for example behind your own auth.
25
26
  *
26
27
  * The check endpoint (/api/check) also accepts the client's cronSecret, so
27
28
  * a platform cron that sends `Authorization: Bearer <CRON_SECRET>` can
@@ -52,7 +53,9 @@ interface HandlerOptions {
52
53
  /**
53
54
  * Callers must send `Authorization: Bearer <secret>`. Defaults to the
54
55
  * client's cronSecret, which defaults to process.env.CRON_SECRET (what
55
- * Vercel sends its cron requests with). Pass null to allow anyone.
56
+ * Vercel sends its cron requests with). An empty string counts as unset.
57
+ * With no secret at all the handler answers 503 unless NODE_ENV is
58
+ * "development" or "test". Pass null to allow anyone.
56
59
  */
57
60
  secret?: string | null;
58
61
  }
@@ -71,41 +74,65 @@ interface CronWatchOptions {
71
74
  store?: Store;
72
75
  /** Where alerts go. Defaults to the console. */
73
76
  alerts?: AlertChannel[];
74
- /** Adds a short diagnosis to failure alerts. See @cronwatch/sdk/anthropic. */
77
+ /** Adds a short diagnosis to every alert except recoveries. See @cronwatch/sdk/anthropic. */
75
78
  triage?: TriageFn;
76
- /** Shared secret that handler() requests must carry. Defaults to process.env.CRON_SECRET. */
79
+ /**
80
+ * Shared secret that handler() requests must carry. Defaults to
81
+ * process.env.CRON_SECRET; an empty string counts as unset. Pass null to
82
+ * let handlers run without one.
83
+ */
77
84
  cronSecret?: string | null;
78
85
  /** How long finished runs are kept. Default "30d". */
79
86
  retention?: Duration;
80
87
  /** Applied to every job unless the job sets its own. */
81
88
  defaults?: Pick<JobOptions, "grace" | "timeout" | "timezone" | "failuresBeforeAlert">;
82
- /** Called with anything that goes wrong outside a job: an alert channel failing, a triage timeout. */
89
+ /**
90
+ * Applied to every run's output and error before it is stored, shown or
91
+ * sent to an alert channel or triage. The default blanks values that look
92
+ * like secrets (password=..., URL credentials, bearer tokens, AWS, GitHub,
93
+ * Slack, Stripe and API key formats). Pass your own function, or false to
94
+ * keep output exactly as logged.
95
+ */
96
+ redact?: ((text: string) => string) | false;
97
+ /**
98
+ * "now" (the default) sends alerts from this process. "check" sends nothing
99
+ * from here: each alert is queued in the store and the next check, in a
100
+ * process that delivers now, sends it (with triage). For a process that
101
+ * records runs but cannot reach the network, such as a sandboxed backup job.
102
+ * Its `alerts` and `triage` are not used.
103
+ */
104
+ deliver?: "now" | "check";
105
+ /** Called with anything that goes wrong outside a job: the store failing, an alert channel failing, a triage timeout. */
83
106
  onError?: (error: unknown, where: string) => void;
84
107
  /** The clock. Tests use this. */
85
108
  now?: () => number;
86
109
  }
87
- interface ExecuteResult<T> {
88
- run: Run;
89
- result: T | undefined;
90
- error: unknown;
91
- threw: boolean;
92
- }
93
110
  declare class CronWatch {
94
111
  readonly store: Store;
95
112
  readonly alerts: AlertChannel[];
96
113
  readonly triage: TriageFn | undefined;
114
+ /** The secret handler() requests must carry, or null when none is set. */
97
115
  readonly cronSecret: string | null;
98
116
  readonly retentionMs: number;
99
117
  readonly now: () => number;
100
118
  readonly onError: (error: unknown, where: string) => void;
119
+ /** cronSecret was passed as null: handlers may run without a secret. */
120
+ private readonly secretOptOut;
121
+ private readonly redact;
122
+ /** "check": queue alerts for another process's check instead of sending them. See CronWatchOptions.deliver. */
123
+ private readonly deferDelivery;
101
124
  private readonly defaults;
102
125
  private readonly definitions;
103
126
  private readonly synced;
127
+ /** The tail of each job's queue of state updates. See serial(). */
128
+ private readonly queues;
104
129
  private ready;
105
130
  private checking;
106
131
  private lastPruneAt;
107
132
  private timer;
133
+ private firstTick;
108
134
  private usingDefaultStore;
135
+ private warnedNoSecret;
109
136
  constructor(options?: CronWatchOptions);
110
137
  /** Declare a job. Call it once, at module level, and keep the handle. */
111
138
  job(name: string, options?: JobOptions): JobHandle;
@@ -115,25 +142,61 @@ declare class CronWatch {
115
142
  /** The definitions declared in this process. */
116
143
  definedJobs(): JobDefinition[];
117
144
  private handle;
145
+ private warnNoSecret;
118
146
  private ensureReady;
119
147
  private sync;
120
- /** Runs a function as a recorded run. Never throws for the job's own error; see `threw`. */
121
- execute<T>(definition: JobDefinition, fn: JobFn<T>, trigger: string): Promise<ExecuteResult<T>>;
122
148
  /**
123
- * Look for missed and stuck runs across every job, send alerts, and prune
124
- * old runs. Call it from an interval (start()), a cron hitting the mounted
125
- * routes, or by hand. Concurrent calls share one check.
149
+ * Runs `fn` after every earlier state update for the same job has settled,
150
+ * so two runs (or a run and a check) in this process never read and write
151
+ * the job's state over each other. Other processes are not coordinated.
152
+ */
153
+ private serial;
154
+ private readState;
155
+ /**
156
+ * Runs a function as a recorded run. The function always runs, whatever
157
+ * the store is doing: store errors go to onError, and the result is the
158
+ * function's own outcome. Never throws for the job's own error; see `threw`.
159
+ */
160
+ private execute;
161
+ /** Whether a check already marked this run as timed out, for a failure that finished late. */
162
+ private markedTimedOut;
163
+ /**
164
+ * Record a finished run (ok, failed, or timed out by a check), evaluate it
165
+ * against the job's state and send what that produces. Never throws.
166
+ */
167
+ private finishRun;
168
+ /**
169
+ * The runs before `run`, newest first, with up to BASELINE_WINDOW
170
+ * successful ones when the store has them. One small read normally; a
171
+ * larger one only when failures crowd the successes out of it.
172
+ */
173
+ private history;
174
+ /**
175
+ * Look for missed and stuck runs across every job, send alerts, retry
176
+ * alerts no channel accepted, and prune old runs. Call it from an interval
177
+ * (start()), a cron hitting the mounted routes, or by hand. Concurrent
178
+ * calls share one check.
126
179
  */
127
180
  check(): Promise<CheckResult>;
128
181
  private runCheck;
182
+ /** A job's summary and its newest runs, without alerting. */
183
+ private snapshot;
129
184
  /** Every job the store knows about, with its health. Does not send alerts. */
130
185
  jobs(): Promise<JobSummary[]>;
186
+ /** Every job's summary with its newest `limit` runs, read together. What the dashboard shows. */
187
+ jobsWithRuns(limit?: number): Promise<{
188
+ job: JobSummary;
189
+ runs: Run[];
190
+ }[]>;
131
191
  jobSummary(name: string): Promise<JobSummary | null>;
192
+ /** A job's runs, newest first. `limit` is a whole number from 1 to 500. */
132
193
  runs(name: string, limit?: number): Promise<Run[]>;
133
194
  getRun(id: string): Promise<Run | null>;
134
195
  /** Stop alerts for a job for a while. State keeps updating underneath. */
135
196
  silence(name: string, duration: Duration): Promise<JobState>;
136
197
  unsilence(name: string): Promise<JobState>;
198
+ /** Read, change and write one job's state, in turn with every other update to it. */
199
+ private patchState;
137
200
  /** Remove a job and its runs from the store. A job still declared in code comes back on its next run. */
138
201
  forget(name: string): Promise<void>;
139
202
  /** The dashboard and JSON API as fetch-style handlers. See createRoutes(). */
@@ -142,18 +205,27 @@ declare class CronWatch {
142
205
  start(every?: Duration): void;
143
206
  stop(): void;
144
207
  close(): Promise<void>;
145
- private summarize;
146
- /** Save an evaluation's state, honouring silence, and send its alerts. */
208
+ /** Save an evaluation's state, honouring silence, and return what should be sent. Call inside serial(). */
147
209
  private settle;
210
+ /**
211
+ * Compose, triage and send each draft. The state was saved before this
212
+ * (settle), so a slow channel holds up nothing else; afterwards only the
213
+ * delivery fields are written back, onto a fresh read of the state.
214
+ */
148
215
  private dispatch;
216
+ /** Send the alerts that no channel accepted last time, once each. */
217
+ private retryUndelivered;
218
+ /** Mark delivered alerts done and keep failed ones for the next check. lastAlertAt moves only on a delivery. */
219
+ private recordDelivery;
220
+ /** Send to every channel at once. True when at least one accepted it, or there are none. */
221
+ private deliver;
222
+ private addTriage;
149
223
  }
150
224
  /** Writes alerts to the console. The default channel. */
151
225
  declare function consoleChannel(): AlertChannel;
152
226
  /** Wrap any function as an alert channel. */
153
227
  declare function custom(name: string, send: (alert: Alert) => Promise<void> | void): AlertChannel;
154
228
 
155
- declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
156
-
157
229
  /**
158
230
  * Keeps everything in process memory. The default when no store is given,
159
231
  * good for tests and for trying the library out. State is gone on restart,
@@ -168,15 +240,14 @@ declare function memory(): Store;
168
240
  declare function parseDuration(value: Duration, label?: string): number;
169
241
  /** 90000 -> "1m 30s". For messages, not for parsing back. */
170
242
  declare function formatDuration(ms: number): string;
171
- /** "5 minutes ago", "in 2h". Relative to `now`. */
172
- declare function formatRelative(at: number, now: number): string;
173
243
 
174
244
  interface ParsedSchedule {
175
245
  kind: "cron" | "interval";
176
246
  source: string;
247
+ /** The IANA timezone a cron is read in, when one was given. */
248
+ timezone?: string;
177
249
  /** For intervals, the period in milliseconds. */
178
250
  everyMs?: number;
179
- cron?: Cron;
180
251
  }
181
252
  /**
182
253
  * "0 2 * * *" (cron, five or six fields), "@hourly", or "every 5m".
@@ -187,23 +258,8 @@ interface ParsedSchedule {
187
258
  * timezone: "UTC" for those.
188
259
  */
189
260
  declare function parseSchedule(schedule: string, timezone?: string): ParsedSchedule;
190
- /** The next time the cron fires strictly after `from`. */
261
+ /** The next time the schedule fires strictly after `from`. For an interval, counted from the last run when there is one. */
191
262
  declare function nextFire(parsed: ParsedSchedule, from: number, lastRunAt: number | null): number | null;
192
- /**
193
- * The most recent time the cron fired at or before `now`, or null when the
194
- * expression never fires in the year before `now`.
195
- *
196
- * croner only looks forward, so this searches: widen a window behind `now`
197
- * until a fire time falls inside it, then bisect on the window's start for
198
- * the latest start whose next fire is still at or before `now`.
199
- */
200
- declare function previousFire(parsed: ParsedSchedule, now: number): number | null;
201
-
202
- interface AlertDraft {
203
- type: Condition | "recovered";
204
- run: Run | null;
205
- details: Record<string, unknown>;
206
- }
207
263
 
208
264
  /** Turns a draft into the title and message every channel shows. */
209
265
  declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now: number): Alert;
@@ -216,4 +272,4 @@ declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now:
216
272
  */
217
273
  declare function cronwatch(options?: CronWatchOptions): CronWatch;
218
274
 
219
- export { Alert, AlertChannel, CheckResult, Condition, CronWatch, type CronWatchOptions, Duration, type HandlerFn, type HandlerOptions, type JobContext, JobDefinition, type JobFn, type JobHandle, JobOptions, JobState, JobSummary, type Routes, type RoutesOptions, Run, Store, StoredJobDefinition, TriageFn, composeAlert, consoleChannel, createRoutes, cronwatch, custom, formatDuration, formatRelative, json, memory, nextFire, parseDuration, parseSchedule, previousFire };
275
+ export { Alert, AlertChannel, AlertDraft, CheckResult, CronWatch, type CronWatchOptions, Duration, type FetchHandler, type HandlerFn, type HandlerOptions, type JobContext, JobDefinition, type JobFn, type JobHandle, JobOptions, JobState, JobSummary, type ParsedSchedule, type Routes, type RoutesOptions, Run, Store, StoredJobDefinition, TriageFn, composeAlert, consoleChannel, createRoutes, cronwatch, custom, formatDuration, memory, nextFire, parseDuration, parseSchedule };