@chirpier/chirpier-js 0.1.3 → 0.1.4
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 +13 -14
- package/dist/__tests__/chirpier.test.d.ts +2 -0
- package/dist/__tests__/chirpier.test.d.ts.map +1 -0
- package/dist/__tests__/chirpier.test.js +205 -0
- package/dist/constants.d.ts +3 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -3
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +139 -46
- package/package.json +3 -1
- package/src/__tests__/{sdk.test.ts → chirpier.test.ts} +15 -8
- package/src/constants.ts +4 -3
- package/src/index.ts +106 -34
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Chirpier SDK
|
|
2
2
|
|
|
3
|
-
The Chirpier SDK is a lightweight,
|
|
3
|
+
The Chirpier SDK is a lightweight, library for monitoring and tracking streams of data in both browser and server environments. With built-in retry logic, and offline handling, the Chirpier SDK makes it easy to collect and send data to the Chirpier API.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -12,6 +12,7 @@ The Chirpier SDK is a lightweight, versatile library for monitoring and tracking
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
You can install the Chirpier SDK via npm:
|
|
15
|
+
|
|
15
16
|
```
|
|
16
17
|
npm install @chirpier/chirpier-js
|
|
17
18
|
```
|
|
@@ -23,8 +24,9 @@ npm install @chirpier/chirpier-js
|
|
|
23
24
|
To start using the SDK, you need to initialize it with your API key. The SDK works in both browser and Node.js environments.
|
|
24
25
|
|
|
25
26
|
#### In a Browser
|
|
27
|
+
|
|
26
28
|
```
|
|
27
|
-
import { initialize, monitor } from '@chirpier/chirpier-js';
|
|
29
|
+
import { initialize, monitor, Event } from '@chirpier/chirpier-js';
|
|
28
30
|
|
|
29
31
|
// Initialize the SDK with your API key
|
|
30
32
|
initialize({ key: 'your-api-key' });
|
|
@@ -34,13 +36,14 @@ monitor({
|
|
|
34
36
|
group_id: '02e4f4d8-415e-4fc1-b01a-677ac5bc9207',
|
|
35
37
|
stream_name: 'Sales',
|
|
36
38
|
value: 15.30,
|
|
37
|
-
});
|
|
39
|
+
} as Event);
|
|
38
40
|
```
|
|
39
41
|
|
|
40
42
|
#### In a Server (e.g., Express.js)
|
|
43
|
+
|
|
41
44
|
```
|
|
42
45
|
const express = require('express');
|
|
43
|
-
const { initialize, monitor } = require('@chirpier/chirpier-js');
|
|
46
|
+
const { initialize, monitor, Event } = require('@chirpier/chirpier-js');
|
|
44
47
|
|
|
45
48
|
const app = express();
|
|
46
49
|
const port = 3000;
|
|
@@ -50,15 +53,15 @@ initialize({ key: 'your-api-key' });
|
|
|
50
53
|
|
|
51
54
|
app.use(express.json());
|
|
52
55
|
|
|
53
|
-
app.post('/
|
|
54
|
-
const { group_id,
|
|
56
|
+
app.post('/monitor', (req, res) => {
|
|
57
|
+
const { group_id, stream_name, value } = req.body;
|
|
55
58
|
|
|
56
|
-
if (!group_id || !
|
|
59
|
+
if (!group_id || !stream_name || !value) {
|
|
57
60
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
// Monitor an event
|
|
61
|
-
monitor({ group_id,
|
|
64
|
+
monitor({ group_id, stream_name, value } as Event);
|
|
62
65
|
|
|
63
66
|
res.status(200).json({ message: 'Event tracked successfully' });
|
|
64
67
|
});
|
|
@@ -69,6 +72,7 @@ app.listen(port, () => {
|
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
## Example
|
|
75
|
+
|
|
72
76
|
```
|
|
73
77
|
// Initialize the SDK with your API key
|
|
74
78
|
initialize({ key: 'your-api-key' });
|
|
@@ -76,12 +80,7 @@ initialize({ key: 'your-api-key' });
|
|
|
76
80
|
// Monitor an event
|
|
77
81
|
monitor({
|
|
78
82
|
group_id: 'group UUID',
|
|
79
|
-
stream_name: '
|
|
83
|
+
stream_name: 'My measurement',
|
|
80
84
|
value: 15.3,
|
|
81
85
|
});
|
|
82
86
|
```
|
|
83
|
-
|
|
84
|
-
## Advanced Usage
|
|
85
|
-
Handling Offline Scenarios
|
|
86
|
-
|
|
87
|
-
The SDK automatically queues events when the network is unavailable and sends them when the connection is restored.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chirpier.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/chirpier.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var index_1 = require("../index");
|
|
43
|
+
var constants_1 = require("../constants");
|
|
44
|
+
var axios_mock_adapter_1 = __importDefault(require("axios-mock-adapter"));
|
|
45
|
+
var axios_1 = __importDefault(require("axios"));
|
|
46
|
+
describe("Chirpier SDK", function () {
|
|
47
|
+
describe("Initialization", function () {
|
|
48
|
+
test("should throw error if monitor is called before initialize", function () {
|
|
49
|
+
var event = {
|
|
50
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
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.");
|
|
56
|
+
});
|
|
57
|
+
test("should initialize with default values", function () {
|
|
58
|
+
(0, index_1.initialize)({
|
|
59
|
+
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
60
|
+
logLevel: index_1.LogLevel.None
|
|
61
|
+
});
|
|
62
|
+
var chirpier = index_1.Chirpier.getInstance({});
|
|
63
|
+
// Setup mock server
|
|
64
|
+
var mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
65
|
+
mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
|
|
66
|
+
expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["apiEndpoint"]).toBe(constants_1.DEFAULT_API_ENDPOINT);
|
|
67
|
+
expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["retries"]).toBe(constants_1.DEFAULT_RETRIES);
|
|
68
|
+
expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["timeout"]).toBe(constants_1.DEFAULT_TIMEOUT);
|
|
69
|
+
expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["batchSize"]).toBe(constants_1.DEFAULT_BATCH_SIZE);
|
|
70
|
+
expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier["flushDelay"]).toBe(constants_1.DEFAULT_FLUSH_DELAY);
|
|
71
|
+
index_1.Chirpier.stop();
|
|
72
|
+
});
|
|
73
|
+
test("should throw error if key is not provided", function () {
|
|
74
|
+
expect(function () {
|
|
75
|
+
(0, index_1.initialize)({
|
|
76
|
+
key: "api_key",
|
|
77
|
+
logLevel: index_1.LogLevel.None
|
|
78
|
+
});
|
|
79
|
+
}).toThrow(index_1.ChirpierError);
|
|
80
|
+
expect(function () {
|
|
81
|
+
(0, index_1.initialize)({
|
|
82
|
+
key: "api_key",
|
|
83
|
+
logLevel: index_1.LogLevel.None
|
|
84
|
+
});
|
|
85
|
+
}).toThrow("Invalid API key: Not a valid JWT");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe("monitor", function () {
|
|
89
|
+
test("event should be sent", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
90
|
+
var mock, event;
|
|
91
|
+
return __generator(this, function (_a) {
|
|
92
|
+
switch (_a.label) {
|
|
93
|
+
case 0:
|
|
94
|
+
mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
95
|
+
mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
|
|
96
|
+
(0, index_1.initialize)({
|
|
97
|
+
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
98
|
+
logLevel: index_1.LogLevel.None
|
|
99
|
+
});
|
|
100
|
+
event = {
|
|
101
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
102
|
+
stream_name: "test-stream",
|
|
103
|
+
value: 1,
|
|
104
|
+
};
|
|
105
|
+
(0, index_1.monitor)(event);
|
|
106
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
|
|
107
|
+
case 1:
|
|
108
|
+
_a.sent(); // Wait for flush
|
|
109
|
+
expect(mock.history.post.length).toBe(1);
|
|
110
|
+
expect(mock.history.post[0].url).toBe(constants_1.DEFAULT_API_ENDPOINT);
|
|
111
|
+
expect(JSON.parse(mock.history.post[0].data)).toEqual([
|
|
112
|
+
{
|
|
113
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
114
|
+
stream_name: "test-stream",
|
|
115
|
+
value: 1,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
// Clean up the mock
|
|
119
|
+
mock.reset();
|
|
120
|
+
index_1.Chirpier.stop();
|
|
121
|
+
return [2 /*return*/];
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}); });
|
|
125
|
+
test("should throw error for invalid event", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
126
|
+
var chirpier, invalidEvent;
|
|
127
|
+
return __generator(this, function (_a) {
|
|
128
|
+
switch (_a.label) {
|
|
129
|
+
case 0:
|
|
130
|
+
(0, index_1.initialize)({
|
|
131
|
+
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
132
|
+
logLevel: index_1.LogLevel.None
|
|
133
|
+
});
|
|
134
|
+
chirpier = index_1.Chirpier.getInstance({});
|
|
135
|
+
invalidEvent = {
|
|
136
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
137
|
+
};
|
|
138
|
+
return [4 /*yield*/, expect(chirpier === null || chirpier === void 0 ? void 0 : chirpier.monitor(invalidEvent)).rejects.toThrow(index_1.ChirpierError)];
|
|
139
|
+
case 1:
|
|
140
|
+
_a.sent();
|
|
141
|
+
// Clean up the mock
|
|
142
|
+
index_1.Chirpier.stop();
|
|
143
|
+
return [2 /*return*/];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}); });
|
|
147
|
+
test("should batch events and flush when batch size is reached", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
148
|
+
var mock, event;
|
|
149
|
+
return __generator(this, function (_a) {
|
|
150
|
+
switch (_a.label) {
|
|
151
|
+
case 0:
|
|
152
|
+
mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
153
|
+
mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
|
|
154
|
+
(0, index_1.initialize)({
|
|
155
|
+
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
156
|
+
logLevel: index_1.LogLevel.None
|
|
157
|
+
});
|
|
158
|
+
event = {
|
|
159
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
160
|
+
stream_name: "test-stream",
|
|
161
|
+
value: 1,
|
|
162
|
+
};
|
|
163
|
+
(0, index_1.monitor)(event);
|
|
164
|
+
(0, index_1.monitor)(event);
|
|
165
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
|
|
166
|
+
case 1:
|
|
167
|
+
_a.sent(); // Wait for flush
|
|
168
|
+
expect(mock.history.post.length).toBe(1);
|
|
169
|
+
expect(JSON.parse(mock.history.post[0].data).length).toBe(2);
|
|
170
|
+
mock.reset();
|
|
171
|
+
index_1.Chirpier.stop();
|
|
172
|
+
return [2 /*return*/];
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}); });
|
|
176
|
+
test("should flush events after interval", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
177
|
+
var mock, event;
|
|
178
|
+
return __generator(this, function (_a) {
|
|
179
|
+
switch (_a.label) {
|
|
180
|
+
case 0:
|
|
181
|
+
mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
182
|
+
mock.onPost(constants_1.DEFAULT_API_ENDPOINT).reply(200, { success: true });
|
|
183
|
+
(0, index_1.initialize)({
|
|
184
|
+
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
185
|
+
logLevel: index_1.LogLevel.None
|
|
186
|
+
});
|
|
187
|
+
event = {
|
|
188
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
189
|
+
stream_name: "test-stream",
|
|
190
|
+
value: 1,
|
|
191
|
+
};
|
|
192
|
+
(0, index_1.monitor)(event);
|
|
193
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
|
|
194
|
+
case 1:
|
|
195
|
+
_a.sent(); // Wait for flush
|
|
196
|
+
expect(mock.history.post.length).toBe(1);
|
|
197
|
+
expect(JSON.parse(mock.history.post[0].data).length).toBe(1);
|
|
198
|
+
mock.reset();
|
|
199
|
+
index_1.Chirpier.stop();
|
|
200
|
+
return [2 /*return*/];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}); });
|
|
204
|
+
});
|
|
205
|
+
});
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const DEFAULT_API_ENDPOINT = "https://events.chirpier.co/v1.0/events";
|
|
2
|
-
export declare const DEFAULT_RETRIES =
|
|
2
|
+
export declare const DEFAULT_RETRIES = 15;
|
|
3
3
|
export declare const DEFAULT_TIMEOUT = 5000;
|
|
4
|
-
export declare const DEFAULT_BATCH_SIZE =
|
|
4
|
+
export declare const DEFAULT_BATCH_SIZE = 100;
|
|
5
5
|
export declare const DEFAULT_FLUSH_DELAY = 500;
|
|
6
|
+
export declare const MAX_QUEUE_SIZE = 2000;
|
|
6
7
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,MAAM,CAAC;AACtC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,cAAc,OAAO,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// constants.ts
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.DEFAULT_FLUSH_DELAY = exports.DEFAULT_BATCH_SIZE = exports.DEFAULT_TIMEOUT = exports.DEFAULT_RETRIES = exports.DEFAULT_API_ENDPOINT = void 0;
|
|
4
|
+
exports.MAX_QUEUE_SIZE = exports.DEFAULT_FLUSH_DELAY = exports.DEFAULT_BATCH_SIZE = exports.DEFAULT_TIMEOUT = exports.DEFAULT_RETRIES = exports.DEFAULT_API_ENDPOINT = void 0;
|
|
5
5
|
exports.DEFAULT_API_ENDPOINT = 'https://events.chirpier.co/v1.0/events';
|
|
6
|
-
exports.DEFAULT_RETRIES =
|
|
6
|
+
exports.DEFAULT_RETRIES = 15;
|
|
7
7
|
exports.DEFAULT_TIMEOUT = 5000;
|
|
8
|
-
exports.DEFAULT_BATCH_SIZE =
|
|
8
|
+
exports.DEFAULT_BATCH_SIZE = 100;
|
|
9
9
|
exports.DEFAULT_FLUSH_DELAY = 500;
|
|
10
|
+
exports.MAX_QUEUE_SIZE = 2000;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
export declare enum LogLevel {
|
|
2
|
+
None = 0,
|
|
3
|
+
Error = 1,
|
|
4
|
+
Info = 2,
|
|
5
|
+
Debug = 3
|
|
6
|
+
}
|
|
1
7
|
interface Options {
|
|
2
8
|
key: string;
|
|
9
|
+
logLevel?: LogLevel;
|
|
3
10
|
}
|
|
4
11
|
export interface Event {
|
|
5
12
|
group_id: string;
|
|
@@ -21,8 +28,11 @@ export declare class Chirpier {
|
|
|
21
28
|
private readonly axiosInstance;
|
|
22
29
|
private eventQueue;
|
|
23
30
|
private readonly batchSize;
|
|
24
|
-
private readonly
|
|
31
|
+
private readonly flushDelay;
|
|
25
32
|
private flushTimeoutId;
|
|
33
|
+
private readonly queueLock;
|
|
34
|
+
private readonly flushLock;
|
|
35
|
+
private readonly logLevel;
|
|
26
36
|
/**
|
|
27
37
|
* Initializes a new instance of the Chirpier class.
|
|
28
38
|
* @param options - Configuration options for the SDK.
|
|
@@ -54,7 +64,7 @@ export declare class Chirpier {
|
|
|
54
64
|
* @param events - The array of events to send.
|
|
55
65
|
*/
|
|
56
66
|
private sendEvents;
|
|
57
|
-
static stop(): void
|
|
67
|
+
static stop(): Promise<void>;
|
|
58
68
|
}
|
|
59
69
|
/**
|
|
60
70
|
* Initializes the Chirpier SDK.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,oBAAY,QAAQ;IAClB,IAAI,IAAI;IACR,KAAK,IAAI;IACT,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAGD,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;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;AAQD;;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,CAAqB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;OAGG;IACH,OAAO;IA6CP;;;;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;IAyBjD;;OAEG;YACW,UAAU;IA8DxB;;;OAGG;YACW,UAAU;WAKJ,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAa1C;AAqCD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsBjD;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAW1C"}
|
package/dist/index.js
CHANGED
|
@@ -63,12 +63,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
63
63
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
64
64
|
};
|
|
65
65
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
-
exports.monitor = exports.initialize = exports.Chirpier = exports.ChirpierError = void 0;
|
|
66
|
+
exports.monitor = exports.initialize = exports.Chirpier = exports.ChirpierError = exports.LogLevel = void 0;
|
|
67
67
|
// Import necessary dependencies
|
|
68
68
|
var axios_1 = __importDefault(require("axios"));
|
|
69
69
|
var axios_retry_1 = __importDefault(require("axios-retry"));
|
|
70
70
|
var js_base64_1 = require("js-base64");
|
|
71
71
|
var constants_1 = require("./constants");
|
|
72
|
+
var async_lock_1 = __importDefault(require("async-lock"));
|
|
73
|
+
// Define logging levels
|
|
74
|
+
var LogLevel;
|
|
75
|
+
(function (LogLevel) {
|
|
76
|
+
LogLevel[LogLevel["None"] = 0] = "None";
|
|
77
|
+
LogLevel[LogLevel["Error"] = 1] = "Error";
|
|
78
|
+
LogLevel[LogLevel["Info"] = 2] = "Info";
|
|
79
|
+
LogLevel[LogLevel["Debug"] = 3] = "Debug";
|
|
80
|
+
})(LogLevel = exports.LogLevel || (exports.LogLevel = {}));
|
|
72
81
|
// Custom error class for Chirpier-specific errors
|
|
73
82
|
var ChirpierError = /** @class */ (function (_super) {
|
|
74
83
|
__extends(ChirpierError, _super);
|
|
@@ -92,7 +101,9 @@ var Chirpier = /** @class */ (function () {
|
|
|
92
101
|
function Chirpier(options) {
|
|
93
102
|
this.eventQueue = [];
|
|
94
103
|
this.flushTimeoutId = null;
|
|
95
|
-
|
|
104
|
+
this.queueLock = new async_lock_1.default();
|
|
105
|
+
this.flushLock = new async_lock_1.default();
|
|
106
|
+
var key = options.key, _a = options.logLevel, logLevel = _a === void 0 ? LogLevel.None : _a;
|
|
96
107
|
if (!key || typeof key !== "string") {
|
|
97
108
|
throw new ChirpierError("API key is required and must be a string");
|
|
98
109
|
}
|
|
@@ -101,7 +112,8 @@ var Chirpier = /** @class */ (function () {
|
|
|
101
112
|
this.retries = constants_1.DEFAULT_RETRIES;
|
|
102
113
|
this.timeout = constants_1.DEFAULT_TIMEOUT;
|
|
103
114
|
this.batchSize = constants_1.DEFAULT_BATCH_SIZE;
|
|
104
|
-
this.
|
|
115
|
+
this.flushDelay = constants_1.DEFAULT_FLUSH_DELAY;
|
|
116
|
+
this.logLevel = logLevel;
|
|
105
117
|
// Create axios instance with authorization header
|
|
106
118
|
this.axiosInstance = axios_1.default.create({
|
|
107
119
|
headers: { Authorization: "Bearer ".concat(this.apiKey) },
|
|
@@ -161,19 +173,28 @@ var Chirpier = /** @class */ (function () {
|
|
|
161
173
|
if (!this.isValidEvent(event)) {
|
|
162
174
|
throw new ChirpierError("Invalid event format. Must include group_id, stream_name, and numeric value.");
|
|
163
175
|
}
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
return [4 /*yield*/, this.queueLock.acquire("queue", function () { return __awaiter(_this, void 0, void 0, function () {
|
|
177
|
+
return __generator(this, function (_a) {
|
|
178
|
+
if (this.eventQueue.length >= constants_1.MAX_QUEUE_SIZE) {
|
|
179
|
+
throw new ChirpierError("Event queue is full.");
|
|
180
|
+
}
|
|
181
|
+
this.eventQueue.push({ event: event, timestamp: Date.now(), retryCount: 0 });
|
|
182
|
+
return [2 /*return*/];
|
|
183
|
+
});
|
|
184
|
+
}); })];
|
|
168
185
|
case 1:
|
|
169
186
|
_a.sent();
|
|
170
|
-
return [3 /*break*/, 3];
|
|
187
|
+
if (!(this.eventQueue.length >= this.batchSize)) return [3 /*break*/, 3];
|
|
188
|
+
return [4 /*yield*/, this.flushQueue()];
|
|
171
189
|
case 2:
|
|
190
|
+
_a.sent();
|
|
191
|
+
return [3 /*break*/, 4];
|
|
192
|
+
case 3:
|
|
172
193
|
if (!this.flushTimeoutId) {
|
|
173
|
-
this.flushTimeoutId = setTimeout(function () { return _this.flushQueue(); }, this.
|
|
194
|
+
this.flushTimeoutId = setTimeout(function () { return _this.flushQueue(); }, this.flushDelay);
|
|
174
195
|
}
|
|
175
|
-
_a.label =
|
|
176
|
-
case
|
|
196
|
+
_a.label = 4;
|
|
197
|
+
case 4: return [2 /*return*/];
|
|
177
198
|
}
|
|
178
199
|
});
|
|
179
200
|
});
|
|
@@ -183,32 +204,89 @@ var Chirpier = /** @class */ (function () {
|
|
|
183
204
|
*/
|
|
184
205
|
Chirpier.prototype.flushQueue = function () {
|
|
185
206
|
return __awaiter(this, void 0, void 0, function () {
|
|
186
|
-
var
|
|
207
|
+
var _this = this;
|
|
187
208
|
return __generator(this, function (_a) {
|
|
188
209
|
switch (_a.label) {
|
|
189
|
-
case 0:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
case 0:
|
|
211
|
+
// Acquire the flush lock
|
|
212
|
+
return [4 /*yield*/, this.flushLock.acquire("flush", function () { return __awaiter(_this, void 0, void 0, function () {
|
|
213
|
+
var eventsToSend, error_1, retryableEvents_1, _i, eventsToSend_1, queuedEvent;
|
|
214
|
+
var _this = this;
|
|
215
|
+
return __generator(this, function (_a) {
|
|
216
|
+
switch (_a.label) {
|
|
217
|
+
case 0:
|
|
218
|
+
eventsToSend = [];
|
|
219
|
+
// Extract events from the queue under the queue lock
|
|
220
|
+
return [4 /*yield*/, this.queueLock.acquire("eventQueue", function () { return __awaiter(_this, void 0, void 0, function () {
|
|
221
|
+
return __generator(this, function (_a) {
|
|
222
|
+
if (this.eventQueue.length > 0) {
|
|
223
|
+
eventsToSend = __spreadArray([], this.eventQueue, true);
|
|
224
|
+
this.eventQueue = [];
|
|
225
|
+
}
|
|
226
|
+
return [2 /*return*/];
|
|
227
|
+
});
|
|
228
|
+
}); })];
|
|
229
|
+
case 1:
|
|
230
|
+
// Extract events from the queue under the queue lock
|
|
231
|
+
_a.sent();
|
|
232
|
+
if (eventsToSend.length === 0) {
|
|
233
|
+
return [2 /*return*/];
|
|
234
|
+
}
|
|
235
|
+
_a.label = 2;
|
|
236
|
+
case 2:
|
|
237
|
+
_a.trys.push([2, 4, , 6]);
|
|
238
|
+
// Clear any pending flush timeout
|
|
239
|
+
if (this.flushTimeoutId) {
|
|
240
|
+
clearTimeout(this.flushTimeoutId);
|
|
241
|
+
this.flushTimeoutId = null;
|
|
242
|
+
}
|
|
243
|
+
// Attempt to send events
|
|
244
|
+
return [4 /*yield*/, this.sendEvents(eventsToSend.map(function (qe) { return qe.event; }))];
|
|
245
|
+
case 3:
|
|
246
|
+
// Attempt to send events
|
|
247
|
+
_a.sent();
|
|
248
|
+
if (this.logLevel >= LogLevel.Info) {
|
|
249
|
+
console.info("Successfully sent ".concat(eventsToSend.length, " events"));
|
|
250
|
+
}
|
|
251
|
+
return [3 /*break*/, 6];
|
|
252
|
+
case 4:
|
|
253
|
+
error_1 = _a.sent();
|
|
254
|
+
// Log failure
|
|
255
|
+
if (this.logLevel >= LogLevel.Error) {
|
|
256
|
+
console.error("Failed to send events:", error_1);
|
|
257
|
+
}
|
|
258
|
+
retryableEvents_1 = [];
|
|
259
|
+
for (_i = 0, eventsToSend_1 = eventsToSend; _i < eventsToSend_1.length; _i++) {
|
|
260
|
+
queuedEvent = eventsToSend_1[_i];
|
|
261
|
+
if (queuedEvent.retryCount >= this.retries) {
|
|
262
|
+
if (this.logLevel >= LogLevel.Error) {
|
|
263
|
+
console.error("Dropping event after ".concat(this.retries, " retries:"), queuedEvent.event);
|
|
264
|
+
}
|
|
265
|
+
continue; // Skip adding this event back to the queue
|
|
266
|
+
}
|
|
267
|
+
// Increment retry count and add back to the queue
|
|
268
|
+
queuedEvent.retryCount++;
|
|
269
|
+
retryableEvents_1.push(queuedEvent);
|
|
270
|
+
}
|
|
271
|
+
// Requeue remaining retryable events
|
|
272
|
+
return [4 /*yield*/, this.queueLock.acquire("eventQueue", function () { return __awaiter(_this, void 0, void 0, function () {
|
|
273
|
+
return __generator(this, function (_a) {
|
|
274
|
+
this.eventQueue = __spreadArray(__spreadArray([], retryableEvents_1, true), this.eventQueue, true);
|
|
275
|
+
return [2 /*return*/];
|
|
276
|
+
});
|
|
277
|
+
}); })];
|
|
278
|
+
case 5:
|
|
279
|
+
// Requeue remaining retryable events
|
|
280
|
+
_a.sent();
|
|
281
|
+
return [3 /*break*/, 6];
|
|
282
|
+
case 6: return [2 /*return*/];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}); })];
|
|
200
286
|
case 1:
|
|
201
|
-
|
|
202
|
-
return [4 /*yield*/, this.sendEvents(eventsToSend)];
|
|
203
|
-
case 2:
|
|
287
|
+
// Acquire the flush lock
|
|
204
288
|
_a.sent();
|
|
205
|
-
|
|
206
|
-
return [3 /*break*/, 4];
|
|
207
|
-
case 3:
|
|
208
|
-
error_1 = _a.sent();
|
|
209
|
-
console.error("Failed to send events:", error_1);
|
|
210
|
-
return [3 /*break*/, 4];
|
|
211
|
-
case 4: return [2 /*return*/];
|
|
289
|
+
return [2 /*return*/];
|
|
212
290
|
}
|
|
213
291
|
});
|
|
214
292
|
});
|
|
@@ -231,17 +309,28 @@ var Chirpier = /** @class */ (function () {
|
|
|
231
309
|
};
|
|
232
310
|
// Stop the timeout and uninitialize the Chirpier instance
|
|
233
311
|
Chirpier.stop = function () {
|
|
234
|
-
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
312
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
313
|
+
return __generator(this, function (_a) {
|
|
314
|
+
switch (_a.label) {
|
|
315
|
+
case 0:
|
|
316
|
+
if (!Chirpier.instance) {
|
|
317
|
+
return [2 /*return*/];
|
|
318
|
+
}
|
|
319
|
+
if (Chirpier.instance.flushTimeoutId) {
|
|
320
|
+
clearTimeout(Chirpier.instance.flushTimeoutId);
|
|
321
|
+
Chirpier.instance.flushTimeoutId = null;
|
|
322
|
+
}
|
|
323
|
+
// Flush any remaining events in the queue
|
|
324
|
+
return [4 /*yield*/, Chirpier.instance.flushQueue()];
|
|
325
|
+
case 1:
|
|
326
|
+
// Flush any remaining events in the queue
|
|
327
|
+
_a.sent();
|
|
328
|
+
// Uninitialize the Chirpier instance
|
|
329
|
+
Chirpier.instance = null;
|
|
330
|
+
return [2 /*return*/];
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
245
334
|
};
|
|
246
335
|
Chirpier.instance = null;
|
|
247
336
|
return Chirpier;
|
|
@@ -294,10 +383,14 @@ function initialize(options) {
|
|
|
294
383
|
}
|
|
295
384
|
catch (error) {
|
|
296
385
|
if (error instanceof ChirpierError) {
|
|
297
|
-
|
|
386
|
+
if (options.logLevel && options.logLevel >= LogLevel.Error) {
|
|
387
|
+
console.error("Failed to initialize Chirpier SDK:", error.message);
|
|
388
|
+
}
|
|
298
389
|
}
|
|
299
390
|
else {
|
|
300
|
-
|
|
391
|
+
if (options.logLevel && options.logLevel >= LogLevel.Error) {
|
|
392
|
+
console.error("An unexpected error occurred during Chirpier SDK initialization:", error);
|
|
393
|
+
}
|
|
301
394
|
}
|
|
302
395
|
throw error;
|
|
303
396
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chirpier/chirpier-js",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Chirpier SDK for JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chirpier",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test": "jest --coverage"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"async-lock": "^1.4.1",
|
|
30
31
|
"axios": "^0.24.0",
|
|
31
32
|
"axios-retry": "^4.5.0",
|
|
32
33
|
"js-base64": "^3.7.7",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@eslint/js": "^9.15.0",
|
|
38
39
|
"@jest/globals": "^29.7.0",
|
|
40
|
+
"@types/async-lock": "^1.4.2",
|
|
39
41
|
"@types/jest": "^29.5.13",
|
|
40
42
|
"@types/mocha": "^10.0.8",
|
|
41
43
|
"@types/node": "^22.7.5",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Chirpier, ChirpierError, Event, initialize, monitor } from "../index";
|
|
1
|
+
import { Chirpier, ChirpierError, Event, LogLevel, initialize, monitor } from "../index";
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_API_ENDPOINT,
|
|
4
4
|
DEFAULT_RETRIES,
|
|
@@ -13,7 +13,7 @@ describe("Chirpier SDK", () => {
|
|
|
13
13
|
describe("Initialization", () => {
|
|
14
14
|
test("should throw error if monitor is called before initialize", () => {
|
|
15
15
|
const event: Event = {
|
|
16
|
-
group_id: "
|
|
16
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
17
17
|
stream_name: "test-stream",
|
|
18
18
|
value: 1,
|
|
19
19
|
};
|
|
@@ -27,6 +27,7 @@ describe("Chirpier SDK", () => {
|
|
|
27
27
|
test("should initialize with default values", () => {
|
|
28
28
|
initialize({
|
|
29
29
|
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
30
|
+
logLevel: LogLevel.None
|
|
30
31
|
});
|
|
31
32
|
const chirpier = Chirpier.getInstance({} as any);
|
|
32
33
|
|
|
@@ -38,7 +39,7 @@ describe("Chirpier SDK", () => {
|
|
|
38
39
|
expect(chirpier?.["retries"]).toBe(DEFAULT_RETRIES);
|
|
39
40
|
expect(chirpier?.["timeout"]).toBe(DEFAULT_TIMEOUT);
|
|
40
41
|
expect(chirpier?.["batchSize"]).toBe(DEFAULT_BATCH_SIZE);
|
|
41
|
-
expect(chirpier?.["
|
|
42
|
+
expect(chirpier?.["flushDelay"]).toBe(DEFAULT_FLUSH_DELAY);
|
|
42
43
|
|
|
43
44
|
Chirpier.stop();
|
|
44
45
|
});
|
|
@@ -47,11 +48,13 @@ describe("Chirpier SDK", () => {
|
|
|
47
48
|
expect(() => {
|
|
48
49
|
initialize({
|
|
49
50
|
key: "api_key",
|
|
51
|
+
logLevel: LogLevel.None
|
|
50
52
|
});
|
|
51
53
|
}).toThrow(ChirpierError);
|
|
52
54
|
expect(() => {
|
|
53
55
|
initialize({
|
|
54
56
|
key: "api_key",
|
|
57
|
+
logLevel: LogLevel.None
|
|
55
58
|
});
|
|
56
59
|
}).toThrow("Invalid API key: Not a valid JWT");
|
|
57
60
|
});
|
|
@@ -65,10 +68,11 @@ describe("Chirpier SDK", () => {
|
|
|
65
68
|
|
|
66
69
|
initialize({
|
|
67
70
|
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
71
|
+
logLevel: LogLevel.None
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
const event: Event = {
|
|
71
|
-
group_id: "
|
|
75
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
72
76
|
stream_name: "test-stream",
|
|
73
77
|
value: 1,
|
|
74
78
|
};
|
|
@@ -81,7 +85,7 @@ describe("Chirpier SDK", () => {
|
|
|
81
85
|
expect(mock.history.post[0].url).toBe(DEFAULT_API_ENDPOINT);
|
|
82
86
|
expect(JSON.parse(mock.history.post[0].data)).toEqual([
|
|
83
87
|
{
|
|
84
|
-
group_id: "
|
|
88
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
85
89
|
stream_name: "test-stream",
|
|
86
90
|
value: 1,
|
|
87
91
|
},
|
|
@@ -95,11 +99,12 @@ describe("Chirpier SDK", () => {
|
|
|
95
99
|
test("should throw error for invalid event", async () => {
|
|
96
100
|
initialize({
|
|
97
101
|
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
102
|
+
logLevel: LogLevel.None
|
|
98
103
|
});
|
|
99
104
|
const chirpier = Chirpier.getInstance({} as any);
|
|
100
105
|
|
|
101
106
|
const invalidEvent = {
|
|
102
|
-
group_id: "
|
|
107
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
103
108
|
} as any;
|
|
104
109
|
await expect(chirpier?.monitor(invalidEvent)).rejects.toThrow(
|
|
105
110
|
ChirpierError
|
|
@@ -115,10 +120,11 @@ describe("Chirpier SDK", () => {
|
|
|
115
120
|
|
|
116
121
|
initialize({
|
|
117
122
|
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
123
|
+
logLevel: LogLevel.None
|
|
118
124
|
});
|
|
119
125
|
|
|
120
126
|
const event: Event = {
|
|
121
|
-
group_id: "
|
|
127
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
122
128
|
stream_name: "test-stream",
|
|
123
129
|
value: 1,
|
|
124
130
|
};
|
|
@@ -141,10 +147,11 @@ describe("Chirpier SDK", () => {
|
|
|
141
147
|
|
|
142
148
|
initialize({
|
|
143
149
|
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
|
150
|
+
logLevel: LogLevel.None
|
|
144
151
|
});
|
|
145
152
|
|
|
146
153
|
const event: Event = {
|
|
147
|
-
group_id: "
|
|
154
|
+
group_id: "bfd9299d-817a-452f-bc53-6e154f2281fc",
|
|
148
155
|
stream_name: "test-stream",
|
|
149
156
|
value: 1,
|
|
150
157
|
};
|
package/src/constants.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// constants.ts
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_API_ENDPOINT = 'https://events.chirpier.co/v1.0/events';
|
|
4
|
-
export const DEFAULT_RETRIES =
|
|
4
|
+
export const DEFAULT_RETRIES = 15;
|
|
5
5
|
export const DEFAULT_TIMEOUT = 5000
|
|
6
|
-
export const DEFAULT_BATCH_SIZE =
|
|
7
|
-
export const DEFAULT_FLUSH_DELAY = 500;
|
|
6
|
+
export const DEFAULT_BATCH_SIZE = 100;
|
|
7
|
+
export const DEFAULT_FLUSH_DELAY = 500;
|
|
8
|
+
export const MAX_QUEUE_SIZE = 2000;
|
package/src/index.ts
CHANGED
|
@@ -8,11 +8,22 @@ import {
|
|
|
8
8
|
DEFAULT_TIMEOUT,
|
|
9
9
|
DEFAULT_BATCH_SIZE,
|
|
10
10
|
DEFAULT_FLUSH_DELAY,
|
|
11
|
+
MAX_QUEUE_SIZE,
|
|
11
12
|
} from "./constants";
|
|
13
|
+
import AsyncLock from "async-lock";
|
|
14
|
+
|
|
15
|
+
// Define logging levels
|
|
16
|
+
export enum LogLevel {
|
|
17
|
+
None = 0,
|
|
18
|
+
Error = 1,
|
|
19
|
+
Info = 2,
|
|
20
|
+
Debug = 3,
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
// Define the options interface for Chirpier initialization
|
|
14
24
|
interface Options {
|
|
15
25
|
key: string;
|
|
26
|
+
logLevel?: LogLevel;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
// Define the Event interface for monitoring
|
|
@@ -31,6 +42,12 @@ export class ChirpierError extends Error {
|
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
|
|
45
|
+
interface QueuedEvent {
|
|
46
|
+
event: Event;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
retryCount: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
/**
|
|
35
52
|
* Main Chirpier class for monitoring events.
|
|
36
53
|
*/
|
|
@@ -41,30 +58,32 @@ export class Chirpier {
|
|
|
41
58
|
private readonly retries: number;
|
|
42
59
|
private readonly timeout: number;
|
|
43
60
|
private readonly axiosInstance: AxiosInstance;
|
|
44
|
-
private eventQueue:
|
|
61
|
+
private eventQueue: QueuedEvent[] = [];
|
|
45
62
|
private readonly batchSize: number;
|
|
46
|
-
private readonly
|
|
63
|
+
private readonly flushDelay: number;
|
|
47
64
|
private flushTimeoutId: NodeJS.Timeout | null = null;
|
|
65
|
+
private readonly queueLock = new AsyncLock();
|
|
66
|
+
private readonly flushLock = new AsyncLock();
|
|
67
|
+
private readonly logLevel: LogLevel;
|
|
48
68
|
|
|
49
69
|
/**
|
|
50
70
|
* Initializes a new instance of the Chirpier class.
|
|
51
71
|
* @param options - Configuration options for the SDK.
|
|
52
72
|
*/
|
|
53
73
|
private constructor(options: Options) {
|
|
54
|
-
const {
|
|
55
|
-
key,
|
|
56
|
-
} = options;
|
|
74
|
+
const { key, logLevel = LogLevel.None } = options;
|
|
57
75
|
|
|
58
76
|
if (!key || typeof key !== "string") {
|
|
59
77
|
throw new ChirpierError("API key is required and must be a string");
|
|
60
78
|
}
|
|
61
|
-
|
|
79
|
+
|
|
62
80
|
this.apiKey = key;
|
|
63
81
|
this.apiEndpoint = DEFAULT_API_ENDPOINT;
|
|
64
82
|
this.retries = DEFAULT_RETRIES;
|
|
65
83
|
this.timeout = DEFAULT_TIMEOUT;
|
|
66
84
|
this.batchSize = DEFAULT_BATCH_SIZE;
|
|
67
|
-
this.
|
|
85
|
+
this.flushDelay = DEFAULT_FLUSH_DELAY;
|
|
86
|
+
this.logLevel = logLevel;
|
|
68
87
|
|
|
69
88
|
// Create axios instance with authorization header
|
|
70
89
|
this.axiosInstance = axios.create({
|
|
@@ -137,13 +156,21 @@ export class Chirpier {
|
|
|
137
156
|
);
|
|
138
157
|
}
|
|
139
158
|
|
|
140
|
-
this.
|
|
159
|
+
await this.queueLock.acquire("queue", async () => {
|
|
160
|
+
if (this.eventQueue.length >= MAX_QUEUE_SIZE) {
|
|
161
|
+
throw new ChirpierError("Event queue is full.");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.eventQueue.push({ event, timestamp: Date.now(), retryCount: 0 });
|
|
165
|
+
});
|
|
141
166
|
|
|
142
167
|
if (this.eventQueue.length >= this.batchSize) {
|
|
143
|
-
console.info(`Batch size reached. Flushing queue.`);
|
|
144
168
|
await this.flushQueue();
|
|
145
169
|
} else if (!this.flushTimeoutId) {
|
|
146
|
-
this.flushTimeoutId = setTimeout(
|
|
170
|
+
this.flushTimeoutId = setTimeout(
|
|
171
|
+
() => this.flushQueue(),
|
|
172
|
+
this.flushDelay
|
|
173
|
+
);
|
|
147
174
|
}
|
|
148
175
|
}
|
|
149
176
|
|
|
@@ -151,24 +178,65 @@ export class Chirpier {
|
|
|
151
178
|
* Flushes the event queue by sending all events to the API.
|
|
152
179
|
*/
|
|
153
180
|
private async flushQueue(): Promise<void> {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
181
|
+
// Acquire the flush lock
|
|
182
|
+
await this.flushLock.acquire("flush", async () => {
|
|
183
|
+
let eventsToSend: QueuedEvent[] = [];
|
|
158
184
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
// Extract events from the queue under the queue lock
|
|
186
|
+
await this.queueLock.acquire("eventQueue", async () => {
|
|
187
|
+
if (this.eventQueue.length > 0) {
|
|
188
|
+
eventsToSend = [...this.eventQueue];
|
|
189
|
+
this.eventQueue = [];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
162
192
|
|
|
163
|
-
|
|
164
|
-
|
|
193
|
+
if (eventsToSend.length === 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
165
196
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
197
|
+
try {
|
|
198
|
+
// Clear any pending flush timeout
|
|
199
|
+
if (this.flushTimeoutId) {
|
|
200
|
+
clearTimeout(this.flushTimeoutId);
|
|
201
|
+
this.flushTimeoutId = null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Attempt to send events
|
|
205
|
+
await this.sendEvents(eventsToSend.map((qe) => qe.event));
|
|
206
|
+
|
|
207
|
+
if (this.logLevel >= LogLevel.Info) {
|
|
208
|
+
console.info(`Successfully sent ${eventsToSend.length} events`);
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
// Log failure
|
|
212
|
+
if (this.logLevel >= LogLevel.Error) {
|
|
213
|
+
console.error("Failed to send events:", error);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Requeue failed events with retry count checks
|
|
217
|
+
const retryableEvents: QueuedEvent[] = [];
|
|
218
|
+
for (const queuedEvent of eventsToSend) {
|
|
219
|
+
if (queuedEvent.retryCount >= this.retries) {
|
|
220
|
+
if (this.logLevel >= LogLevel.Error) {
|
|
221
|
+
console.error(
|
|
222
|
+
`Dropping event after ${this.retries} retries:`,
|
|
223
|
+
queuedEvent.event
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
continue; // Skip adding this event back to the queue
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Increment retry count and add back to the queue
|
|
230
|
+
queuedEvent.retryCount++;
|
|
231
|
+
retryableEvents.push(queuedEvent);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Requeue remaining retryable events
|
|
235
|
+
await this.queueLock.acquire("eventQueue", async () => {
|
|
236
|
+
this.eventQueue = [...retryableEvents, ...this.eventQueue];
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
172
240
|
}
|
|
173
241
|
|
|
174
242
|
/**
|
|
@@ -178,9 +246,9 @@ export class Chirpier {
|
|
|
178
246
|
private async sendEvents(events: Event[]): Promise<void> {
|
|
179
247
|
await this.axiosInstance.post(this.apiEndpoint, events);
|
|
180
248
|
}
|
|
181
|
-
|
|
249
|
+
|
|
182
250
|
// Stop the timeout and uninitialize the Chirpier instance
|
|
183
|
-
public static stop(): void {
|
|
251
|
+
public static async stop(): Promise<void> {
|
|
184
252
|
if (!Chirpier.instance) {
|
|
185
253
|
return;
|
|
186
254
|
}
|
|
@@ -189,7 +257,7 @@ export class Chirpier {
|
|
|
189
257
|
Chirpier.instance.flushTimeoutId = null;
|
|
190
258
|
}
|
|
191
259
|
// Flush any remaining events in the queue
|
|
192
|
-
Chirpier.instance.flushQueue();
|
|
260
|
+
await Chirpier.instance.flushQueue();
|
|
193
261
|
// Uninitialize the Chirpier instance
|
|
194
262
|
Chirpier.instance = null;
|
|
195
263
|
}
|
|
@@ -243,12 +311,16 @@ export function initialize(options: Options): void {
|
|
|
243
311
|
Chirpier.getInstance(options);
|
|
244
312
|
} catch (error) {
|
|
245
313
|
if (error instanceof ChirpierError) {
|
|
246
|
-
|
|
314
|
+
if (options.logLevel && options.logLevel >= LogLevel.Error) {
|
|
315
|
+
console.error("Failed to initialize Chirpier SDK:", error.message);
|
|
316
|
+
}
|
|
247
317
|
} else {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
318
|
+
if (options.logLevel && options.logLevel >= LogLevel.Error) {
|
|
319
|
+
console.error(
|
|
320
|
+
"An unexpected error occurred during Chirpier SDK initialization:",
|
|
321
|
+
error
|
|
322
|
+
);
|
|
323
|
+
}
|
|
252
324
|
}
|
|
253
325
|
throw error;
|
|
254
326
|
}
|
|
@@ -265,7 +337,7 @@ export function monitor(event: Event): void {
|
|
|
265
337
|
"Chirpier SDK is not initialized. Please call initialize() first."
|
|
266
338
|
);
|
|
267
339
|
}
|
|
268
|
-
|
|
340
|
+
|
|
269
341
|
instance.monitor(event).catch((error) => {
|
|
270
342
|
console.error("Error in monitor function:", error);
|
|
271
343
|
});
|