@traffical/node 0.9.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.
@@ -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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"}
@@ -31,6 +31,20 @@ export interface EventBatcherOptions {
31
31
  * events are re-queued for retry.
32
32
  */
33
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;
34
48
  /** Callback on flush error */
35
49
  onError?: (error: Error) => void;
36
50
  /** Enable debug logging */
@@ -44,6 +58,9 @@ export declare class EventBatcher {
44
58
  private readonly _batchSize;
45
59
  private readonly _flushIntervalMs;
46
60
  private readonly _requestTimeoutMs;
61
+ private readonly _maxQueueSize;
62
+ private readonly _maxRetries;
63
+ private readonly _retryBackoffMs;
47
64
  private readonly _onError?;
48
65
  private readonly _onSchemaWarnings?;
49
66
  private readonly _debug;
@@ -51,19 +68,45 @@ export declare class EventBatcher {
51
68
  private _flushTimer;
52
69
  private _isFlushing;
53
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;
54
75
  constructor(options: EventBatcherOptions);
55
76
  /**
56
- * 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.
57
80
  */
58
81
  log(event: TrackableEvent): void;
59
82
  /**
60
- * Flush all queued events immediately.
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).
61
91
  */
62
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;
63
102
  /**
64
103
  * Get the number of events in the queue.
65
104
  */
66
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;
67
110
  /**
68
111
  * Check if the batcher is destroyed.
69
112
  */
@@ -77,6 +120,12 @@ export declare class EventBatcher {
77
120
  * Does not wait for flush to complete.
78
121
  */
79
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
+ */
80
129
  private _sendEvents;
81
130
  private _startFlushTimer;
82
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;AAM5F;;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,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;AAED,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,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;gBAEjB,OAAO,EAAE,mBAAmB;IAcxC;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAkBhC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5B;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB9B;;;OAGG;IACH,WAAW,IAAI,IAAI;YAgBL,WAAW;IAqCzB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,IAAI;CAKb"}
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"}
@@ -15,12 +15,18 @@
15
15
  const DEFAULT_BATCH_SIZE = 10;
16
16
  const DEFAULT_FLUSH_INTERVAL_MS = 30_000; // 30 seconds
17
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)
18
21
  export class EventBatcher {
19
22
  _endpoint;
20
23
  _apiKey;
21
24
  _batchSize;
22
25
  _flushIntervalMs;
23
26
  _requestTimeoutMs;
27
+ _maxQueueSize;
28
+ _maxRetries;
29
+ _retryBackoffMs;
24
30
  _onError;
25
31
  _onSchemaWarnings;
26
32
  _debug;
@@ -28,12 +34,19 @@ export class EventBatcher {
28
34
  _flushTimer = null;
29
35
  _isFlushing = false;
30
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;
31
41
  constructor(options) {
32
42
  this._endpoint = options.endpoint;
33
43
  this._apiKey = options.apiKey;
34
44
  this._batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
35
45
  this._flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
36
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;
37
50
  this._onError = options.onError;
38
51
  this._onSchemaWarnings = options.onSchemaWarnings;
39
52
  this._debug = options.debug ?? false;
@@ -41,13 +54,25 @@ export class EventBatcher {
41
54
  this._startFlushTimer();
42
55
  }
43
56
  /**
44
- * 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.
45
60
  */
46
61
  log(event) {
47
62
  if (this._isDestroyed) {
48
63
  this._log("Attempted to log event after destroy, ignoring");
49
64
  return;
50
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
+ }
51
76
  this._queue.push(event);
52
77
  this._log(`Event queued (queue size: ${this._queue.length})`);
53
78
  // Auto-flush if batch is full
@@ -59,36 +84,124 @@ export class EventBatcher {
59
84
  }
60
85
  }
61
86
  /**
62
- * Flush all queued events immediately.
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).
63
95
  */
64
96
  async flush() {
65
- if (this._isFlushing || this._queue.length === 0) {
97
+ if (this._isFlushing || this._isDisabled || this._queue.length === 0) {
66
98
  return;
67
99
  }
68
100
  this._isFlushing = true;
69
- // Take current queue
70
- const events = [...this._queue];
71
- this._queue = [];
72
101
  try {
73
- await this._sendEvents(events);
74
- this._log(`Flushed ${events.length} events successfully`);
75
- }
76
- catch (error) {
77
- // Put events back in queue for retry (at the front)
78
- this._queue.unshift(...events);
79
- this._log(`Flush failed, ${events.length} events re-queued`);
80
- this._onError?.(error instanceof Error ? error : new Error(String(error)));
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
+ }
81
122
  }
82
123
  finally {
83
124
  this._isFlushing = false;
84
125
  }
85
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
+ }
86
191
  /**
87
192
  * Get the number of events in the queue.
88
193
  */
89
194
  get queueSize() {
90
195
  return this._queue.length;
91
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
+ }
92
205
  /**
93
206
  * Check if the batcher is destroyed.
94
207
  */
@@ -131,6 +244,12 @@ export class EventBatcher {
131
244
  });
132
245
  }
133
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
+ */
134
253
  async _sendEvents(events) {
135
254
  // Abort the request if the edge hangs so the flush settles and events
136
255
  // go down the re-queue-for-retry path (same as any network failure).
@@ -152,7 +271,7 @@ export class EventBatcher {
152
271
  clearTimeout(timeoutId);
153
272
  }
154
273
  if (!response.ok) {
155
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
274
+ return response.status;
156
275
  }
157
276
  if (this._onSchemaWarnings) {
158
277
  try {
@@ -165,6 +284,7 @@ export class EventBatcher {
165
284
  // Response parsing is best-effort for dev-mode warnings
166
285
  }
167
286
  }
287
+ return response.status;
168
288
  }
169
289
  _startFlushTimer() {
170
290
  if (this._flushIntervalMs <= 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"event-batcher.js","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"event-batcher.js","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"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"}
@@ -68,6 +68,64 @@ describe("Node TrafficalClient eventLogger", () => {
68
68
  expect(decisions[0]?.unitKey).toBe("user-1");
69
69
  await client.destroy();
70
70
  });
71
+ test("decision and exposure events carry configVersion", async () => {
72
+ setupFetchMock();
73
+ const events = [];
74
+ const client = new TrafficalClient({
75
+ ...clientOpts,
76
+ eventLogger: (event) => events.push(event),
77
+ });
78
+ await client.initialize();
79
+ const decision = client.decide({
80
+ context: { userId: "user-1" },
81
+ defaults: { "ui.color": "#000" },
82
+ });
83
+ client.trackExposure(decision);
84
+ const decisionEvent = events.find((e) => e.type === "decision");
85
+ const exposureEvent = events.find((e) => e.type === "exposure");
86
+ expect(decisionEvent).toBeDefined();
87
+ expect(exposureEvent).toBeDefined();
88
+ // Server mode: getConfigVersion() surfaces the resolve stateVersion.
89
+ expect(decisionEvent.configVersion).toBe(serverResolveResponse.stateVersion);
90
+ expect(exposureEvent.configVersion).toBe(serverResolveResponse.stateVersion);
91
+ await client.destroy();
92
+ });
93
+ test("configVersion is snapshotted at decide() time, not event-build time", async () => {
94
+ // The mock serves stateVersion v1 first, then v2 after the refresh.
95
+ let stateVersion = "2024-01-01T00:00:00Z";
96
+ const fetchMock = mock(async () => {
97
+ return {
98
+ ok: true,
99
+ status: 200,
100
+ statusText: "OK",
101
+ json: async () => ({ ...serverResolveResponse, stateVersion }),
102
+ headers: new Headers({ ETag: '"v1"' }),
103
+ };
104
+ });
105
+ globalThis.fetch = fetchMock;
106
+ const events = [];
107
+ const client = new TrafficalClient({
108
+ ...clientOpts,
109
+ eventLogger: (event) => events.push(event),
110
+ });
111
+ await client.initialize();
112
+ // Decide against v1, THEN refresh to v2 before the exposure is built.
113
+ const decision = client.decide({
114
+ context: { userId: "user-1" },
115
+ defaults: { "ui.color": "#000" },
116
+ });
117
+ expect(decision.metadata.configVersion).toBe("2024-01-01T00:00:00Z");
118
+ stateVersion = "2024-06-01T00:00:00Z";
119
+ await client.refreshConfig();
120
+ expect(client.getConfigVersion()).toBe("2024-06-01T00:00:00Z");
121
+ client.trackExposure(decision);
122
+ // The exposure event carries the decision-time snapshot (v1), not the
123
+ // stateVersion current at event-build time (v2).
124
+ const exposureEvent = events.find((e) => e.type === "exposure");
125
+ expect(exposureEvent).toBeDefined();
126
+ expect(exposureEvent.configVersion).toBe("2024-01-01T00:00:00Z");
127
+ await client.destroy();
128
+ });
71
129
  test("trackExposure() and track() emit exposure + track events", async () => {
72
130
  setupFetchMock();
73
131
  const events = [];
@@ -96,15 +154,17 @@ describe("Node TrafficalClient eventLogger", () => {
96
154
  eventLogger: (event) => events.push(event),
97
155
  });
98
156
  await client.initialize();
99
- const callsAfterInit = fetchMock.mock.calls.length;
100
157
  const decision = client.decide({
101
158
  context: { userId: "user-1" },
102
159
  defaults: { "ui.color": "#000" },
103
160
  });
104
161
  client.trackExposure(decision);
105
162
  await client.flushEvents();
106
- // No additional fetch calls for event delivery (only resolve happened at init).
107
- expect(fetchMock.mock.calls.length).toBe(callsAfterInit);
163
+ // No fetch calls to the EVENT-delivery endpoint (disableCloudEvents). A
164
+ // per-call /v1/resolve may fire in server mode, but nothing hits
165
+ // /v1/events/batch.
166
+ const eventBatchCalls = fetchMock.mock.calls.filter((c) => String(c[0]).includes("/v1/events"));
167
+ expect(eventBatchCalls).toHaveLength(0);
108
168
  expect(events.length).toBeGreaterThan(0);
109
169
  await client.destroy();
110
170
  });