@od-oneapp/analytics 2026.1.1301

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.
Files changed (184) hide show
  1. package/README.md +509 -0
  2. package/dist/ai-YMnynb-t.mjs +3347 -0
  3. package/dist/ai-YMnynb-t.mjs.map +1 -0
  4. package/dist/chunk-DQk6qfdC.mjs +18 -0
  5. package/dist/client-CTzJVFU5.mjs +9 -0
  6. package/dist/client-CTzJVFU5.mjs.map +1 -0
  7. package/dist/client-CcFTauAh.mjs +54 -0
  8. package/dist/client-CcFTauAh.mjs.map +1 -0
  9. package/dist/client-CeOLjbac.mjs +281 -0
  10. package/dist/client-CeOLjbac.mjs.map +1 -0
  11. package/dist/client-D339NFJS.mjs +267 -0
  12. package/dist/client-D339NFJS.mjs.map +1 -0
  13. package/dist/client-next.d.mts +62 -0
  14. package/dist/client-next.d.mts.map +1 -0
  15. package/dist/client-next.mjs +525 -0
  16. package/dist/client-next.mjs.map +1 -0
  17. package/dist/client.d.mts +30 -0
  18. package/dist/client.d.mts.map +1 -0
  19. package/dist/client.mjs +186 -0
  20. package/dist/client.mjs.map +1 -0
  21. package/dist/config-DPS6bSYo.d.mts +34 -0
  22. package/dist/config-DPS6bSYo.d.mts.map +1 -0
  23. package/dist/config-P6P5adJg.mjs +287 -0
  24. package/dist/config-P6P5adJg.mjs.map +1 -0
  25. package/dist/console-8bND3mMU.mjs +128 -0
  26. package/dist/console-8bND3mMU.mjs.map +1 -0
  27. package/dist/ecommerce-Cgu4wlux.mjs +993 -0
  28. package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
  29. package/dist/emitters-6-nKo8i-.mjs +208 -0
  30. package/dist/emitters-6-nKo8i-.mjs.map +1 -0
  31. package/dist/emitters-DldkVSPp.d.mts +12 -0
  32. package/dist/emitters-DldkVSPp.d.mts.map +1 -0
  33. package/dist/index-BfNWgfa5.d.mts +1494 -0
  34. package/dist/index-BfNWgfa5.d.mts.map +1 -0
  35. package/dist/index-BkIWe--N.d.mts +953 -0
  36. package/dist/index-BkIWe--N.d.mts.map +1 -0
  37. package/dist/index-jPzXRn52.d.mts +184 -0
  38. package/dist/index-jPzXRn52.d.mts.map +1 -0
  39. package/dist/manager-DvRRjza6.d.mts +76 -0
  40. package/dist/manager-DvRRjza6.d.mts.map +1 -0
  41. package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
  42. package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
  43. package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
  44. package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
  45. package/dist/providers-http-client.d.mts +37 -0
  46. package/dist/providers-http-client.d.mts.map +1 -0
  47. package/dist/providers-http-client.mjs +320 -0
  48. package/dist/providers-http-client.mjs.map +1 -0
  49. package/dist/providers-http-server.d.mts +31 -0
  50. package/dist/providers-http-server.d.mts.map +1 -0
  51. package/dist/providers-http-server.mjs +297 -0
  52. package/dist/providers-http-server.mjs.map +1 -0
  53. package/dist/providers-http.d.mts +46 -0
  54. package/dist/providers-http.d.mts.map +1 -0
  55. package/dist/providers-http.mjs +4 -0
  56. package/dist/server-edge.d.mts +9 -0
  57. package/dist/server-edge.d.mts.map +1 -0
  58. package/dist/server-edge.mjs +373 -0
  59. package/dist/server-edge.mjs.map +1 -0
  60. package/dist/server-next.d.mts +67 -0
  61. package/dist/server-next.d.mts.map +1 -0
  62. package/dist/server-next.mjs +193 -0
  63. package/dist/server-next.mjs.map +1 -0
  64. package/dist/server.d.mts +10 -0
  65. package/dist/server.mjs +7 -0
  66. package/dist/service-cYtBBL8x.mjs +945 -0
  67. package/dist/service-cYtBBL8x.mjs.map +1 -0
  68. package/dist/shared.d.mts +16 -0
  69. package/dist/shared.d.mts.map +1 -0
  70. package/dist/shared.mjs +93 -0
  71. package/dist/shared.mjs.map +1 -0
  72. package/dist/types-BxBnNQ0V.d.mts +354 -0
  73. package/dist/types-BxBnNQ0V.d.mts.map +1 -0
  74. package/dist/types-CBvxUEaF.d.mts +216 -0
  75. package/dist/types-CBvxUEaF.d.mts.map +1 -0
  76. package/dist/types.d.mts +4 -0
  77. package/dist/types.mjs +0 -0
  78. package/dist/vercel-types-lwakUfoI.d.mts +102 -0
  79. package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
  80. package/package.json +129 -0
  81. package/src/client/index.ts +164 -0
  82. package/src/client/manager.ts +71 -0
  83. package/src/client/next/components.tsx +270 -0
  84. package/src/client/next/hooks.ts +217 -0
  85. package/src/client/next/manager.ts +141 -0
  86. package/src/client/next.ts +144 -0
  87. package/src/client-next.ts +101 -0
  88. package/src/client.ts +89 -0
  89. package/src/examples/ai-sdk-patterns.ts +583 -0
  90. package/src/examples/emitter-patterns.ts +476 -0
  91. package/src/examples/nextjs-emitter-patterns.tsx +403 -0
  92. package/src/next/app-router.tsx +564 -0
  93. package/src/next/client.ts +419 -0
  94. package/src/next/index.ts +84 -0
  95. package/src/next/middleware.ts +429 -0
  96. package/src/next/rsc.tsx +300 -0
  97. package/src/next/server.ts +253 -0
  98. package/src/next/types.d.ts +220 -0
  99. package/src/providers/base-provider.ts +419 -0
  100. package/src/providers/console/client.ts +10 -0
  101. package/src/providers/console/index.ts +152 -0
  102. package/src/providers/console/server.ts +6 -0
  103. package/src/providers/console/types.ts +15 -0
  104. package/src/providers/http/client.ts +464 -0
  105. package/src/providers/http/index.ts +30 -0
  106. package/src/providers/http/server.ts +396 -0
  107. package/src/providers/http/types.ts +135 -0
  108. package/src/providers/posthog/client.ts +518 -0
  109. package/src/providers/posthog/index.ts +11 -0
  110. package/src/providers/posthog/server.ts +329 -0
  111. package/src/providers/posthog/types.ts +104 -0
  112. package/src/providers/segment/client.ts +113 -0
  113. package/src/providers/segment/index.ts +11 -0
  114. package/src/providers/segment/server.ts +115 -0
  115. package/src/providers/segment/types.ts +51 -0
  116. package/src/providers/vercel/client.ts +102 -0
  117. package/src/providers/vercel/index.ts +11 -0
  118. package/src/providers/vercel/server.ts +89 -0
  119. package/src/providers/vercel/types.ts +27 -0
  120. package/src/server/index.ts +103 -0
  121. package/src/server/manager.ts +62 -0
  122. package/src/server/next.ts +210 -0
  123. package/src/server-edge.ts +442 -0
  124. package/src/server-next.ts +39 -0
  125. package/src/server.ts +106 -0
  126. package/src/shared/emitters/ai/README.md +981 -0
  127. package/src/shared/emitters/ai/events/agent.ts +130 -0
  128. package/src/shared/emitters/ai/events/artifacts.ts +167 -0
  129. package/src/shared/emitters/ai/events/chat.ts +126 -0
  130. package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
  131. package/src/shared/emitters/ai/events/completion.ts +103 -0
  132. package/src/shared/emitters/ai/events/content-generation.ts +347 -0
  133. package/src/shared/emitters/ai/events/conversation.ts +332 -0
  134. package/src/shared/emitters/ai/events/product-features.ts +1402 -0
  135. package/src/shared/emitters/ai/events/streaming.ts +114 -0
  136. package/src/shared/emitters/ai/events/tool.ts +93 -0
  137. package/src/shared/emitters/ai/index.ts +69 -0
  138. package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
  139. package/src/shared/emitters/ai/track-ai.ts +50 -0
  140. package/src/shared/emitters/ai/types.ts +1041 -0
  141. package/src/shared/emitters/ai/utils.ts +468 -0
  142. package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
  143. package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
  144. package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
  145. package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
  146. package/src/shared/emitters/ecommerce/events/order.ts +199 -0
  147. package/src/shared/emitters/ecommerce/events/product.ts +205 -0
  148. package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
  149. package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
  150. package/src/shared/emitters/ecommerce/index.ts +46 -0
  151. package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
  152. package/src/shared/emitters/ecommerce/types.ts +314 -0
  153. package/src/shared/emitters/ecommerce/utils.ts +216 -0
  154. package/src/shared/emitters/emitter-types.ts +974 -0
  155. package/src/shared/emitters/emitters.ts +292 -0
  156. package/src/shared/emitters/helpers.ts +419 -0
  157. package/src/shared/emitters/index.ts +66 -0
  158. package/src/shared/index.ts +142 -0
  159. package/src/shared/ingestion/index.ts +66 -0
  160. package/src/shared/ingestion/schemas.ts +386 -0
  161. package/src/shared/ingestion/service.ts +628 -0
  162. package/src/shared/node22-features.ts +848 -0
  163. package/src/shared/providers/console-provider.ts +160 -0
  164. package/src/shared/types/base-types.ts +54 -0
  165. package/src/shared/types/console-types.ts +19 -0
  166. package/src/shared/types/posthog-types.ts +131 -0
  167. package/src/shared/types/segment-types.ts +15 -0
  168. package/src/shared/types/types.ts +397 -0
  169. package/src/shared/types/vercel-types.ts +19 -0
  170. package/src/shared/utils/config-client.ts +19 -0
  171. package/src/shared/utils/config.ts +250 -0
  172. package/src/shared/utils/emitter-adapter.ts +212 -0
  173. package/src/shared/utils/manager.test.ts +36 -0
  174. package/src/shared/utils/manager.ts +1322 -0
  175. package/src/shared/utils/posthog-bootstrap.ts +136 -0
  176. package/src/shared/utils/posthog-client-utils.ts +48 -0
  177. package/src/shared/utils/posthog-next-utils.ts +282 -0
  178. package/src/shared/utils/posthog-server-utils.ts +210 -0
  179. package/src/shared/utils/rate-limit.ts +289 -0
  180. package/src/shared/utils/security.ts +545 -0
  181. package/src/shared/utils/validation-client.ts +161 -0
  182. package/src/shared/utils/validation.ts +399 -0
  183. package/src/shared.ts +155 -0
  184. package/src/types/index.ts +62 -0
@@ -0,0 +1,848 @@
1
+ /**
2
+ * @fileoverview Node.js 22+ Advanced Analytics Features
3
+ * Node.js 22+ Advanced Analytics Features
4
+ *
5
+ * Enterprise-grade analytics enhancements leveraging Node.js 22+ features
6
+ * for superior performance, memory efficiency, and developer experience.
7
+ *
8
+ * ## Key Node 22+ Features Used:
9
+ * - **Promise.withResolvers()**: External promise control for complex analytics workflows
10
+ * - **AbortSignal.timeout()**: Context-aware timeouts for analytics operations
11
+ * - **structuredClone()**: Safe event data isolation and batch processing
12
+ * - **Object.hasOwn()**: Safer property existence checks for event validation
13
+ * - **WeakMap/WeakSet**: Memory-efficient provider and event tracking
14
+ * - **High-resolution timing**: Nanosecond precision performance measurements
15
+ * - **FinalizationRegistry**: Automatic cleanup of analytics resources
16
+ *
17
+ * ## Enhanced Capabilities:
18
+ * - Advanced event batching with configurable strategies
19
+ * - Real-time analytics performance monitoring
20
+ * - Memory-efficient event queuing and processing
21
+ * - Provider health monitoring with automatic failover
22
+ * - Comprehensive analytics debugging and diagnostics
23
+ * - Production-safe error handling with detailed telemetry
24
+ *
25
+ * @module Node22AnalyticsFeatures
26
+ * @version 2.0.0
27
+ * @since Node.js 22.0.0
28
+ */
29
+
30
+ import type {
31
+ AnalyticsConfig,
32
+ AnalyticsProvider,
33
+ EmitterPayload,
34
+ TrackingOptions,
35
+ } from './types/types';
36
+
37
+ /**
38
+ * Advanced event batching configuration
39
+ */
40
+ interface EventBatchConfig {
41
+ readonly maxBatchSize: number;
42
+ readonly batchTimeoutMs: number;
43
+ readonly compressionEnabled: boolean;
44
+ readonly retryAttempts: number;
45
+ readonly retryDelayMs: number;
46
+ readonly enablePrioritization: boolean;
47
+ readonly maxMemoryUsage: number; // bytes
48
+ readonly strategy: 'fifo' | 'lifo' | 'priority' | 'size-optimized';
49
+ }
50
+
51
+ /**
52
+ * Analytics performance metrics
53
+ */
54
+ interface AnalyticsMetrics {
55
+ readonly timestamp: bigint;
56
+ readonly eventCount: number;
57
+ readonly batchCount: number;
58
+ readonly processingTime: number; // nanoseconds
59
+ readonly memoryUsage: number; // bytes
60
+ readonly errorCount: number;
61
+ readonly providerHealth: Map<string, number>; // provider -> health score (0-1)
62
+ readonly throughputEventsPerSecond: number;
63
+ readonly averageLatency: number; // milliseconds
64
+ }
65
+
66
+ /**
67
+ * Provider health status
68
+ */
69
+ interface ProviderHealthStatus {
70
+ readonly providerId: string;
71
+ readonly isHealthy: boolean;
72
+ readonly lastCheck: bigint;
73
+ readonly responseTime: number; // milliseconds
74
+ readonly errorRate: number; // 0-1
75
+ readonly successRate: number; // 0-1
76
+ readonly consecutiveFailures: number;
77
+ readonly estimatedRecoveryTime?: number | undefined; // milliseconds
78
+ }
79
+
80
+ /**
81
+ * Event processing result using Node 22+ features
82
+ */
83
+ interface EventProcessingResult {
84
+ readonly success: boolean;
85
+ readonly processedAt: bigint;
86
+ readonly processingTime: number; // nanoseconds
87
+ readonly batchId?: string;
88
+ readonly providerId: string;
89
+ readonly eventId: string;
90
+ readonly retryAttempt: number;
91
+ readonly error?: Error;
92
+ readonly metrics: {
93
+ memoryUsed: number; // bytes
94
+ networkLatency?: number; // milliseconds
95
+ serializationTime: number; // nanoseconds
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Advanced event batcher using Node 22+ WeakMap and Promise.withResolvers()
101
+ */
102
+ export class AdvancedEventBatcher {
103
+ private readonly eventQueue: EmitterPayload[] = [];
104
+ private readonly batchPromises = new Map<
105
+ string,
106
+ { resolve: (value: EventProcessingResult[]) => void; reject: (error: Error) => void }
107
+ >();
108
+ private readonly eventMetadata = new WeakMap<
109
+ EmitterPayload,
110
+ {
111
+ enqueuedAt: bigint;
112
+ priority: number;
113
+ retryCount: number;
114
+ }
115
+ >();
116
+
117
+ private readonly providerHealthMap = new Map<string, ProviderHealthStatus>();
118
+ private readonly metrics = new Map<string, AnalyticsMetrics>();
119
+ private batchTimer?: NodeJS.Timeout | undefined;
120
+ private isProcessing = false;
121
+
122
+ // Node 22+ FinalizationRegistry for automatic cleanup
123
+ // eslint-disable-next-line unicorn/consistent-function-scoping
124
+ private readonly finalizationRegistry = new FinalizationRegistry((batchId: string) => {
125
+ this.cleanupBatch(batchId);
126
+ });
127
+
128
+ constructor(
129
+ private readonly config: EventBatchConfig,
130
+ private readonly providers: Map<string, AnalyticsProvider>,
131
+ ) {}
132
+
133
+ /**
134
+ * Enqueue event for batched processing with Node 22+ features
135
+ */
136
+ async enqueueEvent(
137
+ payload: EmitterPayload,
138
+ options: TrackingOptions & { priority?: number } = {},
139
+ abortSignal?: AbortSignal,
140
+ ): Promise<EventProcessingResult[]> {
141
+ const eventId = this.generateEventId();
142
+ const enqueuedAt = process.hrtime.bigint();
143
+
144
+ // Use Node 22+ Promise.withResolvers() for external control
145
+ // Polyfill for TypeScript lib that doesn't include es2024 yet
146
+ const withResolvers = (): {
147
+ promise: Promise<EventProcessingResult[]>;
148
+ resolve: (value: EventProcessingResult[]) => void;
149
+ reject: (reason?: unknown) => void;
150
+ } => {
151
+ let resolvePromise!: (value: EventProcessingResult[]) => void;
152
+ let rejectPromise!: (reason?: unknown) => void;
153
+ const promise = new Promise<EventProcessingResult[]>((resolve, reject) => {
154
+ resolvePromise = resolve;
155
+ rejectPromise = reject;
156
+ });
157
+ return { promise, resolve: resolvePromise, reject: rejectPromise };
158
+ };
159
+ const { promise, resolve, reject } = withResolvers();
160
+
161
+ // Store metadata using WeakMap (Node 22+)
162
+ this.eventMetadata.set(payload, {
163
+ enqueuedAt,
164
+ priority: options.priority ?? 0,
165
+ retryCount: 0,
166
+ });
167
+
168
+ // Add to queue with priority sorting if enabled
169
+ if (this.config.enablePrioritization) {
170
+ this.insertWithPriority(payload, options.priority ?? 0);
171
+ } else {
172
+ this.eventQueue.push(payload);
173
+ }
174
+
175
+ // Store promise resolvers for this batch
176
+ this.batchPromises.set(eventId, { resolve, reject });
177
+
178
+ // Handle abort signal if provided
179
+ if (abortSignal) {
180
+ abortSignal.addEventListener(
181
+ 'abort',
182
+ () => {
183
+ reject(new Error('Event processing aborted'));
184
+ this.removeBatchPromise(eventId);
185
+ },
186
+ { once: true },
187
+ );
188
+ }
189
+
190
+ // Trigger batch processing if needed
191
+ await this.maybeProcessBatch();
192
+
193
+ return promise;
194
+ }
195
+
196
+ /**
197
+ * Process events in optimized batches using Node 22+ features
198
+ */
199
+ private async maybeProcessBatch(): Promise<void> {
200
+ if (this.isProcessing) return;
201
+
202
+ const shouldProcess =
203
+ this.eventQueue.length >= this.config.maxBatchSize ||
204
+ this.getOldestEventAge() > this.config.batchTimeoutMs ||
205
+ this.isMemoryThresholdExceeded();
206
+
207
+ if (!shouldProcess) {
208
+ this.scheduleBatchTimeout();
209
+ return;
210
+ }
211
+
212
+ await this.processBatch();
213
+ }
214
+
215
+ /**
216
+ * Process current batch with provider failover and Node 22+ optimizations
217
+ */
218
+ private async processBatch(): Promise<void> {
219
+ if (this.isProcessing || this.eventQueue.length === 0) return;
220
+
221
+ this.isProcessing = true;
222
+ const batchId = this.generateBatchId();
223
+ const startTime = process.hrtime.bigint();
224
+
225
+ try {
226
+ // Clone events for safe processing (Node 22+)
227
+ const eventsToProcess = this.eventQueue
228
+ .splice(0, this.config.maxBatchSize)
229
+ .map(event => structuredClone(event));
230
+
231
+ // Register batch for cleanup
232
+ const batchRef = { batchId };
233
+ this.finalizationRegistry.register(batchRef, batchId);
234
+
235
+ // Process events across healthy providers
236
+ const results = await this.processEventsAcrossProviders(
237
+ eventsToProcess,
238
+ batchId,
239
+ AbortSignal.timeout(this.config.batchTimeoutMs * 2),
240
+ );
241
+
242
+ // Update metrics
243
+ const endTime = process.hrtime.bigint();
244
+ await this.updateMetrics(batchId, eventsToProcess.length, Number(endTime - startTime));
245
+
246
+ // Resolve all pending promises for this batch
247
+ this.resolveBatchPromises(results);
248
+ } catch (error) {
249
+ this.rejectBatchPromises(error as Error);
250
+ } finally {
251
+ this.isProcessing = false;
252
+
253
+ // Continue processing if there are more events
254
+ if (this.eventQueue.length > 0) {
255
+ await this.maybeProcessBatch();
256
+ }
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Process events across providers with health monitoring and failover
262
+ */
263
+ private async processEventsAcrossProviders(
264
+ events: EmitterPayload[],
265
+ batchId: string,
266
+ abortSignal: AbortSignal,
267
+ ): Promise<EventProcessingResult[]> {
268
+ const results: EventProcessingResult[] = [];
269
+ const healthyProviders = await this.getHealthyProviders();
270
+
271
+ if (healthyProviders.length === 0) {
272
+ throw new Error('No healthy analytics providers available');
273
+ }
274
+
275
+ // Process events concurrently across healthy providers
276
+ const processingPromises = events.map(async (event, index) => {
277
+ const eventId = `${batchId}_${index}`;
278
+
279
+ for (const provider of healthyProviders) {
280
+ if (abortSignal.aborted) {
281
+ throw new Error('Batch processing aborted');
282
+ }
283
+
284
+ try {
285
+ const result = await this.processEventWithProvider(event, provider, eventId, abortSignal);
286
+
287
+ results.push(result);
288
+ break; // Success, move to next event
289
+ } catch (error) {
290
+ await this.recordProviderError(provider.constructor.name, error as Error);
291
+
292
+ // Try next provider
293
+ continue;
294
+ }
295
+ }
296
+ });
297
+
298
+ await Promise.allSettled(processingPromises);
299
+ return results;
300
+ }
301
+
302
+ /**
303
+ * Process single event with provider using Node 22+ timing
304
+ */
305
+ private async processEventWithProvider(
306
+ event: EmitterPayload,
307
+ provider: AnalyticsProvider,
308
+ eventId: string,
309
+ abortSignal: AbortSignal,
310
+ ): Promise<EventProcessingResult> {
311
+ const startTime = process.hrtime.bigint();
312
+ const initialMemory = process.memoryUsage().heapUsed;
313
+
314
+ try {
315
+ // Serialize event with timing
316
+ const serializationStart = process.hrtime.bigint();
317
+ JSON.stringify(event);
318
+ const serializationEnd = process.hrtime.bigint();
319
+
320
+ // Process with provider
321
+ await this.callProviderWithTimeout(provider, event, abortSignal);
322
+
323
+ const endTime = process.hrtime.bigint();
324
+ const finalMemory = process.memoryUsage().heapUsed;
325
+
326
+ return {
327
+ success: true,
328
+ processedAt: endTime,
329
+ processingTime: Number(endTime - startTime),
330
+ eventId,
331
+ providerId: provider.constructor.name,
332
+ retryAttempt: this.getRetryCount(event),
333
+ metrics: {
334
+ memoryUsed: finalMemory - initialMemory,
335
+ serializationTime: Number(serializationEnd - serializationStart),
336
+ },
337
+ };
338
+ } catch (error) {
339
+ const endTime = process.hrtime.bigint();
340
+ const finalMemory = process.memoryUsage().heapUsed;
341
+
342
+ return {
343
+ success: false,
344
+ processedAt: endTime,
345
+ processingTime: Number(endTime - startTime),
346
+ eventId,
347
+ providerId: provider.constructor.name,
348
+ retryAttempt: this.getRetryCount(event),
349
+ error: error as Error,
350
+ metrics: {
351
+ memoryUsed: finalMemory - initialMemory,
352
+ serializationTime: 0,
353
+ },
354
+ };
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Call provider method with timeout using AbortSignal.timeout()
360
+ */
361
+ private async callProviderWithTimeout(
362
+ provider: AnalyticsProvider,
363
+ event: EmitterPayload,
364
+ abortSignal: AbortSignal,
365
+ ): Promise<void> {
366
+ const timeoutSignal = AbortSignal.timeout(5000); // 5 second timeout
367
+ const combinedSignal = AbortSignal.any([abortSignal, timeoutSignal]);
368
+
369
+ // Determine the provider method to call based on event type
370
+ if (event.type === 'track') {
371
+ await provider.track(event.event, event.properties ?? {}, event.context);
372
+ } else if (event.type === 'identify' && provider.identify && event.userId) {
373
+ await provider.identify(event.userId, event.traits ?? {}, event.context);
374
+ } else if (event.type === 'page' && provider.page) {
375
+ await provider.page(event.name ?? 'Unknown Page', event.properties ?? {}, event.context);
376
+ } else if (event.type === 'group' && provider.group && event.groupId) {
377
+ await provider.group(event.groupId, event.traits ?? {}, event.context);
378
+ } else if (event.type === 'alias' && provider.alias && event.userId && event.previousId) {
379
+ await provider.alias(event.userId, event.previousId, event.context);
380
+ }
381
+
382
+ if (combinedSignal.aborted) {
383
+ throw new Error('Provider call timed out or was aborted');
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Get healthy providers with real-time health monitoring
389
+ */
390
+ private async getHealthyProviders(): Promise<AnalyticsProvider[]> {
391
+ const healthyProviders: AnalyticsProvider[] = [];
392
+ const now = process.hrtime.bigint();
393
+
394
+ for (const [providerId, provider] of this.providers) {
395
+ const health = this.providerHealthMap.get(providerId);
396
+
397
+ // Check if we need to re-evaluate health
398
+ const needsHealthCheck = !health || Number(now - health.lastCheck) > 30000000000; // 30 seconds in nanoseconds
399
+
400
+ if (needsHealthCheck) {
401
+ await this.checkProviderHealth(providerId, provider);
402
+ }
403
+
404
+ const currentHealth = this.providerHealthMap.get(providerId);
405
+ if (currentHealth?.isHealthy) {
406
+ healthyProviders.push(provider);
407
+ }
408
+ }
409
+
410
+ return healthyProviders;
411
+ }
412
+
413
+ /**
414
+ * Check individual provider health using Node 22+ timing
415
+ */
416
+ private async checkProviderHealth(
417
+ providerId: string,
418
+ provider: AnalyticsProvider,
419
+ ): Promise<void> {
420
+ const startTime = process.hrtime.bigint();
421
+
422
+ try {
423
+ // Simple health check - attempt to track a test event
424
+ const testEvent = {
425
+ event: '__health_check__',
426
+ properties: { timestamp: Date.now() },
427
+ options: { send: false }, // Don't actually send if provider supports it
428
+ };
429
+
430
+ await Promise.race([
431
+ provider.track(testEvent.event, testEvent.properties, testEvent.options),
432
+ new Promise<void>((_resolve, reject) =>
433
+ setTimeout(() => reject(new Error('Health check timeout')), 2000),
434
+ ),
435
+ ]);
436
+
437
+ const endTime = process.hrtime.bigint();
438
+ const responseTime = Number(endTime - startTime) / 1_000_000; // Convert to ms
439
+
440
+ this.providerHealthMap.set(providerId, {
441
+ providerId,
442
+ isHealthy: true,
443
+ lastCheck: endTime,
444
+ responseTime,
445
+ errorRate: 0,
446
+ successRate: 1,
447
+ consecutiveFailures: 0,
448
+ });
449
+ } catch {
450
+ const endTime = process.hrtime.bigint();
451
+ const responseTime = Number(endTime - startTime) / 1_000_000;
452
+
453
+ const existingHealth = this.providerHealthMap.get(providerId);
454
+ const consecutiveFailures = (existingHealth?.consecutiveFailures ?? 0) + 1;
455
+
456
+ this.providerHealthMap.set(providerId, {
457
+ providerId,
458
+ isHealthy: consecutiveFailures < 3, // Healthy if fewer than 3 consecutive failures
459
+ lastCheck: endTime,
460
+ responseTime,
461
+ errorRate: Math.min(1, consecutiveFailures * 0.25),
462
+ successRate: Math.max(0, 1 - consecutiveFailures * 0.25),
463
+ consecutiveFailures,
464
+ estimatedRecoveryTime: consecutiveFailures * 30000, // 30s per failure
465
+ });
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Update analytics metrics using high-resolution timing
471
+ */
472
+ private async updateMetrics(
473
+ batchId: string,
474
+ eventCount: number,
475
+ processingTime: number,
476
+ ): Promise<void> {
477
+ const now = process.hrtime.bigint();
478
+ const memoryUsage = process.memoryUsage().heapUsed;
479
+
480
+ // Calculate throughput
481
+ const processingTimeMs = processingTime / 1_000_000;
482
+ const throughput = processingTimeMs > 0 ? (eventCount / processingTimeMs) * 1000 : 0;
483
+
484
+ const metrics: AnalyticsMetrics = {
485
+ timestamp: now,
486
+ eventCount,
487
+ batchCount: 1,
488
+ processingTime,
489
+ memoryUsage,
490
+ errorCount: 0, // Would be calculated from results
491
+ providerHealth: new Map(
492
+ [...this.providerHealthMap.entries()].map(([id, health]) => [id, health.successRate]),
493
+ ),
494
+ throughputEventsPerSecond: throughput,
495
+ averageLatency: processingTimeMs,
496
+ };
497
+
498
+ this.metrics.set(batchId, metrics);
499
+
500
+ // Keep only recent metrics to prevent memory growth
501
+ if (this.metrics.size > 100) {
502
+ const oldestKey = this.metrics.keys().next().value;
503
+ if (oldestKey !== undefined) {
504
+ this.metrics.delete(oldestKey);
505
+ }
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Get comprehensive analytics performance metrics
511
+ */
512
+ getPerformanceMetrics(): {
513
+ current: AnalyticsMetrics | null | undefined;
514
+ average: Partial<AnalyticsMetrics> | null;
515
+ providerHealth: Map<string, ProviderHealthStatus>;
516
+ queueStatus: {
517
+ queueLength: number;
518
+ oldestEventAge: number;
519
+ memoryUsage: number;
520
+ isProcessing: boolean;
521
+ };
522
+ } {
523
+ const allMetrics = [...this.metrics.values()];
524
+ const current = allMetrics.length > 0 ? allMetrics[allMetrics.length - 1] : null;
525
+
526
+ const average =
527
+ allMetrics.length > 0
528
+ ? {
529
+ eventCount: allMetrics.reduce((sum, m) => sum + m.eventCount, 0) / allMetrics.length,
530
+ processingTime:
531
+ allMetrics.reduce((sum, m) => sum + m.processingTime, 0) / allMetrics.length,
532
+ memoryUsage: allMetrics.reduce((sum, m) => sum + m.memoryUsage, 0) / allMetrics.length,
533
+ throughputEventsPerSecond:
534
+ allMetrics.reduce((sum, m) => sum + m.throughputEventsPerSecond, 0) /
535
+ allMetrics.length,
536
+ averageLatency:
537
+ allMetrics.reduce((sum, m) => sum + m.averageLatency, 0) / allMetrics.length,
538
+ }
539
+ : null;
540
+
541
+ return {
542
+ current,
543
+ average,
544
+ providerHealth: new Map(this.providerHealthMap),
545
+ queueStatus: {
546
+ queueLength: this.eventQueue.length,
547
+ oldestEventAge: this.getOldestEventAge(),
548
+ memoryUsage: process.memoryUsage().heapUsed,
549
+ isProcessing: this.isProcessing,
550
+ },
551
+ };
552
+ }
553
+
554
+ // Helper methods
555
+ private insertWithPriority(payload: EmitterPayload, priority: number): void {
556
+ const index = this.eventQueue.findIndex(event => {
557
+ const eventMeta = this.eventMetadata.get(event);
558
+ return eventMeta && (eventMeta.priority ?? 0) < priority;
559
+ });
560
+
561
+ if (index === -1) {
562
+ this.eventQueue.push(payload);
563
+ } else {
564
+ this.eventQueue.splice(index, 0, payload);
565
+ }
566
+ }
567
+
568
+ private getOldestEventAge(): number {
569
+ if (this.eventQueue.length === 0) return 0;
570
+
571
+ const oldestEvent = this.eventQueue[0];
572
+ if (!oldestEvent) return 0;
573
+
574
+ const metadata = this.eventMetadata.get(oldestEvent);
575
+
576
+ if (!metadata) return 0;
577
+
578
+ return Number(process.hrtime.bigint() - metadata.enqueuedAt) / 1_000_000; // ms
579
+ }
580
+
581
+ private isMemoryThresholdExceeded(): boolean {
582
+ return process.memoryUsage().heapUsed > this.config.maxMemoryUsage;
583
+ }
584
+
585
+ private scheduleBatchTimeout(): void {
586
+ if (this.batchTimer) return;
587
+
588
+ this.batchTimer = setTimeout(() => {
589
+ if (this.batchTimer) {
590
+ clearTimeout(this.batchTimer);
591
+ this.batchTimer = undefined;
592
+ }
593
+ void this.processBatch();
594
+ }, this.config.batchTimeoutMs);
595
+ }
596
+
597
+ private generateEventId(): string {
598
+ return `evt_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
599
+ }
600
+
601
+ private generateBatchId(): string {
602
+ return `batch_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
603
+ }
604
+
605
+ private getRetryCount(event: EmitterPayload): number {
606
+ return this.eventMetadata.get(event)?.retryCount ?? 0;
607
+ }
608
+
609
+ private async recordProviderError(providerId: string, _error: Error): Promise<void> {
610
+ const health = this.providerHealthMap.get(providerId);
611
+ if (health) {
612
+ const consecutiveFailures = health.consecutiveFailures + 1;
613
+ this.providerHealthMap.set(providerId, {
614
+ providerId: health.providerId,
615
+ lastCheck: health.lastCheck,
616
+ responseTime: health.responseTime,
617
+ consecutiveFailures,
618
+ errorRate: Math.min(1, health.errorRate + 0.1),
619
+ successRate: Math.max(0, health.successRate - 0.1),
620
+ isHealthy: consecutiveFailures < 3, // Become unhealthy after 3 failures
621
+ estimatedRecoveryTime: health.estimatedRecoveryTime,
622
+ });
623
+ }
624
+ }
625
+
626
+ private resolveBatchPromises(results: EventProcessingResult[]): void {
627
+ // Group results by event/batch and resolve promises
628
+ const batchGroups = new Map<string, EventProcessingResult[]>();
629
+
630
+ for (const result of results) {
631
+ const batchIdParts = result.eventId?.split('_');
632
+ const batchId = batchIdParts?.[0];
633
+ if (!batchId) continue;
634
+
635
+ if (!batchGroups.has(batchId)) {
636
+ batchGroups.set(batchId, []);
637
+ }
638
+ const group = batchGroups.get(batchId);
639
+ if (group) {
640
+ group.push(result);
641
+ }
642
+ }
643
+
644
+ for (const [batchId, batchResults] of batchGroups) {
645
+ const batchPromise = this.batchPromises.get(batchId);
646
+ if (batchPromise) {
647
+ batchPromise.resolve(batchResults);
648
+ this.batchPromises.delete(batchId);
649
+ }
650
+ }
651
+ }
652
+
653
+ private rejectBatchPromises(error: Error): void {
654
+ for (const [, batchPromise] of this.batchPromises) {
655
+ batchPromise.reject(error);
656
+ }
657
+ this.batchPromises.clear();
658
+ }
659
+
660
+ // Commented out as currently unused but kept for potential future use
661
+ // private _createErrorResults(_batchId: string, error: Error): EventProcessingResult[] {
662
+ // return this.eventQueue.map((event, index) => ({
663
+ // success: false,
664
+ // processedAt: process.hrtime.bigint(),
665
+ // processingTime: 0,
666
+ // eventId: `${_batchId}_${index}`,
667
+ // providerId: 'unknown',
668
+ // retryAttempt: this.getRetryCount(event),
669
+ // error,
670
+ // metrics: {
671
+ // memoryUsed: 0,
672
+ // serializationTime: 0,
673
+ // },
674
+ // }));
675
+ // }
676
+
677
+ private removeBatchPromise(eventId: string): void {
678
+ this.batchPromises.delete(eventId);
679
+ }
680
+
681
+ private cleanupBatch(batchId: string): void {
682
+ // Cleanup any resources associated with the batch
683
+ this.metrics.delete(batchId);
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Enhanced analytics manager with Node 22+ optimizations
689
+ */
690
+ export class Node22AnalyticsManager {
691
+ private readonly eventBatcher: AdvancedEventBatcher;
692
+ private readonly performanceMonitor: Map<string, bigint> = new Map();
693
+
694
+ // Node 22+ WeakSet for tracking processed events (memory efficient)
695
+ private readonly processedEvents = new WeakSet<EmitterPayload>();
696
+
697
+ constructor(
698
+ config: AnalyticsConfig,
699
+ providers: Map<string, AnalyticsProvider>,
700
+ batchConfig: EventBatchConfig,
701
+ ) {
702
+ void config; // Intentionally unused for now
703
+ this.eventBatcher = new AdvancedEventBatcher(batchConfig, providers);
704
+ }
705
+
706
+ /**
707
+ * Enhanced event emission with Node 22+ features
708
+ */
709
+ async emit(
710
+ payload: EmitterPayload,
711
+ options: TrackingOptions & {
712
+ priority?: number;
713
+ timeout?: number;
714
+ retries?: number;
715
+ } = {},
716
+ ): Promise<EventProcessingResult[]> {
717
+ // Prevent duplicate processing using WeakSet (Node 22+)
718
+ if (this.processedEvents.has(payload)) {
719
+ throw new Error('Event has already been processed');
720
+ }
721
+
722
+ const startTime = process.hrtime.bigint();
723
+ this.performanceMonitor.set('emit_start', startTime);
724
+
725
+ try {
726
+ // Create abort signal with timeout
727
+ const abortSignal = options.timeout ? AbortSignal.timeout(options.timeout) : undefined;
728
+
729
+ // Use advanced batcher for processing
730
+ const results = await this.eventBatcher.enqueueEvent(payload, options, abortSignal);
731
+
732
+ // Mark as processed using WeakSet (Node 22+)
733
+ this.processedEvents.add(payload);
734
+
735
+ const endTime = process.hrtime.bigint();
736
+ this.performanceMonitor.set('emit_end', endTime);
737
+
738
+ return results;
739
+ } catch (error) {
740
+ const endTime = process.hrtime.bigint();
741
+ this.performanceMonitor.set('emit_error', endTime);
742
+ throw error;
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Get real-time performance metrics using Node 22+ features
748
+ */
749
+ getAnalyticsMetrics(): ReturnType<AdvancedEventBatcher['getPerformanceMetrics']> & {
750
+ emissionMetrics: {
751
+ averageEmissionTime: number; // nanoseconds
752
+ totalEmissions: number;
753
+ failureRate: number;
754
+ };
755
+ } {
756
+ const batcherMetrics = this.eventBatcher.getPerformanceMetrics();
757
+
758
+ // Calculate emission-specific metrics
759
+ const emissionTimes: number[] = [];
760
+ const totalEmissions = 0;
761
+ const failures = 0;
762
+
763
+ // This would be implemented with actual tracking
764
+ const averageEmissionTime =
765
+ emissionTimes.length > 0
766
+ ? emissionTimes.reduce((sum, time) => sum + time, 0) / emissionTimes.length
767
+ : 0;
768
+
769
+ return {
770
+ ...batcherMetrics,
771
+ emissionMetrics: {
772
+ averageEmissionTime,
773
+ totalEmissions,
774
+ failureRate: totalEmissions > 0 ? failures / totalEmissions : 0,
775
+ },
776
+ };
777
+ }
778
+
779
+ /**
780
+ * Health check for all analytics systems
781
+ */
782
+ async healthCheck(timeout: number = 5000): Promise<{
783
+ overall: 'healthy' | 'degraded' | 'unhealthy';
784
+ providers: Record<string, 'healthy' | 'unhealthy'>;
785
+ metrics: any;
786
+ }> {
787
+ AbortSignal.timeout(timeout);
788
+ const metrics = this.getAnalyticsMetrics();
789
+
790
+ // Determine overall health
791
+ const healthyProviders = [...metrics.providerHealth.values()].filter(
792
+ health => health.isHealthy,
793
+ ).length;
794
+ const totalProviders = metrics.providerHealth.size;
795
+
796
+ let overall: 'healthy' | 'degraded' | 'unhealthy';
797
+ if (healthyProviders === totalProviders) {
798
+ overall = 'healthy';
799
+ } else if (healthyProviders > 0) {
800
+ overall = 'degraded';
801
+ } else {
802
+ overall = 'unhealthy';
803
+ }
804
+
805
+ const providers: Record<string, 'healthy' | 'unhealthy'> = {};
806
+ for (const [providerId, health] of metrics.providerHealth) {
807
+ providers[providerId] = health.isHealthy ? 'healthy' : 'unhealthy';
808
+ }
809
+
810
+ return {
811
+ overall,
812
+ providers,
813
+ metrics,
814
+ };
815
+ }
816
+ }
817
+
818
+ /**
819
+ * Factory function to create Node 22+ enhanced analytics manager
820
+ */
821
+ export function createNode22AnalyticsManager(
822
+ config: AnalyticsConfig,
823
+ providers: Map<string, AnalyticsProvider>,
824
+ batchConfig: Partial<EventBatchConfig> = {},
825
+ ): Node22AnalyticsManager {
826
+ const defaultBatchConfig: EventBatchConfig = {
827
+ maxBatchSize: 50,
828
+ batchTimeoutMs: 5000,
829
+ compressionEnabled: true,
830
+ retryAttempts: 3,
831
+ retryDelayMs: 1000,
832
+ enablePrioritization: true,
833
+ maxMemoryUsage: 50 * 1024 * 1024, // 50MB
834
+ strategy: 'priority',
835
+ ...batchConfig,
836
+ };
837
+
838
+ return new Node22AnalyticsManager(config, providers, defaultBatchConfig);
839
+ }
840
+
841
+ /**
842
+ * Default export: Enhanced analytics utilities
843
+ */
844
+ export const node22Analytics = {
845
+ AdvancedEventBatcher,
846
+ Node22AnalyticsManager,
847
+ createNode22AnalyticsManager,
848
+ } as const;