@traffical/node 0.8.0 → 0.10.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/README.md +16 -6
- package/dist/assignment-logger.test.js +10 -1
- package/dist/assignment-logger.test.js.map +1 -1
- package/dist/canonical-options.test.d.ts +2 -0
- package/dist/canonical-options.test.d.ts.map +1 -0
- package/dist/canonical-options.test.js +167 -0
- package/dist/canonical-options.test.js.map +1 -0
- package/dist/client.d.ts +111 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +280 -50
- package/dist/client.js.map +1 -1
- package/dist/contract-0.7.0.test.d.ts +2 -0
- package/dist/contract-0.7.0.test.d.ts.map +1 -0
- package/dist/contract-0.7.0.test.js +171 -0
- package/dist/contract-0.7.0.test.js.map +1 -0
- package/dist/event-batcher.d.ts +58 -2
- package/dist/event-batcher.d.ts.map +1 -1
- package/dist/event-batcher.js +157 -23
- package/dist/event-batcher.js.map +1 -1
- package/dist/event-batcher.test.js +66 -0
- package/dist/event-batcher.test.js.map +1 -1
- package/dist/event-logger.test.js +63 -3
- package/dist/event-logger.test.js.map +1 -1
- package/dist/malformed-bundle.test.d.ts +12 -0
- package/dist/malformed-bundle.test.d.ts.map +1 -0
- package/dist/malformed-bundle.test.js +110 -0
- package/dist/malformed-bundle.test.js.map +1 -0
- package/dist/track-exposure.test.d.ts +12 -0
- package/dist/track-exposure.test.d.ts.map +1 -0
- package/dist/track-exposure.test.js +112 -0
- package/dist/track-exposure.test.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec 0.7.0 contract behaviors for the Node SDK: bounded event queue with
|
|
3
|
+
* drop-oldest, exponential-backoff retry + re-queue, HTTP 401 kill-switch,
|
|
4
|
+
* trackReward value/decisionId forwarding, positional decide/getParams, and
|
|
5
|
+
* the close()/waitForReady() lifecycle verbs.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test";
|
|
8
|
+
import { EventBatcher } from "./event-batcher.js";
|
|
9
|
+
import { TrafficalClient } from "./client.js";
|
|
10
|
+
function trackEvent(id = "evt") {
|
|
11
|
+
return {
|
|
12
|
+
type: "track",
|
|
13
|
+
id,
|
|
14
|
+
orgId: "org_1",
|
|
15
|
+
projectId: "proj_1",
|
|
16
|
+
env: "test",
|
|
17
|
+
unitKey: "u1",
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
event: "e",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
describe("EventBatcher bounded queue (S8)", () => {
|
|
23
|
+
let originalFetch;
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
originalFetch = globalThis.fetch;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
globalThis.fetch = originalFetch;
|
|
29
|
+
});
|
|
30
|
+
test("drops the oldest event when the queue is full and counts drops", () => {
|
|
31
|
+
// Never auto-flush (huge batchSize + interval), tiny queue cap.
|
|
32
|
+
const batcher = new EventBatcher({
|
|
33
|
+
endpoint: "https://x/v1/events/batch",
|
|
34
|
+
apiKey: "pk",
|
|
35
|
+
batchSize: 1000,
|
|
36
|
+
flushIntervalMs: 999999,
|
|
37
|
+
maxQueueSize: 2,
|
|
38
|
+
});
|
|
39
|
+
batcher.log(trackEvent("a"));
|
|
40
|
+
batcher.log(trackEvent("b"));
|
|
41
|
+
batcher.log(trackEvent("c")); // evicts "a"
|
|
42
|
+
expect(batcher.queueSize).toBe(2);
|
|
43
|
+
expect(batcher.droppedCount).toBe(1);
|
|
44
|
+
batcher.destroySync();
|
|
45
|
+
});
|
|
46
|
+
test("HTTP 401 permanently disables delivery and clears the queue", async () => {
|
|
47
|
+
globalThis.fetch = mock(() => Promise.resolve(new Response("no", { status: 401 })));
|
|
48
|
+
const batcher = new EventBatcher({
|
|
49
|
+
endpoint: "https://x/v1/events/batch",
|
|
50
|
+
apiKey: "pk",
|
|
51
|
+
batchSize: 1000,
|
|
52
|
+
flushIntervalMs: 999999,
|
|
53
|
+
});
|
|
54
|
+
batcher.log(trackEvent("a"));
|
|
55
|
+
await batcher.flush();
|
|
56
|
+
expect(batcher.isDisabled).toBe(true);
|
|
57
|
+
expect(batcher.queueSize).toBe(0);
|
|
58
|
+
// Subsequent logs are dropped (no buffering after kill-switch).
|
|
59
|
+
batcher.log(trackEvent("b"));
|
|
60
|
+
expect(batcher.queueSize).toBe(0);
|
|
61
|
+
batcher.destroySync();
|
|
62
|
+
});
|
|
63
|
+
test("retries a 5xx with backoff, then re-queues the batch", async () => {
|
|
64
|
+
const fetchMock = mock(() => Promise.resolve(new Response("err", { status: 503 })));
|
|
65
|
+
globalThis.fetch = fetchMock;
|
|
66
|
+
const errors = [];
|
|
67
|
+
const batcher = new EventBatcher({
|
|
68
|
+
endpoint: "https://x/v1/events/batch",
|
|
69
|
+
apiKey: "pk",
|
|
70
|
+
batchSize: 1000,
|
|
71
|
+
flushIntervalMs: 999999,
|
|
72
|
+
maxRetries: 2,
|
|
73
|
+
retryBackoffMs: 1,
|
|
74
|
+
onError: (e) => errors.push(e),
|
|
75
|
+
});
|
|
76
|
+
batcher.log(trackEvent("a"));
|
|
77
|
+
await batcher.flush();
|
|
78
|
+
// 1 initial + 2 retries = 3 attempts, then re-queued for a later flush.
|
|
79
|
+
expect(fetchMock.mock.calls.length).toBe(3);
|
|
80
|
+
expect(batcher.queueSize).toBe(1);
|
|
81
|
+
expect(errors).toHaveLength(1);
|
|
82
|
+
expect(batcher.isDisabled).toBe(false);
|
|
83
|
+
batcher.destroySync();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe("Node client contract (S8 + A1)", () => {
|
|
87
|
+
let originalFetch;
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
originalFetch = globalThis.fetch;
|
|
90
|
+
// Config fetch 404 => fail-open; client stays usable with caller defaults.
|
|
91
|
+
globalThis.fetch = mock(() => Promise.resolve(new Response("not found", { status: 404 })));
|
|
92
|
+
});
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
globalThis.fetch = originalFetch;
|
|
95
|
+
});
|
|
96
|
+
test("waitForReady() resolves even when the bundle 404s (fail-open)", async () => {
|
|
97
|
+
const client = new TrafficalClient({
|
|
98
|
+
orgId: "o",
|
|
99
|
+
projectId: "p",
|
|
100
|
+
env: "test",
|
|
101
|
+
apiKey: "pk",
|
|
102
|
+
disableCloudEvents: true,
|
|
103
|
+
});
|
|
104
|
+
await client.initialize();
|
|
105
|
+
await client.waitForReady(); // must not hang
|
|
106
|
+
expect(true).toBe(true);
|
|
107
|
+
await client.close();
|
|
108
|
+
});
|
|
109
|
+
test("positional decide(context, defaults) matches the legacy object bag", async () => {
|
|
110
|
+
const client = new TrafficalClient({
|
|
111
|
+
orgId: "o",
|
|
112
|
+
projectId: "p",
|
|
113
|
+
env: "test",
|
|
114
|
+
apiKey: "pk",
|
|
115
|
+
disableCloudEvents: true,
|
|
116
|
+
trackDecisions: false,
|
|
117
|
+
});
|
|
118
|
+
await client.initialize();
|
|
119
|
+
const positional = client.decide({ userId: "u1" }, { "ui.color": "#000" });
|
|
120
|
+
const bag = client.decide({ context: { userId: "u1" }, defaults: { "ui.color": "#000" } });
|
|
121
|
+
expect(positional.assignments).toEqual(bag.assignments);
|
|
122
|
+
expect(positional.metadata.unitKeyValue).toBe(bag.metadata.unitKeyValue);
|
|
123
|
+
await client.close();
|
|
124
|
+
});
|
|
125
|
+
test("trackReward forwards value and decisionId (previously dropped)", async () => {
|
|
126
|
+
const captured = [];
|
|
127
|
+
const client = new TrafficalClient({
|
|
128
|
+
orgId: "o",
|
|
129
|
+
projectId: "p",
|
|
130
|
+
env: "test",
|
|
131
|
+
apiKey: "pk",
|
|
132
|
+
disableCloudEvents: true,
|
|
133
|
+
trackDecisions: false,
|
|
134
|
+
eventLogger: (e) => captured.push(e),
|
|
135
|
+
});
|
|
136
|
+
await client.initialize();
|
|
137
|
+
const decision = client.decide({ userId: "u1" }, { "ui.color": "#000" });
|
|
138
|
+
client.trackReward({ event: "purchase", value: 42, decisionId: decision.decisionId });
|
|
139
|
+
const track = captured.find((e) => e.type === "track");
|
|
140
|
+
expect(track).toBeDefined();
|
|
141
|
+
expect(track.value).toBe(42);
|
|
142
|
+
expect(track.decisionId).toBe(decision.decisionId);
|
|
143
|
+
await client.close();
|
|
144
|
+
});
|
|
145
|
+
test("track() options bag carries value/values/eventTimestamp", async () => {
|
|
146
|
+
const captured = [];
|
|
147
|
+
const client = new TrafficalClient({
|
|
148
|
+
orgId: "o",
|
|
149
|
+
projectId: "p",
|
|
150
|
+
env: "test",
|
|
151
|
+
apiKey: "pk",
|
|
152
|
+
disableCloudEvents: true,
|
|
153
|
+
trackDecisions: false,
|
|
154
|
+
eventLogger: (e) => captured.push(e),
|
|
155
|
+
});
|
|
156
|
+
await client.initialize();
|
|
157
|
+
const ts = "2024-06-01T00:00:00.000Z";
|
|
158
|
+
client.track("purchase", { orderId: "o1" }, {
|
|
159
|
+
unitKey: "u1",
|
|
160
|
+
value: 10,
|
|
161
|
+
values: { revenue: 10, items: 2 },
|
|
162
|
+
eventTimestamp: ts,
|
|
163
|
+
});
|
|
164
|
+
const track = captured.find((e) => e.type === "track");
|
|
165
|
+
expect(track.value).toBe(10);
|
|
166
|
+
expect(track.values).toEqual({ revenue: 10, items: 2 });
|
|
167
|
+
expect(track.eventTimestamp).toBe(ts);
|
|
168
|
+
await client.close();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=contract-0.7.0.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-0.7.0.test.js","sourceRoot":"","sources":["../src/contract-0.7.0.test.ts"],"names":[],"mappings":"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"}
|
package/dist/event-batcher.d.ts
CHANGED
|
@@ -25,6 +25,26 @@ export interface EventBatcherOptions {
|
|
|
25
25
|
batchSize?: number;
|
|
26
26
|
/** Auto-flush interval in ms (default: 30000) */
|
|
27
27
|
flushIntervalMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Timeout in ms for the event batch POST (default: 10000).
|
|
30
|
+
* On timeout the request is aborted and treated like a failed send:
|
|
31
|
+
* events are re-queued for retry.
|
|
32
|
+
*/
|
|
33
|
+
requestTimeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Maximum number of events buffered in memory (default: 1000). When the
|
|
36
|
+
* queue is full, the OLDEST event is dropped (a counter is bumped) — the
|
|
37
|
+
* queue never grows without bound. Aligns with the Python SDK's model.
|
|
38
|
+
*/
|
|
39
|
+
maxQueueSize?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Max retry attempts (after the first) for a transient delivery failure
|
|
42
|
+
* (network error, timeout, 429, 5xx) before the batch is re-queued for a
|
|
43
|
+
* later flush (default: 3).
|
|
44
|
+
*/
|
|
45
|
+
maxRetries?: number;
|
|
46
|
+
/** Base for exponential retry backoff in ms: base * 2^(attempt-1) (default: 250). */
|
|
47
|
+
retryBackoffMs?: number;
|
|
28
48
|
/** Callback on flush error */
|
|
29
49
|
onError?: (error: Error) => void;
|
|
30
50
|
/** Enable debug logging */
|
|
@@ -37,6 +57,10 @@ export declare class EventBatcher {
|
|
|
37
57
|
private readonly _apiKey;
|
|
38
58
|
private readonly _batchSize;
|
|
39
59
|
private readonly _flushIntervalMs;
|
|
60
|
+
private readonly _requestTimeoutMs;
|
|
61
|
+
private readonly _maxQueueSize;
|
|
62
|
+
private readonly _maxRetries;
|
|
63
|
+
private readonly _retryBackoffMs;
|
|
40
64
|
private readonly _onError?;
|
|
41
65
|
private readonly _onSchemaWarnings?;
|
|
42
66
|
private readonly _debug;
|
|
@@ -44,19 +68,45 @@ export declare class EventBatcher {
|
|
|
44
68
|
private _flushTimer;
|
|
45
69
|
private _isFlushing;
|
|
46
70
|
private _isDestroyed;
|
|
71
|
+
/** Permanently true after an HTTP 401 kill-switch fires. */
|
|
72
|
+
private _isDisabled;
|
|
73
|
+
/** Count of events dropped because the bounded queue overflowed. */
|
|
74
|
+
private _droppedCount;
|
|
47
75
|
constructor(options: EventBatcherOptions);
|
|
48
76
|
/**
|
|
49
|
-
* Log an event (added to batch queue).
|
|
77
|
+
* Log an event (added to the bounded batch queue). When the queue is full
|
|
78
|
+
* the OLDEST event is dropped (drop-oldest) so memory can't grow without
|
|
79
|
+
* bound. After a 401 kill-switch, events are silently discarded.
|
|
50
80
|
*/
|
|
51
81
|
log(event: TrackableEvent): void;
|
|
52
82
|
/**
|
|
53
|
-
* Flush
|
|
83
|
+
* Flush queued events immediately.
|
|
84
|
+
*
|
|
85
|
+
* Drains the queue in batch-sized chunks. Each batch is delivered with
|
|
86
|
+
* exponential-backoff retry on transient failures (network/timeout/429/5xx).
|
|
87
|
+
* A batch that still fails after `maxRetries` is re-queued at the FRONT
|
|
88
|
+
* (bounded) and draining stops until the next flush. A non-retryable 4xx
|
|
89
|
+
* drops the batch. An HTTP 401 permanently disables delivery and clears the
|
|
90
|
+
* queue (auth kill-switch).
|
|
54
91
|
*/
|
|
55
92
|
flush(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Delivers one batch with bounded exponential-backoff retry.
|
|
95
|
+
* onError fires exactly once per batch that ends in a retry-later.
|
|
96
|
+
*/
|
|
97
|
+
private _deliverWithRetry;
|
|
98
|
+
/** Re-queues a failed batch at the front, dropping oldest on overflow. */
|
|
99
|
+
private _requeueFront;
|
|
100
|
+
private _disable;
|
|
101
|
+
private _sleep;
|
|
56
102
|
/**
|
|
57
103
|
* Get the number of events in the queue.
|
|
58
104
|
*/
|
|
59
105
|
get queueSize(): number;
|
|
106
|
+
/** Number of events dropped because the bounded queue overflowed. */
|
|
107
|
+
get droppedCount(): number;
|
|
108
|
+
/** True once an HTTP 401 permanently disabled delivery. */
|
|
109
|
+
get isDisabled(): boolean;
|
|
60
110
|
/**
|
|
61
111
|
* Check if the batcher is destroyed.
|
|
62
112
|
*/
|
|
@@ -70,6 +120,12 @@ export declare class EventBatcher {
|
|
|
70
120
|
* Does not wait for flush to complete.
|
|
71
121
|
*/
|
|
72
122
|
destroySync(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Sends one batch and returns the HTTP status code. Throws only on a
|
|
125
|
+
* transport-level failure (network error / abort), which the caller treats
|
|
126
|
+
* as a transient, retryable error. HTTP status classification (2xx / 401 /
|
|
127
|
+
* 4xx / 5xx) is done by the caller.
|
|
128
|
+
*/
|
|
73
129
|
private _sendEvents;
|
|
74
130
|
private _startFlushTimer;
|
|
75
131
|
private _log;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-batcher.d.ts","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAsB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"event-batcher.d.ts","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAsB,MAAM,iBAAiB,CAAC;AAS5F;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAKD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAmB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IAEjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,4DAA4D;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,oEAAoE;IACpE,OAAO,CAAC,aAAa,CAAK;gBAEd,OAAO,EAAE,mBAAmB;IAiBxC;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IA6BhC;;;;;;;;;OASG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC5B;;;OAGG;YACW,iBAAiB;IAwC/B,0EAA0E;IAC1E,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,MAAM;IASd;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,qEAAqE;IACrE,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,2DAA2D;IAC3D,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB9B;;;OAGG;IACH,WAAW,IAAI,IAAI;IAgBnB;;;;;OAKG;YACW,WAAW;IAuCzB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,IAAI;CAKb"}
|
package/dist/event-batcher.js
CHANGED
|
@@ -14,11 +14,19 @@
|
|
|
14
14
|
*/
|
|
15
15
|
const DEFAULT_BATCH_SIZE = 10;
|
|
16
16
|
const DEFAULT_FLUSH_INTERVAL_MS = 30_000; // 30 seconds
|
|
17
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000; // 10 seconds
|
|
18
|
+
const DEFAULT_MAX_QUEUE_SIZE = 1_000; // bounded queue; overflow drops oldest
|
|
19
|
+
const DEFAULT_MAX_RETRIES = 3; // attempts after the first = 3
|
|
20
|
+
const DEFAULT_RETRY_BACKOFF_MS = 250; // exponential: base * 2^(attempt-1)
|
|
17
21
|
export class EventBatcher {
|
|
18
22
|
_endpoint;
|
|
19
23
|
_apiKey;
|
|
20
24
|
_batchSize;
|
|
21
25
|
_flushIntervalMs;
|
|
26
|
+
_requestTimeoutMs;
|
|
27
|
+
_maxQueueSize;
|
|
28
|
+
_maxRetries;
|
|
29
|
+
_retryBackoffMs;
|
|
22
30
|
_onError;
|
|
23
31
|
_onSchemaWarnings;
|
|
24
32
|
_debug;
|
|
@@ -26,11 +34,19 @@ export class EventBatcher {
|
|
|
26
34
|
_flushTimer = null;
|
|
27
35
|
_isFlushing = false;
|
|
28
36
|
_isDestroyed = false;
|
|
37
|
+
/** Permanently true after an HTTP 401 kill-switch fires. */
|
|
38
|
+
_isDisabled = false;
|
|
39
|
+
/** Count of events dropped because the bounded queue overflowed. */
|
|
40
|
+
_droppedCount = 0;
|
|
29
41
|
constructor(options) {
|
|
30
42
|
this._endpoint = options.endpoint;
|
|
31
43
|
this._apiKey = options.apiKey;
|
|
32
44
|
this._batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
33
45
|
this._flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
46
|
+
this._requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
47
|
+
this._maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
48
|
+
this._maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
49
|
+
this._retryBackoffMs = options.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
|
|
34
50
|
this._onError = options.onError;
|
|
35
51
|
this._onSchemaWarnings = options.onSchemaWarnings;
|
|
36
52
|
this._debug = options.debug ?? false;
|
|
@@ -38,13 +54,25 @@ export class EventBatcher {
|
|
|
38
54
|
this._startFlushTimer();
|
|
39
55
|
}
|
|
40
56
|
/**
|
|
41
|
-
* Log an event (added to batch queue).
|
|
57
|
+
* Log an event (added to the bounded batch queue). When the queue is full
|
|
58
|
+
* the OLDEST event is dropped (drop-oldest) so memory can't grow without
|
|
59
|
+
* bound. After a 401 kill-switch, events are silently discarded.
|
|
42
60
|
*/
|
|
43
61
|
log(event) {
|
|
44
62
|
if (this._isDestroyed) {
|
|
45
63
|
this._log("Attempted to log event after destroy, ignoring");
|
|
46
64
|
return;
|
|
47
65
|
}
|
|
66
|
+
if (this._isDisabled) {
|
|
67
|
+
// Delivery permanently disabled (401) — stop buffering entirely.
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Bounded queue: drop the oldest event on overflow.
|
|
71
|
+
if (this._queue.length >= this._maxQueueSize) {
|
|
72
|
+
this._queue.shift();
|
|
73
|
+
this._droppedCount++;
|
|
74
|
+
this._log(`Queue full, dropped oldest event (dropped total: ${this._droppedCount})`);
|
|
75
|
+
}
|
|
48
76
|
this._queue.push(event);
|
|
49
77
|
this._log(`Event queued (queue size: ${this._queue.length})`);
|
|
50
78
|
// Auto-flush if batch is full
|
|
@@ -56,36 +84,124 @@ export class EventBatcher {
|
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
86
|
/**
|
|
59
|
-
* Flush
|
|
87
|
+
* Flush queued events immediately.
|
|
88
|
+
*
|
|
89
|
+
* Drains the queue in batch-sized chunks. Each batch is delivered with
|
|
90
|
+
* exponential-backoff retry on transient failures (network/timeout/429/5xx).
|
|
91
|
+
* A batch that still fails after `maxRetries` is re-queued at the FRONT
|
|
92
|
+
* (bounded) and draining stops until the next flush. A non-retryable 4xx
|
|
93
|
+
* drops the batch. An HTTP 401 permanently disables delivery and clears the
|
|
94
|
+
* queue (auth kill-switch).
|
|
60
95
|
*/
|
|
61
96
|
async flush() {
|
|
62
|
-
if (this._isFlushing || this._queue.length === 0) {
|
|
97
|
+
if (this._isFlushing || this._isDisabled || this._queue.length === 0) {
|
|
63
98
|
return;
|
|
64
99
|
}
|
|
65
100
|
this._isFlushing = true;
|
|
66
|
-
// Take current queue
|
|
67
|
-
const events = [...this._queue];
|
|
68
|
-
this._queue = [];
|
|
69
101
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
102
|
+
while (this._queue.length > 0 && !this._isDisabled) {
|
|
103
|
+
// Drain a batch OUT of the queue (so concurrent log()s can't alias it).
|
|
104
|
+
const batch = this._queue.splice(0, this._batchSize);
|
|
105
|
+
const outcome = await this._deliverWithRetry(batch);
|
|
106
|
+
if (outcome === "delivered") {
|
|
107
|
+
this._log(`Flushed ${batch.length} events successfully`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (outcome === "dropped") {
|
|
111
|
+
continue; // non-retryable rejection; batch discarded
|
|
112
|
+
}
|
|
113
|
+
if (outcome === "disabled") {
|
|
114
|
+
this._queue = []; // 401 kill-switch
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
// retry-later: put the batch back at the front (bounded) and stop.
|
|
118
|
+
this._requeueFront(batch);
|
|
119
|
+
this._log(`Flush failed, ${batch.length} events re-queued`);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
78
122
|
}
|
|
79
123
|
finally {
|
|
80
124
|
this._isFlushing = false;
|
|
81
125
|
}
|
|
82
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Delivers one batch with bounded exponential-backoff retry.
|
|
129
|
+
* onError fires exactly once per batch that ends in a retry-later.
|
|
130
|
+
*/
|
|
131
|
+
async _deliverWithRetry(batch) {
|
|
132
|
+
let lastError;
|
|
133
|
+
for (let attempt = 0; attempt <= this._maxRetries; attempt++) {
|
|
134
|
+
if (attempt > 0) {
|
|
135
|
+
// Stop retrying inline once we're shutting down; the batch is
|
|
136
|
+
// re-queued and best-effort flushed by the caller.
|
|
137
|
+
if (this._isDestroyed)
|
|
138
|
+
break;
|
|
139
|
+
await this._sleep(this._retryBackoffMs * 2 ** (attempt - 1));
|
|
140
|
+
}
|
|
141
|
+
let status;
|
|
142
|
+
try {
|
|
143
|
+
status = await this._sendEvents(batch);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
// Network error / abort (timeout) — transient, retry.
|
|
147
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (status >= 200 && status < 300)
|
|
151
|
+
return "delivered";
|
|
152
|
+
if (status === 401) {
|
|
153
|
+
this._disable();
|
|
154
|
+
return "disabled";
|
|
155
|
+
}
|
|
156
|
+
if (status === 429 || status >= 500) {
|
|
157
|
+
lastError = new Error(`HTTP ${status}`);
|
|
158
|
+
continue; // transient, retry
|
|
159
|
+
}
|
|
160
|
+
// Other 4xx — permanent rejection, drop the batch.
|
|
161
|
+
this._onError?.(new Error(`HTTP ${status}: batch rejected, dropping ${batch.length} events`));
|
|
162
|
+
return "dropped";
|
|
163
|
+
}
|
|
164
|
+
if (lastError)
|
|
165
|
+
this._onError?.(lastError);
|
|
166
|
+
return "retry-later";
|
|
167
|
+
}
|
|
168
|
+
/** Re-queues a failed batch at the front, dropping oldest on overflow. */
|
|
169
|
+
_requeueFront(batch) {
|
|
170
|
+
this._queue.unshift(...batch);
|
|
171
|
+
while (this._queue.length > this._maxQueueSize) {
|
|
172
|
+
this._queue.shift();
|
|
173
|
+
this._droppedCount++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
_disable() {
|
|
177
|
+
if (this._isDisabled)
|
|
178
|
+
return;
|
|
179
|
+
this._isDisabled = true;
|
|
180
|
+
this._queue = [];
|
|
181
|
+
console.warn("[Traffical] API key rejected (HTTP 401); event delivery disabled for this client");
|
|
182
|
+
}
|
|
183
|
+
_sleep(ms) {
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
const t = setTimeout(resolve, ms);
|
|
186
|
+
if (typeof t.unref === "function") {
|
|
187
|
+
t.unref();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
83
191
|
/**
|
|
84
192
|
* Get the number of events in the queue.
|
|
85
193
|
*/
|
|
86
194
|
get queueSize() {
|
|
87
195
|
return this._queue.length;
|
|
88
196
|
}
|
|
197
|
+
/** Number of events dropped because the bounded queue overflowed. */
|
|
198
|
+
get droppedCount() {
|
|
199
|
+
return this._droppedCount;
|
|
200
|
+
}
|
|
201
|
+
/** True once an HTTP 401 permanently disabled delivery. */
|
|
202
|
+
get isDisabled() {
|
|
203
|
+
return this._isDisabled;
|
|
204
|
+
}
|
|
89
205
|
/**
|
|
90
206
|
* Check if the batcher is destroyed.
|
|
91
207
|
*/
|
|
@@ -128,17 +244,34 @@ export class EventBatcher {
|
|
|
128
244
|
});
|
|
129
245
|
}
|
|
130
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Sends one batch and returns the HTTP status code. Throws only on a
|
|
249
|
+
* transport-level failure (network error / abort), which the caller treats
|
|
250
|
+
* as a transient, retryable error. HTTP status classification (2xx / 401 /
|
|
251
|
+
* 4xx / 5xx) is done by the caller.
|
|
252
|
+
*/
|
|
131
253
|
async _sendEvents(events) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
254
|
+
// Abort the request if the edge hangs so the flush settles and events
|
|
255
|
+
// go down the re-queue-for-retry path (same as any network failure).
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
const timeoutId = setTimeout(() => controller.abort(), this._requestTimeoutMs);
|
|
258
|
+
let response;
|
|
259
|
+
try {
|
|
260
|
+
response = await fetch(this._endpoint, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({ events }),
|
|
267
|
+
signal: controller.signal,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
clearTimeout(timeoutId);
|
|
272
|
+
}
|
|
140
273
|
if (!response.ok) {
|
|
141
|
-
|
|
274
|
+
return response.status;
|
|
142
275
|
}
|
|
143
276
|
if (this._onSchemaWarnings) {
|
|
144
277
|
try {
|
|
@@ -151,6 +284,7 @@ export class EventBatcher {
|
|
|
151
284
|
// Response parsing is best-effort for dev-mode warnings
|
|
152
285
|
}
|
|
153
286
|
}
|
|
287
|
+
return response.status;
|
|
154
288
|
}
|
|
155
289
|
_startFlushTimer() {
|
|
156
290
|
if (this._flushIntervalMs <= 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-batcher.js","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,yBAAyB,GAAG,MAAM,CAAC,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"event-batcher.js","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -119,4 +119,70 @@ describe("EventBatcher schema warnings", () => {
|
|
|
119
119
|
await batcher.destroy();
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
+
describe("EventBatcher request timeout", () => {
|
|
123
|
+
let originalFetch;
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
originalFetch = globalThis.fetch;
|
|
126
|
+
});
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
globalThis.fetch = originalFetch;
|
|
129
|
+
});
|
|
130
|
+
/** A fetch stub that never resolves, but rejects with AbortError when its signal aborts. */
|
|
131
|
+
function installHangingFetch() {
|
|
132
|
+
globalThis.fetch = mock((_url, init) => new Promise((_resolve, reject) => {
|
|
133
|
+
init?.signal?.addEventListener("abort", () => {
|
|
134
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
135
|
+
});
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
test("aborts a hung event POST after requestTimeoutMs and re-queues events for retry", async () => {
|
|
139
|
+
installHangingFetch();
|
|
140
|
+
const errors = [];
|
|
141
|
+
const batcher = new EventBatcher({
|
|
142
|
+
endpoint: "https://test.example.com/v1/events/batch",
|
|
143
|
+
apiKey: "pk_test",
|
|
144
|
+
batchSize: 100,
|
|
145
|
+
flushIntervalMs: 999999,
|
|
146
|
+
requestTimeoutMs: 20,
|
|
147
|
+
onError: (error) => {
|
|
148
|
+
errors.push(error);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
batcher.log(createTrackEvent());
|
|
152
|
+
// Without the abort timeout this would never settle.
|
|
153
|
+
await batcher.flush();
|
|
154
|
+
// Same behavior as a failed send: error surfaced, events re-queued for retry.
|
|
155
|
+
expect(errors).toHaveLength(1);
|
|
156
|
+
expect(errors[0].name === "AbortError" || errors[0].message.toLowerCase().includes("abort")).toBe(true);
|
|
157
|
+
expect(batcher.queueSize).toBe(1);
|
|
158
|
+
batcher.destroySync();
|
|
159
|
+
});
|
|
160
|
+
test("fast response is unaffected and the abort timer is cleaned up", async () => {
|
|
161
|
+
let capturedSignal;
|
|
162
|
+
globalThis.fetch = mock((_url, init) => {
|
|
163
|
+
capturedSignal = init?.signal;
|
|
164
|
+
return Promise.resolve(new Response(JSON.stringify({ accepted: 1 }), { status: 200 }));
|
|
165
|
+
});
|
|
166
|
+
const errors = [];
|
|
167
|
+
const batcher = new EventBatcher({
|
|
168
|
+
endpoint: "https://test.example.com/v1/events/batch",
|
|
169
|
+
apiKey: "pk_test",
|
|
170
|
+
batchSize: 100,
|
|
171
|
+
flushIntervalMs: 999999,
|
|
172
|
+
requestTimeoutMs: 20,
|
|
173
|
+
onError: (error) => {
|
|
174
|
+
errors.push(error);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
batcher.log(createTrackEvent());
|
|
178
|
+
await batcher.flush();
|
|
179
|
+
// Wait past the timeout: if the timer had leaked, the signal would abort.
|
|
180
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
181
|
+
expect(capturedSignal).toBeDefined();
|
|
182
|
+
expect(capturedSignal?.aborted).toBe(false);
|
|
183
|
+
expect(errors).toHaveLength(0);
|
|
184
|
+
expect(batcher.queueSize).toBe(0);
|
|
185
|
+
await batcher.destroy();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
122
188
|
//# sourceMappingURL=event-batcher.test.js.map
|