@warpmetrics/warp 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/transport.js +77 -7
package/package.json
CHANGED
package/src/core/transport.js
CHANGED
|
@@ -26,6 +26,17 @@ const queue = {
|
|
|
26
26
|
|
|
27
27
|
let flushTimeout = null;
|
|
28
28
|
|
|
29
|
+
// Backoff state for 429 retry
|
|
30
|
+
let backoff = {
|
|
31
|
+
active: false,
|
|
32
|
+
delay: 0, // current delay in ms
|
|
33
|
+
retries: 0,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const BACKOFF_BASE = 2000; // 2s initial backoff
|
|
37
|
+
const BACKOFF_MAX = 60000; // 60s cap
|
|
38
|
+
const BACKOFF_JITTER = 0.3; // ±30% jitter
|
|
39
|
+
|
|
29
40
|
// ---------------------------------------------------------------------------
|
|
30
41
|
// Config
|
|
31
42
|
// ---------------------------------------------------------------------------
|
|
@@ -46,6 +57,25 @@ export function clearQueue() {
|
|
|
46
57
|
queue.links.length = 0;
|
|
47
58
|
queue.outcomes.length = 0;
|
|
48
59
|
queue.acts.length = 0;
|
|
60
|
+
resetBackoff();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Reset backoff state. Exported for testing. */
|
|
64
|
+
export function resetBackoff() {
|
|
65
|
+
backoff = { active: false, delay: 0, retries: 0 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Get current backoff state. Exported for testing. */
|
|
69
|
+
export function getBackoff() {
|
|
70
|
+
return { ...backoff };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Compute next backoff delay with jitter. */
|
|
74
|
+
function nextBackoffDelay(retryAfterMs) {
|
|
75
|
+
if (retryAfterMs) return retryAfterMs;
|
|
76
|
+
const base = Math.min(BACKOFF_BASE * Math.pow(2, backoff.retries), BACKOFF_MAX);
|
|
77
|
+
const jitter = base * BACKOFF_JITTER * (Math.random() * 2 - 1);
|
|
78
|
+
return Math.round(base + jitter);
|
|
49
79
|
}
|
|
50
80
|
|
|
51
81
|
// ---------------------------------------------------------------------------
|
|
@@ -57,6 +87,9 @@ function enqueue(type, event) {
|
|
|
57
87
|
|
|
58
88
|
queue[type].push(event);
|
|
59
89
|
|
|
90
|
+
// During backoff, don't schedule additional flushes — the backoff timer handles it
|
|
91
|
+
if (backoff.active) return;
|
|
92
|
+
|
|
60
93
|
const total = queue.runs.length + queue.groups.length + queue.calls.length
|
|
61
94
|
+ queue.links.length + queue.outcomes.length + queue.acts.length;
|
|
62
95
|
|
|
@@ -67,6 +100,19 @@ function enqueue(type, event) {
|
|
|
67
100
|
}
|
|
68
101
|
}
|
|
69
102
|
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Re-queue helper
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function requeue(batch) {
|
|
108
|
+
queue.runs.unshift(...batch.runs);
|
|
109
|
+
queue.groups.unshift(...batch.groups);
|
|
110
|
+
queue.calls.unshift(...batch.calls);
|
|
111
|
+
queue.links.unshift(...batch.links);
|
|
112
|
+
queue.outcomes.unshift(...batch.outcomes);
|
|
113
|
+
queue.acts.unshift(...batch.acts);
|
|
114
|
+
}
|
|
115
|
+
|
|
70
116
|
// ---------------------------------------------------------------------------
|
|
71
117
|
// Flush
|
|
72
118
|
// ---------------------------------------------------------------------------
|
|
@@ -125,11 +171,41 @@ export async function flush() {
|
|
|
125
171
|
body,
|
|
126
172
|
});
|
|
127
173
|
|
|
174
|
+
if (res.status === 429) {
|
|
175
|
+
// Parse Retry-After header (seconds) if present
|
|
176
|
+
const retryAfterHeader = res.headers?.get?.('Retry-After');
|
|
177
|
+
const retryAfterMs = retryAfterHeader ? parseInt(retryAfterHeader, 10) * 1000 : 0;
|
|
178
|
+
const delay = nextBackoffDelay(retryAfterMs || 0);
|
|
179
|
+
|
|
180
|
+
backoff.active = true;
|
|
181
|
+
backoff.retries++;
|
|
182
|
+
backoff.delay = delay;
|
|
183
|
+
|
|
184
|
+
if (config.debug) {
|
|
185
|
+
console.warn(`[warpmetrics] Rate limited (429). Backing off ${delay}ms (retry #${backoff.retries})`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Re-queue events
|
|
189
|
+
requeue(batch);
|
|
190
|
+
|
|
191
|
+
// Schedule retry after backoff delay
|
|
192
|
+
flushTimeout = setTimeout(flush, delay);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
128
196
|
if (!res.ok) {
|
|
129
197
|
const body = await res.text().catch(() => '');
|
|
130
198
|
throw new Error(`HTTP ${res.status}: ${body}`);
|
|
131
199
|
}
|
|
132
200
|
|
|
201
|
+
// Success — reset backoff state
|
|
202
|
+
if (backoff.active) {
|
|
203
|
+
if (config.debug) {
|
|
204
|
+
console.log(`[warpmetrics] Backoff cleared after ${backoff.retries} retries`);
|
|
205
|
+
}
|
|
206
|
+
resetBackoff();
|
|
207
|
+
}
|
|
208
|
+
|
|
133
209
|
if (config.debug) {
|
|
134
210
|
const result = await res.json();
|
|
135
211
|
const d = result.data || result;
|
|
@@ -139,13 +215,7 @@ export async function flush() {
|
|
|
139
215
|
if (config.debug) {
|
|
140
216
|
console.error('[warpmetrics] Flush failed:', err.message);
|
|
141
217
|
}
|
|
142
|
-
|
|
143
|
-
queue.runs.unshift(...batch.runs);
|
|
144
|
-
queue.groups.unshift(...batch.groups);
|
|
145
|
-
queue.calls.unshift(...batch.calls);
|
|
146
|
-
queue.links.unshift(...batch.links);
|
|
147
|
-
queue.outcomes.unshift(...batch.outcomes);
|
|
148
|
-
queue.acts.unshift(...batch.acts);
|
|
218
|
+
requeue(batch);
|
|
149
219
|
throw err;
|
|
150
220
|
}
|
|
151
221
|
}
|