@cherrydotfun/collector-sdk 0.1.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/dist/index.js ADDED
@@ -0,0 +1,431 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CollectorClient: () => CollectorClient
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/queue.ts
28
+ var EventQueue = class {
29
+ constructor(options) {
30
+ this.queue = [];
31
+ this.timer = null;
32
+ this.flushing = false;
33
+ this.pendingFlush = false;
34
+ this._sent = 0;
35
+ this._failed = 0;
36
+ this.batchSize = options.batchSize;
37
+ this.flushIntervalMs = options.flushIntervalMs;
38
+ this.sendBatch = options.sendBatch;
39
+ this.onError = options.onError;
40
+ this.startTimer();
41
+ }
42
+ /** Add an event to the queue. Triggers a flush when batch size is reached. */
43
+ add(event) {
44
+ this.queue.push(event);
45
+ if (this.queue.length >= this.batchSize) {
46
+ this.flush();
47
+ }
48
+ }
49
+ /** Send all queued events immediately. Safe to call concurrently. */
50
+ async flush() {
51
+ if (this.flushing) {
52
+ this.pendingFlush = true;
53
+ return;
54
+ }
55
+ if (this.queue.length === 0) return;
56
+ this.flushing = true;
57
+ try {
58
+ while (this.queue.length > 0) {
59
+ const batch = this.queue.splice(0, this.batchSize);
60
+ try {
61
+ await this.sendBatch(batch);
62
+ this._sent += batch.length;
63
+ } catch (e) {
64
+ this._failed += batch.length;
65
+ if (this.onError && e instanceof Error) {
66
+ this.onError(e);
67
+ }
68
+ }
69
+ this.pendingFlush = false;
70
+ }
71
+ } finally {
72
+ this.flushing = false;
73
+ if (this.pendingFlush) {
74
+ this.pendingFlush = false;
75
+ this.flush();
76
+ }
77
+ }
78
+ }
79
+ /** Stop the periodic flush timer. Does not flush remaining events. */
80
+ destroy() {
81
+ if (this.timer) {
82
+ clearInterval(this.timer);
83
+ this.timer = null;
84
+ }
85
+ }
86
+ /** Number of events currently waiting in the queue. */
87
+ get size() {
88
+ return this.queue.length;
89
+ }
90
+ /** Returns send/fail counters since creation or last {@link resetStats} call. */
91
+ getStats() {
92
+ return { sent: this._sent, failed: this._failed };
93
+ }
94
+ /** Reset all counters to zero. */
95
+ resetStats() {
96
+ this._sent = 0;
97
+ this._failed = 0;
98
+ }
99
+ startTimer() {
100
+ this.timer = setInterval(() => {
101
+ this.flush();
102
+ }, this.flushIntervalMs);
103
+ }
104
+ };
105
+
106
+ // src/client.ts
107
+ var CollectorClient = class {
108
+ constructor(config) {
109
+ this.disabledPatterns = /* @__PURE__ */ new Set();
110
+ this.configRefreshTimer = null;
111
+ this.initialized = false;
112
+ this._lastConfig = null;
113
+ this._dropped = 0;
114
+ this.config = {
115
+ batchSize: 10,
116
+ flushIntervalMs: 5e3,
117
+ configRefreshSec: 300,
118
+ platform: void 0,
119
+ version: void 0,
120
+ debug: false,
121
+ ...config
122
+ };
123
+ this.queue = new EventQueue({
124
+ batchSize: this.config.batchSize,
125
+ flushIntervalMs: this.config.flushIntervalMs,
126
+ sendBatch: (events) => this.sendBatch(events),
127
+ onError: config.onError ? (err) => config.onError(err, "sendBatch") : void 0
128
+ });
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // Lifecycle
132
+ // ---------------------------------------------------------------------------
133
+ /**
134
+ * Initialize the client: fetches filter configuration from the server
135
+ * and starts the periodic config refresh timer.
136
+ * Must be called before sending events.
137
+ */
138
+ async init() {
139
+ await this.refreshConfig();
140
+ this.configRefreshTimer = setInterval(
141
+ () => this.refreshConfig(),
142
+ this.config.configRefreshSec * 1e3
143
+ );
144
+ this.initialized = true;
145
+ this.log_debug("Collector initialized");
146
+ }
147
+ /**
148
+ * Flush all queued events to the server immediately.
149
+ * Returns when the send completes (or fails silently).
150
+ */
151
+ async flush() {
152
+ await this.queue.flush();
153
+ }
154
+ /**
155
+ * Stop all timers and flush remaining events.
156
+ * Call before application exit or when the client is no longer needed.
157
+ */
158
+ destroy() {
159
+ this.queue.destroy();
160
+ if (this.configRefreshTimer) {
161
+ clearInterval(this.configRefreshTimer);
162
+ this.configRefreshTimer = null;
163
+ }
164
+ this.flush();
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // Context setters
168
+ // ---------------------------------------------------------------------------
169
+ /** Set the user ID attached to all subsequent events. */
170
+ setUser(userId) {
171
+ this._userId = userId;
172
+ this.log_debug("User set:", userId);
173
+ }
174
+ /** Set the session ID attached to all subsequent events. */
175
+ setSession(sessionId) {
176
+ this._sessionId = sessionId;
177
+ this.log_debug("Session set:", sessionId);
178
+ }
179
+ /** Set the default module name attached to all subsequent events. */
180
+ setModule(module2) {
181
+ this._module = module2;
182
+ this.log_debug("Module set:", module2);
183
+ }
184
+ /** Set the device ID attached to all subsequent events. */
185
+ setDevice(deviceId) {
186
+ this._deviceId = deviceId;
187
+ this.log_debug("Device set:", deviceId);
188
+ }
189
+ // ---------------------------------------------------------------------------
190
+ // Error events
191
+ // ---------------------------------------------------------------------------
192
+ /**
193
+ * Send an error event.
194
+ * @param message Error description.
195
+ * @param params Optional parameters. The special `stackTrace` key is extracted
196
+ * and sent as the structured `stackTrace.raw` field.
197
+ */
198
+ error(message, params) {
199
+ const { stackTrace, ...rest } = params ?? {};
200
+ this.enqueue({
201
+ type: "error",
202
+ level: "error",
203
+ message,
204
+ params: Object.keys(rest).length > 0 ? rest : void 0,
205
+ stackTrace: stackTrace ? { raw: stackTrace } : void 0
206
+ });
207
+ }
208
+ // ---------------------------------------------------------------------------
209
+ // Log events
210
+ // ---------------------------------------------------------------------------
211
+ /**
212
+ * Send a log event with an explicit level.
213
+ * @param level Severity: `fatal`, `error`, `warn`, `info`, or `debug`.
214
+ * @param message Log message.
215
+ * @param params Optional key-value parameters.
216
+ */
217
+ log(level, message, params) {
218
+ this.enqueue({ type: "log", level, message, params });
219
+ }
220
+ /** Shorthand for `log('fatal', message, params)`. */
221
+ fatal(message, params) {
222
+ this.log("fatal", message, params);
223
+ }
224
+ /** Shorthand for `log('warn', message, params)`. */
225
+ warn(message, params) {
226
+ this.log("warn", message, params);
227
+ }
228
+ /** Shorthand for `log('info', message, params)`. */
229
+ info(message, params) {
230
+ this.log("info", message, params);
231
+ }
232
+ /** Shorthand for `log('debug', message, params)`. */
233
+ debug(message, params) {
234
+ this.log("debug", message, params);
235
+ }
236
+ // ---------------------------------------------------------------------------
237
+ // Analytics events
238
+ // ---------------------------------------------------------------------------
239
+ /**
240
+ * Send an analytics event.
241
+ * @param event Event name (e.g. `screen_view`, `button_click`).
242
+ * @param message Human-readable description (e.g. screen name or action label).
243
+ * @param params Optional key-value parameters.
244
+ */
245
+ analytics(event, message, params) {
246
+ this.enqueue({ type: "analytics", event, message, params });
247
+ }
248
+ // ---------------------------------------------------------------------------
249
+ // Metrics
250
+ // ---------------------------------------------------------------------------
251
+ /**
252
+ * Set a single metric value. Sent immediately (not batched).
253
+ * @param key Metric key (e.g. `ws_connections`).
254
+ * @param value Numeric value.
255
+ */
256
+ async metric(key, value) {
257
+ try {
258
+ await this.doFetch("/api/metrics/set", {
259
+ method: "POST",
260
+ body: JSON.stringify({ key, value })
261
+ });
262
+ } catch (e) {
263
+ this.handleError(e, "metric");
264
+ }
265
+ }
266
+ /**
267
+ * Set multiple metric values in one request. Sent immediately (not batched).
268
+ * Backend accepts up to 100 operations per call.
269
+ * @param ops Array of `{ key, value }` pairs.
270
+ */
271
+ async metricBatch(ops) {
272
+ try {
273
+ await this.doFetch("/api/metrics/batch", {
274
+ method: "POST",
275
+ body: JSON.stringify({ ops })
276
+ });
277
+ } catch (e) {
278
+ this.handleError(e, "metricBatch");
279
+ }
280
+ }
281
+ // ---------------------------------------------------------------------------
282
+ // Direct send (bypass queue)
283
+ // ---------------------------------------------------------------------------
284
+ /**
285
+ * Send a single event immediately via `POST /api/collect`, bypassing the batch queue.
286
+ * Useful for critical events that must not be delayed.
287
+ *
288
+ * The event is enriched with context (userId, sessionId, platform, etc.)
289
+ * and filtered against disabled patterns, same as queued events.
290
+ *
291
+ * @param event Partial event data. `type` and `message` are required.
292
+ */
293
+ async send(event) {
294
+ const full = this.buildEvent(event);
295
+ if (!full) return;
296
+ try {
297
+ await this.doFetch("/api/collect", {
298
+ method: "POST",
299
+ body: JSON.stringify(full)
300
+ });
301
+ } catch (e) {
302
+ this.handleError(e, "send");
303
+ }
304
+ }
305
+ // ---------------------------------------------------------------------------
306
+ // Config
307
+ // ---------------------------------------------------------------------------
308
+ /**
309
+ * Force-refresh the filter configuration from the server.
310
+ * Normally called automatically on the configured interval.
311
+ */
312
+ async refreshConfig() {
313
+ try {
314
+ const cfg = await this.doFetch("/api/config");
315
+ this.disabledPatterns = new Set(cfg.disabled);
316
+ this._lastConfig = cfg;
317
+ this.log_debug("Config refreshed, disabled:", cfg.disabled);
318
+ } catch (e) {
319
+ this.handleError(e, "refreshConfig");
320
+ }
321
+ }
322
+ /**
323
+ * Returns the last successfully fetched server configuration,
324
+ * or `null` if no config has been loaded yet.
325
+ */
326
+ getConfig() {
327
+ return this._lastConfig;
328
+ }
329
+ // ---------------------------------------------------------------------------
330
+ // Observability
331
+ // ---------------------------------------------------------------------------
332
+ /**
333
+ * Returns SDK telemetry counters: events sent, dropped (filtered), failed,
334
+ * and current queue size.
335
+ */
336
+ getStats() {
337
+ const queueStats = this.queue.getStats();
338
+ return {
339
+ sent: queueStats.sent,
340
+ dropped: this._dropped,
341
+ failed: queueStats.failed,
342
+ queueSize: this.queue.size
343
+ };
344
+ }
345
+ /** Whether {@link init} has been called and completed successfully. */
346
+ get isInitialized() {
347
+ return this.initialized;
348
+ }
349
+ // ---------------------------------------------------------------------------
350
+ // Private methods
351
+ // ---------------------------------------------------------------------------
352
+ buildEvent(event) {
353
+ const classPath = event.type === "analytics" ? `analytics.${event.event}` : `${event.type}.${event.level}`;
354
+ if (this.isDisabled(classPath)) {
355
+ this._dropped++;
356
+ this.log_debug(`Event disabled: ${classPath}`);
357
+ return null;
358
+ }
359
+ const full = {
360
+ type: event.type,
361
+ level: event.level,
362
+ event: event.event,
363
+ message: event.message,
364
+ params: event.params,
365
+ stackTrace: event.stackTrace,
366
+ platform: event.platform ?? this.config.platform,
367
+ version: event.version ?? this.config.version,
368
+ userId: this._userId,
369
+ sessionId: this._sessionId,
370
+ module: event.module ?? this._module,
371
+ deviceId: event.deviceId ?? this._deviceId,
372
+ timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
373
+ };
374
+ Object.keys(full).forEach((key) => {
375
+ if (full[key] === void 0) {
376
+ delete full[key];
377
+ }
378
+ });
379
+ return full;
380
+ }
381
+ enqueue(event) {
382
+ const full = this.buildEvent(event);
383
+ if (!full) return;
384
+ this.queue.add(full);
385
+ }
386
+ isDisabled(classPath) {
387
+ if (this.disabledPatterns.has(classPath)) return true;
388
+ const type = classPath.split(".")[0];
389
+ if (this.disabledPatterns.has(`${type}.*`)) return true;
390
+ if (this.disabledPatterns.has(type)) return true;
391
+ if (this.disabledPatterns.has("*")) return true;
392
+ return false;
393
+ }
394
+ async sendBatch(events) {
395
+ await this.doFetch("/api/collect/batch", {
396
+ method: "POST",
397
+ body: JSON.stringify({ events })
398
+ });
399
+ this.log_debug(`Sent batch of ${events.length} events`);
400
+ }
401
+ async doFetch(path, options) {
402
+ const res = await globalThis.fetch(`${this.config.baseUrl}${path}`, {
403
+ ...options,
404
+ headers: {
405
+ "Content-Type": "application/json",
406
+ "x-api-key": this.config.apiKey,
407
+ ...options?.headers
408
+ }
409
+ });
410
+ if (!res.ok) {
411
+ throw new Error(`Collector API error: ${res.status} ${res.statusText}`);
412
+ }
413
+ return res.json();
414
+ }
415
+ handleError(e, context) {
416
+ this.log_debug(`Failed ${context}:`, e);
417
+ if (this.config.onError && e instanceof Error) {
418
+ this.config.onError(e, context);
419
+ }
420
+ }
421
+ log_debug(...args) {
422
+ if (this.config.debug) {
423
+ console.debug("[collector]", ...args);
424
+ }
425
+ }
426
+ };
427
+ // Annotate the CommonJS export names for ESM import in node:
428
+ 0 && (module.exports = {
429
+ CollectorClient
430
+ });
431
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/queue.ts","../src/client.ts"],"sourcesContent":["export { CollectorClient } from './client';\nexport type {\n CollectorConfig,\n CollectorEvent,\n CollectorStats,\n ConfigResponse,\n EventType,\n LogLevel,\n AnalyticsLevel,\n MetricOp,\n IngestResponse,\n BatchIngestResponse,\n} from './types';\n","import { CollectorEvent } from './types';\n\nexport interface QueueOptions {\n batchSize: number;\n flushIntervalMs: number;\n sendBatch: (events: CollectorEvent[]) => Promise<void>;\n onError?: (error: Error) => void;\n}\n\nexport interface QueueStats {\n sent: number;\n failed: number;\n}\n\nexport class EventQueue {\n private queue: CollectorEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly batchSize: number;\n private readonly flushIntervalMs: number;\n private readonly sendBatch: (events: CollectorEvent[]) => Promise<void>;\n private readonly onError?: (error: Error) => void;\n private flushing = false;\n private pendingFlush = false;\n private _sent = 0;\n private _failed = 0;\n\n constructor(options: QueueOptions) {\n this.batchSize = options.batchSize;\n this.flushIntervalMs = options.flushIntervalMs;\n this.sendBatch = options.sendBatch;\n this.onError = options.onError;\n this.startTimer();\n }\n\n /** Add an event to the queue. Triggers a flush when batch size is reached. */\n add(event: CollectorEvent): void {\n this.queue.push(event);\n if (this.queue.length >= this.batchSize) {\n this.flush();\n }\n }\n\n /** Send all queued events immediately. Safe to call concurrently. */\n async flush(): Promise<void> {\n if (this.flushing) {\n this.pendingFlush = true;\n return;\n }\n\n if (this.queue.length === 0) return;\n\n this.flushing = true;\n try {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, this.batchSize);\n try {\n await this.sendBatch(batch);\n this._sent += batch.length;\n } catch (e) {\n this._failed += batch.length;\n if (this.onError && e instanceof Error) {\n this.onError(e);\n }\n }\n this.pendingFlush = false;\n }\n } finally {\n this.flushing = false;\n if (this.pendingFlush) {\n this.pendingFlush = false;\n this.flush();\n }\n }\n }\n\n /** Stop the periodic flush timer. Does not flush remaining events. */\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /** Number of events currently waiting in the queue. */\n get size(): number {\n return this.queue.length;\n }\n\n /** Returns send/fail counters since creation or last {@link resetStats} call. */\n getStats(): QueueStats {\n return { sent: this._sent, failed: this._failed };\n }\n\n /** Reset all counters to zero. */\n resetStats(): void {\n this._sent = 0;\n this._failed = 0;\n }\n\n private startTimer(): void {\n this.timer = setInterval(() => {\n this.flush();\n }, this.flushIntervalMs);\n }\n}\n","import {\n CollectorConfig,\n CollectorEvent,\n CollectorStats,\n ConfigResponse,\n LogLevel,\n MetricOp,\n} from './types';\nimport { EventQueue } from './queue';\n\nexport class CollectorClient {\n private config: Required<Omit<CollectorConfig, 'onError'>> & { onError?: CollectorConfig['onError'] };\n private queue: EventQueue;\n private disabledPatterns: Set<string> = new Set();\n private configRefreshTimer: ReturnType<typeof setInterval> | null = null;\n private initialized = false;\n private _lastConfig: ConfigResponse | null = null;\n private _dropped = 0;\n\n private _userId: string | undefined;\n private _sessionId: string | undefined;\n private _module: string | undefined;\n private _deviceId: string | undefined;\n\n constructor(config: CollectorConfig) {\n this.config = {\n batchSize: 10,\n flushIntervalMs: 5000,\n configRefreshSec: 300,\n platform: undefined as unknown as string,\n version: undefined as unknown as string,\n debug: false,\n ...config,\n };\n\n this.queue = new EventQueue({\n batchSize: this.config.batchSize,\n flushIntervalMs: this.config.flushIntervalMs,\n sendBatch: (events) => this.sendBatch(events),\n onError: config.onError\n ? (err) => config.onError!(err, 'sendBatch')\n : undefined,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /**\n * Initialize the client: fetches filter configuration from the server\n * and starts the periodic config refresh timer.\n * Must be called before sending events.\n */\n async init(): Promise<void> {\n await this.refreshConfig();\n this.configRefreshTimer = setInterval(\n () => this.refreshConfig(),\n this.config.configRefreshSec * 1000,\n );\n this.initialized = true;\n this.log_debug('Collector initialized');\n }\n\n /**\n * Flush all queued events to the server immediately.\n * Returns when the send completes (or fails silently).\n */\n async flush(): Promise<void> {\n await this.queue.flush();\n }\n\n /**\n * Stop all timers and flush remaining events.\n * Call before application exit or when the client is no longer needed.\n */\n destroy(): void {\n this.queue.destroy();\n if (this.configRefreshTimer) {\n clearInterval(this.configRefreshTimer);\n this.configRefreshTimer = null;\n }\n // Final flush — fire and forget\n this.flush();\n }\n\n // ---------------------------------------------------------------------------\n // Context setters\n // ---------------------------------------------------------------------------\n\n /** Set the user ID attached to all subsequent events. */\n setUser(userId: string): void {\n this._userId = userId;\n this.log_debug('User set:', userId);\n }\n\n /** Set the session ID attached to all subsequent events. */\n setSession(sessionId: string): void {\n this._sessionId = sessionId;\n this.log_debug('Session set:', sessionId);\n }\n\n /** Set the default module name attached to all subsequent events. */\n setModule(module: string): void {\n this._module = module;\n this.log_debug('Module set:', module);\n }\n\n /** Set the device ID attached to all subsequent events. */\n setDevice(deviceId: string): void {\n this._deviceId = deviceId;\n this.log_debug('Device set:', deviceId);\n }\n\n // ---------------------------------------------------------------------------\n // Error events\n // ---------------------------------------------------------------------------\n\n /**\n * Send an error event.\n * @param message Error description.\n * @param params Optional parameters. The special `stackTrace` key is extracted\n * and sent as the structured `stackTrace.raw` field.\n */\n error(message: string, params?: Record<string, unknown> & { stackTrace?: string }): void {\n const { stackTrace, ...rest } = params ?? {};\n this.enqueue({\n type: 'error',\n level: 'error',\n message,\n params: Object.keys(rest).length > 0 ? rest : undefined,\n stackTrace: stackTrace ? { raw: stackTrace } : undefined,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Log events\n // ---------------------------------------------------------------------------\n\n /**\n * Send a log event with an explicit level.\n * @param level Severity: `fatal`, `error`, `warn`, `info`, or `debug`.\n * @param message Log message.\n * @param params Optional key-value parameters.\n */\n log(level: LogLevel, message: string, params?: Record<string, unknown>): void {\n this.enqueue({ type: 'log', level, message, params });\n }\n\n /** Shorthand for `log('fatal', message, params)`. */\n fatal(message: string, params?: Record<string, unknown>): void {\n this.log('fatal', message, params);\n }\n\n /** Shorthand for `log('warn', message, params)`. */\n warn(message: string, params?: Record<string, unknown>): void {\n this.log('warn', message, params);\n }\n\n /** Shorthand for `log('info', message, params)`. */\n info(message: string, params?: Record<string, unknown>): void {\n this.log('info', message, params);\n }\n\n /** Shorthand for `log('debug', message, params)`. */\n debug(message: string, params?: Record<string, unknown>): void {\n this.log('debug', message, params);\n }\n\n // ---------------------------------------------------------------------------\n // Analytics events\n // ---------------------------------------------------------------------------\n\n /**\n * Send an analytics event.\n * @param event Event name (e.g. `screen_view`, `button_click`).\n * @param message Human-readable description (e.g. screen name or action label).\n * @param params Optional key-value parameters.\n */\n analytics(event: string, message: string, params?: Record<string, unknown>): void {\n this.enqueue({ type: 'analytics', event, message, params });\n }\n\n // ---------------------------------------------------------------------------\n // Metrics\n // ---------------------------------------------------------------------------\n\n /**\n * Set a single metric value. Sent immediately (not batched).\n * @param key Metric key (e.g. `ws_connections`).\n * @param value Numeric value.\n */\n async metric(key: string, value: number): Promise<void> {\n try {\n await this.doFetch('/api/metrics/set', {\n method: 'POST',\n body: JSON.stringify({ key, value }),\n });\n } catch (e) {\n this.handleError(e, 'metric');\n }\n }\n\n /**\n * Set multiple metric values in one request. Sent immediately (not batched).\n * Backend accepts up to 100 operations per call.\n * @param ops Array of `{ key, value }` pairs.\n */\n async metricBatch(ops: MetricOp[]): Promise<void> {\n try {\n await this.doFetch('/api/metrics/batch', {\n method: 'POST',\n body: JSON.stringify({ ops }),\n });\n } catch (e) {\n this.handleError(e, 'metricBatch');\n }\n }\n\n // ---------------------------------------------------------------------------\n // Direct send (bypass queue)\n // ---------------------------------------------------------------------------\n\n /**\n * Send a single event immediately via `POST /api/collect`, bypassing the batch queue.\n * Useful for critical events that must not be delayed.\n *\n * The event is enriched with context (userId, sessionId, platform, etc.)\n * and filtered against disabled patterns, same as queued events.\n *\n * @param event Partial event data. `type` and `message` are required.\n */\n async send(event: Partial<CollectorEvent> & { type: CollectorEvent['type']; message: string }): Promise<void> {\n const full = this.buildEvent(event);\n if (!full) return; // dropped by filter\n\n try {\n await this.doFetch('/api/collect', {\n method: 'POST',\n body: JSON.stringify(full),\n });\n } catch (e) {\n this.handleError(e, 'send');\n }\n }\n\n // ---------------------------------------------------------------------------\n // Config\n // ---------------------------------------------------------------------------\n\n /**\n * Force-refresh the filter configuration from the server.\n * Normally called automatically on the configured interval.\n */\n async refreshConfig(): Promise<void> {\n try {\n const cfg = await this.doFetch<ConfigResponse>('/api/config');\n this.disabledPatterns = new Set(cfg.disabled);\n this._lastConfig = cfg;\n this.log_debug('Config refreshed, disabled:', cfg.disabled);\n } catch (e) {\n this.handleError(e, 'refreshConfig');\n }\n }\n\n /**\n * Returns the last successfully fetched server configuration,\n * or `null` if no config has been loaded yet.\n */\n getConfig(): ConfigResponse | null {\n return this._lastConfig;\n }\n\n // ---------------------------------------------------------------------------\n // Observability\n // ---------------------------------------------------------------------------\n\n /**\n * Returns SDK telemetry counters: events sent, dropped (filtered), failed,\n * and current queue size.\n */\n getStats(): CollectorStats {\n const queueStats = this.queue.getStats();\n return {\n sent: queueStats.sent,\n dropped: this._dropped,\n failed: queueStats.failed,\n queueSize: this.queue.size,\n };\n }\n\n /** Whether {@link init} has been called and completed successfully. */\n get isInitialized(): boolean {\n return this.initialized;\n }\n\n // ---------------------------------------------------------------------------\n // Private methods\n // ---------------------------------------------------------------------------\n\n private buildEvent(event: Partial<CollectorEvent>): CollectorEvent | null {\n const classPath =\n event.type === 'analytics'\n ? `analytics.${event.event}`\n : `${event.type}.${event.level}`;\n\n if (this.isDisabled(classPath)) {\n this._dropped++;\n this.log_debug(`Event disabled: ${classPath}`);\n return null;\n }\n\n const full: CollectorEvent = {\n type: event.type!,\n level: event.level,\n event: event.event,\n message: event.message!,\n params: event.params,\n stackTrace: event.stackTrace,\n platform: event.platform ?? this.config.platform,\n version: event.version ?? this.config.version,\n userId: this._userId,\n sessionId: this._sessionId,\n module: event.module ?? this._module,\n deviceId: event.deviceId ?? this._deviceId,\n timestamp: event.timestamp ?? new Date().toISOString(),\n };\n\n // Remove undefined fields to keep payloads clean\n (Object.keys(full) as Array<keyof CollectorEvent>).forEach((key) => {\n if (full[key] === undefined) {\n delete full[key];\n }\n });\n\n return full;\n }\n\n private enqueue(event: Partial<CollectorEvent>): void {\n const full = this.buildEvent(event);\n if (!full) return;\n this.queue.add(full);\n }\n\n private isDisabled(classPath: string): boolean {\n if (this.disabledPatterns.has(classPath)) return true;\n const type = classPath.split('.')[0];\n if (this.disabledPatterns.has(`${type}.*`)) return true;\n if (this.disabledPatterns.has(type)) return true;\n if (this.disabledPatterns.has('*')) return true;\n return false;\n }\n\n private async sendBatch(events: CollectorEvent[]): Promise<void> {\n await this.doFetch('/api/collect/batch', {\n method: 'POST',\n body: JSON.stringify({ events }),\n });\n this.log_debug(`Sent batch of ${events.length} events`);\n }\n\n private async doFetch<T = unknown>(path: string, options?: RequestInit): Promise<T> {\n const res = await globalThis.fetch(`${this.config.baseUrl}${path}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.config.apiKey,\n ...options?.headers,\n },\n });\n if (!res.ok) {\n throw new Error(`Collector API error: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n }\n\n private handleError(e: unknown, context: string): void {\n this.log_debug(`Failed ${context}:`, e);\n if (this.config.onError && e instanceof Error) {\n this.config.onError(e, context);\n }\n }\n\n private log_debug(...args: unknown[]): void {\n if (this.config.debug) {\n console.debug('[collector]', ...args);\n }\n }\n}\n"],"mappings":"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","names":["module"]}