caplyr 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +67 -19
- package/dist/index.mjs +67 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36,20 +36,32 @@ var LogShipper = class {
|
|
|
36
36
|
constructor(config) {
|
|
37
37
|
this.buffer = [];
|
|
38
38
|
this.timer = null;
|
|
39
|
+
// Store bound handlers so we can remove them on shutdown
|
|
40
|
+
this.processHandlers = [];
|
|
39
41
|
this.endpoint = config.endpoint ?? "https://api.caplyr.com";
|
|
40
42
|
this.apiKey = config.apiKey;
|
|
41
43
|
this.batchSize = config.batchSize ?? 10;
|
|
42
44
|
this.flushInterval = config.flushInterval ?? 3e4;
|
|
45
|
+
this.maxBufferSize = 1e3;
|
|
43
46
|
this.onError = config.onError;
|
|
44
47
|
this.timer = setInterval(() => this.flush(), this.flushInterval);
|
|
48
|
+
this.timer.unref?.();
|
|
45
49
|
if (typeof process !== "undefined" && process.on) {
|
|
46
|
-
const
|
|
50
|
+
const onBeforeExit = () => {
|
|
51
|
+
this.flush();
|
|
52
|
+
};
|
|
53
|
+
const onSignal = () => {
|
|
47
54
|
this.flush().finally(() => {
|
|
48
55
|
});
|
|
49
56
|
};
|
|
50
|
-
process.on("beforeExit",
|
|
51
|
-
process.on("SIGTERM",
|
|
52
|
-
process.on("SIGINT",
|
|
57
|
+
process.on("beforeExit", onBeforeExit);
|
|
58
|
+
process.on("SIGTERM", onSignal);
|
|
59
|
+
process.on("SIGINT", onSignal);
|
|
60
|
+
this.processHandlers = [
|
|
61
|
+
{ event: "beforeExit", handler: onBeforeExit },
|
|
62
|
+
{ event: "SIGTERM", handler: onSignal },
|
|
63
|
+
{ event: "SIGINT", handler: onSignal }
|
|
64
|
+
];
|
|
53
65
|
}
|
|
54
66
|
}
|
|
55
67
|
/**
|
|
@@ -57,6 +69,10 @@ var LogShipper = class {
|
|
|
57
69
|
* Auto-flushes when batch size is reached.
|
|
58
70
|
*/
|
|
59
71
|
push(log) {
|
|
72
|
+
if (this.buffer.length >= this.maxBufferSize) {
|
|
73
|
+
const excess = this.buffer.length - this.maxBufferSize + 1;
|
|
74
|
+
this.buffer.splice(0, excess);
|
|
75
|
+
}
|
|
60
76
|
this.buffer.push(log);
|
|
61
77
|
if (this.buffer.length >= this.batchSize) {
|
|
62
78
|
this.flush();
|
|
@@ -88,7 +104,18 @@ var LogShipper = class {
|
|
|
88
104
|
}
|
|
89
105
|
}
|
|
90
106
|
/**
|
|
91
|
-
*
|
|
107
|
+
* Remove process signal handlers registered in the constructor.
|
|
108
|
+
*/
|
|
109
|
+
removeProcessHandlers() {
|
|
110
|
+
if (typeof process !== "undefined" && process.removeListener) {
|
|
111
|
+
for (const { event, handler } of this.processHandlers) {
|
|
112
|
+
process.removeListener(event, handler);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.processHandlers = [];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stop the periodic flush timer and remove signal handlers.
|
|
92
119
|
* Call this when tearing down the SDK.
|
|
93
120
|
*/
|
|
94
121
|
destroy() {
|
|
@@ -96,13 +123,19 @@ var LogShipper = class {
|
|
|
96
123
|
clearInterval(this.timer);
|
|
97
124
|
this.timer = null;
|
|
98
125
|
}
|
|
126
|
+
this.removeProcessHandlers();
|
|
99
127
|
this.flush();
|
|
100
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Await the final log flush, stop timers, and remove signal handlers.
|
|
131
|
+
* Preferred over destroy() for clean shutdown.
|
|
132
|
+
*/
|
|
101
133
|
async shutdown() {
|
|
102
134
|
if (this.timer) {
|
|
103
135
|
clearInterval(this.timer);
|
|
104
136
|
this.timer = null;
|
|
105
137
|
}
|
|
138
|
+
this.removeProcessHandlers();
|
|
106
139
|
await this.flush();
|
|
107
140
|
}
|
|
108
141
|
};
|
|
@@ -187,6 +220,7 @@ var Heartbeat = class {
|
|
|
187
220
|
start() {
|
|
188
221
|
this.beat();
|
|
189
222
|
this.timer = setInterval(() => this.beat(), this.interval);
|
|
223
|
+
this.timer.unref?.();
|
|
190
224
|
}
|
|
191
225
|
/**
|
|
192
226
|
* Send a single heartbeat and update local state.
|
|
@@ -206,21 +240,20 @@ var Heartbeat = class {
|
|
|
206
240
|
throw new Error(`Heartbeat failed: ${res.status}`);
|
|
207
241
|
}
|
|
208
242
|
const data = await res.json();
|
|
209
|
-
const localDailyUsed = this.budgetStatus.daily_used;
|
|
210
|
-
const localMonthlyUsed = this.budgetStatus.monthly_used;
|
|
211
243
|
const serverDailyUsed = Number(data.daily_used) || 0;
|
|
212
244
|
const serverMonthlyUsed = Number(data.monthly_used) || 0;
|
|
213
245
|
const serverDailyLimit = data.daily_limit != null ? Number(data.daily_limit) : null;
|
|
214
246
|
const serverMonthlyLimit = data.monthly_limit != null ? Number(data.monthly_limit) : null;
|
|
215
|
-
this.budgetStatus
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
247
|
+
const snapshotDaily = this.budgetStatus.daily_used;
|
|
248
|
+
const snapshotMonthly = this.budgetStatus.monthly_used;
|
|
249
|
+
const mergedDaily = Math.max(serverDailyUsed, snapshotDaily);
|
|
250
|
+
const mergedMonthly = Math.max(serverMonthlyUsed, snapshotMonthly);
|
|
251
|
+
this.budgetStatus.daily_used = mergedDaily + (this.budgetStatus.daily_used - snapshotDaily);
|
|
252
|
+
this.budgetStatus.monthly_used = mergedMonthly + (this.budgetStatus.monthly_used - snapshotMonthly);
|
|
253
|
+
this.budgetStatus.daily_limit = this.pickStricterLimit(serverDailyLimit, this.localDailyLimit);
|
|
254
|
+
this.budgetStatus.monthly_limit = this.pickStricterLimit(serverMonthlyLimit, this.localMonthlyLimit);
|
|
255
|
+
this.budgetStatus.status = data.status;
|
|
256
|
+
this.budgetStatus.kill_switch_active = data.kill_switch_active;
|
|
224
257
|
this.consecutiveFailures = 0;
|
|
225
258
|
const newStatus = data.kill_switch_active ? "OFF" : data.status;
|
|
226
259
|
if (newStatus !== this.status) {
|
|
@@ -765,6 +798,7 @@ function protect(client, config) {
|
|
|
765
798
|
...config
|
|
766
799
|
};
|
|
767
800
|
let shared = instances.get(resolvedConfig.apiKey);
|
|
801
|
+
const isExisting = !!shared;
|
|
768
802
|
if (!shared) {
|
|
769
803
|
const shipper2 = new LogShipper(resolvedConfig);
|
|
770
804
|
const heartbeat2 = new Heartbeat(resolvedConfig);
|
|
@@ -774,7 +808,19 @@ function protect(client, config) {
|
|
|
774
808
|
}
|
|
775
809
|
const { shipper, heartbeat } = shared;
|
|
776
810
|
if (resolvedConfig.budget) {
|
|
777
|
-
const
|
|
811
|
+
const raw = typeof resolvedConfig.budget === "number" ? { monthly: resolvedConfig.budget } : resolvedConfig.budget;
|
|
812
|
+
const budgetConfig = { ...raw };
|
|
813
|
+
if (isExisting) {
|
|
814
|
+
const current = heartbeat.budgetStatus;
|
|
815
|
+
if (budgetConfig.daily !== void 0) {
|
|
816
|
+
const existing = current.daily_limit;
|
|
817
|
+
budgetConfig.daily = existing !== null ? Math.min(existing, budgetConfig.daily) : budgetConfig.daily;
|
|
818
|
+
}
|
|
819
|
+
if (budgetConfig.monthly !== void 0) {
|
|
820
|
+
const existing = current.monthly_limit;
|
|
821
|
+
budgetConfig.monthly = existing !== null ? Math.min(existing, budgetConfig.monthly) : budgetConfig.monthly;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
778
824
|
heartbeat.applyLocalLimits(budgetConfig);
|
|
779
825
|
}
|
|
780
826
|
const provider = detectProvider(client);
|
|
@@ -816,14 +862,16 @@ async function shutdown(apiKey) {
|
|
|
816
862
|
const shared = instances.get(apiKey);
|
|
817
863
|
if (shared) {
|
|
818
864
|
shared.heartbeat.destroy();
|
|
819
|
-
shared.shipper.
|
|
865
|
+
await shared.shipper.shutdown();
|
|
820
866
|
instances.delete(apiKey);
|
|
821
867
|
}
|
|
822
868
|
} else {
|
|
869
|
+
const shutdowns = [];
|
|
823
870
|
for (const [key, shared] of instances) {
|
|
824
871
|
shared.heartbeat.destroy();
|
|
825
|
-
shared.shipper.
|
|
872
|
+
shutdowns.push(shared.shipper.shutdown());
|
|
826
873
|
}
|
|
874
|
+
await Promise.all(shutdowns);
|
|
827
875
|
instances.clear();
|
|
828
876
|
}
|
|
829
877
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -3,20 +3,32 @@ var LogShipper = class {
|
|
|
3
3
|
constructor(config) {
|
|
4
4
|
this.buffer = [];
|
|
5
5
|
this.timer = null;
|
|
6
|
+
// Store bound handlers so we can remove them on shutdown
|
|
7
|
+
this.processHandlers = [];
|
|
6
8
|
this.endpoint = config.endpoint ?? "https://api.caplyr.com";
|
|
7
9
|
this.apiKey = config.apiKey;
|
|
8
10
|
this.batchSize = config.batchSize ?? 10;
|
|
9
11
|
this.flushInterval = config.flushInterval ?? 3e4;
|
|
12
|
+
this.maxBufferSize = 1e3;
|
|
10
13
|
this.onError = config.onError;
|
|
11
14
|
this.timer = setInterval(() => this.flush(), this.flushInterval);
|
|
15
|
+
this.timer.unref?.();
|
|
12
16
|
if (typeof process !== "undefined" && process.on) {
|
|
13
|
-
const
|
|
17
|
+
const onBeforeExit = () => {
|
|
18
|
+
this.flush();
|
|
19
|
+
};
|
|
20
|
+
const onSignal = () => {
|
|
14
21
|
this.flush().finally(() => {
|
|
15
22
|
});
|
|
16
23
|
};
|
|
17
|
-
process.on("beforeExit",
|
|
18
|
-
process.on("SIGTERM",
|
|
19
|
-
process.on("SIGINT",
|
|
24
|
+
process.on("beforeExit", onBeforeExit);
|
|
25
|
+
process.on("SIGTERM", onSignal);
|
|
26
|
+
process.on("SIGINT", onSignal);
|
|
27
|
+
this.processHandlers = [
|
|
28
|
+
{ event: "beforeExit", handler: onBeforeExit },
|
|
29
|
+
{ event: "SIGTERM", handler: onSignal },
|
|
30
|
+
{ event: "SIGINT", handler: onSignal }
|
|
31
|
+
];
|
|
20
32
|
}
|
|
21
33
|
}
|
|
22
34
|
/**
|
|
@@ -24,6 +36,10 @@ var LogShipper = class {
|
|
|
24
36
|
* Auto-flushes when batch size is reached.
|
|
25
37
|
*/
|
|
26
38
|
push(log) {
|
|
39
|
+
if (this.buffer.length >= this.maxBufferSize) {
|
|
40
|
+
const excess = this.buffer.length - this.maxBufferSize + 1;
|
|
41
|
+
this.buffer.splice(0, excess);
|
|
42
|
+
}
|
|
27
43
|
this.buffer.push(log);
|
|
28
44
|
if (this.buffer.length >= this.batchSize) {
|
|
29
45
|
this.flush();
|
|
@@ -55,7 +71,18 @@ var LogShipper = class {
|
|
|
55
71
|
}
|
|
56
72
|
}
|
|
57
73
|
/**
|
|
58
|
-
*
|
|
74
|
+
* Remove process signal handlers registered in the constructor.
|
|
75
|
+
*/
|
|
76
|
+
removeProcessHandlers() {
|
|
77
|
+
if (typeof process !== "undefined" && process.removeListener) {
|
|
78
|
+
for (const { event, handler } of this.processHandlers) {
|
|
79
|
+
process.removeListener(event, handler);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.processHandlers = [];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Stop the periodic flush timer and remove signal handlers.
|
|
59
86
|
* Call this when tearing down the SDK.
|
|
60
87
|
*/
|
|
61
88
|
destroy() {
|
|
@@ -63,13 +90,19 @@ var LogShipper = class {
|
|
|
63
90
|
clearInterval(this.timer);
|
|
64
91
|
this.timer = null;
|
|
65
92
|
}
|
|
93
|
+
this.removeProcessHandlers();
|
|
66
94
|
this.flush();
|
|
67
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Await the final log flush, stop timers, and remove signal handlers.
|
|
98
|
+
* Preferred over destroy() for clean shutdown.
|
|
99
|
+
*/
|
|
68
100
|
async shutdown() {
|
|
69
101
|
if (this.timer) {
|
|
70
102
|
clearInterval(this.timer);
|
|
71
103
|
this.timer = null;
|
|
72
104
|
}
|
|
105
|
+
this.removeProcessHandlers();
|
|
73
106
|
await this.flush();
|
|
74
107
|
}
|
|
75
108
|
};
|
|
@@ -154,6 +187,7 @@ var Heartbeat = class {
|
|
|
154
187
|
start() {
|
|
155
188
|
this.beat();
|
|
156
189
|
this.timer = setInterval(() => this.beat(), this.interval);
|
|
190
|
+
this.timer.unref?.();
|
|
157
191
|
}
|
|
158
192
|
/**
|
|
159
193
|
* Send a single heartbeat and update local state.
|
|
@@ -173,21 +207,20 @@ var Heartbeat = class {
|
|
|
173
207
|
throw new Error(`Heartbeat failed: ${res.status}`);
|
|
174
208
|
}
|
|
175
209
|
const data = await res.json();
|
|
176
|
-
const localDailyUsed = this.budgetStatus.daily_used;
|
|
177
|
-
const localMonthlyUsed = this.budgetStatus.monthly_used;
|
|
178
210
|
const serverDailyUsed = Number(data.daily_used) || 0;
|
|
179
211
|
const serverMonthlyUsed = Number(data.monthly_used) || 0;
|
|
180
212
|
const serverDailyLimit = data.daily_limit != null ? Number(data.daily_limit) : null;
|
|
181
213
|
const serverMonthlyLimit = data.monthly_limit != null ? Number(data.monthly_limit) : null;
|
|
182
|
-
this.budgetStatus
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
214
|
+
const snapshotDaily = this.budgetStatus.daily_used;
|
|
215
|
+
const snapshotMonthly = this.budgetStatus.monthly_used;
|
|
216
|
+
const mergedDaily = Math.max(serverDailyUsed, snapshotDaily);
|
|
217
|
+
const mergedMonthly = Math.max(serverMonthlyUsed, snapshotMonthly);
|
|
218
|
+
this.budgetStatus.daily_used = mergedDaily + (this.budgetStatus.daily_used - snapshotDaily);
|
|
219
|
+
this.budgetStatus.monthly_used = mergedMonthly + (this.budgetStatus.monthly_used - snapshotMonthly);
|
|
220
|
+
this.budgetStatus.daily_limit = this.pickStricterLimit(serverDailyLimit, this.localDailyLimit);
|
|
221
|
+
this.budgetStatus.monthly_limit = this.pickStricterLimit(serverMonthlyLimit, this.localMonthlyLimit);
|
|
222
|
+
this.budgetStatus.status = data.status;
|
|
223
|
+
this.budgetStatus.kill_switch_active = data.kill_switch_active;
|
|
191
224
|
this.consecutiveFailures = 0;
|
|
192
225
|
const newStatus = data.kill_switch_active ? "OFF" : data.status;
|
|
193
226
|
if (newStatus !== this.status) {
|
|
@@ -732,6 +765,7 @@ function protect(client, config) {
|
|
|
732
765
|
...config
|
|
733
766
|
};
|
|
734
767
|
let shared = instances.get(resolvedConfig.apiKey);
|
|
768
|
+
const isExisting = !!shared;
|
|
735
769
|
if (!shared) {
|
|
736
770
|
const shipper2 = new LogShipper(resolvedConfig);
|
|
737
771
|
const heartbeat2 = new Heartbeat(resolvedConfig);
|
|
@@ -741,7 +775,19 @@ function protect(client, config) {
|
|
|
741
775
|
}
|
|
742
776
|
const { shipper, heartbeat } = shared;
|
|
743
777
|
if (resolvedConfig.budget) {
|
|
744
|
-
const
|
|
778
|
+
const raw = typeof resolvedConfig.budget === "number" ? { monthly: resolvedConfig.budget } : resolvedConfig.budget;
|
|
779
|
+
const budgetConfig = { ...raw };
|
|
780
|
+
if (isExisting) {
|
|
781
|
+
const current = heartbeat.budgetStatus;
|
|
782
|
+
if (budgetConfig.daily !== void 0) {
|
|
783
|
+
const existing = current.daily_limit;
|
|
784
|
+
budgetConfig.daily = existing !== null ? Math.min(existing, budgetConfig.daily) : budgetConfig.daily;
|
|
785
|
+
}
|
|
786
|
+
if (budgetConfig.monthly !== void 0) {
|
|
787
|
+
const existing = current.monthly_limit;
|
|
788
|
+
budgetConfig.monthly = existing !== null ? Math.min(existing, budgetConfig.monthly) : budgetConfig.monthly;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
745
791
|
heartbeat.applyLocalLimits(budgetConfig);
|
|
746
792
|
}
|
|
747
793
|
const provider = detectProvider(client);
|
|
@@ -783,14 +829,16 @@ async function shutdown(apiKey) {
|
|
|
783
829
|
const shared = instances.get(apiKey);
|
|
784
830
|
if (shared) {
|
|
785
831
|
shared.heartbeat.destroy();
|
|
786
|
-
shared.shipper.
|
|
832
|
+
await shared.shipper.shutdown();
|
|
787
833
|
instances.delete(apiKey);
|
|
788
834
|
}
|
|
789
835
|
} else {
|
|
836
|
+
const shutdowns = [];
|
|
790
837
|
for (const [key, shared] of instances) {
|
|
791
838
|
shared.heartbeat.destroy();
|
|
792
|
-
shared.shipper.
|
|
839
|
+
shutdowns.push(shared.shipper.shutdown());
|
|
793
840
|
}
|
|
841
|
+
await Promise.all(shutdowns);
|
|
794
842
|
instances.clear();
|
|
795
843
|
}
|
|
796
844
|
}
|