@traffical/node 0.1.2
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 +154 -0
- package/dist/client.d.ts +200 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +419 -0
- package/dist/client.js.map +1 -0
- package/dist/event-batcher.d.ts +74 -0
- package/dist/event-batcher.d.ts.map +1 -0
- package/dist/event-batcher.js +165 -0
- package/dist/event-batcher.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/client.ts +564 -0
- package/src/event-batcher.ts +210 -0
- package/src/index.ts +17 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Traffical Node.js SDK Client
|
|
3
|
+
*
|
|
4
|
+
* HTTP client with caching, background refresh, and graceful degradation.
|
|
5
|
+
* Wraps the pure core-ts resolution engine.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - ETag-based caching for efficient config fetches
|
|
9
|
+
* - Background refresh for keeping config up-to-date
|
|
10
|
+
* - Automatic decision tracking for intent-to-treat analysis
|
|
11
|
+
* - Batched event transport for efficiency
|
|
12
|
+
* - Graceful degradation with local config and schema defaults
|
|
13
|
+
*/
|
|
14
|
+
import { resolveParameters, decide as coreDecide, DecisionDeduplicator, generateExposureId, generateTrackEventId, } from "@traffical/core";
|
|
15
|
+
import { EventBatcher } from "./event-batcher.js";
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Constants
|
|
18
|
+
// =============================================================================
|
|
19
|
+
const SDK_NAME = "node";
|
|
20
|
+
const SDK_VERSION = "0.1.0"; // Should match package.json version
|
|
21
|
+
const DEFAULT_BASE_URL = "https://sdk.traffical.io";
|
|
22
|
+
const DEFAULT_REFRESH_INTERVAL_MS = 60_000; // 1 minute
|
|
23
|
+
const OFFLINE_WARNING_INTERVAL_MS = 300_000; // 5 minutes
|
|
24
|
+
const DECISION_CACHE_MAX_SIZE = 1000; // Max decisions to cache for attribution lookup
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Traffical Client Class
|
|
27
|
+
// =============================================================================
|
|
28
|
+
/**
|
|
29
|
+
* TrafficalClient - the main SDK client for Node.js environments.
|
|
30
|
+
*
|
|
31
|
+
* Features:
|
|
32
|
+
* - ETag-based caching for efficient config fetches
|
|
33
|
+
* - Background refresh for keeping config up-to-date
|
|
34
|
+
* - Automatic decision tracking for intent-to-treat analysis
|
|
35
|
+
* - Batched event transport for efficiency
|
|
36
|
+
* - Graceful degradation with local config and schema defaults
|
|
37
|
+
* - Rate-limited offline warnings
|
|
38
|
+
*/
|
|
39
|
+
export class TrafficalClient {
|
|
40
|
+
_options;
|
|
41
|
+
_state = {
|
|
42
|
+
bundle: null,
|
|
43
|
+
etag: null,
|
|
44
|
+
lastFetchTime: 0,
|
|
45
|
+
lastOfflineWarning: 0,
|
|
46
|
+
refreshTimer: null,
|
|
47
|
+
isInitialized: false,
|
|
48
|
+
};
|
|
49
|
+
_eventBatcher;
|
|
50
|
+
_decisionDedup;
|
|
51
|
+
/** Cache of recent decisions for attribution lookup on rewards */
|
|
52
|
+
_decisionCache = new Map();
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this._options = {
|
|
55
|
+
orgId: options.orgId,
|
|
56
|
+
projectId: options.projectId,
|
|
57
|
+
env: options.env,
|
|
58
|
+
apiKey: options.apiKey,
|
|
59
|
+
baseUrl: options.baseUrl || DEFAULT_BASE_URL,
|
|
60
|
+
localConfig: options.localConfig,
|
|
61
|
+
refreshIntervalMs: options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS,
|
|
62
|
+
strictMode: options.strictMode ?? false,
|
|
63
|
+
trackDecisions: options.trackDecisions !== false, // Default: true
|
|
64
|
+
};
|
|
65
|
+
// Initialize event batcher
|
|
66
|
+
this._eventBatcher = new EventBatcher({
|
|
67
|
+
endpoint: `${this._options.baseUrl}/v1/events/batch`,
|
|
68
|
+
apiKey: options.apiKey,
|
|
69
|
+
batchSize: options.eventBatchSize,
|
|
70
|
+
flushIntervalMs: options.eventFlushIntervalMs,
|
|
71
|
+
debug: options.debugEvents,
|
|
72
|
+
onError: (error) => {
|
|
73
|
+
console.warn(`[Traffical] Event batching error: ${error.message}`);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
// Initialize decision deduplicator
|
|
77
|
+
this._decisionDedup = new DecisionDeduplicator({
|
|
78
|
+
ttlMs: options.decisionDeduplicationTtlMs,
|
|
79
|
+
});
|
|
80
|
+
// Initialize with local config if provided
|
|
81
|
+
if (this._options.localConfig) {
|
|
82
|
+
this._state.bundle = this._options.localConfig;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Initializes the client by fetching the config bundle.
|
|
87
|
+
* This is called automatically by createTrafficalClient.
|
|
88
|
+
*/
|
|
89
|
+
async initialize() {
|
|
90
|
+
await this._fetchConfig();
|
|
91
|
+
this._startBackgroundRefresh();
|
|
92
|
+
this._state.isInitialized = true;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Stops background refresh and cleans up resources.
|
|
96
|
+
*/
|
|
97
|
+
async destroy() {
|
|
98
|
+
if (this._state.refreshTimer) {
|
|
99
|
+
clearInterval(this._state.refreshTimer);
|
|
100
|
+
this._state.refreshTimer = null;
|
|
101
|
+
}
|
|
102
|
+
// Flush remaining events
|
|
103
|
+
await this._eventBatcher.destroy();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Synchronous destroy for process exit handlers.
|
|
107
|
+
* Use destroy() when possible for proper cleanup.
|
|
108
|
+
*/
|
|
109
|
+
destroySync() {
|
|
110
|
+
if (this._state.refreshTimer) {
|
|
111
|
+
clearInterval(this._state.refreshTimer);
|
|
112
|
+
this._state.refreshTimer = null;
|
|
113
|
+
}
|
|
114
|
+
this._eventBatcher.destroySync();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Manually refreshes the config bundle.
|
|
118
|
+
*/
|
|
119
|
+
async refreshConfig() {
|
|
120
|
+
await this._fetchConfig();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Gets the current config bundle version.
|
|
124
|
+
*/
|
|
125
|
+
getConfigVersion() {
|
|
126
|
+
return this._state.bundle?.version ?? null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Flush pending events immediately.
|
|
130
|
+
*/
|
|
131
|
+
async flushEvents() {
|
|
132
|
+
await this._eventBatcher.flush();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Resolves parameters with defaults as fallback.
|
|
136
|
+
*
|
|
137
|
+
* Resolution priority (highest wins):
|
|
138
|
+
* 1. Policy overrides (from remote bundle)
|
|
139
|
+
* 2. Parameter defaults (from remote bundle)
|
|
140
|
+
* 3. Local config (if remote unavailable)
|
|
141
|
+
* 4. Caller defaults
|
|
142
|
+
*/
|
|
143
|
+
getParams(options) {
|
|
144
|
+
const bundle = this._getEffectiveBundle();
|
|
145
|
+
return resolveParameters(bundle, options.context, options.defaults);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Makes a decision with full metadata for tracking.
|
|
149
|
+
*
|
|
150
|
+
* When trackDecisions is enabled (default), automatically sends a DecisionEvent
|
|
151
|
+
* to the backend for intent-to-treat analysis.
|
|
152
|
+
*/
|
|
153
|
+
decide(options) {
|
|
154
|
+
const start = Date.now();
|
|
155
|
+
const bundle = this._getEffectiveBundle();
|
|
156
|
+
const decision = coreDecide(bundle, options.context, options.defaults);
|
|
157
|
+
const latencyMs = Date.now() - start;
|
|
158
|
+
// Cache decision for attribution lookup when trackReward is called
|
|
159
|
+
this._cacheDecision(decision);
|
|
160
|
+
// Auto-track decision if enabled
|
|
161
|
+
if (this._options.trackDecisions) {
|
|
162
|
+
this._trackDecision(decision, latencyMs, Object.keys(options.defaults));
|
|
163
|
+
}
|
|
164
|
+
return decision;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Tracks an exposure event.
|
|
168
|
+
*
|
|
169
|
+
* If the decision includes filtered context (from policies with contextLogging),
|
|
170
|
+
* it will be included in the exposure event for contextual bandit training.
|
|
171
|
+
*/
|
|
172
|
+
trackExposure(decision) {
|
|
173
|
+
const unitKey = decision.metadata.unitKeyValue;
|
|
174
|
+
if (!unitKey) {
|
|
175
|
+
// Can't track without unit key
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const event = {
|
|
179
|
+
type: "exposure",
|
|
180
|
+
id: generateExposureId(), // Unique exposure ID (not same as decision)
|
|
181
|
+
decisionId: decision.decisionId,
|
|
182
|
+
orgId: this._options.orgId,
|
|
183
|
+
projectId: this._options.projectId,
|
|
184
|
+
env: this._options.env,
|
|
185
|
+
unitKey,
|
|
186
|
+
timestamp: new Date().toISOString(),
|
|
187
|
+
assignments: decision.assignments,
|
|
188
|
+
layers: decision.metadata.layers,
|
|
189
|
+
// Include filtered context for contextual bandit training
|
|
190
|
+
context: decision.metadata.filteredContext,
|
|
191
|
+
sdkName: SDK_NAME,
|
|
192
|
+
sdkVersion: SDK_VERSION,
|
|
193
|
+
};
|
|
194
|
+
this._eventBatcher.log(event);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Tracks a user event.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* // Track a purchase with revenue
|
|
201
|
+
* client.track('purchase', { value: 99.99, orderId: 'ord_123' });
|
|
202
|
+
*
|
|
203
|
+
* // Track a simple event
|
|
204
|
+
* client.track('add_to_cart', { itemId: 'sku_456' });
|
|
205
|
+
*
|
|
206
|
+
* // Track with explicit decision attribution
|
|
207
|
+
* client.track('checkout_complete', { value: 1 }, { decisionId: 'dec_xyz' });
|
|
208
|
+
*/
|
|
209
|
+
track(event, properties, options) {
|
|
210
|
+
const value = typeof properties?.value === 'number' ? properties.value : undefined;
|
|
211
|
+
// Auto-populate attribution from cached decision if available
|
|
212
|
+
const attribution = this._getAttributionFromCache(options?.decisionId);
|
|
213
|
+
const trackEvent = {
|
|
214
|
+
type: "track",
|
|
215
|
+
id: generateTrackEventId(),
|
|
216
|
+
orgId: this._options.orgId,
|
|
217
|
+
projectId: this._options.projectId,
|
|
218
|
+
env: this._options.env,
|
|
219
|
+
unitKey: options?.unitKey || "",
|
|
220
|
+
timestamp: new Date().toISOString(),
|
|
221
|
+
event,
|
|
222
|
+
value,
|
|
223
|
+
properties,
|
|
224
|
+
decisionId: options?.decisionId,
|
|
225
|
+
attribution,
|
|
226
|
+
sdkName: SDK_NAME,
|
|
227
|
+
sdkVersion: SDK_VERSION,
|
|
228
|
+
};
|
|
229
|
+
this._eventBatcher.log(trackEvent);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* @deprecated Use track() instead.
|
|
233
|
+
* Tracks a reward event.
|
|
234
|
+
* If decisionId is provided and the decision is cached, attribution is auto-populated.
|
|
235
|
+
*/
|
|
236
|
+
trackReward(options) {
|
|
237
|
+
// Map old API to new track() API
|
|
238
|
+
this.track(options.event, options.properties, {
|
|
239
|
+
decisionId: undefined, // Not available in old API without decisionId
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// ===========================================================================
|
|
243
|
+
// Private Methods
|
|
244
|
+
// ===========================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Gets the effective bundle: remote > local > null
|
|
247
|
+
*/
|
|
248
|
+
_getEffectiveBundle() {
|
|
249
|
+
return this._state.bundle ?? this._options.localConfig ?? null;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Fetches the config bundle from the edge worker.
|
|
253
|
+
* Uses ETag for efficient caching.
|
|
254
|
+
*/
|
|
255
|
+
async _fetchConfig() {
|
|
256
|
+
const url = `${this._options.baseUrl}/v1/config/${this._options.projectId}?env=${this._options.env}`;
|
|
257
|
+
const headers = {
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
Authorization: `Bearer ${this._options.apiKey}`,
|
|
260
|
+
};
|
|
261
|
+
// Add ETag for conditional request
|
|
262
|
+
if (this._state.etag) {
|
|
263
|
+
headers["If-None-Match"] = this._state.etag;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const response = await fetch(url, { method: "GET", headers });
|
|
267
|
+
if (response.status === 304) {
|
|
268
|
+
// Not modified - bundle is still valid
|
|
269
|
+
this._state.lastFetchTime = Date.now();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
274
|
+
}
|
|
275
|
+
const bundle = (await response.json());
|
|
276
|
+
const etag = response.headers.get("ETag");
|
|
277
|
+
this._state.bundle = bundle;
|
|
278
|
+
this._state.etag = etag;
|
|
279
|
+
this._state.lastFetchTime = Date.now();
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
this._logOfflineWarning(error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Starts background refresh timer.
|
|
287
|
+
*/
|
|
288
|
+
_startBackgroundRefresh() {
|
|
289
|
+
if (this._options.refreshIntervalMs <= 0) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this._state.refreshTimer = setInterval(() => {
|
|
293
|
+
this._fetchConfig().catch(() => {
|
|
294
|
+
// Errors are logged in _fetchConfig
|
|
295
|
+
});
|
|
296
|
+
}, this._options.refreshIntervalMs);
|
|
297
|
+
// Unref so timer doesn't keep process alive
|
|
298
|
+
if (typeof this._state.refreshTimer.unref === "function") {
|
|
299
|
+
this._state.refreshTimer.unref();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Logs an offline warning (rate-limited).
|
|
304
|
+
*/
|
|
305
|
+
_logOfflineWarning(error) {
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
if (now - this._state.lastOfflineWarning > OFFLINE_WARNING_INTERVAL_MS) {
|
|
308
|
+
console.warn(`[Traffical] Failed to fetch config: ${error instanceof Error ? error.message : String(error)}. Using ${this._state.bundle ? "cached" : "local"} config.`);
|
|
309
|
+
this._state.lastOfflineWarning = now;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Tracks a decision event (internal).
|
|
314
|
+
* Called automatically when trackDecisions is enabled.
|
|
315
|
+
*/
|
|
316
|
+
_trackDecision(decision, latencyMs, requestedParameters) {
|
|
317
|
+
const unitKey = decision.metadata.unitKeyValue;
|
|
318
|
+
if (!unitKey) {
|
|
319
|
+
// Can't track without unit key
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
// Hash assignments for deduplication
|
|
323
|
+
const hash = DecisionDeduplicator.hashAssignments(decision.assignments);
|
|
324
|
+
// Check deduplication
|
|
325
|
+
if (!this._decisionDedup.checkAndMark(unitKey, hash)) {
|
|
326
|
+
return; // Duplicate, skip
|
|
327
|
+
}
|
|
328
|
+
// Build the decision event
|
|
329
|
+
const event = {
|
|
330
|
+
type: "decision",
|
|
331
|
+
id: decision.decisionId,
|
|
332
|
+
orgId: this._options.orgId,
|
|
333
|
+
projectId: this._options.projectId,
|
|
334
|
+
env: this._options.env,
|
|
335
|
+
unitKey,
|
|
336
|
+
timestamp: decision.metadata.timestamp,
|
|
337
|
+
requestedParameters,
|
|
338
|
+
assignments: decision.assignments,
|
|
339
|
+
layers: decision.metadata.layers,
|
|
340
|
+
latencyMs,
|
|
341
|
+
// Include filtered context if available
|
|
342
|
+
context: decision.metadata.filteredContext,
|
|
343
|
+
sdkName: SDK_NAME,
|
|
344
|
+
sdkVersion: SDK_VERSION,
|
|
345
|
+
};
|
|
346
|
+
this._eventBatcher.log(event);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Caches a decision for attribution lookup when trackReward is called.
|
|
350
|
+
* Maintains a bounded cache to prevent memory leaks.
|
|
351
|
+
*/
|
|
352
|
+
_cacheDecision(decision) {
|
|
353
|
+
// Evict oldest entries if cache is full
|
|
354
|
+
if (this._decisionCache.size >= DECISION_CACHE_MAX_SIZE) {
|
|
355
|
+
// Get first (oldest) key and delete it
|
|
356
|
+
const firstKey = this._decisionCache.keys().next().value;
|
|
357
|
+
if (firstKey) {
|
|
358
|
+
this._decisionCache.delete(firstKey);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
this._decisionCache.set(decision.decisionId, decision);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Gets attribution info from cached decision if available.
|
|
365
|
+
*/
|
|
366
|
+
_getAttributionFromCache(decisionId) {
|
|
367
|
+
if (!decisionId) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
const cachedDecision = this._decisionCache.get(decisionId);
|
|
371
|
+
if (!cachedDecision) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
const attribution = cachedDecision.metadata.layers
|
|
375
|
+
.filter((l) => l.policyId && l.allocationName)
|
|
376
|
+
.map((l) => ({
|
|
377
|
+
layerId: l.layerId,
|
|
378
|
+
policyId: l.policyId,
|
|
379
|
+
allocationName: l.allocationName,
|
|
380
|
+
}));
|
|
381
|
+
return attribution.length > 0 ? attribution : undefined;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// =============================================================================
|
|
385
|
+
// Factory Function
|
|
386
|
+
// =============================================================================
|
|
387
|
+
/**
|
|
388
|
+
* Creates and initializes a Traffical client.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* const traffical = await createTrafficalClient({
|
|
393
|
+
* orgId: "org_123",
|
|
394
|
+
* projectId: "proj_456",
|
|
395
|
+
* env: "production",
|
|
396
|
+
* apiKey: "sk_...",
|
|
397
|
+
* });
|
|
398
|
+
*
|
|
399
|
+
* const params = traffical.getParams({
|
|
400
|
+
* context: { userId: "user_789" },
|
|
401
|
+
* defaults: {
|
|
402
|
+
* "ui.button.color": "#000",
|
|
403
|
+
* },
|
|
404
|
+
* });
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
export async function createTrafficalClient(options) {
|
|
408
|
+
const client = new TrafficalClient(options);
|
|
409
|
+
await client.initialize();
|
|
410
|
+
return client;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Creates a Traffical client without initializing (synchronous).
|
|
414
|
+
* Useful when you want to control initialization timing.
|
|
415
|
+
*/
|
|
416
|
+
export function createTrafficalClientSync(options) {
|
|
417
|
+
return new TrafficalClient(options);
|
|
418
|
+
}
|
|
419
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAWL,iBAAiB,EACjB,MAAM,IAAI,UAAU,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF,MAAM,QAAQ,GAAG,MAAM,CAAC;AACxB,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,oCAAoC;AAEjE,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AACpD,MAAM,2BAA2B,GAAG,MAAM,CAAC,CAAC,WAAW;AACvD,MAAM,2BAA2B,GAAG,OAAO,CAAC,CAAC,YAAY;AACzD,MAAM,uBAAuB,GAAG,IAAI,CAAC,CAAC,gDAAgD;AAiDtF,gFAAgF;AAChF,yBAAyB;AACzB,gFAAgF;AAEhF;;;;;;;;;;GAUG;AACH,MAAM,OAAO,eAAe;IACT,QAAQ,CAQvB;IAEM,MAAM,GAAgB;QAC5B,MAAM,EAAE,IAAI;QACZ,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,KAAK;KACrB,CAAC;IAEe,aAAa,CAAe;IAC5B,cAAc,CAAuB;IACtD,kEAAkE;IACjD,cAAc,GAAgC,IAAI,GAAG,EAAE,CAAC;IAEzE,YAAY,OAA+B;QACzC,IAAI,CAAC,QAAQ,GAAG;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,gBAAgB;YAC5C,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,2BAA2B;YAC3E,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;YACvC,cAAc,EAAE,OAAO,CAAC,cAAc,KAAK,KAAK,EAAE,gBAAgB;SACnE,CAAC;QAEF,2BAA2B;QAC3B,IAAI,CAAC,aAAa,GAAG,IAAI,YAAY,CAAC;YACpC,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,kBAAkB;YACpD,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,SAAS,EAAE,OAAO,CAAC,cAAc;YACjC,eAAe,EAAE,OAAO,CAAC,oBAAoB;YAC7C,KAAK,EAAE,OAAO,CAAC,WAAW;YAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBACjB,OAAO,CAAC,IAAI,CAAC,qCAAqC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACrE,CAAC;SACF,CAAC,CAAC;QAEH,mCAAmC;QACnC,IAAI,CAAC,cAAc,GAAG,IAAI,oBAAoB,CAAC;YAC7C,KAAK,EAAE,OAAO,CAAC,0BAA0B;SAC1C,CAAC,CAAC;QAEH,2CAA2C;QAC3C,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;QACjD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1B,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC/B,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC7B,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;QAClC,CAAC;QAED,yBAAyB;QACzB,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC;IAED;;;OAGG;IACH,WAAW;QACT,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC7B,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;QAClC,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;IAED;;;;;;;;OAQG;IACH,SAAS,CAA2C,OAA0C;QAC5F,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC1C,OAAO,iBAAiB,CAAI,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzE,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAA2C,OAA0C;QACzF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,UAAU,CAAI,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QAErC,mEAAmE;QACnE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAE9B,iCAAiC;QACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC;YACjC,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1E,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,QAAwB;QACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,+BAA+B;YAC/B,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAkB;YAC3B,IAAI,EAAE,UAAU;YAChB,EAAE,EAAE,kBAAkB,EAAE,EAAE,4CAA4C;YACtE,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK;YAC1B,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS;YAClC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG;YACtB,OAAO;YACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM;YAChC,0DAA0D;YAC1D,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,eAAe;YAC1C,OAAO,EAAE,QAAQ;YACjB,UAAU,EAAE,WAAW;SACxB,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CACH,KAAa,EACb,UAAoC,EACpC,OAAmD;QAEnD,MAAM,KAAK,GAAG,OAAO,UAAU,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAEnF,8DAA8D;QAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,wBAAwB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAEvE,MAAM,UAAU,GAAe;YAC7B,IAAI,EAAE,OAAO;YACb,EAAE,EAAE,oBAAoB,EAAE;YAC1B,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK;YAC1B,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS;YAClC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG;YACtB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE;YAC/B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK;YACL,KAAK;YACL,UAAU;YACV,UAAU,EAAE,OAAO,EAAE,UAAU;YAC/B,WAAW;YACX,OAAO,EAAE,QAAQ;YACjB,UAAU,EAAE,WAAW;SACxB,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,OAAqB;QAC/B,iCAAiC;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;YAC5C,UAAU,EAAE,SAAS,EAAE,8CAA8C;SACtE,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,kBAAkB;IAClB,8EAA8E;IAE9E;;OAEG;IACK,mBAAmB;QACzB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,IAAI,IAAI,CAAC;IACjE,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,YAAY;QACxB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,cAAc,IAAI,CAAC,QAAQ,CAAC,SAAS,QAAQ,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QAErG,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;SAChD,CAAC;QAEF,mCAAmC;QACnC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACrB,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAC9C,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YAE9D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,uCAAuC;gBACvC,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvC,OAAO;YACT,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAiB,CAAC;YACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAE1C,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,uBAAuB;QAC7B,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;YAC1C,IAAI,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC7B,oCAAoC;YACtC,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;QAEpC,4CAA4C;QAC5C,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACzD,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,KAAc;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,GAAG,2BAA2B,EAAE,CAAC;YACvE,OAAO,CAAC,IAAI,CACV,uCAAuC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,UAAU,CAC1J,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,kBAAkB,GAAG,GAAG,CAAC;QACvC,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,cAAc,CACpB,QAAwB,EACxB,SAAiB,EACjB,mBAA6B;QAE7B,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,+BAA+B;YAC/B,OAAO;QACT,CAAC;QAED,qCAAqC;QACrC,MAAM,IAAI,GAAG,oBAAoB,CAAC,eAAe,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAExE,sBAAsB;QACtB,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,kBAAkB;QAC5B,CAAC;QAED,2BAA2B;QAC3B,MAAM,KAAK,GAAkB;YAC3B,IAAI,EAAE,UAAU;YAChB,EAAE,EAAE,QAAQ,CAAC,UAAU;YACvB,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK;YAC1B,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS;YAClC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG;YACtB,OAAO;YACP,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,SAAS;YACtC,mBAAmB;YACnB,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM;YAChC,SAAS;YACT,wCAAwC;YACxC,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,eAAe;YAC1C,OAAO,EAAE,QAAQ;YACjB,UAAU,EAAE,WAAW;SACxB,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED;;;OAGG;IACK,cAAc,CAAC,QAAwB;QAC7C,wCAAwC;QACxC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,IAAI,uBAAuB,EAAE,CAAC;YACxD,uCAAuC;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YACzD,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;IAED;;OAEG;IACK,wBAAwB,CAAC,UAAmB;QAClD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3D,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,MAAM;aAC/C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,cAAc,CAAC;aAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACX,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,QAAQ,EAAE,CAAC,CAAC,QAAS;YACrB,cAAc,EAAE,CAAC,CAAC,cAAe;SAClC,CAAC,CAAC,CAAC;QAEN,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1D,CAAC;CACF;AAED,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAA+B;IAE/B,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;IAC1B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAA+B;IAE/B,OAAO,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBatcher - Batched event transport for Node.js environments.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Batches events for efficient network usage
|
|
6
|
+
* - Flushes on batch size or interval (whichever comes first)
|
|
7
|
+
* - Graceful shutdown with final flush
|
|
8
|
+
* - Error handling with configurable callback
|
|
9
|
+
*
|
|
10
|
+
* Unlike the browser EventLogger, this implementation:
|
|
11
|
+
* - Does not use sendBeacon (Node.js doesn't have it)
|
|
12
|
+
* - Does not persist failed events to storage
|
|
13
|
+
* - Uses standard fetch for HTTP requests
|
|
14
|
+
*/
|
|
15
|
+
import type { TrackableEvent } from "@traffical/core";
|
|
16
|
+
/**
|
|
17
|
+
* Options for EventBatcher.
|
|
18
|
+
*/
|
|
19
|
+
export interface EventBatcherOptions {
|
|
20
|
+
/** API endpoint for events */
|
|
21
|
+
endpoint: string;
|
|
22
|
+
/** API key for authentication */
|
|
23
|
+
apiKey: string;
|
|
24
|
+
/** Max events before auto-flush (default: 10) */
|
|
25
|
+
batchSize?: number;
|
|
26
|
+
/** Auto-flush interval in ms (default: 30000) */
|
|
27
|
+
flushIntervalMs?: number;
|
|
28
|
+
/** Callback on flush error */
|
|
29
|
+
onError?: (error: Error) => void;
|
|
30
|
+
/** Enable debug logging */
|
|
31
|
+
debug?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare class EventBatcher {
|
|
34
|
+
private readonly _endpoint;
|
|
35
|
+
private readonly _apiKey;
|
|
36
|
+
private readonly _batchSize;
|
|
37
|
+
private readonly _flushIntervalMs;
|
|
38
|
+
private readonly _onError?;
|
|
39
|
+
private readonly _debug;
|
|
40
|
+
private _queue;
|
|
41
|
+
private _flushTimer;
|
|
42
|
+
private _isFlushing;
|
|
43
|
+
private _isDestroyed;
|
|
44
|
+
constructor(options: EventBatcherOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Log an event (added to batch queue).
|
|
47
|
+
*/
|
|
48
|
+
log(event: TrackableEvent): void;
|
|
49
|
+
/**
|
|
50
|
+
* Flush all queued events immediately.
|
|
51
|
+
*/
|
|
52
|
+
flush(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Get the number of events in the queue.
|
|
55
|
+
*/
|
|
56
|
+
get queueSize(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Check if the batcher is destroyed.
|
|
59
|
+
*/
|
|
60
|
+
get isDestroyed(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Destroy the batcher (cleanup timers and flush remaining events).
|
|
63
|
+
*/
|
|
64
|
+
destroy(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Synchronous destroy (for process exit handlers).
|
|
67
|
+
* Does not wait for flush to complete.
|
|
68
|
+
*/
|
|
69
|
+
destroySync(): void;
|
|
70
|
+
private _sendEvents;
|
|
71
|
+
private _startFlushTimer;
|
|
72
|
+
private _log;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=event-batcher.d.ts.map
|
|
@@ -0,0 +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,MAAM,iBAAiB,CAAC;AAKtD;;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,8BAA8B;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;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,QAAQ,CAAC,CAAyB;IACnD,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;IAYxC;;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;IAezB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,IAAI;CAKb"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBatcher - Batched event transport for Node.js environments.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Batches events for efficient network usage
|
|
6
|
+
* - Flushes on batch size or interval (whichever comes first)
|
|
7
|
+
* - Graceful shutdown with final flush
|
|
8
|
+
* - Error handling with configurable callback
|
|
9
|
+
*
|
|
10
|
+
* Unlike the browser EventLogger, this implementation:
|
|
11
|
+
* - Does not use sendBeacon (Node.js doesn't have it)
|
|
12
|
+
* - Does not persist failed events to storage
|
|
13
|
+
* - Uses standard fetch for HTTP requests
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_BATCH_SIZE = 10;
|
|
16
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 30_000; // 30 seconds
|
|
17
|
+
export class EventBatcher {
|
|
18
|
+
_endpoint;
|
|
19
|
+
_apiKey;
|
|
20
|
+
_batchSize;
|
|
21
|
+
_flushIntervalMs;
|
|
22
|
+
_onError;
|
|
23
|
+
_debug;
|
|
24
|
+
_queue = [];
|
|
25
|
+
_flushTimer = null;
|
|
26
|
+
_isFlushing = false;
|
|
27
|
+
_isDestroyed = false;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this._endpoint = options.endpoint;
|
|
30
|
+
this._apiKey = options.apiKey;
|
|
31
|
+
this._batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
32
|
+
this._flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
33
|
+
this._onError = options.onError;
|
|
34
|
+
this._debug = options.debug ?? false;
|
|
35
|
+
// Start flush timer
|
|
36
|
+
this._startFlushTimer();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Log an event (added to batch queue).
|
|
40
|
+
*/
|
|
41
|
+
log(event) {
|
|
42
|
+
if (this._isDestroyed) {
|
|
43
|
+
this._log("Attempted to log event after destroy, ignoring");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this._queue.push(event);
|
|
47
|
+
this._log(`Event queued (queue size: ${this._queue.length})`);
|
|
48
|
+
// Auto-flush if batch is full
|
|
49
|
+
if (this._queue.length >= this._batchSize) {
|
|
50
|
+
this._log("Batch size reached, flushing");
|
|
51
|
+
this.flush().catch(() => {
|
|
52
|
+
// Errors handled in flush
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Flush all queued events immediately.
|
|
58
|
+
*/
|
|
59
|
+
async flush() {
|
|
60
|
+
if (this._isFlushing || this._queue.length === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this._isFlushing = true;
|
|
64
|
+
// Take current queue
|
|
65
|
+
const events = [...this._queue];
|
|
66
|
+
this._queue = [];
|
|
67
|
+
try {
|
|
68
|
+
await this._sendEvents(events);
|
|
69
|
+
this._log(`Flushed ${events.length} events successfully`);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Put events back in queue for retry (at the front)
|
|
73
|
+
this._queue.unshift(...events);
|
|
74
|
+
this._log(`Flush failed, ${events.length} events re-queued`);
|
|
75
|
+
this._onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
this._isFlushing = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the number of events in the queue.
|
|
83
|
+
*/
|
|
84
|
+
get queueSize() {
|
|
85
|
+
return this._queue.length;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if the batcher is destroyed.
|
|
89
|
+
*/
|
|
90
|
+
get isDestroyed() {
|
|
91
|
+
return this._isDestroyed;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Destroy the batcher (cleanup timers and flush remaining events).
|
|
95
|
+
*/
|
|
96
|
+
async destroy() {
|
|
97
|
+
if (this._isDestroyed) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this._isDestroyed = true;
|
|
101
|
+
// Stop timer
|
|
102
|
+
if (this._flushTimer) {
|
|
103
|
+
clearInterval(this._flushTimer);
|
|
104
|
+
this._flushTimer = null;
|
|
105
|
+
}
|
|
106
|
+
// Final flush
|
|
107
|
+
if (this._queue.length > 0) {
|
|
108
|
+
this._log(`Destroying with ${this._queue.length} events in queue, flushing`);
|
|
109
|
+
await this.flush();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Synchronous destroy (for process exit handlers).
|
|
114
|
+
* Does not wait for flush to complete.
|
|
115
|
+
*/
|
|
116
|
+
destroySync() {
|
|
117
|
+
this._isDestroyed = true;
|
|
118
|
+
if (this._flushTimer) {
|
|
119
|
+
clearInterval(this._flushTimer);
|
|
120
|
+
this._flushTimer = null;
|
|
121
|
+
}
|
|
122
|
+
// Attempt to flush but don't wait
|
|
123
|
+
if (this._queue.length > 0) {
|
|
124
|
+
this.flush().catch(() => {
|
|
125
|
+
// Best effort on shutdown
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async _sendEvents(events) {
|
|
130
|
+
const response = await fetch(this._endpoint, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: {
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({ events }),
|
|
137
|
+
});
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
_startFlushTimer() {
|
|
143
|
+
if (this._flushIntervalMs <= 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this._flushTimer = setInterval(() => {
|
|
147
|
+
if (this._queue.length > 0) {
|
|
148
|
+
this.flush().catch(() => {
|
|
149
|
+
// Errors handled in flush
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}, this._flushIntervalMs);
|
|
153
|
+
// Unref the timer so it doesn't keep the process alive
|
|
154
|
+
// This is important for Node.js servers that need to shutdown gracefully
|
|
155
|
+
if (typeof this._flushTimer.unref === "function") {
|
|
156
|
+
this._flushTimer.unref();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
_log(message) {
|
|
160
|
+
if (this._debug) {
|
|
161
|
+
console.log(`[Traffical EventBatcher] ${message}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=event-batcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-batcher.js","sourceRoot":"","sources":["../src/event-batcher.ts"],"names":[],"mappings":"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"}
|