@theholocron/checkly-client 1.50.0 → 1.52.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 CHANGED
@@ -75,8 +75,40 @@ account id (visible at `https://app.checklyhq.com/settings/account/general`)
75
75
  The token is passed directly or read from `CHECKLY_API_KEY`; the account id
76
76
  from `CHECKLY_ACCOUNT_ID`. Neither has a default — both are required.
77
77
 
78
- > **Note:** This client only covers API checks (`checks.*`, the
79
- > `/v1/checks/api` and `/v1/checks` endpoints) — the subset this org
80
- > currently provisions programmatically. Checkly's full surface (browser
81
- > checks, check groups, alert channels, dashboards) is out of scope until a
82
- > real consumer needs it.
78
+ ## Alert channels
79
+
80
+ ```ts
81
+ import { DISCORD_TEMPLATE } from "@theholocron/checkly-client";
82
+
83
+ // A Discord channel is a WEBHOOK marked WEBHOOK_DISCORD. The template is not filled in for you when you create one
84
+ // through the API, so pass DISCORD_TEMPLATE (Checkly's own Discord message body).
85
+ const channel = await checkly.alertChannels.create({
86
+ type: "WEBHOOK",
87
+ config: {
88
+ name: "Discord #uptime",
89
+ url: "https://discord.com/api/webhooks/…", // a secret: anyone holding it can post
90
+ method: "POST",
91
+ headers: [],
92
+ queryParameters: [],
93
+ template: DISCORD_TEMPLATE,
94
+ webhookType: "WEBHOOK_DISCORD",
95
+ },
96
+ sendFailure: true,
97
+ sendRecovery: true,
98
+ sendDegraded: true,
99
+ // Checks made through the API are never auto-subscribed: list each one.
100
+ subscriptions: [{ checkId: check.id, activated: true }],
101
+ });
102
+
103
+ const all = await checkly.alertChannels.list(); // every page
104
+ const one = await checkly.alertChannels.get(channel.id);
105
+ await checkly.alertChannels.update(channel.id, {/* the complete channel */});
106
+ await checkly.alertChannels.delete(channel.id);
107
+ ```
108
+
109
+ `update` replaces the whole channel (settings and subscriptions), so send everything you want to keep. Checkly offers
110
+ no version check, so do not update one channel from two places at once.
111
+
112
+ > **Note:** This client covers API checks (`checks.*`) and alert channels (`alertChannels.*`) — the subset this org
113
+ > currently provisions programmatically. Checkly's full surface (browser checks, check groups, dashboards) is out of
114
+ > scope until a real consumer needs it.
package/dist/index.d.mts CHANGED
@@ -16,6 +16,90 @@ interface ChecklyClientOptions {
16
16
  errors?: ErrorSink;
17
17
  }
18
18
  //#endregion
19
+ //#region ../../../../../src/alert-channels/alert-channels.d.ts
20
+ type AlertChannelType = "EMAIL" | "SLACK" | "SLACK_APP" | "WEBHOOK" | "SMS" | "PAGERDUTY" | "OPSGENIE" | "CALL";
21
+ type WebhookMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
22
+ /**
23
+ * Which service a webhook channel is formatted for. Checkly stores a Discord channel as a plain `WEBHOOK` with this
24
+ * marker and a body template; there is no separate Discord type.
25
+ */
26
+ type WebhookType = "WEBHOOK_DISCORD" | "WEBHOOK_MSTEAMS" | "WEBHOOK_TELEGRAM" | "WEBHOOK_GITHUB" | "WEBHOOK_SPIKE";
27
+ interface WebhookKeyValue {
28
+ key: string;
29
+ value: string;
30
+ locked?: boolean;
31
+ }
32
+ /** The `config` of a `WEBHOOK` channel, as Checkly returns it. */
33
+ interface WebhookConfig {
34
+ name: string;
35
+ /** Where Checkly posts. For Discord this is a webhook URL and is a secret: anyone holding it can post to the channel. */
36
+ url: string;
37
+ method: WebhookMethod;
38
+ /** Handlebars body template with Checkly's `{{VARIABLES}}`. */
39
+ template?: string;
40
+ headers?: WebhookKeyValue[];
41
+ queryParameters?: WebhookKeyValue[];
42
+ webhookType?: WebhookType;
43
+ webhookSecret?: string;
44
+ }
45
+ /** A subscription targets exactly one check or one check group. */
46
+ type AlertChannelSubscription = {
47
+ id?: number;
48
+ activated: boolean;
49
+ } & ({
50
+ checkId: string;
51
+ groupId?: null;
52
+ } | {
53
+ groupId: number;
54
+ checkId?: null;
55
+ });
56
+ interface AlertChannelSettings {
57
+ sendRecovery?: boolean;
58
+ sendFailure?: boolean;
59
+ sendDegraded?: boolean;
60
+ sslExpiry?: boolean;
61
+ sslExpiryThreshold?: number;
62
+ /** Subscribe checks created later in the UI. Checks made through the API or CLI are never auto-subscribed. */
63
+ autoSubscribe?: boolean;
64
+ /**
65
+ * The full list of subscribed checks. Checkly's update replaces the whole channel, so anything left out here (or
66
+ * in the settings above) is dropped: send the complete channel, not just what changed.
67
+ */
68
+ subscriptions?: AlertChannelSubscription[];
69
+ }
70
+ /** What a channel is made of. A webhook's `config` is checked; the other types are passed through as Checkly defines them. */
71
+ type AlertChannelInput = AlertChannelSettings & ({
72
+ type: "WEBHOOK";
73
+ config: WebhookConfig;
74
+ } | {
75
+ type: Exclude<AlertChannelType, "WEBHOOK">;
76
+ config: Record<string, unknown>;
77
+ });
78
+ interface AlertChannelRecord {
79
+ id: number;
80
+ sendRecovery: boolean;
81
+ sendFailure: boolean;
82
+ sendDegraded: boolean;
83
+ sslExpiry: boolean;
84
+ sslExpiryThreshold: number;
85
+ autoSubscribe: boolean;
86
+ subscriptions: AlertChannelSubscription[];
87
+ created_at: string;
88
+ updated_at: string | null;
89
+ }
90
+ type AlertChannel = AlertChannelRecord & ({
91
+ type: "WEBHOOK";
92
+ config: WebhookConfig;
93
+ } | {
94
+ type: Exclude<AlertChannelType, "WEBHOOK">;
95
+ config: Record<string, unknown>;
96
+ });
97
+ /**
98
+ * The message body of Checkly's own Discord setup (its AI-analysis section left out, since not every account has it), so
99
+ * a channel made through the API reads like one made in the UI.
100
+ */
101
+ declare const DISCORD_TEMPLATE = "{\n \"content\": null,\n \"username\": \"Checkly\",\n \"avatar_url\": \"https://s3.eu-central-1.amazonaws.com/cdn.checklyhq.com/slack/checkly-slack-icon.png\",\n {{! Embeds for the SSL expiry notifications}}\n {{#eq ALERT_TYPE \"ALERT_SSL\" }}\n \"embeds\": [\n {\n \"title\": \"{{ALERT_TITLE}}\",\n \"url\": \"{{RESULT_LINK}}\",\n \"color\": 16762924\n }\n ]\n {{else}}\n {{! Embeds for the standard failure, recovery and degraded notifications}}\n \"embeds\": [\n {\n \"title\": \"{{ALERT_TITLE}}\",\n \"url\": \"{{RESULT_LINK}}\",\n \"fields\": [\n {{#eq ALERT_TYPE \"API\" }}\n {\n \"name\": \"Status code\",\n \"value\": \"{{API_CHECK_RESPONSE_STATUS_CODE}}\"\n },\n {{/eq}}\n {{#if GROUP_NAME}}\n {\n \"name\": \"Group\",\n \"value\": \"{{GROUP_NAME}}\"\n },\n {{/if}}\n {\n \"name\": \"Response Time\",\n \"value\": \"{{RESPONSE_TIME}}ms\"\n },\n {\n \"name\": \"Location\",\n \"value\": \"{{RUN_LOCATION}}\"\n },\n {\n \"name\": \"Tags\",\n \"value\": \"[{{#each TAGS}} {{this}} {{#unless @last}},{{/unless}} {{/each}}]\"\n }\n ],\n \"timestamp\": \"{{STARTED_AT}}\"\n }\n ]\n {{/eq}}\n}\n";
102
+ //#endregion
19
103
  //#region ../../../../../src/checks/checks.d.ts
20
104
  type AssertionSource = "STATUS_CODE" | "JSON_BODY" | "HEADERS" | "TEXT_BODY" | "RESPONSE_TIME";
21
105
  type AssertionComparison = "EQUALS" | "NOT_EQUALS" | "HAS_KEY" | "NOT_HAS_KEY" | "HAS_VALUE" | "NOT_HAS_VALUE" | "IS_EMPTY" | "NOT_EMPTY" | "GREATER_THAN" | "LESS_THAN" | "CONTAINS" | "NOT_CONTAINS" | "IS_NULL" | "NOT_NULL";
@@ -84,6 +168,13 @@ interface ListChecksParams {
84
168
  //#endregion
85
169
  //#region ../../../../../src/index.d.ts
86
170
  declare function createChecklyClient(opts: ChecklyClientOptions): {
171
+ alertChannels: {
172
+ list: () => Promise<AlertChannel[]>;
173
+ get: (id: number) => Promise<AlertChannel>;
174
+ create: (input: AlertChannelInput) => Promise<AlertChannel>;
175
+ update: (id: number, input: AlertChannelInput) => Promise<AlertChannel>;
176
+ delete: (id: number) => Promise<void>;
177
+ };
87
178
  checks: {
88
179
  list: (params?: ListChecksParams) => Promise<ApiCheck[]>;
89
180
  get: (id: string) => Promise<ApiCheck>;
@@ -94,4 +185,4 @@ declare function createChecklyClient(opts: ChecklyClientOptions): {
94
185
  };
95
186
  type ChecklyClient = ReturnType<typeof createChecklyClient>;
96
187
  //#endregion
97
- export { type ApiCheck, type ApiCheckInput, type ApiCheckRequest, type Assertion, type AssertionComparison, type AssertionSource, ChecklyClient, type ChecklyClientOptions, type HttpMethod, type ListChecksParams, createChecklyClient };
188
+ export { type AlertChannel, type AlertChannelInput, type AlertChannelSubscription, type AlertChannelType, type ApiCheck, type ApiCheckInput, type ApiCheckRequest, type Assertion, type AssertionComparison, type AssertionSource, ChecklyClient, type ChecklyClientOptions, DISCORD_TEMPLATE, type HttpMethod, type ListChecksParams, type WebhookConfig, type WebhookKeyValue, type WebhookMethod, type WebhookType, createChecklyClient };
package/dist/index.mjs CHANGED
@@ -1,4 +1,99 @@
1
1
  import { createRestClient } from "@theholocron/http-client";
2
+ //#region ../../../../../src/alert-channels/alert-channels.ts
3
+ /**
4
+ * The message body of Checkly's own Discord setup (its AI-analysis section left out, since not every account has it), so
5
+ * a channel made through the API reads like one made in the UI.
6
+ */
7
+ const DISCORD_TEMPLATE = `{
8
+ "content": null,
9
+ "username": "Checkly",
10
+ "avatar_url": "https://s3.eu-central-1.amazonaws.com/cdn.checklyhq.com/slack/checkly-slack-icon.png",
11
+ {{! Embeds for the SSL expiry notifications}}
12
+ {{#eq ALERT_TYPE "ALERT_SSL" }}
13
+ "embeds": [
14
+ {
15
+ "title": "{{ALERT_TITLE}}",
16
+ "url": "{{RESULT_LINK}}",
17
+ "color": 16762924
18
+ }
19
+ ]
20
+ {{else}}
21
+ {{! Embeds for the standard failure, recovery and degraded notifications}}
22
+ "embeds": [
23
+ {
24
+ "title": "{{ALERT_TITLE}}",
25
+ "url": "{{RESULT_LINK}}",
26
+ "fields": [
27
+ {{#eq ALERT_TYPE "API" }}
28
+ {
29
+ "name": "Status code",
30
+ "value": "{{API_CHECK_RESPONSE_STATUS_CODE}}"
31
+ },
32
+ {{/eq}}
33
+ {{#if GROUP_NAME}}
34
+ {
35
+ "name": "Group",
36
+ "value": "{{GROUP_NAME}}"
37
+ },
38
+ {{/if}}
39
+ {
40
+ "name": "Response Time",
41
+ "value": "{{RESPONSE_TIME}}ms"
42
+ },
43
+ {
44
+ "name": "Location",
45
+ "value": "{{RUN_LOCATION}}"
46
+ },
47
+ {
48
+ "name": "Tags",
49
+ "value": "[{{#each TAGS}} {{this}} {{#unless @last}},{{/unless}} {{/each}}]"
50
+ }
51
+ ],
52
+ "timestamp": "{{STARTED_AT}}"
53
+ }
54
+ ]
55
+ {{/eq}}
56
+ }
57
+ `;
58
+ /** Checkly's page size ceiling for this listing. */
59
+ const PAGE = 100;
60
+ /** A guard so a runaway listing cannot loop forever. */
61
+ const MAX_PAGES = 50;
62
+ function alertChannels(rest) {
63
+ return {
64
+ /** Every alert channel on the account, read page by page. */
65
+ list: async () => {
66
+ const read = (page) => rest.request("/alert-channels", { query: {
67
+ limit: String(PAGE),
68
+ page: String(page)
69
+ } });
70
+ const all = [];
71
+ for (let page = 1; page <= MAX_PAGES; page++) {
72
+ const batch = await read(page);
73
+ all.push(...batch);
74
+ if (batch.length < PAGE) return all;
75
+ }
76
+ if ((await read(51)).length > 0) throw new Error(`more than 5000 alert channels; refusing to read them all`);
77
+ return all;
78
+ },
79
+ /** One alert channel. Throws ProviderApiError 404 if there is none. */
80
+ get: (id) => rest.request(`/alert-channels/${id}`),
81
+ create: (input) => rest.request("/alert-channels", {
82
+ method: "POST",
83
+ body: { ...input }
84
+ }),
85
+ /**
86
+ * Replace a channel. Checkly's update takes the whole channel, so send every setting and subscription you want
87
+ * to keep; there is no version check, so callers must not update the same channel concurrently.
88
+ */
89
+ update: (id, input) => rest.request(`/alert-channels/${id}`, {
90
+ method: "PUT",
91
+ body: { ...input }
92
+ }),
93
+ delete: (id) => rest.request(`/alert-channels/${id}`, { method: "DELETE" })
94
+ };
95
+ }
96
+ //#endregion
2
97
  //#region ../../../../../src/checks/checks.ts
3
98
  /**
4
99
  * `ApiCheckInput.request.body` is holocron's simplified shape (just the raw
@@ -59,7 +154,11 @@ function createChecklyRestClient(opts) {
59
154
  //#endregion
60
155
  //#region ../../../../../src/index.ts
61
156
  function createChecklyClient(opts) {
62
- return { checks: checks(createChecklyRestClient(opts)) };
157
+ const rest = createChecklyRestClient(opts);
158
+ return {
159
+ alertChannels: alertChannels(rest),
160
+ checks: checks(rest)
161
+ };
63
162
  }
64
163
  //#endregion
65
- export { createChecklyClient };
164
+ export { DISCORD_TEMPLATE, createChecklyClient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/checkly-client",
3
- "version": "1.50.0",
3
+ "version": "1.52.0",
4
4
  "description": "A TypeScript client for the Checkly monitoring API",
5
5
  "keywords": [
6
6
  "checkly",
@@ -34,7 +34,7 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "@theholocron/observability": "^0.3.0",
37
- "@theholocron/http-client": "^1.50.0"
37
+ "@theholocron/http-client": "^1.52.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@theholocron/cli": "5.0.0-alpha.79",