@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -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 +835 -293
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +133 -45
  18. package/dist/index.d.ts +133 -45
  19. package/dist/index.js +836 -291
  20. package/dist/index.js.map +1 -1
  21. package/dist/postgres.cjs +152 -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 +152 -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 +184 -74
  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 +185 -75
  38. package/dist/sqlite.js.map +1 -1
  39. package/dist/{types-BQ1P8z55.d.cts → types-DUt2apl0.d.cts} +84 -7
  40. package/dist/{types-BQ1P8z55.d.ts → types-DUt2apl0.d.ts} +84 -7
  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 +97 -34
@@ -84,11 +84,66 @@ interface JobState {
84
84
  open: Partial<Record<Condition, number>>;
85
85
  consecutiveFailures: number;
86
86
  silencedUntil: number | null;
87
+ /** When an alert last reached at least one channel. */
87
88
  lastAlertAt: number | null;
89
+ /**
90
+ * Conditions that alerted and have since closed, waiting for the recovered
91
+ * message that the next successful run sends. Absent in state written
92
+ * before this field existed.
93
+ */
94
+ pendingRecovery?: Condition[];
95
+ /** Alerts that no channel accepted. Each check retries them once. */
96
+ undelivered?: Alert[];
97
+ /**
98
+ * Goes up by one on every write, so a store can refuse a write made from a
99
+ * stale read (see Store.compareAndSetState). Absent counts as 0.
100
+ */
101
+ version?: number;
88
102
  }
89
103
  type AlertType = Condition | "recovered";
90
- interface Alert {
91
- type: AlertType;
104
+ interface BudgetBreach {
105
+ metric: string;
106
+ value: number;
107
+ limit: number;
108
+ basis: string;
109
+ }
110
+ /** What each alert type carries in `details`. */
111
+ interface AlertDetails {
112
+ missed: {
113
+ dueAt: number;
114
+ deadline: number;
115
+ graceMs: number;
116
+ lastRunAt: number | null;
117
+ };
118
+ failed: {
119
+ consecutiveFailures: number;
120
+ threshold: number;
121
+ };
122
+ stuck: {
123
+ consecutiveFailures: number;
124
+ threshold: number;
125
+ };
126
+ slow: {
127
+ durationMs: number;
128
+ thresholdMs: number;
129
+ basis: string;
130
+ };
131
+ over_budget: {
132
+ breaches: BudgetBreach[];
133
+ };
134
+ recovered: {
135
+ after: Condition[];
136
+ };
137
+ }
138
+ /** An alert before it has a title and message. See composeAlert(). */
139
+ type AlertDraft = {
140
+ [K in AlertType]: {
141
+ type: K;
142
+ run: Run | null;
143
+ details: AlertDetails[K];
144
+ };
145
+ }[AlertType];
146
+ interface AlertBase {
92
147
  job: string;
93
148
  definition: StoredJobDefinition;
94
149
  run: Run | null;
@@ -96,11 +151,21 @@ interface Alert {
96
151
  title: string;
97
152
  /** A few lines of plain text with the specifics. */
98
153
  message: string;
99
- details: Record<string, unknown>;
100
- /** A short diagnosis from the triage function, when one is configured. */
101
- triage?: string;
154
+ /**
155
+ * A short diagnosis from the triage function, when one is configured. Null
156
+ * when triage was tried and gave nothing (it threw, timed out or answered
157
+ * empty); it is not tried again for this alert.
158
+ */
159
+ triage?: string | null;
102
160
  at: number;
103
161
  }
162
+ /** Narrow on `type` to read `details`: `if (alert.type === "slow") alert.details.durationMs`. */
163
+ type Alert = {
164
+ [K in AlertType]: AlertBase & {
165
+ type: K;
166
+ details: AlertDetails[K];
167
+ };
168
+ }[AlertType];
104
169
  interface AlertChannel {
105
170
  name: string;
106
171
  send(alert: Alert): Promise<void>;
@@ -120,7 +185,17 @@ interface Store {
120
185
  lastRun(job: string): Promise<Run | null>;
121
186
  runningRuns(): Promise<Run[]>;
122
187
  getState(job: string): Promise<JobState | null>;
188
+ /** Write a job's state unconditionally. Used only when compareAndSetState is missing. */
123
189
  setState(state: JobState): Promise<void>;
190
+ /**
191
+ * Write `state` only when the stored state's version (see JobState.version;
192
+ * absent, or no row at all, counts as 0) equals `expectedVersion`. Returns
193
+ * whether it wrote. This is what keeps two processes sharing a store from
194
+ * overwriting each other's updates: the client reads, computes, and on a
195
+ * refused write reads again. A store without it falls back to setState,
196
+ * which is safe only when one process at a time writes a job's state.
197
+ */
198
+ compareAndSetState?(state: JobState, expectedVersion: number): Promise<boolean>;
124
199
  /** Delete finished runs that started before this time. Returns how many. */
125
200
  prune(before: number): Promise<number>;
126
201
  close?(): Promise<void>;
@@ -136,7 +211,7 @@ interface JobSummary {
136
211
  nextExpectedAt: number | null;
137
212
  consecutiveFailures: number;
138
213
  silencedUntil: number | null;
139
- /** From the last twenty successful runs. */
214
+ /** From the last twenty runs of any status; p50Ms and p95Ms are over the successful ones among them. */
140
215
  stats: {
141
216
  runs: number;
142
217
  okRate: number;
@@ -153,7 +228,9 @@ interface CheckResult {
153
228
  interface TriageContext {
154
229
  alert: Alert;
155
230
  recentRuns: Run[];
231
+ /** Aborts when the client stops waiting for a diagnosis. Pass it to any request you make. */
232
+ signal: AbortSignal;
156
233
  }
157
234
  type TriageFn = (context: TriageContext) => Promise<string | null>;
158
235
 
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 };
236
+ export type { Alert as A, BudgetBreach as B, 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, AlertDraft as e, StoredJobDefinition as f, AlertDetails as g, AlertType as h, Condition as i, JobHealth as j, RunStatus as k, StoredJob as l, TriageContext as m };
package/dist/webhook.cjs CHANGED
@@ -3,6 +3,14 @@
3
3
  var crypto = require('crypto');
4
4
 
5
5
  // src/alerts/webhook.ts
6
+ var TIMEOUT_MS = 1e4;
7
+ function origin(url) {
8
+ try {
9
+ return new URL(url).origin;
10
+ } catch {
11
+ return "(invalid URL)";
12
+ }
13
+ }
6
14
  function webhook(options) {
7
15
  if (!options.url) throw new Error("webhook() needs a url");
8
16
  return {
@@ -13,8 +21,8 @@ function webhook(options) {
13
21
  if (options.secret) {
14
22
  headers["x-cronwatch-signature"] = `sha256=${crypto.createHmac("sha256", options.secret).update(body).digest("hex")}`;
15
23
  }
16
- const response = await fetch(options.url, { method: "POST", headers, body });
17
- if (!response.ok) throw new Error(`Webhook ${options.url} answered ${response.status}`);
24
+ const response = await fetch(options.url, { method: "POST", headers, body, signal: AbortSignal.timeout(TIMEOUT_MS) });
25
+ if (!response.ok) throw new Error(`Webhook ${origin(options.url)} answered ${response.status}`);
18
26
  }
19
27
  };
20
28
  }
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/alerts/webhook.ts"],"names":["createHmac"],"mappings":";;;;;AAeA,IAAM,UAAA,GAAa,GAAA;AAEnB,SAAS,OAAO,GAAA,EAAqB;AACnC,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,MAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,eAAA;AAAA,EACT;AACF;AAMO,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,KAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,MAAM,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,UAAU,GAAG,CAAA;AAEpH,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAC,CAAA,UAAA,EAAa,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAChG;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\nconst TIMEOUT_MS = 10_000;\n\nfunction origin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return \"(invalid URL)\";\n }\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, signal: AbortSignal.timeout(TIMEOUT_MS) });\n // Only the origin: a webhook URL's path or query often is the credential.\n if (!response.ok) throw new Error(`Webhook ${origin(options.url)} answered ${response.status}`);\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { a as AlertChannel } from './types-BQ1P8z55.cjs';
1
+ import { a as AlertChannel } from './types-DUt2apl0.cjs';
2
2
 
3
3
  interface WebhookOptions {
4
4
  url: string;
package/dist/webhook.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AlertChannel } from './types-BQ1P8z55.js';
1
+ import { a as AlertChannel } from './types-DUt2apl0.js';
2
2
 
3
3
  interface WebhookOptions {
4
4
  url: string;
package/dist/webhook.js CHANGED
@@ -1,6 +1,14 @@
1
1
  import { createHmac } from 'crypto';
2
2
 
3
3
  // src/alerts/webhook.ts
4
+ var TIMEOUT_MS = 1e4;
5
+ function origin(url) {
6
+ try {
7
+ return new URL(url).origin;
8
+ } catch {
9
+ return "(invalid URL)";
10
+ }
11
+ }
4
12
  function webhook(options) {
5
13
  if (!options.url) throw new Error("webhook() needs a url");
6
14
  return {
@@ -11,8 +19,8 @@ function webhook(options) {
11
19
  if (options.secret) {
12
20
  headers["x-cronwatch-signature"] = `sha256=${createHmac("sha256", options.secret).update(body).digest("hex")}`;
13
21
  }
14
- const response = await fetch(options.url, { method: "POST", headers, body });
15
- if (!response.ok) throw new Error(`Webhook ${options.url} answered ${response.status}`);
22
+ const response = await fetch(options.url, { method: "POST", headers, body, signal: AbortSignal.timeout(TIMEOUT_MS) });
23
+ if (!response.ok) throw new Error(`Webhook ${origin(options.url)} answered ${response.status}`);
16
24
  }
17
25
  };
18
26
  }
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/alerts/webhook.ts"],"names":[],"mappings":";;;AAeA,IAAM,UAAA,GAAa,GAAA;AAEnB,SAAS,OAAO,GAAA,EAAqB;AACnC,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,MAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,eAAA;AAAA,EACT;AACF;AAMO,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,KAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,MAAM,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,UAAU,GAAG,CAAA;AAEpH,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAC,CAAA,UAAA,EAAa,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAChG;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\nconst TIMEOUT_MS = 10_000;\n\nfunction origin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return \"(invalid URL)\";\n }\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, signal: AbortSignal.timeout(TIMEOUT_MS) });\n // Only the origin: a webhook URL's path or query often is the credential.\n if (!response.ok) throw new Error(`Webhook ${origin(options.url)} answered ${response.status}`);\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cronwatch/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
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
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,57 +22,108 @@
22
22
  "type": "module",
23
23
  "sideEffects": false,
24
24
  "engines": {
25
- "node": ">=20"
25
+ "node": ">=22"
26
26
  },
27
27
  "files": [
28
28
  "dist",
29
- "README.md"
29
+ "README.md",
30
+ "LICENSE"
30
31
  ],
31
32
  "main": "./dist/index.cjs",
32
33
  "module": "./dist/index.js",
33
- "types": "./dist/index.d.ts",
34
+ "types": "./dist/index.d.cts",
35
+ "typesVersions": {
36
+ "*": {
37
+ "sqlite": [
38
+ "./dist/sqlite.d.cts"
39
+ ],
40
+ "postgres": [
41
+ "./dist/postgres.d.cts"
42
+ ],
43
+ "slack": [
44
+ "./dist/slack.d.cts"
45
+ ],
46
+ "discord": [
47
+ "./dist/discord.d.cts"
48
+ ],
49
+ "webhook": [
50
+ "./dist/webhook.d.cts"
51
+ ],
52
+ "anthropic": [
53
+ "./dist/anthropic.d.cts"
54
+ ]
55
+ }
56
+ },
34
57
  "exports": {
35
58
  ".": {
36
- "types": "./dist/index.d.ts",
37
- "import": "./dist/index.js",
38
- "require": "./dist/index.cjs",
39
- "default": "./dist/index.js"
59
+ "import": {
60
+ "types": "./dist/index.d.ts",
61
+ "default": "./dist/index.js"
62
+ },
63
+ "require": {
64
+ "types": "./dist/index.d.cts",
65
+ "default": "./dist/index.cjs"
66
+ }
40
67
  },
41
68
  "./sqlite": {
42
- "types": "./dist/sqlite.d.ts",
43
- "import": "./dist/sqlite.js",
44
- "require": "./dist/sqlite.cjs",
45
- "default": "./dist/sqlite.js"
69
+ "import": {
70
+ "types": "./dist/sqlite.d.ts",
71
+ "default": "./dist/sqlite.js"
72
+ },
73
+ "require": {
74
+ "types": "./dist/sqlite.d.cts",
75
+ "default": "./dist/sqlite.cjs"
76
+ }
46
77
  },
47
78
  "./postgres": {
48
- "types": "./dist/postgres.d.ts",
49
- "import": "./dist/postgres.js",
50
- "require": "./dist/postgres.cjs",
51
- "default": "./dist/postgres.js"
79
+ "import": {
80
+ "types": "./dist/postgres.d.ts",
81
+ "default": "./dist/postgres.js"
82
+ },
83
+ "require": {
84
+ "types": "./dist/postgres.d.cts",
85
+ "default": "./dist/postgres.cjs"
86
+ }
52
87
  },
53
88
  "./slack": {
54
- "types": "./dist/slack.d.ts",
55
- "import": "./dist/slack.js",
56
- "require": "./dist/slack.cjs",
57
- "default": "./dist/slack.js"
89
+ "import": {
90
+ "types": "./dist/slack.d.ts",
91
+ "default": "./dist/slack.js"
92
+ },
93
+ "require": {
94
+ "types": "./dist/slack.d.cts",
95
+ "default": "./dist/slack.cjs"
96
+ }
58
97
  },
59
98
  "./discord": {
60
- "types": "./dist/discord.d.ts",
61
- "import": "./dist/discord.js",
62
- "require": "./dist/discord.cjs",
63
- "default": "./dist/discord.js"
99
+ "import": {
100
+ "types": "./dist/discord.d.ts",
101
+ "default": "./dist/discord.js"
102
+ },
103
+ "require": {
104
+ "types": "./dist/discord.d.cts",
105
+ "default": "./dist/discord.cjs"
106
+ }
64
107
  },
65
108
  "./webhook": {
66
- "types": "./dist/webhook.d.ts",
67
- "import": "./dist/webhook.js",
68
- "require": "./dist/webhook.cjs",
69
- "default": "./dist/webhook.js"
109
+ "import": {
110
+ "types": "./dist/webhook.d.ts",
111
+ "default": "./dist/webhook.js"
112
+ },
113
+ "require": {
114
+ "types": "./dist/webhook.d.cts",
115
+ "default": "./dist/webhook.cjs"
116
+ }
70
117
  },
71
118
  "./anthropic": {
72
- "types": "./dist/anthropic.d.ts",
73
- "import": "./dist/anthropic.js",
74
- "require": "./dist/anthropic.cjs",
75
- "default": "./dist/anthropic.js"
119
+ "import": {
120
+ "types": "./dist/anthropic.d.ts",
121
+ "default": "./dist/anthropic.js"
122
+ },
123
+ "require": {
124
+ "types": "./dist/anthropic.d.cts",
125
+ "default": "./dist/anthropic.cjs"
126
+ }
76
127
  },
77
128
  "./package.json": "./package.json"
78
129
  },
@@ -86,10 +137,22 @@
86
137
  "croner": "^10.0.1"
87
138
  },
88
139
  "peerDependencies": {
140
+ "@anthropic-ai/sdk": ">=0.115.0 <0.129.0",
141
+ "@types/better-sqlite3": ">=7.6",
142
+ "@types/pg": ">=8.11",
89
143
  "better-sqlite3": ">=9",
90
144
  "pg": ">=8"
91
145
  },
92
146
  "peerDependenciesMeta": {
147
+ "@anthropic-ai/sdk": {
148
+ "optional": true
149
+ },
150
+ "@types/better-sqlite3": {
151
+ "optional": true
152
+ },
153
+ "@types/pg": {
154
+ "optional": true
155
+ },
93
156
  "better-sqlite3": {
94
157
  "optional": true
95
158
  },
@@ -98,9 +161,9 @@
98
161
  }
99
162
  },
100
163
  "devDependencies": {
101
- "@anthropic-ai/sdk": "^0.127.0",
164
+ "@anthropic-ai/sdk": "^0.128.0",
102
165
  "@types/better-sqlite3": "^7.6.13",
103
- "@types/node": "^20.19.0",
166
+ "@types/node": "^22.20.4",
104
167
  "@types/pg": "^8.15.0",
105
168
  "better-sqlite3": "^13.0.3",
106
169
  "pg": "^8.23.0",