@chirpier/chirpier-js 0.1.2 → 0.1.3

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
@@ -13,7 +13,7 @@ The Chirpier SDK is a lightweight, versatile library for monitoring and tracking
13
13
 
14
14
  You can install the Chirpier SDK via npm:
15
15
  ```
16
- npm install @chirpier/sdk
16
+ npm install @chirpier/chirpier-js
17
17
  ```
18
18
 
19
19
  ## Getting Started
@@ -24,7 +24,7 @@ To start using the SDK, you need to initialize it with your API key. The SDK wor
24
24
 
25
25
  #### In a Browser
26
26
  ```
27
- import { initialize, monitor } from '@chirpier/sdk-js';
27
+ import { initialize, monitor } from '@chirpier/chirpier-js';
28
28
 
29
29
  // Initialize the SDK with your API key
30
30
  initialize({ key: 'your-api-key' });
@@ -32,7 +32,7 @@ initialize({ key: 'your-api-key' });
32
32
  // Send a data stream tied to a group of streams
33
33
  monitor({
34
34
  group_id: '02e4f4d8-415e-4fc1-b01a-677ac5bc9207',
35
- stream: 'Sales',
35
+ stream_name: 'Sales',
36
36
  value: 15.30,
37
37
  });
38
38
  ```
@@ -40,7 +40,7 @@ monitor({
40
40
  #### In a Server (e.g., Express.js)
41
41
  ```
42
42
  const express = require('express');
43
- const { initialize, monitor } = require('@chirpier/sdk-js');
43
+ const { initialize, monitor } = require('@chirpier/chirpier-js');
44
44
 
45
45
  const app = express();
46
46
  const port = 3000;
@@ -76,7 +76,7 @@ initialize({ key: 'your-api-key' });
76
76
  // Monitor an event
77
77
  monitor({
78
78
  group_id: 'group UUID',
79
- stream: 'Sales',
79
+ stream_name: 'Sales',
80
80
  value: 15.3,
81
81
  });
82
82
  ```
@@ -85,7 +85,3 @@ monitor({
85
85
  Handling Offline Scenarios
86
86
 
87
87
  The SDK automatically queues events when the network is unavailable and sends them when the connection is restored.
88
-
89
- ## Custom Storage Mechanisms
90
-
91
- The SDK uses localStorage for browser environments and in memory storage for Node.js. If you need a custom storage mechanism, you can extend the SDK by implementing the Storage interface.
@@ -43,216 +43,153 @@ var index_1 = require("../index");
43
43
  var constants_1 = require("../constants");
44
44
  var axios_mock_adapter_1 = __importDefault(require("axios-mock-adapter"));
45
45
  var axios_1 = __importDefault(require("axios"));
46
- var server_1 = require("./mocks/server");
47
- var uuid_1 = require("@lukeed/uuid");
48
- jest.mock("@lukeed/uuid");
49
46
  describe("Chirpier SDK", function () {
50
- var chirpier;
51
- afterEach(function () {
52
- // Clean up mock server
53
- (0, server_1.cleanupMockServer)();
54
- });
55
47
  describe("Initialization", function () {
56
- test("should initialize with default values", function () {
57
- chirpier = new index_1.Chirpier({
58
- key: "api_key",
59
- });
60
- // Setup mock server
61
- var mock = new axios_mock_adapter_1.default(axios_1.default);
62
- mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
63
- expect(chirpier["apiEndpoint"]).toBe(constants_1.DEFAULT_API_ENDPOINT);
64
- expect(chirpier["retries"]).toBe(constants_1.DEFAULT_RETRIES);
48
+ test("should throw error if monitor is called before initialize", function () {
49
+ var event = {
50
+ group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
51
+ stream_name: "test-stream",
52
+ value: 1,
53
+ };
54
+ expect(function () { return (0, index_1.monitor)(event); }).toThrow(index_1.ChirpierError);
55
+ expect(function () { return (0, index_1.monitor)(event); }).toThrow("Chirpier SDK is not initialized. Please call initialize() first.");
65
56
  });
66
- test("should initialize with custom values using mock server", function () {
67
- chirpier = new index_1.Chirpier({
68
- key: "api_key",
57
+ test("should initialize with default values", function () {
58
+ (0, index_1.initialize)({
59
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
69
60
  });
61
+ var chirpier = index_1.Chirpier.getInstance({});
70
62
  // Setup mock server
71
63
  var mock = new axios_mock_adapter_1.default(axios_1.default);
72
64
  mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
73
- var customChirpier = new index_1.Chirpier({
74
- key: "api_key",
75
- apiEndpoint: constants_1.DEFAULT_API_ENDPOINT,
76
- retries: 5,
77
- });
78
- expect(customChirpier["apiEndpoint"]).toBe(constants_1.DEFAULT_API_ENDPOINT);
79
- expect(customChirpier["retries"]).toBe(5);
65
+ expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["apiEndpoint"]).toBe(constants_1.DEFAULT_API_ENDPOINT);
66
+ expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["retries"]).toBe(constants_1.DEFAULT_RETRIES);
67
+ expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["timeout"]).toBe(constants_1.DEFAULT_TIMEOUT);
68
+ expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["batchSize"]).toBe(constants_1.DEFAULT_BATCH_SIZE);
69
+ expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["flushTimeout"]).toBe(constants_1.DEFAULT_FLUSH_DELAY);
70
+ index_1.Chirpier.stop();
80
71
  });
81
72
  test("should throw error if key is not provided", function () {
82
- chirpier = new index_1.Chirpier({
83
- key: "api_key",
84
- });
85
- // Setup mock server
86
- var mock = new axios_mock_adapter_1.default(axios_1.default);
87
- mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
88
- expect(function () { return new index_1.Chirpier({}); }).toThrow(index_1.ChirpierError);
89
- });
90
- test("should throw error if key is not a valid JWT", function () {
91
- expect(function () { return (0, index_1.initialize)({ key: "invalid_key" }); }).toThrow(index_1.ChirpierError);
92
- });
93
- test("should initialize successfully with a valid JWT", function () {
94
- var validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
95
- expect(function () { return (0, index_1.initialize)({ key: validJWT }); }).not.toThrow();
73
+ expect(function () {
74
+ (0, index_1.initialize)({
75
+ key: "api_key",
76
+ });
77
+ }).toThrow(index_1.ChirpierError);
78
+ expect(function () {
79
+ (0, index_1.initialize)({
80
+ key: "api_key",
81
+ });
82
+ }).toThrow("Invalid API key: Not a valid JWT");
96
83
  });
97
84
  });
98
85
  describe("monitor", function () {
99
86
  test("event should be sent", function () { return __awaiter(void 0, void 0, void 0, function () {
100
- var mock, validJWT, event;
87
+ var mock, event;
101
88
  return __generator(this, function (_a) {
102
89
  switch (_a.label) {
103
90
  case 0:
104
- chirpier = new index_1.Chirpier({
105
- key: "api_key",
106
- });
107
91
  mock = new axios_mock_adapter_1.default(axios_1.default);
108
92
  mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
109
- validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
110
- (0, index_1.initialize)({ key: validJWT });
93
+ (0, index_1.initialize)({
94
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
95
+ });
111
96
  event = {
112
97
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
113
- stream: "test-stream",
98
+ stream_name: "test-stream",
114
99
  value: 1,
115
100
  };
116
- return [4 /*yield*/, chirpier.monitor(event)];
101
+ (0, index_1.monitor)(event);
102
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
117
103
  case 1:
118
- _a.sent();
119
- return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1000); })];
120
- case 2:
121
104
  _a.sent(); // Wait for flush
122
105
  expect(mock.history.post.length).toBe(1);
123
106
  expect(mock.history.post[0].url).toBe(constants_1.DEFAULT_API_ENDPOINT);
124
107
  expect(JSON.parse(mock.history.post[0].data)).toEqual([
125
108
  {
126
109
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
127
- stream: "test-stream",
110
+ stream_name: "test-stream",
128
111
  value: 1,
129
112
  },
130
113
  ]);
131
114
  // Clean up the mock
132
115
  mock.reset();
116
+ index_1.Chirpier.stop();
133
117
  return [2 /*return*/];
134
118
  }
135
119
  });
136
120
  }); });
137
121
  test("should throw error for invalid event", function () { return __awaiter(void 0, void 0, void 0, function () {
138
- var invalidEvent, mock;
122
+ var chirpier, invalidEvent;
139
123
  return __generator(this, function (_a) {
140
124
  switch (_a.label) {
141
125
  case 0:
142
- chirpier = new index_1.Chirpier({
143
- key: "api_key",
126
+ (0, index_1.initialize)({
127
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
144
128
  });
129
+ chirpier = index_1.Chirpier.getInstance({});
145
130
  invalidEvent = {
146
131
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
147
132
  };
148
- return [4 /*yield*/, expect(chirpier.monitor(invalidEvent)).rejects.toThrow(index_1.ChirpierError)];
133
+ return [4 /*yield*/, expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier.monitor(invalidEvent)).rejects.toThrow(index_1.ChirpierError)];
149
134
  case 1:
150
135
  _a.sent();
151
- mock = new axios_mock_adapter_1.default(axios_1.default);
152
- mock.reset();
136
+ // Clean up the mock
137
+ index_1.Chirpier.stop();
153
138
  return [2 /*return*/];
154
139
  }
155
140
  });
156
141
  }); });
157
142
  test("should batch events and flush when batch size is reached", function () { return __awaiter(void 0, void 0, void 0, function () {
158
- var mock, validJWT, event;
143
+ var mock, event;
159
144
  return __generator(this, function (_a) {
160
145
  switch (_a.label) {
161
146
  case 0:
162
147
  mock = new axios_mock_adapter_1.default(axios_1.default);
163
148
  mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
164
- validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
165
- (0, index_1.initialize)({ key: validJWT, batchSize: 2 });
149
+ (0, index_1.initialize)({
150
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
151
+ });
166
152
  event = {
167
153
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
168
- stream: "test-stream",
154
+ stream_name: "test-stream",
169
155
  value: 1,
170
156
  };
171
157
  (0, index_1.monitor)(event);
172
158
  (0, index_1.monitor)(event);
173
- return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
159
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
174
160
  case 1:
175
161
  _a.sent(); // Wait for flush
176
162
  expect(mock.history.post.length).toBe(1);
177
163
  expect(JSON.parse(mock.history.post[0].data).length).toBe(2);
178
164
  mock.reset();
165
+ index_1.Chirpier.stop();
179
166
  return [2 /*return*/];
180
167
  }
181
168
  });
182
169
  }); });
183
170
  test("should flush events after interval", function () { return __awaiter(void 0, void 0, void 0, function () {
184
- var mock, validJWT, event;
171
+ var mock, event;
185
172
  return __generator(this, function (_a) {
186
173
  switch (_a.label) {
187
174
  case 0:
188
175
  mock = new axios_mock_adapter_1.default(axios_1.default);
189
176
  mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
190
- validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
191
- (0, index_1.initialize)({ key: validJWT, flushInterval: 100 });
177
+ (0, index_1.initialize)({
178
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
179
+ });
192
180
  event = {
193
181
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
194
- stream: "test-stream",
182
+ stream_name: "test-stream",
195
183
  value: 1,
196
184
  };
197
185
  (0, index_1.monitor)(event);
198
- return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1000); })];
186
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
199
187
  case 1:
200
188
  _a.sent(); // Wait for flush
201
189
  expect(mock.history.post.length).toBe(1);
202
190
  expect(JSON.parse(mock.history.post[0].data).length).toBe(1);
203
191
  mock.reset();
204
- return [2 /*return*/];
205
- }
206
- });
207
- }); });
208
- test("should use provided event_id if available", function () { return __awaiter(void 0, void 0, void 0, function () {
209
- var mock, validJWT, event;
210
- return __generator(this, function (_a) {
211
- switch (_a.label) {
212
- case 0:
213
- mock = new axios_mock_adapter_1.default(axios_1.default);
214
- mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
215
- validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
216
- (0, index_1.initialize)({ key: validJWT });
217
- event = {
218
- group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
219
- stream: "test-stream",
220
- value: 1,
221
- event_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
222
- };
223
- (0, index_1.monitor)(event);
224
- return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1000); })];
225
- case 1:
226
- _a.sent(); // Wait for flush
227
- expect(mock.history.post.length).toBe(1);
228
- expect(JSON.parse(mock.history.post[0].data)[0].event_id).toBe("f3438ee9-b964-48aa-b938-a803df440a3c");
229
- mock.reset();
230
- return [2 /*return*/];
231
- }
232
- });
233
- }); });
234
- test("should generate event_id if not provided", function () { return __awaiter(void 0, void 0, void 0, function () {
235
- var mock, validJWT, event;
236
- return __generator(this, function (_a) {
237
- switch (_a.label) {
238
- case 0:
239
- mock = new axios_mock_adapter_1.default(axios_1.default);
240
- mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
241
- uuid_1.v4.mockReturnValue("generated-uuid");
242
- validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
243
- (0, index_1.initialize)({ key: validJWT });
244
- event = {
245
- group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
246
- stream: "test-stream",
247
- value: 1,
248
- };
249
- (0, index_1.monitor)(event);
250
- return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1000); })];
251
- case 1:
252
- _a.sent(); // Wait for flush
253
- expect(mock.history.post.length).toBe(1);
254
- expect(JSON.parse(mock.history.post[0].data)[0].event_id).toBe("generated-uuid");
255
- mock.reset();
192
+ index_1.Chirpier.stop();
256
193
  return [2 /*return*/];
257
194
  }
258
195
  });
@@ -1,4 +1,6 @@
1
- export declare const DEFAULT_API_ENDPOINT = "https://events.chirpier.co/api/events";
1
+ export declare const DEFAULT_API_ENDPOINT = "https://events.chirpier.co/v1.0/events";
2
2
  export declare const DEFAULT_RETRIES = 30;
3
3
  export declare const DEFAULT_TIMEOUT = 5000;
4
+ export declare const DEFAULT_BATCH_SIZE = 50;
5
+ export declare const DEFAULT_FLUSH_DELAY = 500;
4
6
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,0CAA0C,CAAC;AAC5E,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,OAAO,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,2CAA2C,CAAC;AAC7E,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,mBAAmB,MAAM,CAAC"}
package/dist/constants.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  // constants.ts
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.DEFAULT_TIMEOUT = exports.DEFAULT_RETRIES = exports.DEFAULT_API_ENDPOINT = void 0;
5
- exports.DEFAULT_API_ENDPOINT = 'https://events.chirpier.co/api/events';
4
+ exports.DEFAULT_FLUSH_DELAY = exports.DEFAULT_BATCH_SIZE = exports.DEFAULT_TIMEOUT = exports.DEFAULT_RETRIES = exports.DEFAULT_API_ENDPOINT = void 0;
5
+ exports.DEFAULT_API_ENDPOINT = 'https://events.chirpier.co/v1.0/events';
6
6
  exports.DEFAULT_RETRIES = 30;
7
7
  exports.DEFAULT_TIMEOUT = 5000;
8
+ exports.DEFAULT_BATCH_SIZE = 50;
9
+ exports.DEFAULT_FLUSH_DELAY = 500;
package/dist/index.d.ts CHANGED
@@ -1,16 +1,10 @@
1
1
  interface Options {
2
2
  key: string;
3
- apiEndpoint?: string;
4
- retries?: number;
5
- timeout?: number;
6
- batchSize?: number;
7
- flushInterval?: number;
8
3
  }
9
4
  export interface Event {
10
5
  group_id: string;
11
- stream: string;
6
+ stream_name: string;
12
7
  value: number;
13
- event_id?: string;
14
8
  }
15
9
  export declare class ChirpierError extends Error {
16
10
  constructor(message: string);
@@ -19,20 +13,27 @@ export declare class ChirpierError extends Error {
19
13
  * Main Chirpier class for monitoring events.
20
14
  */
21
15
  export declare class Chirpier {
16
+ private static instance;
22
17
  private readonly apiKey;
23
18
  private readonly apiEndpoint;
24
19
  private readonly retries;
25
20
  private readonly timeout;
26
21
  private readonly axiosInstance;
27
22
  private eventQueue;
28
- private flushTimeout;
29
23
  private readonly batchSize;
30
- private readonly flushInterval;
24
+ private readonly flushTimeout;
25
+ private flushTimeoutId;
31
26
  /**
32
27
  * Initializes a new instance of the Chirpier class.
33
28
  * @param options - Configuration options for the SDK.
34
29
  */
35
- constructor({ key, apiEndpoint, retries, timeout, batchSize, flushInterval, }: Options);
30
+ private constructor();
31
+ /**
32
+ * Gets the singleton instance of Chirpier, creating it if it doesn't exist.
33
+ * @param options - Configuration options for the SDK.
34
+ * @returns The Chirpier instance.
35
+ */
36
+ static getInstance(options: Options): Chirpier | null;
36
37
  /**
37
38
  * Validates the event structure.
38
39
  * @param event - The event to validate.
@@ -53,6 +54,7 @@ export declare class Chirpier {
53
54
  * @param events - The array of events to send.
54
55
  */
55
56
  private sendEvents;
57
+ static stop(): void;
56
58
  }
57
59
  /**
58
60
  * Initializes the Chirpier SDK.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAGD,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC;;;OAGG;gBACS,EACV,GAAG,EACH,WAAkC,EAClC,OAAyB,EACzB,OAAyB,EACzB,SAAe,EACf,aAAmB,GACpB,EAAE,OAAO;IAyCV;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAapB;;;OAGG;IACU,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BjD;;OAEG;YACW,UAAU;IA6BxB;;;OAGG;YACW,UAAU;CAGzB;AAwCD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkBjD;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAS1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAGD,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,cAAc,CAA+B;IAErD;;;OAGG;IACH,OAAO;IA8CP;;;;OAIG;WACW,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI;IAO5D;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAapB;;;OAGG;IACU,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBjD;;OAEG;YACW,UAAU;IAqBxB;;;OAGG;YACW,UAAU;WAKV,IAAI,IAAI,IAAI;CAa3B;AAqCD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkBjD;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAW1C"}
package/dist/index.js CHANGED
@@ -14,17 +14,6 @@ var __extends = (this && this.__extends) || (function () {
14
14
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
15
  };
16
16
  })();
17
- var __assign = (this && this.__assign) || function () {
18
- __assign = Object.assign || function(t) {
19
- for (var s, i = 1, n = arguments.length; i < n; i++) {
20
- s = arguments[i];
21
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
22
- t[p] = s[p];
23
- }
24
- return t;
25
- };
26
- return __assign.apply(this, arguments);
27
- };
28
17
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
29
18
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
30
19
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -78,7 +67,6 @@ exports.monitor = exports.initialize = exports.Chirpier = exports.ChirpierError
78
67
  // Import necessary dependencies
79
68
  var axios_1 = __importDefault(require("axios"));
80
69
  var axios_retry_1 = __importDefault(require("axios-retry"));
81
- var uuid_1 = require("@lukeed/uuid");
82
70
  var js_base64_1 = require("js-base64");
83
71
  var constants_1 = require("./constants");
84
72
  // Custom error class for Chirpier-specific errors
@@ -101,19 +89,19 @@ var Chirpier = /** @class */ (function () {
101
89
  * Initializes a new instance of the Chirpier class.
102
90
  * @param options - Configuration options for the SDK.
103
91
  */
104
- function Chirpier(_a) {
105
- var key = _a.key, _b = _a.apiEndpoint, apiEndpoint = _b === void 0 ? constants_1.DEFAULT_API_ENDPOINT : _b, _c = _a.retries, retries = _c === void 0 ? constants_1.DEFAULT_RETRIES : _c, _d = _a.timeout, timeout = _d === void 0 ? constants_1.DEFAULT_TIMEOUT : _d, _e = _a.batchSize, batchSize = _e === void 0 ? 100 : _e, _f = _a.flushInterval, flushInterval = _f === void 0 ? 500 : _f;
92
+ function Chirpier(options) {
106
93
  this.eventQueue = [];
107
- this.flushTimeout = null;
94
+ this.flushTimeoutId = null;
95
+ var key = options.key;
108
96
  if (!key || typeof key !== "string") {
109
97
  throw new ChirpierError("API key is required and must be a string");
110
98
  }
111
99
  this.apiKey = key;
112
- this.apiEndpoint = apiEndpoint;
113
- this.retries = retries;
114
- this.timeout = timeout;
115
- this.batchSize = batchSize;
116
- this.flushInterval = flushInterval;
100
+ this.apiEndpoint = constants_1.DEFAULT_API_ENDPOINT;
101
+ this.retries = constants_1.DEFAULT_RETRIES;
102
+ this.timeout = constants_1.DEFAULT_TIMEOUT;
103
+ this.batchSize = constants_1.DEFAULT_BATCH_SIZE;
104
+ this.flushTimeout = constants_1.DEFAULT_FLUSH_DELAY;
117
105
  // Create axios instance with authorization header
118
106
  this.axiosInstance = axios_1.default.create({
119
107
  headers: { Authorization: "Bearer ".concat(this.apiKey) },
@@ -136,6 +124,17 @@ var Chirpier = /** @class */ (function () {
136
124
  shouldResetTimeout: true,
137
125
  });
138
126
  }
127
+ /**
128
+ * Gets the singleton instance of Chirpier, creating it if it doesn't exist.
129
+ * @param options - Configuration options for the SDK.
130
+ * @returns The Chirpier instance.
131
+ */
132
+ Chirpier.getInstance = function (options) {
133
+ if (!Chirpier.instance && options.key) {
134
+ Chirpier.instance = new Chirpier(options);
135
+ }
136
+ return Chirpier.instance;
137
+ };
139
138
  /**
140
139
  * Validates the event structure.
141
140
  * @param event - The event to validate.
@@ -145,8 +144,8 @@ var Chirpier = /** @class */ (function () {
145
144
  return (typeof event.group_id === "string" &&
146
145
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(event.group_id) &&
147
146
  event.group_id.trim().length > 0 &&
148
- typeof event.stream === "string" &&
149
- event.stream.trim().length > 0 &&
147
+ typeof event.stream_name === "string" &&
148
+ event.stream_name.trim().length > 0 &&
150
149
  typeof event.value === "number");
151
150
  };
152
151
  /**
@@ -155,24 +154,27 @@ var Chirpier = /** @class */ (function () {
155
154
  */
156
155
  Chirpier.prototype.monitor = function (event) {
157
156
  return __awaiter(this, void 0, void 0, function () {
158
- var eventWithID;
159
157
  var _this = this;
160
158
  return __generator(this, function (_a) {
161
- if (!this.apiKey) {
162
- throw new ChirpierError("Chirpier SDK must be initialized before calling monitor()");
163
- }
164
- if (!this.isValidEvent(event)) {
165
- throw new ChirpierError("Invalid event format. Must include group_id, stream, and numeric value.");
166
- }
167
- eventWithID = __assign(__assign({}, event), { event_id: event.event_id || (0, uuid_1.v4)() });
168
- this.eventQueue.push(eventWithID);
169
- if (this.eventQueue.length >= this.batchSize) {
170
- this.flushQueue();
171
- }
172
- else if (!this.flushTimeout) {
173
- this.flushTimeout = setTimeout(function () { return _this.flushQueue(); }, this.flushInterval);
159
+ switch (_a.label) {
160
+ case 0:
161
+ if (!this.isValidEvent(event)) {
162
+ throw new ChirpierError("Invalid event format. Must include group_id, stream_name, and numeric value.");
163
+ }
164
+ this.eventQueue.push(event);
165
+ if (!(this.eventQueue.length >= this.batchSize)) return [3 /*break*/, 2];
166
+ console.info("Batch size reached. Flushing queue.");
167
+ return [4 /*yield*/, this.flushQueue()];
168
+ case 1:
169
+ _a.sent();
170
+ return [3 /*break*/, 3];
171
+ case 2:
172
+ if (!this.flushTimeoutId) {
173
+ this.flushTimeoutId = setTimeout(function () { return _this.flushQueue(); }, this.flushTimeout);
174
+ }
175
+ _a.label = 3;
176
+ case 3: return [2 /*return*/];
174
177
  }
175
- return [2 /*return*/];
176
178
  });
177
179
  });
178
180
  };
@@ -182,17 +184,16 @@ var Chirpier = /** @class */ (function () {
182
184
  Chirpier.prototype.flushQueue = function () {
183
185
  return __awaiter(this, void 0, void 0, function () {
184
186
  var eventsToSend, error_1;
185
- var _this = this;
186
187
  return __generator(this, function (_a) {
187
188
  switch (_a.label) {
188
189
  case 0:
190
+ if (this.flushTimeoutId) {
191
+ clearTimeout(this.flushTimeoutId);
192
+ this.flushTimeoutId = null;
193
+ }
189
194
  if (this.eventQueue.length === 0) {
190
195
  return [2 /*return*/];
191
196
  }
192
- if (this.flushTimeout) {
193
- clearTimeout(this.flushTimeout);
194
- this.flushTimeout = null;
195
- }
196
197
  eventsToSend = __spreadArray([], this.eventQueue, true);
197
198
  this.eventQueue = [];
198
199
  _a.label = 1;
@@ -207,12 +208,7 @@ var Chirpier = /** @class */ (function () {
207
208
  error_1 = _a.sent();
208
209
  console.error("Failed to send events:", error_1);
209
210
  return [3 /*break*/, 4];
210
- case 4:
211
- // Schedule next flush if there are more events
212
- if (this.eventQueue.length > 0) {
213
- this.flushTimeout = setTimeout(function () { return _this.flushQueue(); }, this.flushInterval);
214
- }
215
- return [2 /*return*/];
211
+ case 4: return [2 /*return*/];
216
212
  }
217
213
  });
218
214
  });
@@ -233,6 +229,21 @@ var Chirpier = /** @class */ (function () {
233
229
  });
234
230
  });
235
231
  };
232
+ // Stop the timeout and uninitialize the Chirpier instance
233
+ Chirpier.stop = function () {
234
+ if (!Chirpier.instance) {
235
+ return;
236
+ }
237
+ if (Chirpier.instance.flushTimeoutId) {
238
+ clearTimeout(Chirpier.instance.flushTimeoutId);
239
+ Chirpier.instance.flushTimeoutId = null;
240
+ }
241
+ // Flush any remaining events in the queue
242
+ Chirpier.instance.flushQueue();
243
+ // Uninitialize the Chirpier instance
244
+ Chirpier.instance = null;
245
+ };
246
+ Chirpier.instance = null;
236
247
  return Chirpier;
237
248
  }());
238
249
  exports.Chirpier = Chirpier;
@@ -270,8 +281,6 @@ function isValidJWT(token) {
270
281
  return false;
271
282
  }
272
283
  }
273
- // Singleton instance of Chirpier
274
- var chirpierInstance = null;
275
284
  /**
276
285
  * Initializes the Chirpier SDK.
277
286
  * @param options - Configuration options for the SDK.
@@ -281,7 +290,7 @@ function initialize(options) {
281
290
  throw new ChirpierError("Invalid API key: Not a valid JWT");
282
291
  }
283
292
  try {
284
- chirpierInstance = new Chirpier(options);
293
+ Chirpier.getInstance(options);
285
294
  }
286
295
  catch (error) {
287
296
  if (error instanceof ChirpierError) {
@@ -299,10 +308,11 @@ exports.initialize = initialize;
299
308
  * @param event - The event to monitor.
300
309
  */
301
310
  function monitor(event) {
302
- if (!chirpierInstance) {
311
+ var instance = Chirpier.getInstance({});
312
+ if (!instance) {
303
313
  throw new ChirpierError("Chirpier SDK is not initialized. Please call initialize() first.");
304
314
  }
305
- chirpierInstance.monitor(event).catch(function (error) {
315
+ instance.monitor(event).catch(function (error) {
306
316
  console.error("Error in monitor function:", error);
307
317
  });
308
318
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chirpier/chirpier-js",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Chirpier SDK for JavaScript",
5
5
  "keywords": [
6
6
  "chirpier",
@@ -27,7 +27,6 @@
27
27
  "test": "jest --coverage"
28
28
  },
29
29
  "dependencies": {
30
- "@lukeed/uuid": "^2.0.1",
31
30
  "axios": "^0.24.0",
32
31
  "axios-retry": "^4.5.0",
33
32
  "js-base64": "^3.7.7",
@@ -35,15 +34,19 @@
35
34
  "tslib": "^2.3.0"
36
35
  },
37
36
  "devDependencies": {
37
+ "@eslint/js": "^9.15.0",
38
38
  "@jest/globals": "^29.7.0",
39
39
  "@types/jest": "^29.5.13",
40
40
  "@types/mocha": "^10.0.8",
41
41
  "@types/node": "^22.7.5",
42
42
  "@types/uuid": "^10.0.0",
43
43
  "axios-mock-adapter": "^2.0.0",
44
+ "eslint": "^9.15.0",
45
+ "globals": "^15.12.0",
44
46
  "jest": "^29.7.0",
45
47
  "ts-jest": "^29.2.4",
46
48
  "typescript": "^4.4.3",
49
+ "typescript-eslint": "^8.15.0",
47
50
  "webpack": "^5.52.0",
48
51
  "webpack-cli": "^4.8.0"
49
52
  }
@@ -1,222 +1,163 @@
1
1
  import { Chirpier, ChirpierError, Event, initialize, monitor } from "../index";
2
- import { DEFAULT_API_ENDPOINT, DEFAULT_RETRIES } from "../constants";
2
+ import {
3
+ DEFAULT_API_ENDPOINT,
4
+ DEFAULT_RETRIES,
5
+ DEFAULT_TIMEOUT,
6
+ DEFAULT_BATCH_SIZE,
7
+ DEFAULT_FLUSH_DELAY,
8
+ } from "../constants";
3
9
  import MockAdapter from "axios-mock-adapter";
4
10
  import axios from "axios";
5
- import { cleanupMockServer } from "./mocks/server";
6
- import { v4 as uuidv4 } from "@lukeed/uuid";
7
-
8
- jest.mock("@lukeed/uuid");
9
11
 
10
12
  describe("Chirpier SDK", () => {
11
- let chirpier: Chirpier;
12
-
13
- afterEach(() => {
14
- // Clean up mock server
15
- cleanupMockServer();
16
- });
17
-
18
13
  describe("Initialization", () => {
19
- test("should initialize with default values", () => {
20
- chirpier = new Chirpier({
21
- key: "api_key",
22
- });
23
-
24
- // Setup mock server
25
- const mock = new MockAdapter(axios);
26
- mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
14
+ test("should throw error if monitor is called before initialize", () => {
15
+ const event: Event = {
16
+ group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
17
+ stream_name: "test-stream",
18
+ value: 1,
19
+ };
27
20
 
28
- expect(chirpier["apiEndpoint"]).toBe(DEFAULT_API_ENDPOINT);
29
- expect(chirpier["retries"]).toBe(DEFAULT_RETRIES);
21
+ expect(() => monitor(event)).toThrow(ChirpierError);
22
+ expect(() => monitor(event)).toThrow(
23
+ "Chirpier SDK is not initialized. Please call initialize() first."
24
+ );
30
25
  });
31
26
 
32
- test("should initialize with custom values using mock server", () => {
33
- chirpier = new Chirpier({
34
- key: "api_key",
27
+ test("should initialize with default values", () => {
28
+ initialize({
29
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
35
30
  });
31
+ const chirpier = Chirpier.getInstance({} as any);
36
32
 
37
33
  // Setup mock server
38
34
  const mock = new MockAdapter(axios);
39
35
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
40
36
 
41
- const customChirpier = new Chirpier({
42
- key: "api_key",
43
- apiEndpoint: DEFAULT_API_ENDPOINT,
44
- retries: 5,
45
- });
37
+ expect(chirpier?.["apiEndpoint"]).toBe(DEFAULT_API_ENDPOINT);
38
+ expect(chirpier?.["retries"]).toBe(DEFAULT_RETRIES);
39
+ expect(chirpier?.["timeout"]).toBe(DEFAULT_TIMEOUT);
40
+ expect(chirpier?.["batchSize"]).toBe(DEFAULT_BATCH_SIZE);
41
+ expect(chirpier?.["flushTimeout"]).toBe(DEFAULT_FLUSH_DELAY);
46
42
 
47
- expect(customChirpier["apiEndpoint"]).toBe(DEFAULT_API_ENDPOINT);
48
- expect(customChirpier["retries"]).toBe(5);
43
+ Chirpier.stop();
49
44
  });
50
45
 
51
46
  test("should throw error if key is not provided", () => {
52
- chirpier = new Chirpier({
53
- key: "api_key",
54
- });
55
-
56
- // Setup mock server
57
- const mock = new MockAdapter(axios);
58
- mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
59
-
60
- expect(() => new Chirpier({} as any)).toThrow(ChirpierError);
61
- });
62
-
63
- test("should throw error if key is not a valid JWT", () => {
64
- expect(() => initialize({ key: "invalid_key" })).toThrow(ChirpierError);
65
- });
66
-
67
- test("should initialize successfully with a valid JWT", () => {
68
- const validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
69
- expect(() => initialize({ key: validJWT })).not.toThrow();
47
+ expect(() => {
48
+ initialize({
49
+ key: "api_key",
50
+ });
51
+ }).toThrow(ChirpierError);
52
+ expect(() => {
53
+ initialize({
54
+ key: "api_key",
55
+ });
56
+ }).toThrow("Invalid API key: Not a valid JWT");
70
57
  });
71
58
  });
72
59
 
73
60
  describe("monitor", () => {
74
61
  test("event should be sent", async () => {
75
- chirpier = new Chirpier({
76
- key: "api_key",
77
- });
78
-
79
62
  // Setup mock server
80
63
  const mock = new MockAdapter(axios);
81
64
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
82
65
 
83
- const validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
84
- initialize({ key: validJWT });
85
-
66
+ initialize({
67
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
68
+ });
69
+
86
70
  const event: Event = {
87
71
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
88
- stream: "test-stream",
72
+ stream_name: "test-stream",
89
73
  value: 1,
90
74
  };
91
75
 
92
- await chirpier.monitor(event);
76
+ monitor(event);
93
77
 
94
- await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for flush
78
+ await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for flush
95
79
 
96
80
  expect(mock.history.post.length).toBe(1);
97
81
  expect(mock.history.post[0].url).toBe(DEFAULT_API_ENDPOINT);
98
82
  expect(JSON.parse(mock.history.post[0].data)).toEqual([
99
83
  {
100
84
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
101
- stream: "test-stream",
85
+ stream_name: "test-stream",
102
86
  value: 1,
103
87
  },
104
88
  ]);
105
89
 
106
90
  // Clean up the mock
107
91
  mock.reset();
92
+ Chirpier.stop();
108
93
  });
109
94
 
110
95
  test("should throw error for invalid event", async () => {
111
- chirpier = new Chirpier({
112
- key: "api_key",
96
+ initialize({
97
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
113
98
  });
99
+ const chirpier = Chirpier.getInstance({} as any);
100
+
114
101
  const invalidEvent = {
115
102
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
116
103
  } as any;
117
- await expect(chirpier.monitor(invalidEvent)).rejects.toThrow(
104
+ await expect(chirpier?.monitor(invalidEvent)).rejects.toThrow(
118
105
  ChirpierError
119
106
  );
120
107
 
121
108
  // Clean up the mock
122
- const mock = new MockAdapter(axios);
123
- mock.reset();
109
+ Chirpier.stop();
124
110
  });
125
111
 
126
112
  test("should batch events and flush when batch size is reached", async () => {
127
113
  const mock = new MockAdapter(axios);
128
114
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
129
115
 
130
- const validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
131
- initialize({ key: validJWT, batchSize: 2 });
116
+ initialize({
117
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
118
+ });
132
119
 
133
120
  const event: Event = {
134
121
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
135
- stream: "test-stream",
122
+ stream_name: "test-stream",
136
123
  value: 1,
137
124
  };
138
125
 
139
126
  monitor(event);
140
127
  monitor(event);
141
128
 
142
- await new Promise(resolve => setTimeout(resolve, 100)); // Wait for flush
129
+ await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for flush
143
130
 
144
131
  expect(mock.history.post.length).toBe(1);
145
132
  expect(JSON.parse(mock.history.post[0].data).length).toBe(2);
146
133
 
147
134
  mock.reset();
135
+ Chirpier.stop();
148
136
  });
149
137
 
150
138
  test("should flush events after interval", async () => {
151
139
  const mock = new MockAdapter(axios);
152
140
  mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
153
141
 
154
- const validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
155
- initialize({ key: validJWT, flushInterval: 100 });
142
+ initialize({
143
+ key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
144
+ });
156
145
 
157
146
  const event: Event = {
158
147
  group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
159
- stream: "test-stream",
148
+ stream_name: "test-stream",
160
149
  value: 1,
161
150
  };
162
151
 
163
152
  monitor(event);
164
153
 
165
- await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for flush
154
+ await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for flush
166
155
 
167
156
  expect(mock.history.post.length).toBe(1);
168
157
  expect(JSON.parse(mock.history.post[0].data).length).toBe(1);
169
158
 
170
159
  mock.reset();
171
- });
172
-
173
- test("should use provided event_id if available", async () => {
174
- const mock = new MockAdapter(axios);
175
- mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
176
-
177
- const validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
178
- initialize({ key: validJWT });
179
-
180
- const event: Event = {
181
- group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
182
- stream: "test-stream",
183
- value: 1,
184
- event_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
185
- };
186
-
187
- monitor(event);
188
-
189
- await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for flush
190
-
191
- expect(mock.history.post.length).toBe(1);
192
- expect(JSON.parse(mock.history.post[0].data)[0].event_id).toBe("f3438ee9-b964-48aa-b938-a803df440a3c");
193
-
194
- mock.reset();
195
- });
196
-
197
- test("should generate event_id if not provided", async () => {
198
- const mock = new MockAdapter(axios);
199
- mock.onPost(DEFAULT_API_ENDPOINT).reply(200, { success: true });
200
-
201
- (uuidv4 as jest.Mock).mockReturnValue("generated-uuid");
202
-
203
- const validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
204
- initialize({ key: validJWT });
205
-
206
- const event: Event = {
207
- group_id: "f3438ee9-b964-48aa-b938-a803df440a3c",
208
- stream: "test-stream",
209
- value: 1,
210
- };
211
-
212
- monitor(event);
213
-
214
- await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for flush
215
-
216
- expect(mock.history.post.length).toBe(1);
217
- expect(JSON.parse(mock.history.post[0].data)[0].event_id).toBe("generated-uuid");
218
-
219
- mock.reset();
160
+ Chirpier.stop();
220
161
  });
221
162
  });
222
- });
163
+ });
package/src/constants.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // constants.ts
2
2
 
3
- export const DEFAULT_API_ENDPOINT = 'https://events.chirpier.co/api/events';
3
+ export const DEFAULT_API_ENDPOINT = 'https://events.chirpier.co/v1.0/events';
4
4
  export const DEFAULT_RETRIES = 30;
5
- export const DEFAULT_TIMEOUT = 5000
5
+ export const DEFAULT_TIMEOUT = 5000
6
+ export const DEFAULT_BATCH_SIZE = 50;
7
+ export const DEFAULT_FLUSH_DELAY = 500;
package/src/index.ts CHANGED
@@ -1,30 +1,25 @@
1
1
  // Import necessary dependencies
2
2
  import axios, { AxiosInstance } from "axios";
3
3
  import axiosRetry from "axios-retry";
4
- import { v4 as uuidv4 } from "@lukeed/uuid";
5
4
  import { Base64 } from "js-base64";
6
5
  import {
7
6
  DEFAULT_API_ENDPOINT,
8
7
  DEFAULT_RETRIES,
9
8
  DEFAULT_TIMEOUT,
9
+ DEFAULT_BATCH_SIZE,
10
+ DEFAULT_FLUSH_DELAY,
10
11
  } from "./constants";
11
12
 
12
13
  // Define the options interface for Chirpier initialization
13
14
  interface Options {
14
15
  key: string;
15
- apiEndpoint?: string;
16
- retries?: number;
17
- timeout?: number;
18
- batchSize?: number;
19
- flushInterval?: number;
20
16
  }
21
17
 
22
18
  // Define the Event interface for monitoring
23
19
  export interface Event {
24
20
  group_id: string;
25
- stream: string;
21
+ stream_name: string;
26
22
  value: number;
27
- event_id?: string;
28
23
  }
29
24
 
30
25
  // Custom error class for Chirpier-specific errors
@@ -40,37 +35,36 @@ export class ChirpierError extends Error {
40
35
  * Main Chirpier class for monitoring events.
41
36
  */
42
37
  export class Chirpier {
38
+ private static instance: Chirpier | null = null;
43
39
  private readonly apiKey: string;
44
40
  private readonly apiEndpoint: string;
45
41
  private readonly retries: number;
46
42
  private readonly timeout: number;
47
43
  private readonly axiosInstance: AxiosInstance;
48
44
  private eventQueue: Event[] = [];
49
- private flushTimeout: NodeJS.Timeout | null = null;
50
45
  private readonly batchSize: number;
51
- private readonly flushInterval: number;
46
+ private readonly flushTimeout: number;
47
+ private flushTimeoutId: NodeJS.Timeout | null = null;
52
48
 
53
49
  /**
54
50
  * Initializes a new instance of the Chirpier class.
55
51
  * @param options - Configuration options for the SDK.
56
52
  */
57
- constructor({
58
- key,
59
- apiEndpoint = DEFAULT_API_ENDPOINT,
60
- retries = DEFAULT_RETRIES,
61
- timeout = DEFAULT_TIMEOUT,
62
- batchSize = 100,
63
- flushInterval = 500,
64
- }: Options) {
53
+ private constructor(options: Options) {
54
+ const {
55
+ key,
56
+ } = options;
57
+
65
58
  if (!key || typeof key !== "string") {
66
59
  throw new ChirpierError("API key is required and must be a string");
67
60
  }
61
+
68
62
  this.apiKey = key;
69
- this.apiEndpoint = apiEndpoint;
70
- this.retries = retries;
71
- this.timeout = timeout;
72
- this.batchSize = batchSize;
73
- this.flushInterval = flushInterval;
63
+ this.apiEndpoint = DEFAULT_API_ENDPOINT;
64
+ this.retries = DEFAULT_RETRIES;
65
+ this.timeout = DEFAULT_TIMEOUT;
66
+ this.batchSize = DEFAULT_BATCH_SIZE;
67
+ this.flushTimeout = DEFAULT_FLUSH_DELAY;
74
68
 
75
69
  // Create axios instance with authorization header
76
70
  this.axiosInstance = axios.create({
@@ -102,6 +96,18 @@ export class Chirpier {
102
96
  });
103
97
  }
104
98
 
99
+ /**
100
+ * Gets the singleton instance of Chirpier, creating it if it doesn't exist.
101
+ * @param options - Configuration options for the SDK.
102
+ * @returns The Chirpier instance.
103
+ */
104
+ public static getInstance(options: Options): Chirpier | null {
105
+ if (!Chirpier.instance && options.key) {
106
+ Chirpier.instance = new Chirpier(options);
107
+ }
108
+ return Chirpier.instance;
109
+ }
110
+
105
111
  /**
106
112
  * Validates the event structure.
107
113
  * @param event - The event to validate.
@@ -114,8 +120,8 @@ export class Chirpier {
114
120
  event.group_id
115
121
  ) &&
116
122
  event.group_id.trim().length > 0 &&
117
- typeof event.stream === "string" &&
118
- event.stream.trim().length > 0 &&
123
+ typeof event.stream_name === "string" &&
124
+ event.stream_name.trim().length > 0 &&
119
125
  typeof event.value === "number"
120
126
  );
121
127
  }
@@ -125,28 +131,19 @@ export class Chirpier {
125
131
  * @param event - The event to monitor.
126
132
  */
127
133
  public async monitor(event: Event): Promise<void> {
128
- if (!this.apiKey) {
129
- throw new ChirpierError("Chirpier SDK must be initialized before calling monitor()");
130
- }
131
-
132
134
  if (!this.isValidEvent(event)) {
133
135
  throw new ChirpierError(
134
- "Invalid event format. Must include group_id, stream, and numeric value."
136
+ "Invalid event format. Must include group_id, stream_name, and numeric value."
135
137
  );
136
138
  }
137
139
 
138
- // Ensure event_id is only set once
139
- const eventWithID = { ...event, event_id: event.event_id || uuidv4() };
140
-
141
- this.eventQueue.push(eventWithID);
140
+ this.eventQueue.push(event);
142
141
 
143
142
  if (this.eventQueue.length >= this.batchSize) {
144
- this.flushQueue();
145
- } else if (!this.flushTimeout) {
146
- this.flushTimeout = setTimeout(
147
- () => this.flushQueue(),
148
- this.flushInterval
149
- );
143
+ console.info(`Batch size reached. Flushing queue.`);
144
+ await this.flushQueue();
145
+ } else if (!this.flushTimeoutId) {
146
+ this.flushTimeoutId = setTimeout(() => this.flushQueue(), this.flushTimeout);
150
147
  }
151
148
  }
152
149
 
@@ -154,14 +151,14 @@ export class Chirpier {
154
151
  * Flushes the event queue by sending all events to the API.
155
152
  */
156
153
  private async flushQueue(): Promise<void> {
154
+ if (this.flushTimeoutId) {
155
+ clearTimeout(this.flushTimeoutId);
156
+ this.flushTimeoutId = null;
157
+ }
158
+
157
159
  if (this.eventQueue.length === 0) {
158
160
  return;
159
161
  }
160
-
161
- if (this.flushTimeout) {
162
- clearTimeout(this.flushTimeout);
163
- this.flushTimeout = null;
164
- }
165
162
 
166
163
  const eventsToSend = [...this.eventQueue];
167
164
  this.eventQueue = [];
@@ -172,14 +169,6 @@ export class Chirpier {
172
169
  } catch (error) {
173
170
  console.error("Failed to send events:", error);
174
171
  }
175
-
176
- // Schedule next flush if there are more events
177
- if (this.eventQueue.length > 0) {
178
- this.flushTimeout = setTimeout(
179
- () => this.flushQueue(),
180
- this.flushInterval
181
- );
182
- }
183
172
  }
184
173
 
185
174
  /**
@@ -189,6 +178,21 @@ export class Chirpier {
189
178
  private async sendEvents(events: Event[]): Promise<void> {
190
179
  await this.axiosInstance.post(this.apiEndpoint, events);
191
180
  }
181
+
182
+ // Stop the timeout and uninitialize the Chirpier instance
183
+ public static stop(): void {
184
+ if (!Chirpier.instance) {
185
+ return;
186
+ }
187
+ if (Chirpier.instance.flushTimeoutId) {
188
+ clearTimeout(Chirpier.instance.flushTimeoutId);
189
+ Chirpier.instance.flushTimeoutId = null;
190
+ }
191
+ // Flush any remaining events in the queue
192
+ Chirpier.instance.flushQueue();
193
+ // Uninitialize the Chirpier instance
194
+ Chirpier.instance = null;
195
+ }
192
196
  }
193
197
 
194
198
  /**
@@ -226,9 +230,6 @@ function isValidJWT(token: string): boolean {
226
230
  }
227
231
  }
228
232
 
229
- // Singleton instance of Chirpier
230
- let chirpierInstance: Chirpier | null = null;
231
-
232
233
  /**
233
234
  * Initializes the Chirpier SDK.
234
235
  * @param options - Configuration options for the SDK.
@@ -239,7 +240,7 @@ export function initialize(options: Options): void {
239
240
  }
240
241
 
241
242
  try {
242
- chirpierInstance = new Chirpier(options);
243
+ Chirpier.getInstance(options);
243
244
  } catch (error) {
244
245
  if (error instanceof ChirpierError) {
245
246
  console.error("Failed to initialize Chirpier SDK:", error.message);
@@ -258,12 +259,14 @@ export function initialize(options: Options): void {
258
259
  * @param event - The event to monitor.
259
260
  */
260
261
  export function monitor(event: Event): void {
261
- if (!chirpierInstance) {
262
+ const instance = Chirpier.getInstance({} as Options);
263
+ if (!instance) {
262
264
  throw new ChirpierError(
263
265
  "Chirpier SDK is not initialized. Please call initialize() first."
264
266
  );
265
267
  }
266
- chirpierInstance.monitor(event).catch((error) => {
268
+
269
+ instance.monitor(event).catch((error) => {
267
270
  console.error("Error in monitor function:", error);
268
271
  });
269
272
  }
@@ -1,6 +0,0 @@
1
- import axios from "axios";
2
-
3
- // Cleanup mock server after tests
4
- export function cleanupMockServer() {
5
- axios.defaults.adapter = undefined;
6
- }