@cronwatch/sdk 0.1.0 → 0.3.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/LICENSE +21 -0
- package/README.md +27 -12
- package/dist/anthropic.cjs +19 -5
- 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 +19 -5
- package/dist/anthropic.js.map +1 -1
- package/dist/discord.cjs +14 -2
- package/dist/discord.cjs.map +1 -1
- package/dist/discord.d.cts +6 -2
- package/dist/discord.d.ts +6 -2
- package/dist/discord.js +13 -3
- package/dist/discord.js.map +1 -1
- package/dist/index.cjs +835 -293
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -45
- package/dist/index.d.ts +133 -45
- package/dist/index.js +836 -291
- package/dist/index.js.map +1 -1
- package/dist/postgres.cjs +152 -73
- package/dist/postgres.cjs.map +1 -1
- package/dist/postgres.d.cts +2 -2
- package/dist/postgres.d.ts +2 -2
- package/dist/postgres.js +152 -73
- package/dist/postgres.js.map +1 -1
- package/dist/slack.cjs +12 -6
- 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 +12 -6
- package/dist/slack.js.map +1 -1
- package/dist/sqlite.cjs +184 -74
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +3 -1
- package/dist/sqlite.d.ts +3 -1
- package/dist/sqlite.js +185 -75
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-BQ1P8z55.d.cts → types-DUt2apl0.d.cts} +84 -7
- package/dist/{types-BQ1P8z55.d.ts → types-DUt2apl0.d.ts} +84 -7
- package/dist/webhook.cjs +10 -2
- 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 +10 -2
- package/dist/webhook.js.map +1 -1
- package/package.json +97 -34
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,
|
|
2
|
-
export { g as AlertType, E as ExpectRule,
|
|
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-DUt2apl0.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-DUt2apl0.cjs';
|
|
4
3
|
|
|
5
4
|
/** What a job function receives. */
|
|
6
5
|
interface JobContext {
|
|
@@ -20,8 +19,11 @@ 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
|
|
24
|
-
*
|
|
22
|
+
* Defaults to process.env.CRONWATCH_TOKEN; an empty string counts as unset.
|
|
23
|
+
* With no token while NODE_ENV is "development" or "test", the routes make
|
|
24
|
+
* a random one and print a sign-in link to the server log on their first
|
|
25
|
+
* request; with no token otherwise they answer 503. Pass `null` to opt out
|
|
26
|
+
* and serve them open everywhere, for example behind your own auth.
|
|
25
27
|
*
|
|
26
28
|
* The check endpoint (/api/check) also accepts the client's cronSecret, so
|
|
27
29
|
* a platform cron that sends `Authorization: Bearer <CRON_SECRET>` can
|
|
@@ -52,7 +54,9 @@ interface HandlerOptions {
|
|
|
52
54
|
/**
|
|
53
55
|
* Callers must send `Authorization: Bearer <secret>`. Defaults to the
|
|
54
56
|
* client's cronSecret, which defaults to process.env.CRON_SECRET (what
|
|
55
|
-
* Vercel sends its cron requests with).
|
|
57
|
+
* Vercel sends its cron requests with). An empty string counts as unset.
|
|
58
|
+
* With no secret at all the handler answers 503 unless NODE_ENV is
|
|
59
|
+
* "development" or "test". Pass null to allow anyone.
|
|
56
60
|
*/
|
|
57
61
|
secret?: string | null;
|
|
58
62
|
}
|
|
@@ -71,41 +75,68 @@ interface CronWatchOptions {
|
|
|
71
75
|
store?: Store;
|
|
72
76
|
/** Where alerts go. Defaults to the console. */
|
|
73
77
|
alerts?: AlertChannel[];
|
|
74
|
-
/** Adds a short diagnosis to
|
|
78
|
+
/** Adds a short diagnosis to every alert except recoveries. See @cronwatch/sdk/anthropic. */
|
|
75
79
|
triage?: TriageFn;
|
|
76
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Shared secret that handler() requests must carry. Defaults to
|
|
82
|
+
* process.env.CRON_SECRET; an empty string counts as unset. Pass null to
|
|
83
|
+
* let handlers run without one.
|
|
84
|
+
*/
|
|
77
85
|
cronSecret?: string | null;
|
|
78
86
|
/** How long finished runs are kept. Default "30d". */
|
|
79
87
|
retention?: Duration;
|
|
80
88
|
/** Applied to every job unless the job sets its own. */
|
|
81
89
|
defaults?: Pick<JobOptions, "grace" | "timeout" | "timezone" | "failuresBeforeAlert">;
|
|
82
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Applied to every run's output and error before it is stored, shown or
|
|
92
|
+
* sent to an alert channel or triage. The default blanks values that look
|
|
93
|
+
* like secrets (password=..., Authorization headers, URL credentials, bearer
|
|
94
|
+
* tokens, JWTs, PEM private keys, webhook URLs, AWS, GitHub, Slack, Stripe,
|
|
95
|
+
* Google and API key formats). Pass your own function, or false to keep
|
|
96
|
+
* output exactly as logged. A function that throws or returns something
|
|
97
|
+
* other than a string is reported to onError and the default is used.
|
|
98
|
+
*/
|
|
99
|
+
redact?: ((text: string) => string) | false;
|
|
100
|
+
/**
|
|
101
|
+
* "now" (the default) sends alerts from this process. "check" sends nothing
|
|
102
|
+
* from here: each alert is queued in the store and the next check, in a
|
|
103
|
+
* process that delivers now, sends it (with triage). For a process that
|
|
104
|
+
* records runs but cannot reach the network, such as a sandboxed backup job.
|
|
105
|
+
* Its `alerts` and `triage` are not used.
|
|
106
|
+
*/
|
|
107
|
+
deliver?: "now" | "check";
|
|
108
|
+
/** Called with anything that goes wrong outside a job: the store failing, an alert channel failing, a triage timeout. */
|
|
83
109
|
onError?: (error: unknown, where: string) => void;
|
|
84
110
|
/** The clock. Tests use this. */
|
|
85
111
|
now?: () => number;
|
|
86
112
|
}
|
|
87
|
-
interface ExecuteResult<T> {
|
|
88
|
-
run: Run;
|
|
89
|
-
result: T | undefined;
|
|
90
|
-
error: unknown;
|
|
91
|
-
threw: boolean;
|
|
92
|
-
}
|
|
93
113
|
declare class CronWatch {
|
|
94
114
|
readonly store: Store;
|
|
95
115
|
readonly alerts: AlertChannel[];
|
|
96
116
|
readonly triage: TriageFn | undefined;
|
|
117
|
+
/** The secret handler() requests must carry, or null when none is set. */
|
|
97
118
|
readonly cronSecret: string | null;
|
|
98
119
|
readonly retentionMs: number;
|
|
99
120
|
readonly now: () => number;
|
|
100
121
|
readonly onError: (error: unknown, where: string) => void;
|
|
122
|
+
/** cronSecret was passed as null: handlers may run without a secret. */
|
|
123
|
+
private readonly secretOptOut;
|
|
124
|
+
private readonly redact;
|
|
125
|
+
/** "check": queue alerts for another process's check instead of sending them. See CronWatchOptions.deliver. */
|
|
126
|
+
private readonly deferDelivery;
|
|
101
127
|
private readonly defaults;
|
|
102
128
|
private readonly definitions;
|
|
103
129
|
private readonly synced;
|
|
130
|
+
/** The tail of each job's queue of state updates. See serial(). */
|
|
131
|
+
private readonly queues;
|
|
104
132
|
private ready;
|
|
105
133
|
private checking;
|
|
106
134
|
private lastPruneAt;
|
|
107
135
|
private timer;
|
|
136
|
+
private firstTick;
|
|
108
137
|
private usingDefaultStore;
|
|
138
|
+
private warnedNoSecret;
|
|
139
|
+
private warnedDeferredStart;
|
|
109
140
|
constructor(options?: CronWatchOptions);
|
|
110
141
|
/** Declare a job. Call it once, at module level, and keep the handle. */
|
|
111
142
|
job(name: string, options?: JobOptions): JobHandle;
|
|
@@ -115,25 +146,80 @@ declare class CronWatch {
|
|
|
115
146
|
/** The definitions declared in this process. */
|
|
116
147
|
definedJobs(): JobDefinition[];
|
|
117
148
|
private handle;
|
|
149
|
+
private warnNoSecret;
|
|
150
|
+
/** onError, for places that must carry on even when onError itself throws. */
|
|
151
|
+
private report;
|
|
118
152
|
private ensureReady;
|
|
119
153
|
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
154
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
155
|
+
* Runs `fn` after every earlier state update for the same job has settled,
|
|
156
|
+
* so two runs (or a run and a check) in this process never read and write
|
|
157
|
+
* the job's state over each other. Other processes are coordinated by
|
|
158
|
+
* updateState() instead.
|
|
159
|
+
*/
|
|
160
|
+
private serial;
|
|
161
|
+
private readState;
|
|
162
|
+
/**
|
|
163
|
+
* Every read-modify-write of a job's state goes through here. In turn with
|
|
164
|
+
* this process's other updates to the job (serial()), it reads the state,
|
|
165
|
+
* asks `change` for the next one, and writes it with the version one
|
|
166
|
+
* higher, only if the stored version is still the one read. When another
|
|
167
|
+
* process wrote in between, the write is refused and it starts again from
|
|
168
|
+
* a fresh read, up to STATE_ATTEMPTS times. So `change` may run more than
|
|
169
|
+
* once and must only compute: whatever it returns from the attempt that
|
|
170
|
+
* was written is the result. Nothing is written when the state is
|
|
171
|
+
* unchanged. Returns the state as stored.
|
|
172
|
+
*/
|
|
173
|
+
private updateState;
|
|
174
|
+
/** A conditional write, or for a store without compareAndSetState, a plain one that always succeeds. */
|
|
175
|
+
private writeState;
|
|
176
|
+
/**
|
|
177
|
+
* Runs a function as a recorded run. The function always runs, whatever
|
|
178
|
+
* the store is doing: store errors go to onError, and the result is the
|
|
179
|
+
* function's own outcome. Never throws for the job's own error; see `threw`.
|
|
180
|
+
*/
|
|
181
|
+
private execute;
|
|
182
|
+
/** Whether a check already marked this run as timed out, for a failure that finished late. */
|
|
183
|
+
private markedTimedOut;
|
|
184
|
+
/**
|
|
185
|
+
* Record a finished run (ok, failed, or timed out by a check), evaluate it
|
|
186
|
+
* against the job's state and send what that produces. Never throws.
|
|
187
|
+
*/
|
|
188
|
+
private finishRun;
|
|
189
|
+
/**
|
|
190
|
+
* The runs before `run`, newest first, with up to BASELINE_WINDOW
|
|
191
|
+
* successful ones when the store has them. One small read normally; a
|
|
192
|
+
* larger one only when failures crowd the successes out of it.
|
|
193
|
+
*/
|
|
194
|
+
private history;
|
|
195
|
+
/**
|
|
196
|
+
* Look for missed and stuck runs across every job, send alerts, retry
|
|
197
|
+
* alerts no channel accepted, and prune old runs. Call it from an interval
|
|
198
|
+
* (start()), a cron hitting the mounted routes, or by hand. Concurrent
|
|
199
|
+
* calls share one check.
|
|
126
200
|
*/
|
|
127
201
|
check(): Promise<CheckResult>;
|
|
128
202
|
private runCheck;
|
|
203
|
+
/** A job's summary and its newest runs, without alerting. A job that cannot be evaluated is reported and shown as failing. */
|
|
204
|
+
private snapshot;
|
|
205
|
+
/** The summary of a job whose evaluation failed, from whatever can still be read. */
|
|
206
|
+
private unevaluable;
|
|
129
207
|
/** Every job the store knows about, with its health. Does not send alerts. */
|
|
130
208
|
jobs(): Promise<JobSummary[]>;
|
|
209
|
+
/** Every job's summary with its newest `limit` runs, read together. What the dashboard shows. */
|
|
210
|
+
jobsWithRuns(limit?: number): Promise<{
|
|
211
|
+
job: JobSummary;
|
|
212
|
+
runs: Run[];
|
|
213
|
+
}[]>;
|
|
131
214
|
jobSummary(name: string): Promise<JobSummary | null>;
|
|
215
|
+
/** A job's runs, newest first. `limit` is a whole number from 1 to 500. */
|
|
132
216
|
runs(name: string, limit?: number): Promise<Run[]>;
|
|
133
217
|
getRun(id: string): Promise<Run | null>;
|
|
134
218
|
/** Stop alerts for a job for a while. State keeps updating underneath. */
|
|
135
219
|
silence(name: string, duration: Duration): Promise<JobState>;
|
|
136
220
|
unsilence(name: string): Promise<JobState>;
|
|
221
|
+
/** Read, change and write one job's state, in turn with every other update to it. */
|
|
222
|
+
private patchState;
|
|
137
223
|
/** Remove a job and its runs from the store. A job still declared in code comes back on its next run. */
|
|
138
224
|
forget(name: string): Promise<void>;
|
|
139
225
|
/** The dashboard and JSON API as fetch-style handlers. See createRoutes(). */
|
|
@@ -142,18 +228,36 @@ declare class CronWatch {
|
|
|
142
228
|
start(every?: Duration): void;
|
|
143
229
|
stop(): void;
|
|
144
230
|
close(): Promise<void>;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Compose, triage and send each draft. The state was saved before this
|
|
233
|
+
* (updateState), so a slow channel holds up nothing else; afterwards only
|
|
234
|
+
* the delivery fields are written back, onto a fresh read of the state.
|
|
235
|
+
*/
|
|
148
236
|
private dispatch;
|
|
237
|
+
/**
|
|
238
|
+
* Send the alerts that no channel accepted last time, once each, oldest
|
|
239
|
+
* first. `state` is the job's state as this check left it: an alert that
|
|
240
|
+
* no longer describes it (staleAlert) is dropped instead. Retries across a
|
|
241
|
+
* check share RETRY_BUDGET_MS of wall-clock time; once it is spent the rest
|
|
242
|
+
* stay queued for the next check.
|
|
243
|
+
*/
|
|
244
|
+
private retryUndelivered;
|
|
245
|
+
/**
|
|
246
|
+
* Mark delivered alerts done, drop stale ones, and keep failed ones for the
|
|
247
|
+
* next check. A failed alert replaces its stored copy, so a triage made on
|
|
248
|
+
* this attempt is kept. lastAlertAt moves only on a delivery.
|
|
249
|
+
*/
|
|
250
|
+
private recordDelivery;
|
|
251
|
+
/** Send to every channel at once. True when at least one accepted it, or there are none. */
|
|
252
|
+
private deliver;
|
|
253
|
+
/** Sets alert.triage to the diagnosis, or to null when there is none, so it is tried once per alert. */
|
|
254
|
+
private addTriage;
|
|
149
255
|
}
|
|
150
256
|
/** Writes alerts to the console. The default channel. */
|
|
151
257
|
declare function consoleChannel(): AlertChannel;
|
|
152
258
|
/** Wrap any function as an alert channel. */
|
|
153
259
|
declare function custom(name: string, send: (alert: Alert) => Promise<void> | void): AlertChannel;
|
|
154
260
|
|
|
155
|
-
declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
|
|
156
|
-
|
|
157
261
|
/**
|
|
158
262
|
* Keeps everything in process memory. The default when no store is given,
|
|
159
263
|
* good for tests and for trying the library out. State is gone on restart,
|
|
@@ -168,15 +272,14 @@ declare function memory(): Store;
|
|
|
168
272
|
declare function parseDuration(value: Duration, label?: string): number;
|
|
169
273
|
/** 90000 -> "1m 30s". For messages, not for parsing back. */
|
|
170
274
|
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
275
|
|
|
174
276
|
interface ParsedSchedule {
|
|
175
277
|
kind: "cron" | "interval";
|
|
176
278
|
source: string;
|
|
279
|
+
/** The IANA timezone a cron is read in, when one was given. */
|
|
280
|
+
timezone?: string;
|
|
177
281
|
/** For intervals, the period in milliseconds. */
|
|
178
282
|
everyMs?: number;
|
|
179
|
-
cron?: Cron;
|
|
180
283
|
}
|
|
181
284
|
/**
|
|
182
285
|
* "0 2 * * *" (cron, five or six fields), "@hourly", or "every 5m".
|
|
@@ -187,23 +290,8 @@ interface ParsedSchedule {
|
|
|
187
290
|
* timezone: "UTC" for those.
|
|
188
291
|
*/
|
|
189
292
|
declare function parseSchedule(schedule: string, timezone?: string): ParsedSchedule;
|
|
190
|
-
/** The next time the
|
|
293
|
+
/** The next time the schedule fires strictly after `from`. For an interval, counted from the last run when there is one. */
|
|
191
294
|
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
295
|
|
|
208
296
|
/** Turns a draft into the title and message every channel shows. */
|
|
209
297
|
declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now: number): Alert;
|
|
@@ -216,4 +304,4 @@ declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now:
|
|
|
216
304
|
*/
|
|
217
305
|
declare function cronwatch(options?: CronWatchOptions): CronWatch;
|
|
218
306
|
|
|
219
|
-
export { Alert, AlertChannel,
|
|
307
|
+
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,
|
|
2
|
-
export { g as AlertType, E as ExpectRule,
|
|
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-DUt2apl0.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-DUt2apl0.js';
|
|
4
3
|
|
|
5
4
|
/** What a job function receives. */
|
|
6
5
|
interface JobContext {
|
|
@@ -20,8 +19,11 @@ 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
|
|
24
|
-
*
|
|
22
|
+
* Defaults to process.env.CRONWATCH_TOKEN; an empty string counts as unset.
|
|
23
|
+
* With no token while NODE_ENV is "development" or "test", the routes make
|
|
24
|
+
* a random one and print a sign-in link to the server log on their first
|
|
25
|
+
* request; with no token otherwise they answer 503. Pass `null` to opt out
|
|
26
|
+
* and serve them open everywhere, for example behind your own auth.
|
|
25
27
|
*
|
|
26
28
|
* The check endpoint (/api/check) also accepts the client's cronSecret, so
|
|
27
29
|
* a platform cron that sends `Authorization: Bearer <CRON_SECRET>` can
|
|
@@ -52,7 +54,9 @@ interface HandlerOptions {
|
|
|
52
54
|
/**
|
|
53
55
|
* Callers must send `Authorization: Bearer <secret>`. Defaults to the
|
|
54
56
|
* client's cronSecret, which defaults to process.env.CRON_SECRET (what
|
|
55
|
-
* Vercel sends its cron requests with).
|
|
57
|
+
* Vercel sends its cron requests with). An empty string counts as unset.
|
|
58
|
+
* With no secret at all the handler answers 503 unless NODE_ENV is
|
|
59
|
+
* "development" or "test". Pass null to allow anyone.
|
|
56
60
|
*/
|
|
57
61
|
secret?: string | null;
|
|
58
62
|
}
|
|
@@ -71,41 +75,68 @@ interface CronWatchOptions {
|
|
|
71
75
|
store?: Store;
|
|
72
76
|
/** Where alerts go. Defaults to the console. */
|
|
73
77
|
alerts?: AlertChannel[];
|
|
74
|
-
/** Adds a short diagnosis to
|
|
78
|
+
/** Adds a short diagnosis to every alert except recoveries. See @cronwatch/sdk/anthropic. */
|
|
75
79
|
triage?: TriageFn;
|
|
76
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Shared secret that handler() requests must carry. Defaults to
|
|
82
|
+
* process.env.CRON_SECRET; an empty string counts as unset. Pass null to
|
|
83
|
+
* let handlers run without one.
|
|
84
|
+
*/
|
|
77
85
|
cronSecret?: string | null;
|
|
78
86
|
/** How long finished runs are kept. Default "30d". */
|
|
79
87
|
retention?: Duration;
|
|
80
88
|
/** Applied to every job unless the job sets its own. */
|
|
81
89
|
defaults?: Pick<JobOptions, "grace" | "timeout" | "timezone" | "failuresBeforeAlert">;
|
|
82
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Applied to every run's output and error before it is stored, shown or
|
|
92
|
+
* sent to an alert channel or triage. The default blanks values that look
|
|
93
|
+
* like secrets (password=..., Authorization headers, URL credentials, bearer
|
|
94
|
+
* tokens, JWTs, PEM private keys, webhook URLs, AWS, GitHub, Slack, Stripe,
|
|
95
|
+
* Google and API key formats). Pass your own function, or false to keep
|
|
96
|
+
* output exactly as logged. A function that throws or returns something
|
|
97
|
+
* other than a string is reported to onError and the default is used.
|
|
98
|
+
*/
|
|
99
|
+
redact?: ((text: string) => string) | false;
|
|
100
|
+
/**
|
|
101
|
+
* "now" (the default) sends alerts from this process. "check" sends nothing
|
|
102
|
+
* from here: each alert is queued in the store and the next check, in a
|
|
103
|
+
* process that delivers now, sends it (with triage). For a process that
|
|
104
|
+
* records runs but cannot reach the network, such as a sandboxed backup job.
|
|
105
|
+
* Its `alerts` and `triage` are not used.
|
|
106
|
+
*/
|
|
107
|
+
deliver?: "now" | "check";
|
|
108
|
+
/** Called with anything that goes wrong outside a job: the store failing, an alert channel failing, a triage timeout. */
|
|
83
109
|
onError?: (error: unknown, where: string) => void;
|
|
84
110
|
/** The clock. Tests use this. */
|
|
85
111
|
now?: () => number;
|
|
86
112
|
}
|
|
87
|
-
interface ExecuteResult<T> {
|
|
88
|
-
run: Run;
|
|
89
|
-
result: T | undefined;
|
|
90
|
-
error: unknown;
|
|
91
|
-
threw: boolean;
|
|
92
|
-
}
|
|
93
113
|
declare class CronWatch {
|
|
94
114
|
readonly store: Store;
|
|
95
115
|
readonly alerts: AlertChannel[];
|
|
96
116
|
readonly triage: TriageFn | undefined;
|
|
117
|
+
/** The secret handler() requests must carry, or null when none is set. */
|
|
97
118
|
readonly cronSecret: string | null;
|
|
98
119
|
readonly retentionMs: number;
|
|
99
120
|
readonly now: () => number;
|
|
100
121
|
readonly onError: (error: unknown, where: string) => void;
|
|
122
|
+
/** cronSecret was passed as null: handlers may run without a secret. */
|
|
123
|
+
private readonly secretOptOut;
|
|
124
|
+
private readonly redact;
|
|
125
|
+
/** "check": queue alerts for another process's check instead of sending them. See CronWatchOptions.deliver. */
|
|
126
|
+
private readonly deferDelivery;
|
|
101
127
|
private readonly defaults;
|
|
102
128
|
private readonly definitions;
|
|
103
129
|
private readonly synced;
|
|
130
|
+
/** The tail of each job's queue of state updates. See serial(). */
|
|
131
|
+
private readonly queues;
|
|
104
132
|
private ready;
|
|
105
133
|
private checking;
|
|
106
134
|
private lastPruneAt;
|
|
107
135
|
private timer;
|
|
136
|
+
private firstTick;
|
|
108
137
|
private usingDefaultStore;
|
|
138
|
+
private warnedNoSecret;
|
|
139
|
+
private warnedDeferredStart;
|
|
109
140
|
constructor(options?: CronWatchOptions);
|
|
110
141
|
/** Declare a job. Call it once, at module level, and keep the handle. */
|
|
111
142
|
job(name: string, options?: JobOptions): JobHandle;
|
|
@@ -115,25 +146,80 @@ declare class CronWatch {
|
|
|
115
146
|
/** The definitions declared in this process. */
|
|
116
147
|
definedJobs(): JobDefinition[];
|
|
117
148
|
private handle;
|
|
149
|
+
private warnNoSecret;
|
|
150
|
+
/** onError, for places that must carry on even when onError itself throws. */
|
|
151
|
+
private report;
|
|
118
152
|
private ensureReady;
|
|
119
153
|
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
154
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
155
|
+
* Runs `fn` after every earlier state update for the same job has settled,
|
|
156
|
+
* so two runs (or a run and a check) in this process never read and write
|
|
157
|
+
* the job's state over each other. Other processes are coordinated by
|
|
158
|
+
* updateState() instead.
|
|
159
|
+
*/
|
|
160
|
+
private serial;
|
|
161
|
+
private readState;
|
|
162
|
+
/**
|
|
163
|
+
* Every read-modify-write of a job's state goes through here. In turn with
|
|
164
|
+
* this process's other updates to the job (serial()), it reads the state,
|
|
165
|
+
* asks `change` for the next one, and writes it with the version one
|
|
166
|
+
* higher, only if the stored version is still the one read. When another
|
|
167
|
+
* process wrote in between, the write is refused and it starts again from
|
|
168
|
+
* a fresh read, up to STATE_ATTEMPTS times. So `change` may run more than
|
|
169
|
+
* once and must only compute: whatever it returns from the attempt that
|
|
170
|
+
* was written is the result. Nothing is written when the state is
|
|
171
|
+
* unchanged. Returns the state as stored.
|
|
172
|
+
*/
|
|
173
|
+
private updateState;
|
|
174
|
+
/** A conditional write, or for a store without compareAndSetState, a plain one that always succeeds. */
|
|
175
|
+
private writeState;
|
|
176
|
+
/**
|
|
177
|
+
* Runs a function as a recorded run. The function always runs, whatever
|
|
178
|
+
* the store is doing: store errors go to onError, and the result is the
|
|
179
|
+
* function's own outcome. Never throws for the job's own error; see `threw`.
|
|
180
|
+
*/
|
|
181
|
+
private execute;
|
|
182
|
+
/** Whether a check already marked this run as timed out, for a failure that finished late. */
|
|
183
|
+
private markedTimedOut;
|
|
184
|
+
/**
|
|
185
|
+
* Record a finished run (ok, failed, or timed out by a check), evaluate it
|
|
186
|
+
* against the job's state and send what that produces. Never throws.
|
|
187
|
+
*/
|
|
188
|
+
private finishRun;
|
|
189
|
+
/**
|
|
190
|
+
* The runs before `run`, newest first, with up to BASELINE_WINDOW
|
|
191
|
+
* successful ones when the store has them. One small read normally; a
|
|
192
|
+
* larger one only when failures crowd the successes out of it.
|
|
193
|
+
*/
|
|
194
|
+
private history;
|
|
195
|
+
/**
|
|
196
|
+
* Look for missed and stuck runs across every job, send alerts, retry
|
|
197
|
+
* alerts no channel accepted, and prune old runs. Call it from an interval
|
|
198
|
+
* (start()), a cron hitting the mounted routes, or by hand. Concurrent
|
|
199
|
+
* calls share one check.
|
|
126
200
|
*/
|
|
127
201
|
check(): Promise<CheckResult>;
|
|
128
202
|
private runCheck;
|
|
203
|
+
/** A job's summary and its newest runs, without alerting. A job that cannot be evaluated is reported and shown as failing. */
|
|
204
|
+
private snapshot;
|
|
205
|
+
/** The summary of a job whose evaluation failed, from whatever can still be read. */
|
|
206
|
+
private unevaluable;
|
|
129
207
|
/** Every job the store knows about, with its health. Does not send alerts. */
|
|
130
208
|
jobs(): Promise<JobSummary[]>;
|
|
209
|
+
/** Every job's summary with its newest `limit` runs, read together. What the dashboard shows. */
|
|
210
|
+
jobsWithRuns(limit?: number): Promise<{
|
|
211
|
+
job: JobSummary;
|
|
212
|
+
runs: Run[];
|
|
213
|
+
}[]>;
|
|
131
214
|
jobSummary(name: string): Promise<JobSummary | null>;
|
|
215
|
+
/** A job's runs, newest first. `limit` is a whole number from 1 to 500. */
|
|
132
216
|
runs(name: string, limit?: number): Promise<Run[]>;
|
|
133
217
|
getRun(id: string): Promise<Run | null>;
|
|
134
218
|
/** Stop alerts for a job for a while. State keeps updating underneath. */
|
|
135
219
|
silence(name: string, duration: Duration): Promise<JobState>;
|
|
136
220
|
unsilence(name: string): Promise<JobState>;
|
|
221
|
+
/** Read, change and write one job's state, in turn with every other update to it. */
|
|
222
|
+
private patchState;
|
|
137
223
|
/** Remove a job and its runs from the store. A job still declared in code comes back on its next run. */
|
|
138
224
|
forget(name: string): Promise<void>;
|
|
139
225
|
/** The dashboard and JSON API as fetch-style handlers. See createRoutes(). */
|
|
@@ -142,18 +228,36 @@ declare class CronWatch {
|
|
|
142
228
|
start(every?: Duration): void;
|
|
143
229
|
stop(): void;
|
|
144
230
|
close(): Promise<void>;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Compose, triage and send each draft. The state was saved before this
|
|
233
|
+
* (updateState), so a slow channel holds up nothing else; afterwards only
|
|
234
|
+
* the delivery fields are written back, onto a fresh read of the state.
|
|
235
|
+
*/
|
|
148
236
|
private dispatch;
|
|
237
|
+
/**
|
|
238
|
+
* Send the alerts that no channel accepted last time, once each, oldest
|
|
239
|
+
* first. `state` is the job's state as this check left it: an alert that
|
|
240
|
+
* no longer describes it (staleAlert) is dropped instead. Retries across a
|
|
241
|
+
* check share RETRY_BUDGET_MS of wall-clock time; once it is spent the rest
|
|
242
|
+
* stay queued for the next check.
|
|
243
|
+
*/
|
|
244
|
+
private retryUndelivered;
|
|
245
|
+
/**
|
|
246
|
+
* Mark delivered alerts done, drop stale ones, and keep failed ones for the
|
|
247
|
+
* next check. A failed alert replaces its stored copy, so a triage made on
|
|
248
|
+
* this attempt is kept. lastAlertAt moves only on a delivery.
|
|
249
|
+
*/
|
|
250
|
+
private recordDelivery;
|
|
251
|
+
/** Send to every channel at once. True when at least one accepted it, or there are none. */
|
|
252
|
+
private deliver;
|
|
253
|
+
/** Sets alert.triage to the diagnosis, or to null when there is none, so it is tried once per alert. */
|
|
254
|
+
private addTriage;
|
|
149
255
|
}
|
|
150
256
|
/** Writes alerts to the console. The default channel. */
|
|
151
257
|
declare function consoleChannel(): AlertChannel;
|
|
152
258
|
/** Wrap any function as an alert channel. */
|
|
153
259
|
declare function custom(name: string, send: (alert: Alert) => Promise<void> | void): AlertChannel;
|
|
154
260
|
|
|
155
|
-
declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
|
|
156
|
-
|
|
157
261
|
/**
|
|
158
262
|
* Keeps everything in process memory. The default when no store is given,
|
|
159
263
|
* good for tests and for trying the library out. State is gone on restart,
|
|
@@ -168,15 +272,14 @@ declare function memory(): Store;
|
|
|
168
272
|
declare function parseDuration(value: Duration, label?: string): number;
|
|
169
273
|
/** 90000 -> "1m 30s". For messages, not for parsing back. */
|
|
170
274
|
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
275
|
|
|
174
276
|
interface ParsedSchedule {
|
|
175
277
|
kind: "cron" | "interval";
|
|
176
278
|
source: string;
|
|
279
|
+
/** The IANA timezone a cron is read in, when one was given. */
|
|
280
|
+
timezone?: string;
|
|
177
281
|
/** For intervals, the period in milliseconds. */
|
|
178
282
|
everyMs?: number;
|
|
179
|
-
cron?: Cron;
|
|
180
283
|
}
|
|
181
284
|
/**
|
|
182
285
|
* "0 2 * * *" (cron, five or six fields), "@hourly", or "every 5m".
|
|
@@ -187,23 +290,8 @@ interface ParsedSchedule {
|
|
|
187
290
|
* timezone: "UTC" for those.
|
|
188
291
|
*/
|
|
189
292
|
declare function parseSchedule(schedule: string, timezone?: string): ParsedSchedule;
|
|
190
|
-
/** The next time the
|
|
293
|
+
/** The next time the schedule fires strictly after `from`. For an interval, counted from the last run when there is one. */
|
|
191
294
|
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
295
|
|
|
208
296
|
/** Turns a draft into the title and message every channel shows. */
|
|
209
297
|
declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now: number): Alert;
|
|
@@ -216,4 +304,4 @@ declare function composeAlert(draft: AlertDraft, def: StoredJobDefinition, now:
|
|
|
216
304
|
*/
|
|
217
305
|
declare function cronwatch(options?: CronWatchOptions): CronWatch;
|
|
218
306
|
|
|
219
|
-
export { Alert, AlertChannel,
|
|
307
|
+
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 };
|