@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.
@@ -0,0 +1,210 @@
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
+
16
+ import type { TrackableEvent } from "@traffical/core";
17
+
18
+ const DEFAULT_BATCH_SIZE = 10;
19
+ const DEFAULT_FLUSH_INTERVAL_MS = 30_000; // 30 seconds
20
+
21
+ /**
22
+ * Options for EventBatcher.
23
+ */
24
+ export interface EventBatcherOptions {
25
+ /** API endpoint for events */
26
+ endpoint: string;
27
+ /** API key for authentication */
28
+ apiKey: string;
29
+ /** Max events before auto-flush (default: 10) */
30
+ batchSize?: number;
31
+ /** Auto-flush interval in ms (default: 30000) */
32
+ flushIntervalMs?: number;
33
+ /** Callback on flush error */
34
+ onError?: (error: Error) => void;
35
+ /** Enable debug logging */
36
+ debug?: boolean;
37
+ }
38
+
39
+ export class EventBatcher {
40
+ private readonly _endpoint: string;
41
+ private readonly _apiKey: string;
42
+ private readonly _batchSize: number;
43
+ private readonly _flushIntervalMs: number;
44
+ private readonly _onError?: (error: Error) => void;
45
+ private readonly _debug: boolean;
46
+
47
+ private _queue: TrackableEvent[] = [];
48
+ private _flushTimer: ReturnType<typeof setInterval> | null = null;
49
+ private _isFlushing = false;
50
+ private _isDestroyed = false;
51
+
52
+ constructor(options: EventBatcherOptions) {
53
+ this._endpoint = options.endpoint;
54
+ this._apiKey = options.apiKey;
55
+ this._batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
56
+ this._flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
57
+ this._onError = options.onError;
58
+ this._debug = options.debug ?? false;
59
+
60
+ // Start flush timer
61
+ this._startFlushTimer();
62
+ }
63
+
64
+ /**
65
+ * Log an event (added to batch queue).
66
+ */
67
+ log(event: TrackableEvent): void {
68
+ if (this._isDestroyed) {
69
+ this._log("Attempted to log event after destroy, ignoring");
70
+ return;
71
+ }
72
+
73
+ this._queue.push(event);
74
+ this._log(`Event queued (queue size: ${this._queue.length})`);
75
+
76
+ // Auto-flush if batch is full
77
+ if (this._queue.length >= this._batchSize) {
78
+ this._log("Batch size reached, flushing");
79
+ this.flush().catch(() => {
80
+ // Errors handled in flush
81
+ });
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Flush all queued events immediately.
87
+ */
88
+ async flush(): Promise<void> {
89
+ if (this._isFlushing || this._queue.length === 0) {
90
+ return;
91
+ }
92
+
93
+ this._isFlushing = true;
94
+
95
+ // Take current queue
96
+ const events = [...this._queue];
97
+ this._queue = [];
98
+
99
+ try {
100
+ await this._sendEvents(events);
101
+ this._log(`Flushed ${events.length} events successfully`);
102
+ } catch (error) {
103
+ // Put events back in queue for retry (at the front)
104
+ this._queue.unshift(...events);
105
+ this._log(`Flush failed, ${events.length} events re-queued`);
106
+ this._onError?.(error instanceof Error ? error : new Error(String(error)));
107
+ } finally {
108
+ this._isFlushing = false;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Get the number of events in the queue.
114
+ */
115
+ get queueSize(): number {
116
+ return this._queue.length;
117
+ }
118
+
119
+ /**
120
+ * Check if the batcher is destroyed.
121
+ */
122
+ get isDestroyed(): boolean {
123
+ return this._isDestroyed;
124
+ }
125
+
126
+ /**
127
+ * Destroy the batcher (cleanup timers and flush remaining events).
128
+ */
129
+ async destroy(): Promise<void> {
130
+ if (this._isDestroyed) {
131
+ return;
132
+ }
133
+
134
+ this._isDestroyed = true;
135
+
136
+ // Stop timer
137
+ if (this._flushTimer) {
138
+ clearInterval(this._flushTimer);
139
+ this._flushTimer = null;
140
+ }
141
+
142
+ // Final flush
143
+ if (this._queue.length > 0) {
144
+ this._log(`Destroying with ${this._queue.length} events in queue, flushing`);
145
+ await this.flush();
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Synchronous destroy (for process exit handlers).
151
+ * Does not wait for flush to complete.
152
+ */
153
+ destroySync(): void {
154
+ this._isDestroyed = true;
155
+
156
+ if (this._flushTimer) {
157
+ clearInterval(this._flushTimer);
158
+ this._flushTimer = null;
159
+ }
160
+
161
+ // Attempt to flush but don't wait
162
+ if (this._queue.length > 0) {
163
+ this.flush().catch(() => {
164
+ // Best effort on shutdown
165
+ });
166
+ }
167
+ }
168
+
169
+ private async _sendEvents(events: TrackableEvent[]): Promise<void> {
170
+ const response = await fetch(this._endpoint, {
171
+ method: "POST",
172
+ headers: {
173
+ "Content-Type": "application/json",
174
+ Authorization: `Bearer ${this._apiKey}`,
175
+ },
176
+ body: JSON.stringify({ events }),
177
+ });
178
+
179
+ if (!response.ok) {
180
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
181
+ }
182
+ }
183
+
184
+ private _startFlushTimer(): void {
185
+ if (this._flushIntervalMs <= 0) {
186
+ return;
187
+ }
188
+
189
+ this._flushTimer = setInterval(() => {
190
+ if (this._queue.length > 0) {
191
+ this.flush().catch(() => {
192
+ // Errors handled in flush
193
+ });
194
+ }
195
+ }, this._flushIntervalMs);
196
+
197
+ // Unref the timer so it doesn't keep the process alive
198
+ // This is important for Node.js servers that need to shutdown gracefully
199
+ if (typeof this._flushTimer.unref === "function") {
200
+ this._flushTimer.unref();
201
+ }
202
+ }
203
+
204
+ private _log(message: string): void {
205
+ if (this._debug) {
206
+ console.log(`[Traffical EventBatcher] ${message}`);
207
+ }
208
+ }
209
+ }
210
+
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @traffical/node
3
+ *
4
+ * Traffical SDK for Node.js environments.
5
+ * Provides HTTP client with caching, background refresh, and event tracking.
6
+ */
7
+
8
+ // Re-export everything from core
9
+ export * from "@traffical/core";
10
+
11
+ // Export Node-specific client
12
+ export {
13
+ TrafficalClient,
14
+ createTrafficalClient,
15
+ createTrafficalClientSync,
16
+ } from "./client.js";
17
+