@cronwatch/sdk 0.1.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 +69 -0
- package/dist/anthropic.cjs +84 -0
- package/dist/anthropic.cjs.map +1 -0
- package/dist/anthropic.d.cts +35 -0
- package/dist/anthropic.d.ts +35 -0
- package/dist/anthropic.js +78 -0
- package/dist/anthropic.js.map +1 -0
- package/dist/discord.cjs +42 -0
- package/dist/discord.cjs.map +1 -0
- package/dist/discord.d.cts +11 -0
- package/dist/discord.d.ts +11 -0
- package/dist/discord.js +40 -0
- package/dist/discord.js.map +1 -0
- package/dist/index.cjs +1317 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +219 -0
- package/dist/index.d.ts +219 -0
- package/dist/index.js +1302 -0
- package/dist/index.js.map +1 -0
- package/dist/postgres.cjs +142 -0
- package/dist/postgres.cjs.map +1 -0
- package/dist/postgres.d.cts +19 -0
- package/dist/postgres.d.ts +19 -0
- package/dist/postgres.js +136 -0
- package/dist/postgres.js.map +1 -0
- package/dist/slack.cjs +44 -0
- package/dist/slack.cjs.map +1 -0
- package/dist/slack.d.cts +12 -0
- package/dist/slack.d.ts +12 -0
- package/dist/slack.js +42 -0
- package/dist/slack.js.map +1 -0
- package/dist/sqlite.cjs +151 -0
- package/dist/sqlite.cjs.map +1 -0
- package/dist/sqlite.d.cts +17 -0
- package/dist/sqlite.d.ts +17 -0
- package/dist/sqlite.js +144 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/types-BQ1P8z55.d.cts +159 -0
- package/dist/types-BQ1P8z55.d.ts +159 -0
- package/dist/webhook.cjs +24 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.cts +20 -0
- package/dist/webhook.d.ts +20 -0
- package/dist/webhook.js +22 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +114 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stores/sqlite.ts"],"names":[],"mappings":"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","file":"sqlite.js","sourcesContent":["import Database from \"better-sqlite3\";\nimport { chmodSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { JobState, Run, Store, StoredJob, StoredJobDefinition } from \"../types.js\";\n\nexport interface SqliteOptions {\n /** File path. The directory is created if missing. \":memory:\" works too. */\n path?: string;\n /** Bring your own open better-sqlite3 database instead. */\n database?: Database.Database;\n}\n\ninterface JobRow { name: string; definition: string; created_at: number; updated_at: number }\ninterface RunRow {\n id: string; job: string; status: Run[\"status\"]; started_at: number; finished_at: number | null;\n duration_ms: number | null; error: string | null; output: string | null; metrics: string; trigger: string;\n}\n\nfunction rowToRun(r: RunRow): Run {\n return {\n id: r.id, job: r.job, status: r.status, startedAt: r.started_at, finishedAt: r.finished_at,\n durationMs: r.duration_ms, error: r.error, output: r.output, metrics: JSON.parse(r.metrics), trigger: r.trigger,\n };\n}\n\n/**\n * Keeps everything in one SQLite file through better-sqlite3. The right\n * choice for a single server. WAL mode, so the app's reads never block on a\n * run being written.\n */\nexport function sqlite(options: SqliteOptions = {}): Store {\n let db: Database.Database | null = options.database ?? null;\n const file = options.path ?? \"./data/cronwatch.db\";\n\n const open = (): Database.Database => {\n if (db) return db;\n if (file !== \":memory:\") {\n mkdirSync(path.dirname(file), { recursive: true });\n }\n db = new Database(file);\n if (file !== \":memory:\") {\n try { chmodSync(file, 0o600); } catch { /* best effort: not every filesystem cares */ }\n }\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"busy_timeout = 5000\");\n db.pragma(\"synchronous = NORMAL\");\n return db;\n };\n\n return {\n async init() {\n const d = open();\n d.exec(`\n CREATE TABLE IF NOT EXISTS cronwatch_jobs (\n name TEXT PRIMARY KEY,\n definition TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS cronwatch_runs (\n id TEXT PRIMARY KEY,\n job TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at INTEGER NOT NULL,\n finished_at INTEGER,\n duration_ms INTEGER,\n error TEXT,\n output TEXT,\n metrics TEXT NOT NULL DEFAULT '{}',\n trigger TEXT NOT NULL DEFAULT 'run'\n );\n CREATE INDEX IF NOT EXISTS cronwatch_runs_job_started ON cronwatch_runs (job, started_at DESC);\n CREATE INDEX IF NOT EXISTS cronwatch_runs_running ON cronwatch_runs (status) WHERE status = 'running';\n CREATE TABLE IF NOT EXISTS cronwatch_state (\n job TEXT PRIMARY KEY,\n state TEXT NOT NULL\n );\n `);\n },\n async upsertJob(definition: StoredJobDefinition, now: number) {\n open().prepare(`\n INSERT INTO cronwatch_jobs (name, definition, created_at, updated_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(name) DO UPDATE SET definition = excluded.definition, updated_at = excluded.updated_at\n `).run(definition.name, JSON.stringify(definition), now, now);\n },\n async getJob(name) {\n const r = open().prepare(\"SELECT * FROM cronwatch_jobs WHERE name = ?\").get(name) as JobRow | undefined;\n return r ? { name: r.name, definition: JSON.parse(r.definition), createdAt: r.created_at, updatedAt: r.updated_at } : null;\n },\n async listJobs(): Promise<StoredJob[]> {\n const rows = open().prepare(\"SELECT * FROM cronwatch_jobs ORDER BY name\").all() as JobRow[];\n return rows.map((r) => ({ name: r.name, definition: JSON.parse(r.definition), createdAt: r.created_at, updatedAt: r.updated_at }));\n },\n async deleteJob(name) {\n const d = open();\n d.transaction(() => {\n d.prepare(\"DELETE FROM cronwatch_runs WHERE job = ?\").run(name);\n d.prepare(\"DELETE FROM cronwatch_state WHERE job = ?\").run(name);\n d.prepare(\"DELETE FROM cronwatch_jobs WHERE name = ?\").run(name);\n })();\n },\n async insertRun(run) {\n open().prepare(`\n INSERT INTO cronwatch_runs (id, job, status, started_at, finished_at, duration_ms, error, output, metrics, trigger)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(run.id, run.job, run.status, run.startedAt, run.finishedAt, run.durationMs, run.error, run.output, JSON.stringify(run.metrics), run.trigger);\n },\n async updateRun(run) {\n open().prepare(`\n UPDATE cronwatch_runs SET status = ?, finished_at = ?, duration_ms = ?, error = ?, output = ?, metrics = ? WHERE id = ?\n `).run(run.status, run.finishedAt, run.durationMs, run.error, run.output, JSON.stringify(run.metrics), run.id);\n },\n async getRun(id) {\n const r = open().prepare(\"SELECT * FROM cronwatch_runs WHERE id = ?\").get(id) as RunRow | undefined;\n return r ? rowToRun(r) : null;\n },\n async listRuns(job, limit) {\n const rows = open().prepare(\"SELECT * FROM cronwatch_runs WHERE job = ? ORDER BY started_at DESC, rowid DESC LIMIT ?\").all(job, limit) as RunRow[];\n return rows.map(rowToRun);\n },\n async lastRun(job) {\n const r = open().prepare(\"SELECT * FROM cronwatch_runs WHERE job = ? ORDER BY started_at DESC, rowid DESC LIMIT 1\").get(job) as RunRow | undefined;\n return r ? rowToRun(r) : null;\n },\n async runningRuns() {\n const rows = open().prepare(\"SELECT * FROM cronwatch_runs WHERE status = 'running' ORDER BY started_at\").all() as RunRow[];\n return rows.map(rowToRun);\n },\n async getState(job) {\n const r = open().prepare(\"SELECT state FROM cronwatch_state WHERE job = ?\").get(job) as { state: string } | undefined;\n return r ? (JSON.parse(r.state) as JobState) : null;\n },\n async setState(state) {\n open().prepare(`\n INSERT INTO cronwatch_state (job, state) VALUES (?, ?)\n ON CONFLICT(job) DO UPDATE SET state = excluded.state\n `).run(state.job, JSON.stringify(state));\n },\n async prune(before) {\n const info = open().prepare(\"DELETE FROM cronwatch_runs WHERE status != 'running' AND started_at < ?\").run(before);\n return info.changes;\n },\n async close() {\n if (db && !options.database) {\n db.close();\n db = null;\n }\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything public about a job, a run and an alert. Kept in one file so the
|
|
3
|
+
* store and alert adapters, which are built as separate entry points, share
|
|
4
|
+
* one definition.
|
|
5
|
+
*/
|
|
6
|
+
/** "15m", "1h30m", "90s", "2d", or a number of milliseconds. */
|
|
7
|
+
type Duration = string | number;
|
|
8
|
+
type ExpectRule = string | RegExp | ((output: string) => boolean);
|
|
9
|
+
interface JobOptions {
|
|
10
|
+
/**
|
|
11
|
+
* When the job is supposed to run. A five or six field cron expression
|
|
12
|
+
* ("0 2 * * *"), a cron nickname ("@hourly"), or an interval ("every 5m").
|
|
13
|
+
* Leave it out for a job that has no fixed cadence: failures, duration and
|
|
14
|
+
* budgets are still watched, but nothing is ever reported as missed.
|
|
15
|
+
*/
|
|
16
|
+
schedule?: string;
|
|
17
|
+
/** IANA timezone the cron expression is read in. Defaults to the process timezone. */
|
|
18
|
+
timezone?: string;
|
|
19
|
+
/** How late a run may start before it counts as missed. Default "10m". */
|
|
20
|
+
grace?: Duration;
|
|
21
|
+
/** A run still going after this long is treated as stuck and marked timeout. Default "1h". */
|
|
22
|
+
timeout?: Duration;
|
|
23
|
+
/**
|
|
24
|
+
* Alert when a successful run takes longer than this. Without it, a run is
|
|
25
|
+
* slow when it takes more than twice the p95 of recent runs (and over 10s),
|
|
26
|
+
* once there are at least five runs to compare against.
|
|
27
|
+
*/
|
|
28
|
+
maxDuration?: Duration;
|
|
29
|
+
/**
|
|
30
|
+
* Ceilings for metrics reported with job.metric(). { cost: 2 } alerts when a
|
|
31
|
+
* run reports cost above 2. Metrics without a ceiling alert when a run
|
|
32
|
+
* reports more than three times the recent median, once there are at least
|
|
33
|
+
* five runs to compare against.
|
|
34
|
+
*/
|
|
35
|
+
budget?: Record<string, number>;
|
|
36
|
+
/**
|
|
37
|
+
* A successful run must produce output that satisfies this, or it counts as
|
|
38
|
+
* failed. A string must appear in the output, a RegExp must match it, and a
|
|
39
|
+
* function must return true for it. Catches the job that exits cleanly and
|
|
40
|
+
* did nothing.
|
|
41
|
+
*/
|
|
42
|
+
expect?: ExpectRule;
|
|
43
|
+
/** Alert on the Nth consecutive failure rather than the first. Default 1. */
|
|
44
|
+
failuresBeforeAlert?: number;
|
|
45
|
+
description?: string;
|
|
46
|
+
tags?: string[];
|
|
47
|
+
}
|
|
48
|
+
interface JobDefinition extends JobOptions {
|
|
49
|
+
name: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The definition as it is written to a store. `expect` cannot be serialised
|
|
53
|
+
* when it is a RegExp or a function, so it is described instead.
|
|
54
|
+
*/
|
|
55
|
+
interface StoredJobDefinition extends Omit<JobDefinition, "expect"> {
|
|
56
|
+
expect?: string;
|
|
57
|
+
}
|
|
58
|
+
interface StoredJob {
|
|
59
|
+
name: string;
|
|
60
|
+
definition: StoredJobDefinition;
|
|
61
|
+
createdAt: number;
|
|
62
|
+
updatedAt: number;
|
|
63
|
+
}
|
|
64
|
+
type RunStatus = "running" | "ok" | "failed" | "timeout";
|
|
65
|
+
interface Run {
|
|
66
|
+
id: string;
|
|
67
|
+
job: string;
|
|
68
|
+
status: RunStatus;
|
|
69
|
+
/** Epoch milliseconds. */
|
|
70
|
+
startedAt: number;
|
|
71
|
+
finishedAt: number | null;
|
|
72
|
+
durationMs: number | null;
|
|
73
|
+
error: string | null;
|
|
74
|
+
/** Lines written with job.log(), or the string the job returned. Capped at 16 KB. */
|
|
75
|
+
output: string | null;
|
|
76
|
+
metrics: Record<string, number>;
|
|
77
|
+
/** What started the run: "handler", "run" or a value you pass. */
|
|
78
|
+
trigger: string;
|
|
79
|
+
}
|
|
80
|
+
type Condition = "missed" | "failed" | "stuck" | "slow" | "over_budget";
|
|
81
|
+
interface JobState {
|
|
82
|
+
job: string;
|
|
83
|
+
/** Conditions currently open, with the time each one opened. */
|
|
84
|
+
open: Partial<Record<Condition, number>>;
|
|
85
|
+
consecutiveFailures: number;
|
|
86
|
+
silencedUntil: number | null;
|
|
87
|
+
lastAlertAt: number | null;
|
|
88
|
+
}
|
|
89
|
+
type AlertType = Condition | "recovered";
|
|
90
|
+
interface Alert {
|
|
91
|
+
type: AlertType;
|
|
92
|
+
job: string;
|
|
93
|
+
definition: StoredJobDefinition;
|
|
94
|
+
run: Run | null;
|
|
95
|
+
/** One line, suitable as a notification title. */
|
|
96
|
+
title: string;
|
|
97
|
+
/** A few lines of plain text with the specifics. */
|
|
98
|
+
message: string;
|
|
99
|
+
details: Record<string, unknown>;
|
|
100
|
+
/** A short diagnosis from the triage function, when one is configured. */
|
|
101
|
+
triage?: string;
|
|
102
|
+
at: number;
|
|
103
|
+
}
|
|
104
|
+
interface AlertChannel {
|
|
105
|
+
name: string;
|
|
106
|
+
send(alert: Alert): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
interface Store {
|
|
109
|
+
/** Called once before first use. Create tables here. */
|
|
110
|
+
init?(): Promise<void>;
|
|
111
|
+
upsertJob(definition: StoredJobDefinition, now: number): Promise<void>;
|
|
112
|
+
getJob(name: string): Promise<StoredJob | null>;
|
|
113
|
+
listJobs(): Promise<StoredJob[]>;
|
|
114
|
+
deleteJob(name: string): Promise<void>;
|
|
115
|
+
insertRun(run: Run): Promise<void>;
|
|
116
|
+
updateRun(run: Run): Promise<void>;
|
|
117
|
+
getRun(id: string): Promise<Run | null>;
|
|
118
|
+
/** Newest first. */
|
|
119
|
+
listRuns(job: string, limit: number): Promise<Run[]>;
|
|
120
|
+
lastRun(job: string): Promise<Run | null>;
|
|
121
|
+
runningRuns(): Promise<Run[]>;
|
|
122
|
+
getState(job: string): Promise<JobState | null>;
|
|
123
|
+
setState(state: JobState): Promise<void>;
|
|
124
|
+
/** Delete finished runs that started before this time. Returns how many. */
|
|
125
|
+
prune(before: number): Promise<number>;
|
|
126
|
+
close?(): Promise<void>;
|
|
127
|
+
}
|
|
128
|
+
type JobHealth = "healthy" | "late" | "failing" | "stuck" | "silenced" | "never_ran";
|
|
129
|
+
interface JobSummary {
|
|
130
|
+
name: string;
|
|
131
|
+
definition: StoredJobDefinition;
|
|
132
|
+
health: JobHealth;
|
|
133
|
+
open: Condition[];
|
|
134
|
+
lastRun: Run | null;
|
|
135
|
+
/** When the schedule says the next run is due. Null without a schedule. */
|
|
136
|
+
nextExpectedAt: number | null;
|
|
137
|
+
consecutiveFailures: number;
|
|
138
|
+
silencedUntil: number | null;
|
|
139
|
+
/** From the last twenty successful runs. */
|
|
140
|
+
stats: {
|
|
141
|
+
runs: number;
|
|
142
|
+
okRate: number;
|
|
143
|
+
p50Ms: number | null;
|
|
144
|
+
p95Ms: number | null;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
interface CheckResult {
|
|
148
|
+
checkedAt: number;
|
|
149
|
+
jobs: JobSummary[];
|
|
150
|
+
alerts: Alert[];
|
|
151
|
+
pruned: number;
|
|
152
|
+
}
|
|
153
|
+
interface TriageContext {
|
|
154
|
+
alert: Alert;
|
|
155
|
+
recentRuns: Run[];
|
|
156
|
+
}
|
|
157
|
+
type TriageFn = (context: TriageContext) => Promise<string | null>;
|
|
158
|
+
|
|
159
|
+
export type { Alert as A, CheckResult as C, Duration as D, ExpectRule as E, JobOptions as J, Run as R, Store as S, TriageFn as T, AlertChannel as a, JobDefinition as b, JobSummary as c, JobState as d, Condition as e, StoredJobDefinition as f, AlertType as g, JobHealth as h, RunStatus as i, StoredJob as j, TriageContext as k };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything public about a job, a run and an alert. Kept in one file so the
|
|
3
|
+
* store and alert adapters, which are built as separate entry points, share
|
|
4
|
+
* one definition.
|
|
5
|
+
*/
|
|
6
|
+
/** "15m", "1h30m", "90s", "2d", or a number of milliseconds. */
|
|
7
|
+
type Duration = string | number;
|
|
8
|
+
type ExpectRule = string | RegExp | ((output: string) => boolean);
|
|
9
|
+
interface JobOptions {
|
|
10
|
+
/**
|
|
11
|
+
* When the job is supposed to run. A five or six field cron expression
|
|
12
|
+
* ("0 2 * * *"), a cron nickname ("@hourly"), or an interval ("every 5m").
|
|
13
|
+
* Leave it out for a job that has no fixed cadence: failures, duration and
|
|
14
|
+
* budgets are still watched, but nothing is ever reported as missed.
|
|
15
|
+
*/
|
|
16
|
+
schedule?: string;
|
|
17
|
+
/** IANA timezone the cron expression is read in. Defaults to the process timezone. */
|
|
18
|
+
timezone?: string;
|
|
19
|
+
/** How late a run may start before it counts as missed. Default "10m". */
|
|
20
|
+
grace?: Duration;
|
|
21
|
+
/** A run still going after this long is treated as stuck and marked timeout. Default "1h". */
|
|
22
|
+
timeout?: Duration;
|
|
23
|
+
/**
|
|
24
|
+
* Alert when a successful run takes longer than this. Without it, a run is
|
|
25
|
+
* slow when it takes more than twice the p95 of recent runs (and over 10s),
|
|
26
|
+
* once there are at least five runs to compare against.
|
|
27
|
+
*/
|
|
28
|
+
maxDuration?: Duration;
|
|
29
|
+
/**
|
|
30
|
+
* Ceilings for metrics reported with job.metric(). { cost: 2 } alerts when a
|
|
31
|
+
* run reports cost above 2. Metrics without a ceiling alert when a run
|
|
32
|
+
* reports more than three times the recent median, once there are at least
|
|
33
|
+
* five runs to compare against.
|
|
34
|
+
*/
|
|
35
|
+
budget?: Record<string, number>;
|
|
36
|
+
/**
|
|
37
|
+
* A successful run must produce output that satisfies this, or it counts as
|
|
38
|
+
* failed. A string must appear in the output, a RegExp must match it, and a
|
|
39
|
+
* function must return true for it. Catches the job that exits cleanly and
|
|
40
|
+
* did nothing.
|
|
41
|
+
*/
|
|
42
|
+
expect?: ExpectRule;
|
|
43
|
+
/** Alert on the Nth consecutive failure rather than the first. Default 1. */
|
|
44
|
+
failuresBeforeAlert?: number;
|
|
45
|
+
description?: string;
|
|
46
|
+
tags?: string[];
|
|
47
|
+
}
|
|
48
|
+
interface JobDefinition extends JobOptions {
|
|
49
|
+
name: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The definition as it is written to a store. `expect` cannot be serialised
|
|
53
|
+
* when it is a RegExp or a function, so it is described instead.
|
|
54
|
+
*/
|
|
55
|
+
interface StoredJobDefinition extends Omit<JobDefinition, "expect"> {
|
|
56
|
+
expect?: string;
|
|
57
|
+
}
|
|
58
|
+
interface StoredJob {
|
|
59
|
+
name: string;
|
|
60
|
+
definition: StoredJobDefinition;
|
|
61
|
+
createdAt: number;
|
|
62
|
+
updatedAt: number;
|
|
63
|
+
}
|
|
64
|
+
type RunStatus = "running" | "ok" | "failed" | "timeout";
|
|
65
|
+
interface Run {
|
|
66
|
+
id: string;
|
|
67
|
+
job: string;
|
|
68
|
+
status: RunStatus;
|
|
69
|
+
/** Epoch milliseconds. */
|
|
70
|
+
startedAt: number;
|
|
71
|
+
finishedAt: number | null;
|
|
72
|
+
durationMs: number | null;
|
|
73
|
+
error: string | null;
|
|
74
|
+
/** Lines written with job.log(), or the string the job returned. Capped at 16 KB. */
|
|
75
|
+
output: string | null;
|
|
76
|
+
metrics: Record<string, number>;
|
|
77
|
+
/** What started the run: "handler", "run" or a value you pass. */
|
|
78
|
+
trigger: string;
|
|
79
|
+
}
|
|
80
|
+
type Condition = "missed" | "failed" | "stuck" | "slow" | "over_budget";
|
|
81
|
+
interface JobState {
|
|
82
|
+
job: string;
|
|
83
|
+
/** Conditions currently open, with the time each one opened. */
|
|
84
|
+
open: Partial<Record<Condition, number>>;
|
|
85
|
+
consecutiveFailures: number;
|
|
86
|
+
silencedUntil: number | null;
|
|
87
|
+
lastAlertAt: number | null;
|
|
88
|
+
}
|
|
89
|
+
type AlertType = Condition | "recovered";
|
|
90
|
+
interface Alert {
|
|
91
|
+
type: AlertType;
|
|
92
|
+
job: string;
|
|
93
|
+
definition: StoredJobDefinition;
|
|
94
|
+
run: Run | null;
|
|
95
|
+
/** One line, suitable as a notification title. */
|
|
96
|
+
title: string;
|
|
97
|
+
/** A few lines of plain text with the specifics. */
|
|
98
|
+
message: string;
|
|
99
|
+
details: Record<string, unknown>;
|
|
100
|
+
/** A short diagnosis from the triage function, when one is configured. */
|
|
101
|
+
triage?: string;
|
|
102
|
+
at: number;
|
|
103
|
+
}
|
|
104
|
+
interface AlertChannel {
|
|
105
|
+
name: string;
|
|
106
|
+
send(alert: Alert): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
interface Store {
|
|
109
|
+
/** Called once before first use. Create tables here. */
|
|
110
|
+
init?(): Promise<void>;
|
|
111
|
+
upsertJob(definition: StoredJobDefinition, now: number): Promise<void>;
|
|
112
|
+
getJob(name: string): Promise<StoredJob | null>;
|
|
113
|
+
listJobs(): Promise<StoredJob[]>;
|
|
114
|
+
deleteJob(name: string): Promise<void>;
|
|
115
|
+
insertRun(run: Run): Promise<void>;
|
|
116
|
+
updateRun(run: Run): Promise<void>;
|
|
117
|
+
getRun(id: string): Promise<Run | null>;
|
|
118
|
+
/** Newest first. */
|
|
119
|
+
listRuns(job: string, limit: number): Promise<Run[]>;
|
|
120
|
+
lastRun(job: string): Promise<Run | null>;
|
|
121
|
+
runningRuns(): Promise<Run[]>;
|
|
122
|
+
getState(job: string): Promise<JobState | null>;
|
|
123
|
+
setState(state: JobState): Promise<void>;
|
|
124
|
+
/** Delete finished runs that started before this time. Returns how many. */
|
|
125
|
+
prune(before: number): Promise<number>;
|
|
126
|
+
close?(): Promise<void>;
|
|
127
|
+
}
|
|
128
|
+
type JobHealth = "healthy" | "late" | "failing" | "stuck" | "silenced" | "never_ran";
|
|
129
|
+
interface JobSummary {
|
|
130
|
+
name: string;
|
|
131
|
+
definition: StoredJobDefinition;
|
|
132
|
+
health: JobHealth;
|
|
133
|
+
open: Condition[];
|
|
134
|
+
lastRun: Run | null;
|
|
135
|
+
/** When the schedule says the next run is due. Null without a schedule. */
|
|
136
|
+
nextExpectedAt: number | null;
|
|
137
|
+
consecutiveFailures: number;
|
|
138
|
+
silencedUntil: number | null;
|
|
139
|
+
/** From the last twenty successful runs. */
|
|
140
|
+
stats: {
|
|
141
|
+
runs: number;
|
|
142
|
+
okRate: number;
|
|
143
|
+
p50Ms: number | null;
|
|
144
|
+
p95Ms: number | null;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
interface CheckResult {
|
|
148
|
+
checkedAt: number;
|
|
149
|
+
jobs: JobSummary[];
|
|
150
|
+
alerts: Alert[];
|
|
151
|
+
pruned: number;
|
|
152
|
+
}
|
|
153
|
+
interface TriageContext {
|
|
154
|
+
alert: Alert;
|
|
155
|
+
recentRuns: Run[];
|
|
156
|
+
}
|
|
157
|
+
type TriageFn = (context: TriageContext) => Promise<string | null>;
|
|
158
|
+
|
|
159
|
+
export type { Alert as A, CheckResult as C, Duration as D, ExpectRule as E, JobOptions as J, Run as R, Store as S, TriageFn as T, AlertChannel as a, JobDefinition as b, JobSummary as c, JobState as d, Condition as e, StoredJobDefinition as f, AlertType as g, JobHealth as h, RunStatus as i, StoredJob as j, TriageContext as k };
|
package/dist/webhook.cjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// src/alerts/webhook.ts
|
|
6
|
+
function webhook(options) {
|
|
7
|
+
if (!options.url) throw new Error("webhook() needs a url");
|
|
8
|
+
return {
|
|
9
|
+
name: "webhook",
|
|
10
|
+
async send(alert) {
|
|
11
|
+
const body = JSON.stringify(alert);
|
|
12
|
+
const headers = { "content-type": "application/json", "user-agent": "cronwatch", ...options.headers };
|
|
13
|
+
if (options.secret) {
|
|
14
|
+
headers["x-cronwatch-signature"] = `sha256=${crypto.createHmac("sha256", options.secret).update(body).digest("hex")}`;
|
|
15
|
+
}
|
|
16
|
+
const response = await fetch(options.url, { method: "POST", headers, body });
|
|
17
|
+
if (!response.ok) throw new Error(`Webhook ${options.url} answered ${response.status}`);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.webhook = webhook;
|
|
23
|
+
//# sourceMappingURL=webhook.cjs.map
|
|
24
|
+
//# sourceMappingURL=webhook.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/alerts/webhook.ts"],"names":["createHmac"],"mappings":";;;;;AAmBO,SAAS,QAAQ,OAAA,EAAuC;AAC7D,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,EAAK,MAAM,IAAI,MAAM,uBAAuB,CAAA;AACzD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,MAAM,KAAK,KAAA,EAAO;AAChB,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACjC,MAAA,MAAM,OAAA,GAAkC,EAAE,cAAA,EAAgB,kBAAA,EAAoB,cAAc,WAAA,EAAa,GAAG,QAAQ,OAAA,EAAQ;AAC5H,MAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,QAAA,OAAA,CAAQ,uBAAuB,CAAA,GAAI,CAAA,OAAA,EAAUA,iBAAA,CAAW,QAAA,EAAU,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAAA,MAC9G;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,CAAA;AAC3E,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,OAAA,CAAQ,GAAG,CAAA,UAAA,EAAa,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACxF;AAAA,GACF;AACF","file":"webhook.cjs","sourcesContent":["import { createHmac } from \"node:crypto\";\nimport type { AlertChannel } from \"../types.js\";\n\nexport interface WebhookOptions {\n url: string;\n /** Extra request headers, for an Authorization header say. */\n headers?: Record<string, string>;\n /**\n * When set, each request carries `X-CronWatch-Signature: sha256=<hex>`,\n * the HMAC-SHA256 of the raw body with this secret, so the receiver can\n * verify it.\n */\n secret?: string;\n}\n\n/**\n * POSTs the alert as JSON to any URL. The body is the Alert object:\n * { type, job, title, message, run, details, triage, at, definition }.\n */\nexport function webhook(options: WebhookOptions): AlertChannel {\n if (!options.url) throw new Error(\"webhook() needs a url\");\n return {\n name: \"webhook\",\n async send(alert) {\n const body = JSON.stringify(alert);\n const headers: Record<string, string> = { \"content-type\": \"application/json\", \"user-agent\": \"cronwatch\", ...options.headers };\n if (options.secret) {\n headers[\"x-cronwatch-signature\"] = `sha256=${createHmac(\"sha256\", options.secret).update(body).digest(\"hex\")}`;\n }\n const response = await fetch(options.url, { method: \"POST\", headers, body });\n if (!response.ok) throw new Error(`Webhook ${options.url} answered ${response.status}`);\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { a as AlertChannel } from './types-BQ1P8z55.cjs';
|
|
2
|
+
|
|
3
|
+
interface WebhookOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
/** Extra request headers, for an Authorization header say. */
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* When set, each request carries `X-CronWatch-Signature: sha256=<hex>`,
|
|
9
|
+
* the HMAC-SHA256 of the raw body with this secret, so the receiver can
|
|
10
|
+
* verify it.
|
|
11
|
+
*/
|
|
12
|
+
secret?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* POSTs the alert as JSON to any URL. The body is the Alert object:
|
|
16
|
+
* { type, job, title, message, run, details, triage, at, definition }.
|
|
17
|
+
*/
|
|
18
|
+
declare function webhook(options: WebhookOptions): AlertChannel;
|
|
19
|
+
|
|
20
|
+
export { type WebhookOptions, webhook };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { a as AlertChannel } from './types-BQ1P8z55.js';
|
|
2
|
+
|
|
3
|
+
interface WebhookOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
/** Extra request headers, for an Authorization header say. */
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* When set, each request carries `X-CronWatch-Signature: sha256=<hex>`,
|
|
9
|
+
* the HMAC-SHA256 of the raw body with this secret, so the receiver can
|
|
10
|
+
* verify it.
|
|
11
|
+
*/
|
|
12
|
+
secret?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* POSTs the alert as JSON to any URL. The body is the Alert object:
|
|
16
|
+
* { type, job, title, message, run, details, triage, at, definition }.
|
|
17
|
+
*/
|
|
18
|
+
declare function webhook(options: WebhookOptions): AlertChannel;
|
|
19
|
+
|
|
20
|
+
export { type WebhookOptions, webhook };
|
package/dist/webhook.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHmac } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/alerts/webhook.ts
|
|
4
|
+
function webhook(options) {
|
|
5
|
+
if (!options.url) throw new Error("webhook() needs a url");
|
|
6
|
+
return {
|
|
7
|
+
name: "webhook",
|
|
8
|
+
async send(alert) {
|
|
9
|
+
const body = JSON.stringify(alert);
|
|
10
|
+
const headers = { "content-type": "application/json", "user-agent": "cronwatch", ...options.headers };
|
|
11
|
+
if (options.secret) {
|
|
12
|
+
headers["x-cronwatch-signature"] = `sha256=${createHmac("sha256", options.secret).update(body).digest("hex")}`;
|
|
13
|
+
}
|
|
14
|
+
const response = await fetch(options.url, { method: "POST", headers, body });
|
|
15
|
+
if (!response.ok) throw new Error(`Webhook ${options.url} answered ${response.status}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { webhook };
|
|
21
|
+
//# sourceMappingURL=webhook.js.map
|
|
22
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/alerts/webhook.ts"],"names":[],"mappings":";;;AAmBO,SAAS,QAAQ,OAAA,EAAuC;AAC7D,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,EAAK,MAAM,IAAI,MAAM,uBAAuB,CAAA;AACzD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,MAAM,KAAK,KAAA,EAAO;AAChB,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACjC,MAAA,MAAM,OAAA,GAAkC,EAAE,cAAA,EAAgB,kBAAA,EAAoB,cAAc,WAAA,EAAa,GAAG,QAAQ,OAAA,EAAQ;AAC5H,MAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,QAAA,OAAA,CAAQ,uBAAuB,CAAA,GAAI,CAAA,OAAA,EAAU,UAAA,CAAW,QAAA,EAAU,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAAA,MAC9G;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,CAAA;AAC3E,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,OAAA,CAAQ,GAAG,CAAA,UAAA,EAAa,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACxF;AAAA,GACF;AACF","file":"webhook.js","sourcesContent":["import { createHmac } from \"node:crypto\";\nimport type { AlertChannel } from \"../types.js\";\n\nexport interface WebhookOptions {\n url: string;\n /** Extra request headers, for an Authorization header say. */\n headers?: Record<string, string>;\n /**\n * When set, each request carries `X-CronWatch-Signature: sha256=<hex>`,\n * the HMAC-SHA256 of the raw body with this secret, so the receiver can\n * verify it.\n */\n secret?: string;\n}\n\n/**\n * POSTs the alert as JSON to any URL. The body is the Alert object:\n * { type, job, title, message, run, details, triage, at, definition }.\n */\nexport function webhook(options: WebhookOptions): AlertChannel {\n if (!options.url) throw new Error(\"webhook() needs a url\");\n return {\n name: \"webhook\",\n async send(alert) {\n const body = JSON.stringify(alert);\n const headers: Record<string, string> = { \"content-type\": \"application/json\", \"user-agent\": \"cronwatch\", ...options.headers };\n if (options.secret) {\n headers[\"x-cronwatch-signature\"] = `sha256=${createHmac(\"sha256\", options.secret).update(body).digest(\"hex\")}`;\n }\n const response = await fetch(options.url, { method: \"POST\", headers, body });\n if (!response.ok) throw new Error(`Webhook ${options.url} answered ${response.status}`);\n },\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cronwatch/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cron and scheduled-job monitoring that lives inside your app. Wrap a job, mount a route, get told when it is missed, failed, slow or over budget. No server to run.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/phillips-jon/cronwatch.git",
|
|
9
|
+
"directory": "packages/sdk"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://cronwatch.dev",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cron",
|
|
14
|
+
"monitoring",
|
|
15
|
+
"scheduled jobs",
|
|
16
|
+
"heartbeat",
|
|
17
|
+
"vercel cron",
|
|
18
|
+
"nextjs",
|
|
19
|
+
"agents",
|
|
20
|
+
"mcp"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"module": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js",
|
|
38
|
+
"require": "./dist/index.cjs",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./sqlite": {
|
|
42
|
+
"types": "./dist/sqlite.d.ts",
|
|
43
|
+
"import": "./dist/sqlite.js",
|
|
44
|
+
"require": "./dist/sqlite.cjs",
|
|
45
|
+
"default": "./dist/sqlite.js"
|
|
46
|
+
},
|
|
47
|
+
"./postgres": {
|
|
48
|
+
"types": "./dist/postgres.d.ts",
|
|
49
|
+
"import": "./dist/postgres.js",
|
|
50
|
+
"require": "./dist/postgres.cjs",
|
|
51
|
+
"default": "./dist/postgres.js"
|
|
52
|
+
},
|
|
53
|
+
"./slack": {
|
|
54
|
+
"types": "./dist/slack.d.ts",
|
|
55
|
+
"import": "./dist/slack.js",
|
|
56
|
+
"require": "./dist/slack.cjs",
|
|
57
|
+
"default": "./dist/slack.js"
|
|
58
|
+
},
|
|
59
|
+
"./discord": {
|
|
60
|
+
"types": "./dist/discord.d.ts",
|
|
61
|
+
"import": "./dist/discord.js",
|
|
62
|
+
"require": "./dist/discord.cjs",
|
|
63
|
+
"default": "./dist/discord.js"
|
|
64
|
+
},
|
|
65
|
+
"./webhook": {
|
|
66
|
+
"types": "./dist/webhook.d.ts",
|
|
67
|
+
"import": "./dist/webhook.js",
|
|
68
|
+
"require": "./dist/webhook.cjs",
|
|
69
|
+
"default": "./dist/webhook.js"
|
|
70
|
+
},
|
|
71
|
+
"./anthropic": {
|
|
72
|
+
"types": "./dist/anthropic.d.ts",
|
|
73
|
+
"import": "./dist/anthropic.js",
|
|
74
|
+
"require": "./dist/anthropic.cjs",
|
|
75
|
+
"default": "./dist/anthropic.js"
|
|
76
|
+
},
|
|
77
|
+
"./package.json": "./package.json"
|
|
78
|
+
},
|
|
79
|
+
"scripts": {
|
|
80
|
+
"build": "tsup",
|
|
81
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
82
|
+
"test": "TZ=UTC node --import tsx --test test/*.test.ts",
|
|
83
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"croner": "^10.0.1"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"better-sqlite3": ">=9",
|
|
90
|
+
"pg": ">=8"
|
|
91
|
+
},
|
|
92
|
+
"peerDependenciesMeta": {
|
|
93
|
+
"better-sqlite3": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"pg": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"devDependencies": {
|
|
101
|
+
"@anthropic-ai/sdk": "^0.127.0",
|
|
102
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
103
|
+
"@types/node": "^20.19.0",
|
|
104
|
+
"@types/pg": "^8.15.0",
|
|
105
|
+
"better-sqlite3": "^13.0.3",
|
|
106
|
+
"pg": "^8.23.0",
|
|
107
|
+
"tsup": "^8.5.1",
|
|
108
|
+
"tsx": "^4.23.0",
|
|
109
|
+
"typescript": "^5.9.3"
|
|
110
|
+
},
|
|
111
|
+
"publishConfig": {
|
|
112
|
+
"access": "public"
|
|
113
|
+
}
|
|
114
|
+
}
|