@chirpier/chirpier-js 0.1.6 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chirpier/chirpier-js",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Chirpier SDK for JavaScript",
5
5
  "keywords": [
6
6
  "chirpier",
@@ -30,9 +30,10 @@
30
30
  "async-lock": "^1.4.1",
31
31
  "axios": "^0.24.0",
32
32
  "axios-retry": "^4.5.0",
33
- "js-base64": "^3.7.7",
33
+ "dotenv": "^16.4.7",
34
34
  "ts-node": "^10.9.2",
35
- "tslib": "^2.3.0"
35
+ "tslib": "^2.3.0",
36
+ "uuid": "^11.1.0"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@eslint/js": "^9.15.0",
@@ -41,13 +42,12 @@
41
42
  "@types/jest": "^29.5.13",
42
43
  "@types/mocha": "^10.0.8",
43
44
  "@types/node": "^22.7.5",
44
- "@types/uuid": "^10.0.0",
45
45
  "axios-mock-adapter": "^2.0.0",
46
46
  "eslint": "^9.15.0",
47
47
  "globals": "^15.12.0",
48
48
  "jest": "^29.7.0",
49
49
  "ts-jest": "^29.2.4",
50
- "typescript": "^4.4.3",
50
+ "typescript": "^5.6.3",
51
51
  "typescript-eslint": "^8.15.0",
52
52
  "webpack": "^5.52.0",
53
53
  "webpack-cli": "^4.8.0"
@@ -1,192 +1,396 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import axios from "axios";
5
+ import MockAdapter from "axios-mock-adapter";
1
6
  import {
2
- Chirpier,
7
+ Client,
3
8
  ChirpierError,
4
- Event,
9
+ Log,
5
10
  LogLevel,
11
+ createClient,
12
+ flush,
6
13
  initialize,
7
- monitor,
14
+ logEvent,
15
+ stop,
8
16
  } from "../index";
9
17
  import {
10
18
  DEFAULT_API_ENDPOINT,
11
- DEFAULT_RETRIES,
12
- DEFAULT_TIMEOUT,
13
- DEFAULT_BATCH_SIZE,
14
- DEFAULT_FLUSH_DELAY,
15
19
  } from "../constants";
16
- import MockAdapter from "axios-mock-adapter";
17
- import axios from "axios";
18
20
 
19
21
  describe("Chirpier SDK", () => {
22
+ afterEach(async () => {
23
+ await stop();
24
+ });
25
+
20
26
  describe("Initialization", () => {
21
- test("should throw error if monitor is called before initialize", () => {
22
- const event: Event = {
23
- group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
24
- stream_name: "test-stream",
27
+ test("should throw error if logEvent is called before initialize", async () => {
28
+ const log: Log = {
29
+ event: "test-event",
25
30
  value: 1,
26
31
  };
27
32
 
28
- expect(() => monitor(event)).toThrow(ChirpierError);
29
- expect(() => monitor(event)).toThrow(
33
+ await expect(logEvent(log)).rejects.toThrow(ChirpierError);
34
+ await expect(logEvent(log)).rejects.toThrow(
30
35
  "Chirpier SDK is not initialized. Please call initialize() first."
31
36
  );
32
37
  });
33
38
 
34
- test("should initialize with default values", () => {
39
+ test("should initialize with default values", async () => {
40
+ const mock = new MockAdapter(axios);
41
+ mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
42
+
35
43
  initialize({
36
- key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
44
+ key: "chp_test_default_key",
37
45
  logLevel: LogLevel.None,
38
46
  });
39
- const chirpier = Chirpier.getInstance({
40
- key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
41
- });
42
47
 
43
- // Setup mock server
48
+ await logEvent({ event: "sdk.initialized", value: 1 });
49
+ await new Promise((resolve) => setTimeout(resolve, 2000));
50
+ expect(mock.history.post[0].url).toBe(DEFAULT_API_ENDPOINT);
51
+ });
52
+
53
+ test("should initialize with custom apiEndpoint", async () => {
54
+ const customEndpoint = "https://localhost:3001/v1.0/logs";
44
55
  const mock = new MockAdapter(axios);
45
- mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
56
+ mock.onPost(customEndpoint).reply(200, { success: true });
46
57
 
47
- expect(chirpier?.["apiEndpoint"]).toBe(DEFAULT_API_ENDPOINT);
48
- expect(chirpier?.["retries"]).toBe(DEFAULT_RETRIES);
49
- expect(chirpier?.["timeout"]).toBe(DEFAULT_TIMEOUT);
50
- expect(chirpier?.["batchSize"]).toBe(DEFAULT_BATCH_SIZE);
51
- expect(chirpier?.["flushDelay"]).toBe(DEFAULT_FLUSH_DELAY);
58
+ initialize({
59
+ key: "chp_test_custom_endpoint",
60
+ apiEndpoint: customEndpoint,
61
+ logLevel: LogLevel.None,
62
+ });
52
63
 
53
- Chirpier.stop();
64
+ await logEvent({ event: "sdk.custom-endpoint", value: 1 });
65
+ await new Promise((resolve) => setTimeout(resolve, 2000));
66
+ expect(mock.history.post[0].url).toBe(customEndpoint);
54
67
  });
55
68
 
56
- test("should throw error if key is not provided", () => {
69
+ test("should throw error for invalid apiEndpoint", () => {
57
70
  expect(() => {
58
71
  initialize({
59
- key: "api_key",
60
- logLevel: LogLevel.None,
72
+ key: "chp_test_invalid_endpoint",
73
+ apiEndpoint: "not-a-url",
61
74
  });
62
- }).toThrow(ChirpierError);
75
+ }).toThrow("apiEndpoint must be a valid absolute URL");
76
+ });
77
+
78
+ test("should throw error for invalid key prefix", () => {
63
79
  expect(() => {
64
80
  initialize({
65
- key: "api_key",
81
+ key: "invalid_key",
66
82
  logLevel: LogLevel.None,
67
83
  });
68
- }).toThrow("Invalid API key: Not a valid JWT");
84
+ }).toThrow("Invalid API key: must start with 'chp_'");
85
+ });
86
+
87
+ test("should load key from process environment", () => {
88
+ const previousKey = process.env.CHIRPIER_API_KEY;
89
+ process.env.CHIRPIER_API_KEY = "chp_env_key";
90
+
91
+ try {
92
+ initialize({ logLevel: LogLevel.None });
93
+ expect(() => initialize({ logLevel: LogLevel.None })).not.toThrow();
94
+ } finally {
95
+ if (previousKey === undefined) {
96
+ delete process.env.CHIRPIER_API_KEY;
97
+ } else {
98
+ process.env.CHIRPIER_API_KEY = previousKey;
99
+ }
100
+ }
101
+ });
102
+
103
+ test("should load key from .env fallback", () => {
104
+ const previousKey = process.env.CHIRPIER_API_KEY;
105
+ const previousCwd = process.cwd();
106
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "chirpier-js-"));
107
+
108
+ try {
109
+ delete process.env.CHIRPIER_API_KEY;
110
+ fs.writeFileSync(path.join(tempDir, ".env"), "CHIRPIER_API_KEY=chp_dotenv_key\n");
111
+ process.chdir(tempDir);
112
+
113
+ initialize({ logLevel: LogLevel.None });
114
+ expect(() => initialize({ logLevel: LogLevel.None })).not.toThrow();
115
+ } finally {
116
+ process.chdir(previousCwd);
117
+ if (previousKey === undefined) {
118
+ delete process.env.CHIRPIER_API_KEY;
119
+ } else {
120
+ process.env.CHIRPIER_API_KEY = previousKey;
121
+ }
122
+ }
69
123
  });
70
124
  });
71
125
 
72
- describe("monitor", () => {
73
- test("event should be sent", async () => {
74
- // Setup mock server
126
+ describe("logEvent", () => {
127
+ test("log should be sent", async () => {
75
128
  const mock = new MockAdapter(axios);
76
129
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
77
130
 
78
131
  initialize({
79
- key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
132
+ key: "chp_log_send_key",
80
133
  logLevel: LogLevel.None,
81
134
  });
82
135
 
83
- const event: Event = {
84
- group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
85
- stream_name: "test-stream",
136
+ const log: Log = {
137
+ agent: "api.worker",
138
+ event: "request.finished",
86
139
  value: 1,
87
140
  };
88
141
 
89
- monitor(event);
90
-
91
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for flush
142
+ await logEvent(log);
143
+ await new Promise((resolve) => setTimeout(resolve, 2000));
92
144
 
93
145
  expect(mock.history.post.length).toBe(1);
94
146
  expect(mock.history.post[0].url).toBe(DEFAULT_API_ENDPOINT);
95
147
  expect(JSON.parse(mock.history.post[0].data)).toEqual([
96
148
  {
97
- group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
98
- stream_name: "test-stream",
149
+ log_id: expect.any(String),
150
+ agent: "api.worker",
151
+ event: "request.finished",
99
152
  value: 1,
100
153
  },
101
154
  ]);
155
+ expect(JSON.parse(mock.history.post[0].data)[0].log_id).toMatch(
156
+ /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
157
+ );
158
+ });
102
159
 
103
- // Clean up the mock
104
- mock.reset();
105
- Chirpier.stop();
160
+ test("should preserve provided log_id", async () => {
161
+ const mock = new MockAdapter(axios);
162
+ mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
163
+
164
+ initialize({
165
+ key: "chp_log_id_key",
166
+ logLevel: LogLevel.None,
167
+ });
168
+
169
+ await logEvent({
170
+ log_id: "9f97d65f-fb30-4062-b4d0-8617c03fe4f6",
171
+ event: "request.finished",
172
+ value: 1,
173
+ });
174
+
175
+ await new Promise((resolve) => setTimeout(resolve, 2000));
176
+ const payload = JSON.parse(mock.history.post[0].data);
177
+ expect(payload[0].log_id).toBe("9f97d65f-fb30-4062-b4d0-8617c03fe4f6");
106
178
  });
107
179
 
108
- test("should silently drop invalid event", async () => {
180
+ test("agent whitespace should be omitted", async () => {
109
181
  const mock = new MockAdapter(axios);
110
182
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
111
183
 
112
184
  initialize({
113
- key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
114
- logLevel: LogLevel.Debug,
185
+ key: "chp_log_whitespace_agent",
186
+ logLevel: LogLevel.None,
115
187
  });
116
188
 
117
- const invalidEvent = {
118
- group_id: "invalid-uuid",
119
- stream_name: "",
120
- value: 0,
121
- } as Event;
189
+ await logEvent({
190
+ agent: " ",
191
+ event: "metric.tick",
192
+ value: 42,
193
+ });
122
194
 
123
- const consoleSpy = jest.spyOn(console, "debug");
195
+ await new Promise((resolve) => setTimeout(resolve, 2000));
196
+ const payload = JSON.parse(mock.history.post[0].data);
197
+ expect(payload[0].agent).toBeUndefined();
198
+ });
124
199
 
125
- await monitor(invalidEvent);
200
+ test("should support meta payload", async () => {
201
+ const mock = new MockAdapter(axios);
202
+ mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
126
203
 
127
- expect(consoleSpy).toHaveBeenCalledWith(
128
- "Invalid event format, dropping event:",
129
- invalidEvent
130
- );
131
- expect(mock.history.post.length).toBe(0); // No request should be made
204
+ initialize({
205
+ key: "chp_log_meta_key",
206
+ logLevel: LogLevel.None,
207
+ });
208
+
209
+ await logEvent({
210
+ agent: "api.worker",
211
+ event: "request.finished",
212
+ value: 200,
213
+ meta: {
214
+ path: "/v1.0/logs",
215
+ status: "ok",
216
+ },
217
+ });
132
218
 
133
- // Clean up
134
- mock.reset();
135
- consoleSpy.mockRestore();
136
- Chirpier.stop();
219
+ await new Promise((resolve) => setTimeout(resolve, 2000));
220
+ const payload = JSON.parse(mock.history.post[0].data);
221
+ expect(payload[0].meta.path).toBe("/v1.0/logs");
137
222
  });
138
223
 
139
- test("should batch events and flush when batch size is reached", async () => {
224
+ test("should support occurred_at timestamp", async () => {
140
225
  const mock = new MockAdapter(axios);
141
226
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
142
227
 
143
228
  initialize({
144
- key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
229
+ key: "chp_log_occurred_at_key",
145
230
  logLevel: LogLevel.None,
146
231
  });
147
232
 
148
- const event: Event = {
149
- group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
150
- stream_name: "test-stream",
233
+ const occurredAt = new Date(Date.now() - 2 * 60 * 60 * 1000);
234
+
235
+ await logEvent({
236
+ event: "request.finished",
151
237
  value: 1,
152
- };
238
+ occurred_at: occurredAt,
239
+ });
153
240
 
154
- monitor(event);
155
- monitor(event);
241
+ await new Promise((resolve) => setTimeout(resolve, 2000));
242
+ const payload = JSON.parse(mock.history.post[0].data);
243
+ expect(payload[0].occurred_at).toBe(occurredAt.toISOString());
244
+ });
156
245
 
157
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for flush
246
+ test("should throw error for invalid log", async () => {
247
+ const mock = new MockAdapter(axios);
248
+ mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
158
249
 
159
- expect(mock.history.post.length).toBe(1);
160
- expect(JSON.parse(mock.history.post[0].data).length).toBe(2);
250
+ initialize({
251
+ key: "chp_invalid_log_key",
252
+ logLevel: LogLevel.Debug,
253
+ });
254
+
255
+ await expect(
256
+ logEvent({
257
+ event: "",
258
+ value: 0,
259
+ })
260
+ ).rejects.toThrow(ChirpierError);
161
261
 
162
- mock.reset();
163
- Chirpier.stop();
262
+ await expect(
263
+ logEvent({
264
+ event: "too-old",
265
+ value: 1,
266
+ occurred_at: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000),
267
+ })
268
+ ).rejects.toThrow(ChirpierError);
269
+
270
+ await expect(
271
+ logEvent({
272
+ event: "too-future",
273
+ value: 1,
274
+ occurred_at: new Date(Date.now() + 25 * 60 * 60 * 1000),
275
+ })
276
+ ).rejects.toThrow(ChirpierError);
277
+
278
+ await expect(
279
+ logEvent({
280
+ log_id: "not-a-uuid",
281
+ event: "bad-log-id",
282
+ value: 1,
283
+ })
284
+ ).rejects.toThrow(ChirpierError);
285
+
286
+ expect(mock.history.post.length).toBe(0);
164
287
  });
165
288
 
166
- test("should flush events after interval", async () => {
289
+ test("should batch logs and flush when batch size is reached", async () => {
167
290
  const mock = new MockAdapter(axios);
168
291
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
169
292
 
170
293
  initialize({
171
- key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
294
+ key: "chp_batch_key",
172
295
  logLevel: LogLevel.None,
173
296
  });
174
297
 
175
- const event: Event = {
176
- group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
177
- stream_name: "test-stream",
298
+ await logEvent({ event: "batch.event", value: 1 });
299
+ await logEvent({ event: "batch.event", value: 2 });
300
+
301
+ await new Promise((resolve) => setTimeout(resolve, 2000));
302
+
303
+ expect(mock.history.post.length).toBe(1);
304
+ expect(JSON.parse(mock.history.post[0].data).length).toBe(2);
305
+ });
306
+ });
307
+
308
+ describe("Client API", () => {
309
+ test("createClient supports direct logging", async () => {
310
+ const mock = new MockAdapter(axios);
311
+ mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
312
+
313
+ const client: Client = createClient({ key: "chp_direct_client_key" });
314
+ await client.log({
315
+ event: "direct.client.log",
178
316
  value: 1,
179
- };
317
+ });
180
318
 
181
- monitor(event);
319
+ await new Promise((resolve) => setTimeout(resolve, 2000));
320
+ expect(mock.history.post.length).toBe(1);
321
+ expect(JSON.parse(mock.history.post[0].data)[0].event).toBe("direct.client.log");
322
+
323
+ await client.shutdown();
324
+ });
182
325
 
183
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for flush
326
+ test("flush should force delivery of queued logs", async () => {
327
+ const mock = new MockAdapter(axios);
328
+ mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
329
+
330
+ initialize({
331
+ key: "chp_flush_key",
332
+ logLevel: LogLevel.None,
333
+ flushDelay: 10000,
334
+ });
184
335
 
336
+ await logEvent({ event: "queued.before.flush", value: 1 });
337
+ expect(mock.history.post.length).toBe(0);
338
+
339
+ await flush();
185
340
  expect(mock.history.post.length).toBe(1);
186
- expect(JSON.parse(mock.history.post[0].data).length).toBe(1);
341
+ expect(JSON.parse(mock.history.post[0].data)[0].event).toBe("queued.before.flush");
342
+ });
343
+
344
+ test("getEventLogs uses servicer endpoint with period, limit, and offset", async () => {
345
+ const mock = new MockAdapter(axios);
346
+ mock.onGet("https://api.chirpier.co/v1.0/events/evt_123/logs?period=hour&limit=25&offset=10").reply(200, []);
347
+
348
+ const client: Client = createClient({ key: "chp_client_logs_key" });
349
+ try {
350
+ await client.getEventLogs("evt_123", { period: "hour", limit: 25, offset: 10 });
351
+ expect(mock.history.get[0].url).toBe("https://api.chirpier.co/v1.0/events/evt_123/logs?period=hour&limit=25&offset=10");
352
+ } finally {
353
+ await client.shutdown();
354
+ }
355
+ });
356
+
357
+ test("getAlertDeliveries uses pagination params", async () => {
358
+ const mock = new MockAdapter(axios);
359
+ mock.onGet("https://api.chirpier.co/v1.0/alerts/alrt_123/deliveries?kind=test&limit=20&offset=5").reply(200, []);
360
+
361
+ const client: Client = createClient({ key: "chp_client_alert_key" });
362
+ try {
363
+ await client.getAlertDeliveries("alrt_123", { kind: "test", limit: 20, offset: 5 });
364
+ expect(mock.history.get[0].url).toBe("https://api.chirpier.co/v1.0/alerts/alrt_123/deliveries?kind=test&limit=20&offset=5");
365
+ } finally {
366
+ await client.shutdown();
367
+ }
368
+ });
369
+
370
+ test("archiveAlert posts to servicer endpoint", async () => {
371
+ const mock = new MockAdapter(axios);
372
+ mock.onPost("https://api.chirpier.co/v1.0/alerts/alrt_123/archive").reply(200, {});
373
+
374
+ const client: Client = createClient({ key: "chp_client_alert_key" });
375
+ try {
376
+ await client.archiveAlert("alrt_123");
377
+ expect(mock.history.post[0].url).toBe("https://api.chirpier.co/v1.0/alerts/alrt_123/archive");
378
+ } finally {
379
+ await client.shutdown();
380
+ }
381
+ });
187
382
 
188
- mock.reset();
189
- Chirpier.stop();
383
+ test("testWebhook posts to servicer endpoint", async () => {
384
+ const mock = new MockAdapter(axios);
385
+ mock.onPost("https://api.chirpier.co/v1.0/webhooks/whk_123/test").reply(200);
386
+
387
+ const client: Client = createClient({ key: "chp_client_webhook_key" });
388
+ try {
389
+ await client.testWebhook("whk_123");
390
+ expect(mock.history.post[0].url).toBe("https://api.chirpier.co/v1.0/webhooks/whk_123/test");
391
+ } finally {
392
+ await client.shutdown();
393
+ }
190
394
  });
191
395
  });
192
396
  });
package/src/constants.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  // constants.ts
2
2
 
3
- export const DEFAULT_RETRIES = 15;
4
- export const DEFAULT_TIMEOUT = 5000
5
- export const DEFAULT_BATCH_SIZE = 350;
6
- export const DEFAULT_FLUSH_DELAY = 500;
7
- export const MAX_QUEUE_SIZE = 50000;
8
- export const DEFAULT_API_ENDPOINT = "https://eu-west.chirpier.co/v1.0/events"
3
+ export const DEFAULT_RETRIES = 15 as const;
4
+ export const DEFAULT_TIMEOUT = 5000 as const;
5
+ export const DEFAULT_BATCH_SIZE = 500 as const;
6
+ export const DEFAULT_FLUSH_DELAY = 500 as const;
7
+ export const MAX_QUEUE_SIZE = 5000 as const;
8
+ export const DEFAULT_API_ENDPOINT = "https://logs.chirpier.co/v1.0/logs" as const;
9
+ export const DEFAULT_SERVICER_ENDPOINT = "https://api.chirpier.co/v1.0" as const;