@od-oneapp/analytics 2026.2.1701 → 2026.2.2001-canary.1
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/CHANGELOG.md +1 -1
- package/{ai-YMnynb-t.mjs → ai-BSaTnqoc.mjs} +2 -2
- package/{ai-YMnynb-t.mjs.map → ai-BSaTnqoc.mjs.map} +1 -1
- package/{client-D339NFJS.mjs → client-B0v807xN.mjs} +1 -1
- package/{client-D339NFJS.mjs.map → client-B0v807xN.mjs.map} +1 -1
- package/{client-CcFTauAh.mjs → client-BrLI6DF9.mjs} +1 -1
- package/{client-CcFTauAh.mjs.map → client-BrLI6DF9.mjs.map} +1 -1
- package/{client-CeOLjbac.mjs → client-ClA-Qeks.mjs} +21 -6
- package/client-ClA-Qeks.mjs.map +1 -0
- package/client-next.d.mts +7 -7
- package/client-next.d.mts.map +1 -1
- package/client-next.mjs +1616 -31
- package/client-next.mjs.map +1 -1
- package/{client-CTzJVFU5.mjs → client-uX7stpb0.mjs} +2 -2
- package/{client-CTzJVFU5.mjs.map → client-uX7stpb0.mjs.map} +1 -1
- package/client.d.mts +9 -9
- package/client.mjs +8 -116
- package/client.mjs.map +1 -1
- package/{config-DPS6bSYo.d.mts → config-BA1zbRTh.d.mts} +2 -2
- package/{config-DPS6bSYo.d.mts.map → config-BA1zbRTh.d.mts.map} +1 -1
- package/{config-P6P5adJg.mjs → config-DKN8G0TI.mjs} +1 -1
- package/{config-P6P5adJg.mjs.map → config-DKN8G0TI.mjs.map} +1 -1
- package/{console-8bND3mMU.mjs → console-CDP0gY0K.mjs} +2 -2
- package/console-CDP0gY0K.mjs.map +1 -0
- package/core-C3oFD9Vr.mjs +95 -0
- package/core-C3oFD9Vr.mjs.map +1 -0
- package/{ecommerce-Cgu4wlux.mjs → ecommerce-DAo98u3I.mjs} +2 -2
- package/{ecommerce-Cgu4wlux.mjs.map → ecommerce-DAo98u3I.mjs.map} +1 -1
- package/{emitters-DldkVSPp.d.mts → emitters-B2gDJtjd.d.mts} +2 -2
- package/{emitters-DldkVSPp.d.mts.map → emitters-B2gDJtjd.d.mts.map} +1 -1
- package/{emitters-6-nKo8i-.mjs → emitters-RheHbeX2.mjs} +1 -1
- package/{emitters-6-nKo8i-.mjs.map → emitters-RheHbeX2.mjs.map} +1 -1
- package/{index-jPzXRn52.d.mts → index-Bq051UsT.d.mts} +3 -3
- package/{index-jPzXRn52.d.mts.map → index-Bq051UsT.d.mts.map} +1 -1
- package/{index-BfNWgfa5.d.mts → index-CuDXrdLY.d.mts} +14 -2
- package/{index-BfNWgfa5.d.mts.map → index-CuDXrdLY.d.mts.map} +1 -1
- package/{index-BkIWe--N.d.mts → index-uQHwlV0P.d.mts} +2 -2
- package/{index-BkIWe--N.d.mts.map → index-uQHwlV0P.d.mts.map} +1 -1
- package/logs-8LsA--uG.mjs +112 -0
- package/logs-8LsA--uG.mjs.map +1 -0
- package/{manager-DvRRjza6.d.mts → manager-Dh9loalA.d.mts} +3 -2
- package/manager-Dh9loalA.d.mts.map +1 -0
- package/module-D5xR4CAy.mjs +5808 -0
- package/module-D5xR4CAy.mjs.map +1 -0
- package/package.json +21 -27
- package/{posthog-bootstrap-DWxFrxlt.d.mts → posthog-bootstrap-D7Ot--Qc.d.mts} +3 -3
- package/{posthog-bootstrap-DWxFrxlt.d.mts.map → posthog-bootstrap-D7Ot--Qc.d.mts.map} +1 -1
- package/{posthog-bootstrap-CYfIy_WS.mjs → posthog-bootstrap-DkDBbvMJ.mjs} +81 -46
- package/posthog-bootstrap-DkDBbvMJ.mjs.map +1 -0
- package/providers-http-client.d.mts +1 -1
- package/providers-http-client.d.mts.map +1 -1
- package/providers-http-client.mjs +35 -7
- package/providers-http-client.mjs.map +1 -1
- package/providers-http-server.d.mts +1 -1
- package/providers-http-server.d.mts.map +1 -1
- package/providers-http-server.mjs +19 -3
- package/providers-http-server.mjs.map +1 -1
- package/providers-http.d.mts +9 -1
- package/providers-http.d.mts.map +1 -1
- package/server-edge.d.mts +3 -3
- package/server-edge.mjs +4 -4
- package/server-edge.mjs.map +1 -1
- package/server-next.d.mts +9 -9
- package/server-next.mjs +5 -5
- package/server.d.mts +9 -9
- package/server.mjs +5 -5
- package/{service-Duqnlppl.mjs → service-tn0JFfVH.mjs} +49 -116
- package/service-tn0JFfVH.mjs.map +1 -0
- package/shared.d.mts +4 -4
- package/shared.mjs +3 -3
- package/{types-BxBnNQ0V.d.mts → types-BbXOa_UL.d.mts} +1 -1
- package/{types-BxBnNQ0V.d.mts.map → types-BbXOa_UL.d.mts.map} +1 -1
- package/{types-CBvxUEaF.d.mts → types-DC5uYgdR.d.mts} +1 -1
- package/{types-CBvxUEaF.d.mts.map → types-DC5uYgdR.d.mts.map} +1 -1
- package/types.d.mts +3 -3
- package/{vercel-types-lwakUfoI.d.mts → vercel-types-tUdlBxJ-.d.mts} +1 -1
- package/{vercel-types-lwakUfoI.d.mts.map → vercel-types-tUdlBxJ-.d.mts.map} +1 -1
- package/client-CeOLjbac.mjs.map +0 -1
- package/console-8bND3mMU.mjs.map +0 -1
- package/manager-DvRRjza6.d.mts.map +0 -1
- package/posthog-bootstrap-CYfIy_WS.mjs.map +0 -1
- package/service-Duqnlppl.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"posthog-bootstrap-CYfIy_WS.mjs","names":[],"sources":["../src/shared/utils/rate-limit.ts","../src/shared/utils/security.ts","../src/shared/utils/manager.ts","../src/shared/emitters/helpers.ts","../src/shared/utils/posthog-bootstrap.ts"],"sourcesContent":["/**\n * @fileoverview Rate limiting utilities for analytics calls\n * Rate limiting utilities for analytics calls\n *\n * Prevents overwhelming provider APIs, hitting rate limits, and excessive costs.\n * Uses token bucket algorithm for smooth rate limiting with bursts.\n *\n * @module rate-limit\n */\n\nimport { logWarn } from '@repo/shared/logger';\n\n/**\n * Rate limiter configuration\n */\nexport interface RateLimiterConfig {\n /** Maximum number of calls per window */\n maxCalls: number;\n /** Window size in milliseconds */\n windowMs: number;\n /** Maximum number of concurrent calls */\n maxConcurrent?: number;\n /** Whether to queue excess calls or drop them */\n queueExcess?: boolean;\n /** Maximum queue size */\n maxQueueSize?: number;\n}\n\n/**\n * Rate limiter for analytics calls\n *\n * Implements token bucket algorithm with optional call queuing.\n *\n * @example\n * ```typescript\n * const limiter = new RateLimiter({\n * maxCalls: 100,\n * windowMs: 1000, // 100 calls per second\n * maxConcurrent: 10,\n * });\n *\n * // Check if call is allowed\n * if (await limiter.tryAcquire()) {\n * await analytics.track('Event');\n * limiter.release();\n * }\n *\n * // Or use wrapper\n * await limiter.execute(() => analytics.track('Event'));\n * ```\n */\nexport class RateLimiter {\n private callCount = 0;\n private concurrentCount = 0;\n private windowStart = Date.now();\n private queue: Array<() => void> = [];\n private readonly config: Required<RateLimiterConfig>;\n\n constructor(config: RateLimiterConfig) {\n this.config = {\n maxCalls: config.maxCalls,\n windowMs: config.windowMs,\n maxConcurrent: config.maxConcurrent ?? Infinity,\n queueExcess: config.queueExcess ?? false,\n maxQueueSize: config.maxQueueSize ?? 1000,\n };\n }\n\n /**\n * Try to acquire a rate limit token\n *\n * @returns Promise resolving to whether the call is allowed\n */\n async tryAcquire(): Promise<boolean> {\n // Reset window if needed\n const now = Date.now();\n if (now - this.windowStart >= this.config.windowMs) {\n this.callCount = 0;\n this.windowStart = now;\n }\n\n // Check rate limit\n if (this.callCount >= this.config.maxCalls) {\n if (this.config.queueExcess && this.queue.length < this.config.maxQueueSize) {\n // Queue the call - will re-check rate limit when dequeued\n return new Promise<boolean>(resolve => {\n this.queue.push(() => {\n // Re-check rate limit when dequeued to prevent bypassing\n const now = Date.now();\n if (now - this.windowStart >= this.config.windowMs) {\n this.callCount = 0;\n this.windowStart = now;\n }\n if (this.callCount < this.config.maxCalls) {\n this.callCount++;\n resolve(true);\n } else {\n resolve(false);\n }\n });\n });\n }\n\n if (process.env.NODE_ENV === 'development') {\n logWarn('Rate limit exceeded', {\n maxCalls: this.config.maxCalls,\n windowMs: this.config.windowMs,\n currentCount: this.callCount,\n });\n }\n\n return false;\n }\n\n // Check concurrency limit\n if (this.concurrentCount >= this.config.maxConcurrent) {\n if (this.config.queueExcess && this.queue.length < this.config.maxQueueSize) {\n // Queue the call\n return new Promise<boolean>(resolve => {\n this.queue.push(() => resolve(true));\n });\n }\n\n return false;\n }\n\n this.callCount++;\n this.concurrentCount++;\n return true;\n }\n\n /**\n * Release a rate limit token\n */\n release(): void {\n this.concurrentCount = Math.max(0, this.concurrentCount - 1);\n\n // Process queue - only process if concurrency allows and rate limit allows\n if (this.queue.length > 0 && this.concurrentCount < this.config.maxConcurrent) {\n // Reset rate limit window if needed before processing queue\n const now = Date.now();\n if (now - this.windowStart >= this.config.windowMs) {\n this.callCount = 0;\n this.windowStart = now;\n }\n\n // Only process if rate limit allows\n if (this.callCount < this.config.maxCalls) {\n const next = this.queue.shift();\n if (next) {\n this.concurrentCount++;\n next();\n }\n }\n }\n }\n\n /**\n * Execute a function with rate limiting\n *\n * @param fn - Function to execute\n * @returns Promise resolving to function result\n */\n async execute<T>(fn: () => Promise<T>): Promise<T | null> {\n const allowed = await this.tryAcquire();\n\n if (!allowed) {\n return null;\n }\n\n try {\n return await fn();\n } finally {\n this.release();\n }\n }\n\n /**\n * Get current rate limiter stats\n */\n getStats(): {\n callCount: number;\n concurrentCount: number;\n queueSize: number;\n remainingCalls: number;\n windowStart: number;\n } {\n return {\n callCount: this.callCount,\n concurrentCount: this.concurrentCount,\n queueSize: this.queue.length,\n remainingCalls: Math.max(0, this.config.maxCalls - this.callCount),\n windowStart: this.windowStart,\n };\n }\n\n /**\n * Reset the rate limiter\n */\n reset(): void {\n this.callCount = 0;\n this.concurrentCount = 0;\n this.windowStart = Date.now();\n this.queue = [];\n }\n}\n\n/**\n * Simple in-memory rate limiter using Map\n *\n * Useful for per-provider or per-user rate limiting.\n */\nexport class MapRateLimiter {\n private limiters = new Map<string, RateLimiter>();\n private readonly config: RateLimiterConfig;\n\n constructor(config: RateLimiterConfig) {\n this.config = config;\n }\n\n /**\n * Get or create a rate limiter for a key\n */\n private getLimiter(key: string): RateLimiter {\n let limiter = this.limiters.get(key);\n\n if (!limiter) {\n limiter = new RateLimiter(this.config);\n this.limiters.set(key, limiter);\n }\n\n return limiter;\n }\n\n /**\n * Try to acquire a rate limit token for a key\n */\n async tryAcquire(key: string): Promise<boolean> {\n return this.getLimiter(key).tryAcquire();\n }\n\n /**\n * Release a rate limit token for a key\n */\n release(key: string): void {\n this.getLimiter(key).release();\n }\n\n /**\n * Execute a function with rate limiting for a key\n */\n async execute<T>(key: string, fn: () => Promise<T>): Promise<T | null> {\n return this.getLimiter(key).execute(fn);\n }\n\n /**\n * Get stats for a key\n */\n getStats(key: string): ReturnType<RateLimiter['getStats']> | null {\n const limiter = this.limiters.get(key);\n return limiter ? limiter.getStats() : null;\n }\n\n /**\n * Reset rate limiter for a key\n */\n reset(key: string): void {\n const limiter = this.limiters.get(key);\n if (limiter) {\n limiter.reset();\n }\n }\n\n /**\n * Clean up old limiters (call periodically)\n */\n cleanup(maxAge: number = 60000): void {\n const now = Date.now();\n\n for (const [key, limiter] of this.limiters.entries()) {\n const stats = limiter.getStats();\n\n // Remove if inactive for maxAge\n if (now - stats.windowStart > maxAge && stats.callCount === 0 && stats.queueSize === 0) {\n this.limiters.delete(key);\n }\n }\n }\n}\n","/**\n * @fileoverview Security and sanitization utilities for analytics data\n *\n * This module provides comprehensive security utilities for analytics data:\n *\n * - **PII Detection**: Detects personally identifiable information (emails, phones, SSNs, credit cards, IPs)\n * - **PII Redaction**: Redacts PII from strings before sending to analytics\n * - **XSS Protection**: Strips HTML and script tags to prevent XSS attacks\n * - **Property Validation**: Validates property keys to prevent prototype pollution\n * - **Payload Sanitization**: Comprehensive sanitization with size limits and depth checks\n * - **Size Limits**: Enforces maximum property and payload sizes\n *\n * **Security Features**:\n * - Maximum property value size: 100KB\n * - Maximum payload size: 1MB\n * - Maximum nesting depth: 10 levels\n * - Dangerous key filtering (prevents prototype pollution)\n * - HTML/script tag removal\n *\n * @module @od-oneapp/analytics/shared/utils/security\n */\n\nimport { logWarn } from '@repo/shared/logger';\n\n/**\n * Maximum allowed property value size (100KB).\n *\n * Prevents sending excessively large property values to analytics providers.\n *\n * @internal\n */\nconst MAX_PROPERTY_VALUE_SIZE = 100 * 1024;\n\n/**\n * Maximum allowed total payload size (1MB).\n *\n * Prevents sending excessively large payloads to analytics providers.\n *\n * @internal\n */\nconst MAX_PAYLOAD_SIZE = 1024 * 1024;\n\n/**\n * Maximum allowed object nesting depth.\n *\n * Prevents deeply nested objects that could cause performance issues or stack overflows.\n *\n * @internal\n */\nconst MAX_DEPTH = 10;\n\n/**\n * PII patterns for detection and filtering\n * Note: These regexes are intentionally complex for accurate PII detection.\n * They are used only on bounded, sanitized input with size limits.\n */\n/* eslint-disable security/detect-unsafe-regex */\nconst PII_PATTERNS = {\n email: /\\b[\\w%+.-]+@[\\d.A-Za-z-]+\\.[A-Za-z|]{2,}\\b/g,\n phone: /\\b(?:\\+?1[.-]?)?\\(?(\\d{3})\\)?[.-]?(\\d{3})[.-]?(\\d{4})\\b/g,\n ssn: /\\b\\d{3}-?\\d{2}-?\\d{4}\\b/g,\n creditCard: /\\b(?:\\d{4}[ -]?){3}\\d{4}\\b/g,\n ipv4: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n ipv6: /\\b(?:[\\da-f]{1,4}:){7}[\\da-f]{1,4}\\b/gi,\n};\n/* eslint-enable security/detect-unsafe-regex */\n\n/**\n * Dangerous property keys that should never be allowed\n */\nconst DANGEROUS_KEYS = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n]);\n\n/**\n * HTML/Script patterns for XSS protection\n * Note: These regexes are intentionally complex for accurate XSS detection.\n * They are used only on bounded, sanitized input with size limits.\n */\n/* eslint-disable security/detect-unsafe-regex */\nconst XSS_PATTERNS = {\n scriptTag: /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n onEvent: /\\bon\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi,\n javascript: /javascript:/gi,\n dataUri: /data:text\\/html/gi,\n};\n/* eslint-enable security/detect-unsafe-regex */\n\n/**\n * Sanitization options.\n *\n * Configures how sanitization should be performed, including size limits,\n * depth limits, and what content to strip.\n */\nexport interface SanitizationOptions {\n /** Maximum object nesting depth */\n maxDepth?: number;\n /** Maximum size per property value in bytes */\n maxPropertySize?: number;\n /** Maximum total payload size in bytes */\n maxPayloadSize?: number;\n /** Pattern for allowed property keys */\n allowedKeyPattern?: RegExp;\n /** Whether to strip PII */\n stripPII?: boolean;\n /** Whether to strip HTML/scripts */\n stripHTML?: boolean;\n /** Whether to allow prototype pollution keys */\n allowDangerousKeys?: boolean;\n}\n\n/**\n * Sanitization result.\n *\n * Contains the sanitized data, whether modifications were made, and any warnings\n * generated during sanitization.\n */\nexport interface SanitizationResult<T = unknown> {\n /** Sanitized data */\n data: T;\n /** Whether sanitization made changes */\n modified: boolean;\n /** Warnings generated during sanitization */\n warnings: string[];\n}\n\n/**\n * Check if a value contains PII (Personally Identifiable Information).\n *\n * Detects common PII patterns including:\n * - Email addresses\n * - Phone numbers\n * - Social Security Numbers\n * - Credit card numbers\n * - IPv4 and IPv6 addresses\n *\n * @param {string} value - String value to check for PII\n * @returns {boolean} `true` if PII is detected, `false` otherwise\n *\n * @example\n * ```typescript\n * if (containsPII(userInput)) {\n * const sanitized = redactPII(userInput);\n * // Use sanitized value\n * }\n * ```\n */\nexport function containsPII(value: string): boolean {\n return Object.values(PII_PATTERNS).some(pattern => pattern.test(value));\n}\n\n/**\n * Redact PII from a string.\n *\n * Replaces detected PII patterns with `[REDACTED_TYPE]` placeholders.\n * Useful for logging or analytics where PII should not be stored.\n *\n * @param {string} value - String value to redact PII from\n * @returns {string} String with PII redacted\n *\n * @example\n * ```typescript\n * const sanitized = redactPII('Contact john@example.com at 555-1234');\n * // Returns: 'Contact [REDACTED_EMAIL] at [REDACTED_PHONE]'\n * ```\n */\nexport function redactPII(value: string): string {\n let redacted = value;\n\n for (const [type, pattern] of Object.entries(PII_PATTERNS)) {\n redacted = redacted.replace(pattern, `[REDACTED_${type.toUpperCase()}]`);\n }\n\n return redacted;\n}\n\n/**\n * Strip HTML and script tags from a string.\n *\n * Removes HTML tags, script tags, event handlers, and other potentially dangerous\n * content to prevent XSS attacks. Also removes `data:text/html` URIs.\n *\n * @param {string} value - String value to strip HTML from\n * @returns {string} String with HTML and scripts removed\n *\n * @example\n * ```typescript\n * const sanitized = stripHTML('<script>alert(\"xss\")</script>Hello');\n * // Returns: 'Hello'\n * ```\n */\nexport function stripHTML(value: string): string {\n let stripped = value;\n\n for (const pattern of Object.values(XSS_PATTERNS)) {\n stripped = stripped.replace(pattern, '');\n }\n\n // Additional cleanup for remaining tags\n stripped = stripped.replaceAll(/<[^>]*>/g, '');\n\n return stripped;\n}\n\n/**\n * Validate that a property key is safe.\n *\n * Checks for dangerous keys that could cause prototype pollution or other\n * security issues. Also validates against allowed key patterns if provided.\n *\n * @param {string} key - Property key to validate\n * @param {SanitizationOptions} [options] - Sanitization options\n * @returns {{ valid: boolean; reason?: string }} Validation result with reason if invalid\n *\n * @example\n * ```typescript\n * const result = isValidPropertyKey('__proto__');\n * if (!result.valid) {\n * console.warn('Unsafe key:', result.reason);\n * }\n * ```\n */\nexport function isValidPropertyKey(\n key: string,\n options: SanitizationOptions = {},\n): { valid: boolean; reason?: string } {\n // Check for dangerous keys\n if (!options.allowDangerousKeys && DANGEROUS_KEYS.has(key)) {\n return { valid: false, reason: 'Dangerous key that could cause prototype pollution' };\n }\n\n // Check against allowed pattern\n if (options.allowedKeyPattern && !options.allowedKeyPattern.test(key)) {\n return { valid: false, reason: 'Key does not match allowed pattern' };\n }\n\n // Check for special characters that could cause issues\n if (/[<>[\\\\\\]{}]/.test(key)) {\n return { valid: false, reason: 'Key contains potentially dangerous characters' };\n }\n\n return { valid: true };\n}\n\n/**\n * Get the size of a value in bytes\n */\nfunction getValueSize(value: unknown): number {\n if (typeof value === 'string') {\n return new Blob([value]).size;\n }\n\n try {\n return new Blob([JSON.stringify(value)]).size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Sanitize a single property value\n */\nfunction sanitizeValue(\n value: unknown,\n options: SanitizationOptions,\n warnings: string[],\n): { value: unknown; modified: boolean } {\n if (value === null || value === undefined) {\n return { value, modified: false };\n }\n\n let modified = false;\n\n // Handle strings\n if (typeof value === 'string') {\n let sanitized = value;\n\n // Check size\n const size = getValueSize(value);\n const maxSize = options.maxPropertySize ?? MAX_PROPERTY_VALUE_SIZE;\n\n if (size > maxSize) {\n sanitized = value.slice(0, Math.max(0, maxSize));\n warnings.push(`String value truncated from ${size} to ${maxSize} bytes`);\n modified = true;\n }\n\n // Strip PII if requested\n if (options.stripPII && containsPII(sanitized)) {\n sanitized = redactPII(sanitized);\n warnings.push('PII detected and redacted from value');\n modified = true;\n }\n\n // Strip HTML if requested\n if (options.stripHTML) {\n const stripped = stripHTML(sanitized);\n if (stripped !== sanitized) {\n sanitized = stripped;\n warnings.push('HTML/scripts stripped from value');\n modified = true;\n }\n }\n\n return { value: sanitized, modified };\n }\n\n // Handle numbers, booleans, etc.\n if (typeof value !== 'object') {\n return { value, modified: false };\n }\n\n // Arrays and objects handled by sanitizeProperties\n return { value, modified: false };\n}\n\n/**\n * Sanitize analytics properties recursively.\n *\n * Comprehensive sanitization of analytics properties including:\n * - Size limit enforcement\n * - Depth limit enforcement\n * - PII detection and redaction\n * - HTML/script tag removal\n * - Dangerous key filtering\n * - Circular reference detection\n *\n * @param {T} properties - Properties to sanitize\n * @param {SanitizationOptions} [options] - Sanitization options\n * @returns {SanitizationResult<T>} Sanitization result with sanitized data and warnings\n *\n * @throws {Error} If properties contain circular references or exceed payload size limit\n *\n * @example\n * ```typescript\n * const result = sanitizeProperties({\n * user_email: 'test@example.com',\n * phone: '555-1234',\n * description: '<script>alert(\"xss\")</script>Hello',\n * }, {\n * stripPII: true,\n * stripHTML: true,\n * });\n *\n * console.log(result.data);\n * // {\n * // user_email: '[REDACTED_EMAIL]',\n * // phone: '[REDACTED_PHONE]',\n * // description: 'Hello'\n * // }\n * ```\n */\nexport function sanitizeProperties<T extends Record<string, unknown>>(\n properties: T,\n options: SanitizationOptions = {},\n): SanitizationResult<T> {\n const warnings: string[] = [];\n let modified = false;\n\n // Check for circular references\n try {\n JSON.stringify(properties);\n } catch {\n throw new Error('Properties contain circular references');\n }\n\n // Check total payload size\n const totalSize = getValueSize(properties);\n const maxPayloadSize = options.maxPayloadSize ?? MAX_PAYLOAD_SIZE;\n\n if (totalSize > maxPayloadSize) {\n throw new Error(`Payload size ${totalSize} bytes exceeds maximum ${maxPayloadSize} bytes`);\n }\n\n /**\n * Recursive sanitization helper\n */\n function sanitizeRecursive(\n obj: Record<string, unknown>,\n depth: number = 0,\n ): Record<string, unknown> {\n const maxDepth = options.maxDepth ?? MAX_DEPTH;\n\n if (depth > maxDepth) {\n warnings.push(`Maximum nesting depth ${maxDepth} exceeded, truncating`);\n modified = true;\n return {};\n }\n\n const sanitized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Validate key\n const keyValidation = isValidPropertyKey(key, options);\n if (!keyValidation.valid) {\n warnings.push(`Skipping dangerous key \"${key}\": ${keyValidation.reason}`);\n modified = true;\n continue;\n }\n\n // Handle null/undefined\n if (value === null || value === undefined) {\n sanitized[key] = value;\n continue;\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n sanitized[key] = value.map(item => {\n if (typeof item === 'object' && item !== null) {\n return sanitizeRecursive(item as Record<string, unknown>, depth + 1);\n }\n\n const result = sanitizeValue(item, options, warnings);\n if (result.modified) modified = true;\n return result.value;\n });\n continue;\n }\n\n // Handle objects\n if (typeof value === 'object') {\n sanitized[key] = sanitizeRecursive(value as Record<string, unknown>, depth + 1);\n continue;\n }\n\n // Handle primitives\n const result = sanitizeValue(value, options, warnings);\n if (result.modified) modified = true;\n sanitized[key] = result.value;\n }\n\n return sanitized;\n }\n\n const sanitized = sanitizeRecursive(properties) as T;\n\n // Log warnings in development\n if (warnings.length > 0 && process.env.NODE_ENV === 'development') {\n logWarn('Analytics properties sanitization warnings', { warnings });\n }\n\n return {\n data: sanitized,\n modified,\n warnings,\n };\n}\n\n/**\n * Validate event name.\n *\n * Checks that an event name is safe and follows best practices:\n * - Non-empty string\n * - Not exceeding 255 characters\n * - No XSS patterns\n *\n * @param {string} event - Event name to validate\n * @returns {{ valid: boolean; reason?: string }} Validation result with reason if invalid\n *\n * @example\n * ```typescript\n * const result = validateEventName('Button Clicked');\n * if (!result.valid) {\n * console.error('Invalid event name:', result.reason);\n * }\n * ```\n */\nexport function validateEventName(event: string): {\n valid: boolean;\n reason?: string;\n} {\n if (!event || typeof event !== 'string') {\n return { valid: false, reason: 'Event name must be a non-empty string' };\n }\n\n if (event.trim() === '') {\n return { valid: false, reason: 'Event name cannot be empty or whitespace only' };\n }\n\n if (event.length > 255) {\n return { valid: false, reason: 'Event name cannot exceed 255 characters' };\n }\n\n // Check for XSS patterns\n if (Object.values(XSS_PATTERNS).some(pattern => pattern.test(event))) {\n return { valid: false, reason: 'Event name contains potentially malicious content' };\n }\n\n return { valid: true };\n}\n\n/**\n * Safe property value types that can be tracked.\n *\n * Only includes primitive types that are safe to send to analytics providers.\n */\nexport type SafePropertyValue = string | number | boolean | null | undefined;\n\n/**\n * Safe properties object type.\n *\n * Properties object with only safe value types.\n */\nexport type SafeProperties = Record<string, SafePropertyValue | SafePropertyValue[]>;\n\n/**\n * Create a type-safe analytics properties object.\n *\n * Sanitizes raw properties and returns a type-safe properties object.\n * Automatically strips PII in production and HTML/scripts always.\n *\n * @param {Record<string, unknown>} properties - Raw properties to make safe\n * @returns {SafeProperties} Type-safe properties object\n *\n * @example\n * ```typescript\n * const safeProps = createSafeProperties({\n * userId: 'user-123',\n * email: 'user@example.com', // Will be redacted in production\n * html: '<script>alert(\"xss\")</script>' // Will be stripped\n * });\n * ```\n */\nexport function createSafeProperties(properties: Record<string, unknown>): SafeProperties {\n const result = sanitizeProperties(properties, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n if (result.warnings.length > 0 && process.env.NODE_ENV === 'development') {\n logWarn('Created safe properties with sanitization', {\n warnings: result.warnings,\n });\n }\n\n return result.data as SafeProperties;\n}\n","/**\n * @fileoverview Analytics Manager - Core orchestration for multi-provider analytics\n *\n * This module provides the core AnalyticsManager class that orchestrates multiple\n * analytics providers. It includes:\n *\n * - **Multi-Provider Support**: Manages multiple providers simultaneously\n * - **Event Deduplication**: LRU cache prevents duplicate events\n * - **Rate Limiting**: Per-provider rate limiting (100 calls/second default)\n * - **Batch Processing**: Concurrent batch processing with configurable concurrency\n * - **Performance Metrics**: Tracks provider performance and reliability\n * - **Error Handling**: Comprehensive error handling with graceful degradation\n *\n * **Node.js 22+ Features Used**:\n * - `Promise.withResolvers()`: External promise control for complex workflows\n * - `AbortSignal.timeout()`: Context-aware timeouts for provider operations\n * - High-resolution timing: Precise performance measurement (`process.hrtime.bigint()`)\n * - `Object.hasOwn()`: Safer property existence checks\n *\n * **Optimizations**:\n * - Spread operator instead of `structuredClone()` for better performance\n * - LRU cache for event deduplication (prevents memory leaks)\n * - Concurrent batch processing with configurable concurrency\n * - Comprehensive input sanitization and validation\n * - Per-provider rate limiting\n *\n * @module @od-oneapp/analytics/shared/utils/manager\n */\n\nimport { RateLimiter } from './rate-limit';\nimport { sanitizeProperties, validateEventName } from './security';\n\nimport type {\n EmitterAliasPayload,\n EmitterGroupPayload,\n EmitterIdentifyPayload,\n EmitterPagePayload,\n EmitterPayload,\n EmitterScreenPayload,\n EmitterTrackPayload,\n} from '../emitters/emitter-types';\nimport type {\n AnalyticsConfig,\n AnalyticsContext,\n AnalyticsProvider,\n EcommerceEventSpec,\n GroupTraits,\n PageProperties,\n Properties,\n PropertyObject,\n ProviderRegistry,\n TrackingOptions,\n UserTraits,\n} from '../types/types';\n\n/**\n * LRU Cache for event deduplication.\n *\n * Prevents memory leaks by limiting cache size. Uses least-recently-used eviction\n * policy to maintain bounded memory usage.\n *\n * @template K - Cache key type\n * @template V - Cache value type\n *\n * @internal\n */\nclass LRUCache<K, V> {\n private cache = new Map<K, V>();\n private readonly maxSize: number;\n\n constructor(maxSize: number = 1000) {\n this.maxSize = maxSize;\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value !== undefined) {\n // Move to end (most recently used)\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n set(key: K, value: V): void {\n // Remove if exists (to re-add at end)\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n // Add to end\n this.cache.set(key, value);\n\n // Evict oldest if over capacity\n if (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey !== undefined) {\n this.cache.delete(firstKey);\n }\n }\n }\n\n has(key: K): boolean {\n return this.cache.get(key) !== undefined;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n/**\n * Provider metrics for tracking performance and reliability.\n *\n * Tracks initialization time, call counts, error counts, and last usage time\n * for each provider to monitor performance and reliability.\n *\n * @internal\n */\ninterface ProviderMetrics {\n initTime: bigint;\n callCount: number;\n errorCount: number;\n lastUsed: bigint;\n}\n\n/**\n * Event metadata for deduplication and tracking.\n *\n * Stores metadata about events to prevent duplicate processing and track\n * event processing status.\n *\n * @internal\n */\ninterface EventMetadata {\n timestamp: bigint;\n processed: boolean;\n}\n\n/**\n * Analytics Manager - Main entry point for analytics tracking.\n *\n * Orchestrates multiple analytics providers, handles event deduplication,\n * rate limiting, batch processing, and error handling.\n *\n * **Key Features**:\n * - Multi-provider support (PostHog, Segment, Vercel, Console)\n * - Event deduplication via LRU cache\n * - Rate limiting (100 calls/second per provider)\n * - Batch processing with concurrency control\n * - Performance metrics tracking\n * - Graceful error handling\n *\n * **Usage**:\n * ```typescript\n * const manager = new AnalyticsManager(config, providerRegistry);\n * await manager.initialize();\n * await manager.emit(track('Event Name', { property: 'value' }));\n * ```\n */\nexport class AnalyticsManager {\n private providers = new Map<string, AnalyticsProvider>();\n private context: AnalyticsContext = {};\n private isInitialized = false;\n\n // Provider metrics tracking (provider name -> metrics)\n private readonly providerMetrics = new Map<string, ProviderMetrics>();\n\n // Event deduplication with LRU cache (prevents memory leaks)\n private readonly eventCache = new LRUCache<string, EventMetadata>(1000);\n\n // Rate limiting per provider (100 calls per second default)\n private readonly rateLimiter = new RateLimiter({\n maxCalls: 100,\n windowMs: 1000,\n maxConcurrent: 10,\n queueExcess: false,\n });\n\n constructor(\n private config: AnalyticsConfig,\n private availableProviders: ProviderRegistry,\n ) {}\n\n /**\n * Initialize all configured providers\n * Throws error if all providers fail to initialize\n */\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n const initPromises: Promise<void>[] = [];\n const initStartTime = process.hrtime.bigint();\n\n for (const [providerName, providerConfig] of Object.entries(this.config.providers)) {\n const providerFactory = this.availableProviders[providerName];\n\n if (providerFactory) {\n try {\n const provider = providerFactory(providerConfig);\n this.providers.set(providerName, provider);\n\n // Initialize provider with enhanced error handling and timeout (Node 22+)\n initPromises.push(\n (async () => {\n const providerInitStart: bigint = typeof process.hrtime?.bigint === 'function'\n ? process.hrtime.bigint()\n : BigInt(Date.now() * 1000000); // Fallback for non-Node environments\n\n try {\n // Use AbortSignal.timeout() for initialization timeout (Node 22+)\n const timeoutSignal = AbortSignal.timeout(10000); // 10 second timeout\n\n const initPromise = provider.initialize(providerConfig);\n\n // Race against timeout\n await Promise.race([\n initPromise,\n new Promise<void>((_resolve, reject) => {\n timeoutSignal.addEventListener('abort', () =>\n reject(new Error(`Provider ${providerName} initialization timed out`)),\n );\n }),\n ]);\n\n // Track successful initialization metrics\n const now: bigint = typeof process.hrtime?.bigint === 'function'\n ? process.hrtime.bigint()\n : BigInt(Date.now() * 1000000); // Fallback for non-Node environments\n this.providerMetrics.set(providerName, {\n initTime: now - providerInitStart,\n callCount: 0,\n errorCount: 0,\n lastUsed: now,\n });\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'initialize',\n });\n }\n // Remove failed provider to ensure it doesn't affect others\n this.providers.delete(providerName);\n throw error; // Re-throw to track failure\n }\n })(),\n );\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'create',\n });\n }\n }\n } else if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Provider ${providerName} not available in this environment`);\n }\n }\n\n // Wait for all providers to initialize with enhanced monitoring\n const results = await Promise.allSettled(initPromises);\n const initEndTime = process.hrtime.bigint();\n\n const successCount = results.filter(r => r.status === 'fulfilled').length;\n const failureCount = results.filter(r => r.status === 'rejected').length;\n\n // FIX #10: Throw error if all providers fail\n if (successCount === 0 && initPromises.length > 0) {\n throw new Error(\n `Analytics initialization failed: All ${initPromises.length} providers failed to initialize`,\n );\n }\n\n this.isInitialized = true;\n\n if (this.config.debug && this.config.onInfo) {\n const initTimeMs = Number(initEndTime - initStartTime) / 1_000_000;\n this.config.onInfo(\n `Analytics initialized in ${initTimeMs.toFixed(2)}ms with ${successCount} providers ` +\n `(${failureCount} failed): ${[...this.providers.keys()].join(', ')}`,\n );\n }\n }\n\n /**\n * Set global analytics context\n * FIX #13: Use spread operator instead of structuredClone for better performance\n */\n setContext(context: AnalyticsContext): void {\n // Use spread operator for better performance\n this.context = { ...this.context, ...context };\n\n // Update context on providers that support it with enhanced error handling\n for (const [providerName, provider] of this.providers) {\n if (provider.setContext) {\n try {\n // Use spread operator instead of structuredClone\n provider.setContext({ ...this.context });\n\n // Update metrics for successful context update\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'setContext',\n context: Object.keys(context).join(', '),\n });\n }\n }\n }\n }\n }\n\n /**\n * Get current analytics context\n * FIX #13: Use spread operator instead of structuredClone\n */\n getContext(): AnalyticsContext {\n return { ...this.context };\n }\n\n /**\n * Track an event\n * FIX #3: Replace any types with proper TypeScript types\n * FIX #14/#15: Integrate security utilities and rate limiting\n */\n async track(payload: EmitterTrackPayload): Promise<void>;\n async track(event: string, properties?: Properties, options?: TrackingOptions): Promise<void>;\n async track(\n eventOrPayload: string | EmitterTrackPayload,\n properties?: Properties,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof eventOrPayload === 'object') {\n const payload = eventOrPayload;\n return this.track(payload.event, payload.properties, {\n ...options,\n // Merge context from payload\n context: { ...this.context, ...payload.context },\n });\n }\n\n // Traditional track call\n const event = eventOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n event,\n method: 'track',\n });\n }\n return;\n }\n\n // FIX #14: Validate event name\n const eventValidation = validateEventName(event);\n if (!eventValidation.valid) {\n if (this.config.onError) {\n this.config.onError(new Error(`Invalid event name: ${eventValidation.reason}`), {\n provider: 'analytics',\n event,\n method: 'track',\n });\n }\n return;\n }\n\n // FIX #14: Sanitize properties\n const sanitized = sanitizeProperties(properties ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = this.getTargetProviders(options);\n const enhancedProperties = { ...this.context, ...sanitized.data };\n\n const promises = [...targetProviders.entries()].map(async ([name, provider]) => {\n // FIX #15: Apply rate limiting\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${name}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${name}`), {\n provider: name,\n event,\n method: 'track',\n });\n }\n return;\n }\n\n try {\n await provider.track(event, enhancedProperties as PropertyObject, this.context);\n\n // Update metrics\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n // Update error metrics\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.errorCount++;\n }\n\n // Error boundary: report error but don't let it affect other providers\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n event,\n method: 'track',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n });\n\n // Use allSettled to ensure all providers are called even if some fail\n await Promise.allSettled(promises);\n }\n\n /**\n * Identify a user\n * FIX #3: Replace any types with proper TypeScript types\n */\n async identify(payload: EmitterIdentifyPayload): Promise<void>;\n async identify(userId: string, traits?: UserTraits, options?: TrackingOptions): Promise<void>;\n async identify(\n userIdOrPayload: string | EmitterIdentifyPayload,\n traits?: UserTraits,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof userIdOrPayload === 'object') {\n const payload = userIdOrPayload;\n return this.identify(payload.userId, payload.traits, {\n ...options,\n // Merge context from payload\n context: { ...this.context, ...payload.context },\n });\n }\n\n // Traditional identify call\n const userId = userIdOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n method: 'identify',\n userId,\n });\n }\n return;\n }\n\n // Sanitize traits\n const sanitized = sanitizeProperties(traits ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n // Update context with user info\n this.setContext({ userId, ...sanitized.data });\n\n const targetProviders = this.getTargetProviders(options);\n const enhancedTraits = { ...sanitized.data } as UserTraits;\n\n const promises = [...targetProviders.entries()].map(async ([name, provider]) => {\n if (provider.identify) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${name}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${name}`), {\n provider: name,\n userId,\n method: 'identify',\n });\n }\n return;\n }\n\n try {\n // Pass context as TrackingOptions (provider.identify expects TrackingOptions as third parameter)\n await provider.identify(userId, enhancedTraits, {\n ...options,\n context: this.context,\n });\n\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n method: 'identify',\n userId,\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Track a page view\n * FIX #3: Replace any types with proper TypeScript types\n */\n async page(payload: EmitterPagePayload): Promise<void>;\n async page(name?: string, properties?: PageProperties, options?: TrackingOptions): Promise<void>;\n async page(\n nameOrPayload?: string | EmitterPagePayload,\n properties?: PageProperties,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof nameOrPayload === 'object') {\n const payload = nameOrPayload;\n return this.page(payload.name, payload.properties, {\n ...options,\n // Merge context from payload\n context: { ...this.context, ...payload.context },\n });\n }\n\n // Traditional page call\n const name = nameOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n name,\n method: 'page',\n });\n }\n return;\n }\n\n // Sanitize properties\n const sanitized = sanitizeProperties(properties ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = this.getTargetProviders(options);\n const enhancedProperties = { ...sanitized.data } as PageProperties;\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.page) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n name,\n method: 'page',\n });\n }\n return;\n }\n\n try {\n // Pass context as TrackingOptions (provider.page expects TrackingOptions as third parameter)\n await provider.page(name ?? '', enhancedProperties, {\n ...options,\n context: this.context,\n });\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n name: name ?? '',\n method: 'page',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Track a screen view (mobile equivalent of page)\n */\n async screen(payload: EmitterScreenPayload): Promise<void>;\n async screen(\n name?: string,\n properties?: PageProperties,\n options?: TrackingOptions,\n ): Promise<void>;\n async screen(\n nameOrPayload?: string | EmitterScreenPayload,\n properties?: PageProperties,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof nameOrPayload === 'object') {\n const payload = nameOrPayload;\n return this.screen(payload.name, payload.properties, {\n ...options,\n context: { ...this.context, ...payload.context },\n });\n }\n\n const name = nameOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n name,\n method: 'screen',\n });\n }\n return;\n }\n\n const sanitized = sanitizeProperties(properties ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = this.getTargetProviders(options);\n const enhancedProperties = { ...sanitized.data } as PageProperties;\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.screen) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n name,\n method: 'screen',\n });\n }\n return;\n }\n\n try {\n await provider.screen(name ?? '', enhancedProperties, {\n ...options,\n context: this.context,\n });\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n name: name ?? '',\n method: 'screen',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Associate user with a group\n * FIX #3: Replace any types with proper TypeScript types\n */\n async group(payload: EmitterGroupPayload): Promise<void>;\n async group(groupId: string, traits?: GroupTraits, options?: TrackingOptions): Promise<void>;\n async group(\n groupIdOrPayload: string | EmitterGroupPayload,\n traits?: GroupTraits,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof groupIdOrPayload === 'object') {\n const payload = groupIdOrPayload;\n return this.group(payload.groupId, payload.traits, {\n ...options,\n // Merge context from payload\n context: { ...this.context, ...payload.context },\n });\n }\n\n // Traditional group call\n const groupId = groupIdOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n groupId,\n method: 'group',\n });\n }\n return;\n }\n\n // Sanitize traits\n const sanitized = sanitizeProperties(traits ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n // Update context with group info\n this.setContext({ organizationId: groupId, ...sanitized.data });\n\n const targetProviders = this.getTargetProviders(options);\n const enhancedTraits = { ...sanitized.data } as GroupTraits;\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.group) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n groupId,\n method: 'group',\n });\n }\n return;\n }\n\n try {\n // Pass context as TrackingOptions (provider.group expects TrackingOptions as third parameter)\n await provider.group(groupId, enhancedTraits, {\n ...options,\n context: this.context,\n });\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n groupId,\n method: 'group',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Alias one user ID to another\n * No any types - already properly typed\n */\n async alias(payload: EmitterAliasPayload): Promise<void>;\n async alias(userId: string, previousId: string, options?: TrackingOptions): Promise<void>;\n async alias(\n userIdOrPayload: string | EmitterAliasPayload,\n previousId?: string,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof userIdOrPayload === 'object') {\n const payload = userIdOrPayload;\n return this.alias(payload.userId, payload.previousId, {\n ...options,\n // Merge context from payload\n context: { ...this.context, ...payload.context },\n });\n }\n\n // Traditional alias call\n const userId = userIdOrPayload;\n const prevId = previousId as string;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n method: 'alias',\n previousId: prevId,\n userId,\n });\n }\n return;\n }\n\n const targetProviders = this.getTargetProviders(options);\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.alias) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n userId,\n previousId,\n method: 'alias',\n });\n }\n return;\n }\n\n try {\n await provider.alias(userId, prevId, this.context);\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'alias',\n previousId: prevId,\n userId,\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Get list of active provider names\n */\n getActiveProviders(): string[] {\n return [...this.providers.keys()];\n }\n\n /**\n * Get a specific provider instance\n */\n getProvider(name: string): AnalyticsProvider | undefined {\n return this.providers.get(name);\n }\n\n /**\n * Reset analytics context and identity\n */\n reset(): void {\n this.context = {};\n this.eventCache.clear();\n }\n\n /**\n * Shutdown all providers and cleanup resources\n */\n async shutdown(): Promise<void> {\n const shutdownPromises = [...this.providers.entries()].map(async ([name, provider]) => {\n try {\n if (typeof (provider as any).destroy === 'function') {\n await (provider as any).destroy();\n }\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n method: 'shutdown',\n });\n }\n }\n });\n\n await Promise.allSettled(shutdownPromises);\n this.providers.clear();\n this.isInitialized = false;\n }\n\n /**\n * Process any emitter payload with Node 22+ optimizations\n * FIX #12: Use LRU cache for event deduplication (prevents memory leaks)\n */\n async emit(payload: EmitterPayload, options?: { timeout?: number }): Promise<void> {\n const emitStartTime = process.hrtime.bigint();\n\n // FIX #12: Use LRU cache for deduplication instead of WeakMap\n // Create order-independent cache key by sorting object keys\n const createCacheKey = (p: EmitterPayload): string => {\n const sorted = (obj: unknown): unknown => {\n if (obj === null || typeof obj !== 'object' || obj instanceof Array) {\n return obj;\n }\n const sortedObj: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sortedObj[key] = sorted((obj as Record<string, unknown>)[key]);\n }\n return sortedObj;\n };\n return JSON.stringify(sorted(p));\n };\n const cacheKey = createCacheKey(payload);\n if (this.eventCache.has(cacheKey)) {\n const metadata = this.eventCache.get(cacheKey);\n if (metadata?.processed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo('Skipping duplicate event processing');\n }\n return;\n }\n }\n\n // Mark as being processed and track metadata\n this.eventCache.set(cacheKey, {\n timestamp: emitStartTime,\n processed: true,\n });\n\n try {\n // Apply timeout if specified using AbortSignal.timeout() (Node 22+)\n if (options?.timeout) {\n const timeoutSignal = AbortSignal.timeout(options.timeout);\n await Promise.race([\n this.processPayloadByType(payload),\n new Promise<never>((_resolve, reject) => {\n timeoutSignal.addEventListener('abort', () =>\n reject(new Error(`Event processing timed out after ${options.timeout}ms`)),\n );\n }),\n ]);\n } else {\n await this.processPayloadByType(payload);\n }\n\n // Log performance metrics in debug mode\n if (this.config.debug && this.config.onInfo) {\n const processingTime = Number(process.hrtime.bigint() - emitStartTime) / 1_000_000;\n this.config.onInfo(`Event processed in ${processingTime.toFixed(2)}ms`);\n }\n } catch (error) {\n // Update metadata to mark as failed\n this.eventCache.set(cacheKey, {\n timestamp: emitStartTime,\n processed: false,\n });\n throw error;\n }\n }\n\n /**\n * Route payload to appropriate method based on type\n */\n private async processPayloadByType(payload: EmitterPayload): Promise<void> {\n switch (payload.type) {\n case 'track':\n return this.track(payload);\n case 'identify':\n return this.identify(payload);\n case 'page':\n return this.page(payload);\n case 'screen':\n return this.screen(payload);\n case 'group':\n return this.group(payload);\n case 'alias':\n return this.alias(payload);\n }\n }\n\n /**\n * Process an emitter payload (legacy method - use emit() instead)\n * @deprecated Use emit() for better type safety\n */\n async processEmitterPayload(payload: EmitterPayload): Promise<void> {\n return this.emit(payload);\n }\n\n /**\n * Batch emit multiple payloads with Node 22+ optimizations\n * FIX #14: Process chunks concurrently instead of sequentially\n * FIX #13: Use spread operator instead of structuredClone\n */\n async emitBatch(\n payloads: EmitterPayload[],\n options?: {\n timeout?: number;\n concurrency?: number;\n failFast?: boolean;\n },\n ): Promise<void> {\n const batchStartTime = process.hrtime.bigint();\n const concurrency = options?.concurrency ?? 10; // Default concurrency limit\n\n if (payloads.length === 0) return;\n\n // FIX #13: Use structuredClone for deep cloning to ensure nested data safety\n const clonedPayloads = payloads.map(p => structuredClone(p));\n\n // Process in chunks for better memory management\n const chunks: EmitterPayload[][] = [];\n for (let i = 0; i < clonedPayloads.length; i += concurrency) {\n chunks.push(clonedPayloads.slice(i, i + concurrency));\n }\n\n const processChunk = async (chunk: EmitterPayload[]) => {\n const chunkPromises = chunk.map(payload =>\n this.emit(\n payload,\n options?.timeout !== undefined ? { timeout: options.timeout } : undefined,\n ),\n );\n\n if (options?.failFast) {\n await Promise.all(chunkPromises);\n } else {\n await Promise.allSettled(chunkPromises);\n }\n };\n\n // FIX #14: Process chunks concurrently instead of sequentially\n const chunkPromises = chunks.map(chunk => processChunk(chunk));\n await Promise.all(chunkPromises);\n\n // Log batch processing metrics in debug mode\n if (this.config.debug && this.config.onInfo) {\n const batchTime = Number(process.hrtime.bigint() - batchStartTime) / 1_000_000;\n this.config.onInfo(\n `Batch of ${payloads.length} events processed in ${batchTime.toFixed(2)}ms ` +\n `(concurrency: ${concurrency})`,\n );\n }\n }\n\n /**\n * Create a bound emitter function for convenience\n */\n createEmitter(): (payload: EmitterPayload) => Promise<void> {\n // Extract arrow function to avoid consistent-function-scoping warning\n const boundEmit = this.emit.bind(this);\n return boundEmit;\n }\n\n /**\n * Track an ecommerce event specification\n * @deprecated Use emit(ecommerce.EVENT_NAME(...)) instead\n */\n async trackEcommerce(eventSpec: EcommerceEventSpec): Promise<void> {\n return this.track(eventSpec.name, eventSpec.properties);\n }\n\n /**\n * Get target providers based on tracking options\n */\n private getTargetProviders(options?: TrackingOptions): Map<string, AnalyticsProvider> {\n let targetProviders = new Map(this.providers);\n\n if (options) {\n // Handle runtime provider additions with enhanced error handling\n if (options.providers && Object.hasOwn(options, 'providers')) {\n for (const [name, config] of Object.entries(options.providers)) {\n const factory = this.availableProviders[name];\n if (factory) {\n try {\n const provider = factory(config);\n targetProviders.set(name, provider);\n\n // Initialize runtime metrics tracking (Node 22+)\n this.providerMetrics.set(name, {\n initTime: process.hrtime.bigint(),\n callCount: 0,\n errorCount: 0,\n lastUsed: process.hrtime.bigint(),\n });\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n method: 'runtime-create',\n });\n }\n }\n }\n }\n }\n\n // Handle 'only' option with safer property checking\n if (options.only && Object.hasOwn(options, 'only')) {\n const onlyProviders = new Map();\n for (const name of options.only) {\n if (targetProviders.has(name)) {\n onlyProviders.set(name, targetProviders.get(name));\n }\n }\n targetProviders = onlyProviders;\n }\n\n // Handle 'exclude' option with safer property checking\n if (options.exclude && Object.hasOwn(options, 'exclude')) {\n for (const name of options.exclude) {\n targetProviders.delete(name);\n }\n }\n }\n\n return targetProviders;\n }\n\n /**\n * Get analytics performance metrics using Node 22+ features\n */\n getAnalyticsMetrics(): {\n providers: Record<\n string,\n {\n initTimeMs: number;\n callCount: number;\n errorCount: number;\n lastUsedMs: number;\n successRate: number;\n }\n >;\n events: {\n totalProcessed: number;\n cacheSize: number;\n memoryUsage: {\n rss: number;\n heapTotal: number;\n heapUsed: number;\n external: number;\n arrayBuffers: number;\n };\n };\n } {\n const now = process.hrtime.bigint();\n const providers: Record<\n string,\n {\n initTimeMs: number;\n callCount: number;\n errorCount: number;\n lastUsedMs: number;\n successRate: number;\n }\n > = {};\n\n // Collect provider metrics\n for (const [providerName, metrics] of this.providerMetrics) {\n providers[providerName] = {\n initTimeMs: Number(metrics.initTime) / 1_000_000,\n callCount: metrics.callCount,\n errorCount: metrics.errorCount,\n lastUsedMs: Number(now - metrics.lastUsed) / 1_000_000,\n successRate: metrics.callCount > 0 ? 1 - metrics.errorCount / metrics.callCount : 1,\n };\n }\n\n return {\n providers,\n events: {\n totalProcessed: this.eventCache.size,\n cacheSize: this.eventCache.size,\n memoryUsage: process.memoryUsage(),\n },\n };\n }\n\n /**\n * Health check for analytics system using Node 22+ timing\n * FIX #16: Send actual test events instead of just setContext\n */\n async healthCheck(timeout: number = 5000): Promise<{\n healthy: boolean;\n providers: Record<string, boolean>;\n metrics: ReturnType<AnalyticsManager['getAnalyticsMetrics']>;\n totalCheckTime: number;\n }> {\n const checkStartTime = process.hrtime.bigint();\n const providerHealth: Record<string, boolean> = {};\n\n // Use AbortSignal.timeout() for health check timeout (Node 22+)\n const timeoutSignal = AbortSignal.timeout(timeout);\n\n try {\n const healthPromises = [...this.providers.entries()].map(async ([name, provider]) => {\n try {\n // FIX #16: Send actual test event instead of just setContext\n await Promise.race([\n provider.track('__health_check__', { timestamp: Date.now(), provider: name }),\n new Promise<void>((_resolve, reject) => {\n timeoutSignal.addEventListener('abort', () =>\n reject(new Error('Health check timeout')),\n );\n }),\n ]);\n providerHealth[name] = true;\n } catch {\n providerHealth[name] = false;\n }\n });\n\n await Promise.allSettled(healthPromises);\n\n const checkEndTime = process.hrtime.bigint();\n const totalCheckTime = Number(checkEndTime - checkStartTime) / 1_000_000;\n\n const healthyCount = Object.values(providerHealth).filter(Boolean).length;\n\n return {\n healthy: healthyCount > 0 && this.isInitialized,\n providers: providerHealth,\n metrics: this.getAnalyticsMetrics(),\n totalCheckTime,\n };\n } catch {\n return {\n healthy: false,\n providers: providerHealth,\n metrics: this.getAnalyticsMetrics(),\n totalCheckTime: Number(process.hrtime.bigint() - checkStartTime) / 1_000_000,\n };\n }\n }\n}\n\n/**\n * Factory function to create analytics manager\n */\nexport function createAnalyticsManager(\n config: AnalyticsConfig,\n availableProviders: ProviderRegistry,\n): AnalyticsManager {\n return new AnalyticsManager(config, availableProviders);\n}\n","/**\n * @fileoverview Helper Functions for Analytics Emitters\n *\n * These utilities make it easier to use the emitter-first approach for analytics.\n * Provides builders, batch processing, session management, and convenience\n * functions for common tracking patterns.\n *\n * **Builders**:\n * - `ContextBuilder`: Build consistent context across events\n * - `PayloadBuilder`: Chain emitter creation with shared options\n * - `EventBatch`: Batch related events with shared context\n *\n * **Session Management**:\n * - `createUserSession()`: Create a user session tracking flow\n * - `createAnonymousSession()`: Create an anonymous user tracking flow\n *\n * **Convenience Functions**:\n * - `withMetadata()`: Add consistent metadata to events\n * - `withUTM()`: Add UTM parameters to events\n * - Type guards: `isTrackPayload()`, `isIdentifyPayload()`, etc.\n *\n * @module @od-oneapp/analytics/shared/emitters/helpers\n */\n\nimport { alias, group, identify, page, track } from './emitters';\n\nimport type {\n EmitterAliasPayload,\n EmitterContext,\n EmitterGroupPayload,\n EmitterIdentifyPayload,\n EmitterOptions,\n EmitterPagePayload,\n EmitterPayload,\n EmitterTrackPayload,\n} from './emitter-types';\n\n/**\n * Builder for creating consistent context across analytics events.\n *\n * @remarks\n * Use this builder to create a reusable context object that can be applied\n * to multiple events. This ensures consistency and reduces repetition.\n *\n * @example\n * ```typescript\n * const context = new ContextBuilder()\n * .setUser('user-123', { plan: 'pro' })\n * .setOrganization('org-456')\n * .setPage({ path: '/dashboard', title: 'Dashboard' })\n * .build();\n *\n * await analytics.emit(track('Button Clicked', {}, { context }));\n * ```\n */\nexport class ContextBuilder {\n private context: EmitterContext = {};\n\n constructor(initialContext?: Partial<EmitterContext>) {\n if (initialContext) {\n this.context = { ...initialContext };\n }\n }\n\n setUser(_userId: string, traits?: Record<string, any>): this {\n this.context.traits = { ...this.context.traits, ...traits };\n return this;\n }\n\n setOrganization(groupId: string): this {\n this.context.groupId = groupId;\n return this;\n }\n\n setPage(pageInfo: { path?: string; url?: string; title?: string; referrer?: string }): this {\n this.context.page = { ...this.context.page, ...pageInfo };\n return this;\n }\n\n setCampaign(utmParams: Record<string, string>): this {\n this.context.campaign = { ...this.context.campaign, ...utmParams };\n return this;\n }\n\n setDevice(deviceInfo: Record<string, any>): this {\n this.context.device = { ...this.context.device, ...deviceInfo };\n return this;\n }\n\n build(): EmitterContext {\n return { ...this.context };\n }\n}\n\n/**\n * Builder for chaining emitter creation with shared options.\n *\n * @remarks\n * Use this builder to create multiple events with shared options like\n * timestamp, anonymousId, or integrations. This is useful when tracking\n * multiple related events in a single flow.\n *\n * @example\n * ```typescript\n * const builder = new PayloadBuilder(context)\n * .withTimestamp(new Date())\n * .withAnonymousId('anon-123');\n *\n * await analytics.emit(builder.track('Event 1', {}));\n * await analytics.emit(builder.track('Event 2', {}));\n * ```\n */\nexport class PayloadBuilder {\n private options: EmitterOptions = {};\n\n constructor(context?: EmitterContext) {\n if (context) {\n this.options.context = context;\n }\n }\n\n withTimestamp(timestamp: Date | string): this {\n this.options.timestamp = timestamp;\n return this;\n }\n\n withAnonymousId(anonymousId: string): this {\n this.options.anonymousId = anonymousId;\n return this;\n }\n\n withIntegrations(integrations: Record<string, boolean | Record<string, any>>): this {\n this.options.integrations = integrations;\n return this;\n }\n\n track(event: string, properties?: Record<string, any>): EmitterTrackPayload {\n return track(event, properties, this.options);\n }\n\n identify(userId: string, traits?: Record<string, any>): EmitterIdentifyPayload {\n return identify(userId, traits, this.options);\n }\n\n page(name?: string, properties?: Record<string, any>): EmitterPagePayload {\n return page(undefined, name, properties, this.options);\n }\n\n group(groupId: string, traits?: Record<string, any>): EmitterGroupPayload {\n return group(groupId, traits, this.options);\n }\n\n alias(userId: string, previousId: string): EmitterAliasPayload {\n return alias(userId, previousId, this.options);\n }\n}\n\n/**\n * Batch processor for related events with shared context.\n *\n * @remarks\n * Use this class to collect multiple events and emit them together with\n * a shared context. This is useful for tracking user flows or multi-step\n * processes where events should be grouped together.\n *\n * @example\n * ```typescript\n * const batch = new EventBatch(context)\n * .addTrack('Step 1 Completed', { step: 1 })\n * .addTrack('Step 2 Completed', { step: 2 })\n * .addTrack('Flow Completed', { totalSteps: 2 });\n *\n * for (const event of batch.getEvents()) {\n * await analytics.emit(event);\n * }\n * ```\n */\nexport class EventBatch {\n private events: EmitterPayload[] = [];\n private sharedContext: EmitterContext;\n\n constructor(context?: EmitterContext) {\n this.sharedContext = context ?? {};\n }\n\n add(payload: EmitterPayload): this {\n // Merge shared context with payload context\n const enrichedPayload = {\n ...payload,\n context: { ...this.sharedContext, ...payload.context },\n };\n this.events.push(enrichedPayload);\n return this;\n }\n\n addTrack(event: string, properties?: Record<string, any>): this {\n return this.add(track(event, properties, { context: this.sharedContext }));\n }\n\n addIdentify(userId: string, traits?: Record<string, any>): this {\n return this.add(identify(userId, traits, { context: this.sharedContext }));\n }\n\n addPage(name?: string, properties?: Record<string, any>): this {\n return this.add(page(undefined, name, properties, { context: this.sharedContext }));\n }\n\n addGroup(groupId: string, traits?: Record<string, any>): this {\n return this.add(group(groupId, traits, { context: this.sharedContext }));\n }\n\n getEvents(): EmitterPayload[] {\n return [...this.events];\n }\n\n clear(): void {\n this.events = [];\n }\n}\n\n/**\n * Creates a user session tracking flow with pre-configured context.\n *\n * @remarks\n * This helper creates a session object with methods for tracking events\n * within a specific user session. All events will include the sessionId\n * and user context automatically.\n *\n * @param userId - Unique identifier for the user\n * @param sessionId - Unique identifier for the session\n * @returns Session object with `identify`, `track`, `page`, and `group` methods\n *\n * @example\n * ```typescript\n * const session = createUserSession('user-123', 'session-456');\n * await analytics.emit(session.track('Button Clicked', { button: 'signup' }));\n * await analytics.emit(session.page('Dashboard'));\n * ```\n */\nexport function createUserSession(userId: string, sessionId: string) {\n const context = new ContextBuilder().setUser(userId).build();\n\n return {\n // Identify the user\n identify: (traits?: Record<string, any>) =>\n identify(userId, { ...traits, sessionId }, { context }),\n\n // Track an event in this session\n track: (event: string, properties?: Record<string, any>) =>\n track(event, { ...properties, sessionId }, { context }),\n\n // Track a page view in this session\n page: (name?: string, properties?: Record<string, any>) =>\n page(undefined, name, { ...properties, sessionId }, { context }),\n\n // Associate with a group\n group: (groupId: string, traits?: Record<string, any>) =>\n group(groupId, { ...traits, sessionId }, { context }),\n };\n}\n\n/**\n * Creates an anonymous user tracking flow.\n *\n * @remarks\n * This helper creates a session object for tracking anonymous users before\n * they are identified. When the user signs up or logs in, use the `alias`\n * method to link their anonymous activity to their user ID.\n *\n * @param anonymousId - Unique identifier for the anonymous user\n * @returns Session object with `track`, `page`, `identify`, and `alias` methods\n *\n * @example\n * ```typescript\n * const anonymousSession = createAnonymousSession('anon-123');\n * await analytics.emit(anonymousSession.track('Page Viewed', { page: 'home' }));\n *\n * // Later, when user signs up\n * await analytics.emit(anonymousSession.alias('user-123'));\n * await analytics.emit(anonymousSession.identify('user-123', { email: 'user@example.com' }));\n * ```\n */\nexport function createAnonymousSession(anonymousId: string) {\n const options: EmitterOptions = { anonymousId };\n\n return {\n // Track an event\n track: (event: string, properties?: Record<string, any>) => track(event, properties, options),\n\n // Track a page view\n page: (name?: string, properties?: Record<string, any>) =>\n page(undefined, name, properties, options),\n\n // Convert to identified user\n identify: (userId: string, traits?: Record<string, any>) => identify(userId, traits, options),\n\n // Alias when user signs up\n alias: (userId: string) => alias(userId, anonymousId, options),\n };\n}\n\n/**\n * Type guard to check if a payload is a track event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is a track event\n */\nexport const isTrackPayload = (payload: EmitterPayload): payload is EmitterTrackPayload =>\n payload.type === 'track';\n\n/**\n * Type guard to check if a payload is an identify event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is an identify event\n */\nexport const isIdentifyPayload = (payload: EmitterPayload): payload is EmitterIdentifyPayload =>\n payload.type === 'identify';\n\n/**\n * Type guard to check if a payload is a page event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is a page event\n */\nexport const isPagePayload = (payload: EmitterPayload): payload is EmitterPagePayload =>\n payload.type === 'page';\n\n/**\n * Type guard to check if a payload is a group event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is a group event\n */\nexport const isGroupPayload = (payload: EmitterPayload): payload is EmitterGroupPayload =>\n payload.type === 'group';\n\n/**\n * Type guard to check if a payload is an alias event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is an alias event\n */\nexport const isAliasPayload = (payload: EmitterPayload): payload is EmitterAliasPayload =>\n payload.type === 'alias';\n\n/**\n * Adds consistent metadata to an analytics event payload.\n *\n * @remarks\n * This helper enriches an event payload with metadata that will be included\n * in the event's context.app object. Useful for tracking version, source,\n * or other application-level metadata.\n *\n * @param payload - The event payload to enrich\n * @param metadata - Metadata to add (version, source, or custom properties)\n * @returns The enriched payload with metadata in context.app\n *\n * @example\n * ```typescript\n * const event = track('Button Clicked', { button: 'signup' });\n * const enriched = withMetadata(event, { version: '1.0.0', source: 'webapp' });\n * await analytics.emit(enriched);\n * ```\n */\nexport function withMetadata<T extends EmitterPayload>(\n payload: T,\n metadata: { version?: string; source?: string; [key: string]: unknown },\n): T {\n return {\n ...payload,\n context: {\n ...payload.context,\n app: {\n ...payload.context?.app,\n ...metadata,\n },\n },\n };\n}\n\n/**\n * Adds UTM parameters to an analytics event payload.\n *\n * @remarks\n * This helper enriches an event payload with UTM campaign parameters that\n * will be included in the event's context.campaign object. Useful for\n * tracking marketing campaign attribution.\n *\n * @param payload - The event payload to enrich\n * @param utm - UTM parameters (source, medium, campaign, term, content)\n * @returns The enriched payload with UTM parameters in context.campaign\n *\n * @example\n * ```typescript\n * const event = track('Signup Started', {});\n * const enriched = withUTM(event, {\n * source: 'google',\n * medium: 'cpc',\n * campaign: 'summer-sale'\n * });\n * await analytics.emit(enriched);\n * ```\n */\nexport function withUTM<T extends EmitterPayload>(\n payload: T,\n utm: { source?: string; medium?: string; campaign?: string; term?: string; content?: string },\n): T {\n return {\n ...payload,\n context: {\n ...payload.context,\n campaign: {\n ...payload.context?.campaign,\n ...utm,\n },\n },\n };\n}\n","/**\n * @fileoverview PostHog bootstrap utilities for Next.js server-side rendering\n * PostHog bootstrap utilities for Next.js server-side rendering\n * Enables consistent feature flag delivery from server to client\n */\n\nimport type { BootstrapData, PostHogCookie } from '../types/posthog-types';\n\n/**\n * Generate a unique distinct ID (compatible with PostHog format)\n */\nexport function generateDistinctId(): string {\n // Use a similar format to PostHog's default distinct ID generation\n const timestamp = Date.now().toString(36);\n const randomStr = Math.random().toString(36).slice(2, 15);\n return `${timestamp}-${randomStr}`;\n}\n\n/**\n * Parse PostHog cookie to extract distinct ID\n */\nexport function parsePostHogCookie(\n cookieValue: string,\n _projectApiKey: string,\n): PostHogCookie | null {\n try {\n const parsed = JSON.parse(cookieValue);\n\n // Validate the cookie structure\n if (parsed && typeof parsed.distinct_id === 'string') {\n return parsed as PostHogCookie;\n }\n\n return null;\n } catch {\n // Silently fail - cookies may be malformed\n return null;\n }\n}\n\n/**\n * Get PostHog cookie name for a project\n */\nexport function getPostHogCookieName(projectApiKey: string): string {\n return `ph_${projectApiKey}_posthog`;\n}\n\n/**\n * Extract distinct ID from cookies (Next.js compatible)\n */\nexport function getDistinctIdFromCookies(\n cookies: any, // Next.js cookies object or cookie string\n projectApiKey: string,\n): string | null {\n try {\n const cookieName = getPostHogCookieName(projectApiKey);\n\n // Handle Next.js cookies() object\n if (cookies && typeof cookies.get === 'function') {\n const cookie = cookies.get(cookieName);\n if (cookie?.value) {\n const parsed = parsePostHogCookie(cookie.value, projectApiKey);\n return parsed?.distinct_id ?? null;\n }\n }\n\n // Handle raw cookie string\n if (typeof cookies === 'string') {\n const cookieMatch = cookies.match(new RegExp(`${cookieName}=([^;]+)`));\n if (cookieMatch?.[1]) {\n const parsed = parsePostHogCookie(decodeURIComponent(cookieMatch[1]), projectApiKey);\n return parsed?.distinct_id ?? null;\n }\n }\n\n return null;\n } catch {\n // Silently fail - cookies may be malformed or missing\n return null;\n }\n}\n\n/**\n * Create bootstrap data for PostHog client initialization\n */\nexport function createBootstrapData(distinctId: string): BootstrapData {\n return {\n distinctID: distinctId,\n };\n}\n\n/**\n * Cached bootstrap data fetcher (React cache compatible)\n */\nconst bootstrapCache = new Map<string, { data: BootstrapData; timestamp: number }>();\nconst CACHE_TTL = 30000; // 30 seconds\n\nexport function getCachedBootstrapData(distinctId: string): BootstrapData | null {\n const cached = bootstrapCache.get(distinctId);\n\n if (cached && Date.now() - cached.timestamp < CACHE_TTL) {\n return cached.data;\n }\n\n // Clean up expired entries\n for (const [key, value] of bootstrapCache.entries()) {\n if (Date.now() - value.timestamp >= CACHE_TTL) {\n bootstrapCache.delete(key);\n }\n }\n\n return null;\n}\n\nexport function setCachedBootstrapData(distinctId: string, data: BootstrapData): void {\n bootstrapCache.set(distinctId, {\n data,\n timestamp: Date.now(),\n });\n}\n\n/**\n * Validate bootstrap data structure\n */\nexport function validateBootstrapData(data: any): data is BootstrapData {\n return data && typeof data === 'object' && typeof data.distinctID === 'string';\n}\n\n/**\n * Create minimal bootstrap data when full data is unavailable\n */\nexport function createMinimalBootstrapData(distinctId?: string): BootstrapData {\n return {\n distinctID: distinctId ?? generateDistinctId(),\n };\n}\n"],"mappings":"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"}
|
package/service-Duqnlppl.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service-Duqnlppl.mjs","names":[],"sources":["../../../integrations/segment/src/analytics-provider/server.ts","../../../integrations/vercel/src/analytics-provider/server.ts","../src/server/manager.ts","../src/shared/utils/validation.ts","../src/shared/ingestion/schemas.ts","../src/shared/ingestion/service.ts"],"sourcesContent":["/**\n * @fileoverview Segment server-side (Node.js) analytics provider\n *\n * Provides server-side integration with Segment Analytics using @segment/analytics-next.\n * Implements the @od-oneapp/analytics AnalyticsProvider interface for use in the analytics\n * manager's provider registry.\n *\n * @module @integrations/segment/analytics-provider/server\n */\n\nimport type {\n AnalyticsContext,\n AnalyticsProvider,\n GroupTraits,\n PageProperties,\n Properties,\n ProviderConfig,\n SegmentConfig,\n UserTraits,\n} from './types';\n\n// Type for Analytics server instance\ninterface AnalyticsServerInstance {\n track: (params: { event: string; properties?: Properties }) => Promise<unknown>;\n identify: (params: { userId: string; traits?: UserTraits }) => Promise<unknown>;\n page: (params: { name?: string; properties?: PageProperties }) => Promise<unknown>;\n group: (params: { groupId: string; traits?: GroupTraits }) => Promise<unknown>;\n alias: (params: { userId: string; previousId: string }) => Promise<unknown>;\n}\n\nexport class SegmentServerProvider implements AnalyticsProvider {\n readonly name = 'segment';\n private analytics: AnalyticsServerInstance | null = null;\n private config: SegmentConfig;\n private isInitialized = false;\n\n constructor(config: ProviderConfig) {\n if (!config.writeKey) {\n throw new Error('Segment writeKey is required');\n }\n\n this.config = {\n options: config.options as Record<string, unknown>,\n writeKey: config.writeKey,\n };\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n const { Analytics } = await import(\n /* webpackChunkName: \"segment-analytics\" */\n '@segment/analytics-next'\n );\n\n this.analytics = new Analytics({\n writeKey: this.config.writeKey,\n ...this.config.options,\n } as ConstructorParameters<typeof Analytics>[0]) as unknown as AnalyticsServerInstance;\n\n this.isInitialized = true;\n }\n\n async track(\n event: string,\n properties: Properties = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.analytics) return;\n await this.analytics.track({ event, properties });\n }\n\n async identify(\n userId: string,\n traits: UserTraits = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.analytics) return;\n await this.analytics.identify({ userId, traits });\n }\n\n async page(\n name?: string,\n properties: PageProperties = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.analytics) return;\n await this.analytics.page({ ...(name && { name }), properties });\n }\n\n async group(\n groupId: string,\n traits: GroupTraits = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.analytics) return;\n await this.analytics.group({ groupId, traits });\n }\n\n async alias(userId: string, previousId: string, _context?: AnalyticsContext): Promise<void> {\n if (!this.analytics) return;\n await this.analytics.alias({ userId, previousId });\n }\n}\n","/**\n * @fileoverview Vercel Analytics server-side (Node.js) provider\n *\n * Provides minimal server-side integration with Vercel Analytics. Implements the\n * @od-oneapp/analytics AnalyticsProvider interface. Vercel Analytics is primarily\n * client-side focused; server-side tracking is limited.\n *\n * @module @integrations/vercel/analytics-provider/server\n */\n\nimport type {\n AnalyticsProvider,\n GroupTraits,\n PageProperties,\n Properties,\n ProviderConfig,\n UserTraits,\n} from './types';\n\nexport class VercelServerProvider implements AnalyticsProvider {\n readonly name = 'vercel';\n private isInitialized = false;\n private readonly config: ProviderConfig;\n\n constructor(config: ProviderConfig) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n }\n\n async track(event: string, properties: Properties = {}): Promise<void> {\n if (!this.isInitialized) return;\n\n void event;\n void properties;\n void this.config;\n // Server-side tracking is limited — most tracking happens client-side.\n }\n\n async identify(userId: string, traits: UserTraits = {}): Promise<void> {\n await this.track('User Identified', { userId, ...traits });\n }\n\n async page(name?: string, properties: PageProperties = {}): Promise<void> {\n await this.track('Page View (Server)', { page: name, ...properties });\n }\n\n async group(groupId: string, traits: GroupTraits = {}): Promise<void> {\n await this.track('Group Identified', { groupId, ...traits });\n }\n\n async alias(userId: string, previousId: string): Promise<void> {\n await this.track('User Aliased', { previousId, userId });\n }\n}\n","/**\n * @fileoverview Server analytics manager with static provider registry\n * Server analytics manager with static provider registry\n */\n\nimport { SegmentServerProvider } from '@integrations/segment/analytics-provider/server';\nimport { VercelServerProvider } from '@integrations/vercel/analytics-provider/server';\n\nimport { ConsoleProvider } from '../providers/console/server';\nimport { HttpServerProvider } from '../providers/http/server';\nimport { createAnalyticsManager } from '../shared/utils/manager';\n\nimport type { AnalyticsConfig, AnalyticsManager, ProviderRegistry } from '../shared/types/types';\n\n// Static provider registry for server environments\nconst SERVER_PROVIDERS: ProviderRegistry = {\n console: config => new ConsoleProvider(config),\n http: config => new HttpServerProvider(config),\n segment: config => new SegmentServerProvider(config),\n vercel: config => new VercelServerProvider(config),\n};\n\n/**\n * Create and initialize a server analytics instance\n * This is the primary way to create analytics for server-side applications\n *\n * @example\n * ```typescript\n * const analytics = await createServerAnalytics({\n * providers: {\n * segment: { writeKey: process.env.SEGMENT_KEY! },\n * },\n * });\n * await analytics.page('/admin', { title: 'Admin Dashboard' });\n * ```\n * @param config - Analytics configuration including providers and settings\n * @returns Promise resolving to initialized analytics manager\n */\nexport async function createServerAnalytics(config: AnalyticsConfig): Promise<AnalyticsManager> {\n const manager = createAnalyticsManager(config, SERVER_PROVIDERS);\n await manager.initialize();\n return manager;\n}\n\n/**\n * Create a server analytics instance without initializing\n * Useful when you need to control initialization timing\n *\n * @example\n * ```typescript\n * const analytics = createServerAnalyticsUninitialized(config);\n * if (shouldEmit) {\n * await analytics.initialize();\n * await analytics.track('CRON Completed');\n * }\n * ```\n * @param config - Analytics configuration including providers and settings\n * @returns Uninitialized analytics manager instance\n */\nexport function createServerAnalyticsUninitialized(config: AnalyticsConfig): AnalyticsManager {\n return createAnalyticsManager(config, SERVER_PROVIDERS);\n}\n","/**\n * @fileoverview Validation utilities for analytics configuration\n *\n * This module provides comprehensive validation for analytics configurations,\n * including provider validation, environment-specific checks, and helpful\n * warnings for common misconfigurations.\n *\n * **Features**:\n * - Configuration structure validation\n * - Provider-specific field validation\n * - Environment variable validation\n * - Environment-specific warnings\n * - Detailed error reporting\n *\n * @module @od-oneapp/analytics/shared/utils/validation\n */\n\nimport { logError, logInfo, logWarn } from '@repo/shared/logger';\n\nimport { PROVIDER_REQUIREMENTS } from './config';\n\nimport type { AnalyticsConfig, ProviderConfig } from '../types/types';\n\n/**\n * Validation error structure.\n *\n * Represents a single validation error with field, message, and provider context.\n */\nexport interface ValidationError {\n /** Field name that failed validation */\n field: string;\n /** Human-readable error message */\n message: string;\n /** Provider name (or 'global' for config-level errors) */\n provider: string;\n}\n\n/**\n * Validation result structure.\n *\n * Contains validation errors, warnings, and overall validity status.\n */\nexport interface ValidationResult {\n /** Array of validation errors */\n errors: ValidationError[];\n /** Whether the configuration is valid (no errors) */\n isValid: boolean;\n /** Array of warning messages */\n warnings: string[];\n}\n\n/**\n * Comprehensive configuration validation.\n *\n * Validates the entire analytics configuration structure, including:\n * - Configuration object structure\n * - Providers object existence\n * - Individual provider configurations\n * - Environment-specific warnings\n *\n * Accepts `unknown` input for defensive validation of potentially malformed configs.\n *\n * @param {unknown} config - Analytics configuration to validate\n * @returns {ValidationResult} Validation result with errors, warnings, and validity status\n *\n * @example\n * ```typescript\n * const result = validateAnalyticsConfig(config);\n * if (!result.isValid) {\n * console.error('Validation errors:', result.errors);\n * }\n * if (result.warnings.length > 0) {\n * console.warn('Warnings:', result.warnings);\n * }\n * ```\n */\nexport function validateAnalyticsConfig(config: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const warnings: string[] = [];\n\n // Check if config exists and is an object\n if (!config || typeof config !== 'object') {\n errors.push({\n provider: 'global',\n field: 'config',\n message: 'Analytics configuration is required and must be an object',\n });\n return { isValid: false, errors, warnings };\n }\n\n const typedConfig = config as AnalyticsConfig;\n\n // Check if providers object exists\n if (!typedConfig.providers || typeof typedConfig.providers !== 'object') {\n errors.push({\n provider: 'global',\n field: 'providers',\n message: 'Providers configuration is required and must be an object',\n });\n return { isValid: false, errors, warnings };\n }\n\n // Check if at least one provider is configured\n const providerCount = Object.keys(typedConfig.providers).length;\n if (providerCount === 0) {\n warnings.push('No providers configured. Analytics will not track any events.');\n }\n\n // Validate each provider\n for (const [providerName, providerConfig] of Object.entries(typedConfig.providers)) {\n const providerErrors = validateProvider(providerName, providerConfig);\n errors.push(...providerErrors);\n }\n\n // Environment-specific warnings\n const isBrowser = typeof window !== 'undefined';\n\n // Mixpanel warning for client-side usage\n if (isBrowser && typedConfig.providers.mixpanel) {\n warnings.push(\n 'Mixpanel provider configured on client-side. Consider using server-side for better performance.',\n );\n }\n\n if (!isBrowser && typedConfig.providers.vercel) {\n warnings.push(\n 'Vercel Analytics has limited server-side support. Consider using client-side for better features.',\n );\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\n/**\n * Validate a single provider configuration.\n *\n * Checks that the provider is known and that all required fields are present.\n *\n * @param {string} providerName - Name of the provider to validate\n * @param {ProviderConfig} config - Provider configuration to validate\n * @returns {ValidationError[]} Array of validation errors (empty if valid)\n *\n * @example\n * ```typescript\n * const errors = validateProvider('posthog', { apiKey: 'phc_xxx' });\n * if (errors.length > 0) {\n * console.error('Provider errors:', errors);\n * }\n * ```\n */\nexport function validateProvider(providerName: string, config: ProviderConfig): ValidationError[] {\n const errors: ValidationError[] = [];\n\n // Check if provider is known\n const knownProviders = ['segment', 'posthog', 'vercel', 'console', 'mixpanel'];\n if (!knownProviders.includes(providerName)) {\n errors.push({\n provider: providerName,\n field: 'name',\n message: `Unknown provider '${providerName}'. Known providers: ${knownProviders.join(', ')}`,\n });\n return errors;\n }\n\n // Check required fields\n const requiredFields = PROVIDER_REQUIREMENTS[providerName] ?? [];\n for (const field of requiredFields) {\n const value = config[field as keyof ProviderConfig];\n\n if (!value) {\n errors.push({\n provider: providerName,\n field,\n message: `Required field '${field}' is missing for provider '${providerName}'`,\n });\n } else if (typeof value === 'string' && value.trim() === '') {\n errors.push({\n provider: providerName,\n field,\n message: `Required field '${field}' cannot be empty for provider '${providerName}'`,\n });\n }\n }\n\n // Provider-specific validation\n switch (providerName) {\n case 'segment':\n if (config.writeKey && !isValidSegmentWriteKey(config.writeKey)) {\n errors.push({\n provider: providerName,\n field: 'writeKey',\n message: 'Segment writeKey appears to be invalid format',\n });\n }\n break;\n\n case 'posthog':\n if (config.apiKey && !isValidPostHogApiKey(config.apiKey)) {\n errors.push({\n provider: providerName,\n field: 'apiKey',\n message: 'PostHog apiKey appears to be invalid format',\n });\n }\n break;\n }\n\n return errors;\n}\n\n/**\n * Validate environment variables for analytics.\n *\n * Checks common analytics environment variables for:\n * - Empty values\n * - Placeholder text\n * - Development environment warnings\n *\n * @returns {ValidationResult} Validation result with warnings about environment variables\n *\n * @example\n * ```typescript\n * const result = validateEnvironmentVariables();\n * if (result.warnings.length > 0) {\n * console.warn('Environment warnings:', result.warnings);\n * }\n * ```\n */\nexport function validateEnvironmentVariables(): ValidationResult {\n const errors: ValidationError[] = [];\n const warnings: string[] = [];\n\n // Check for common environment variables\n const envVars = {\n POSTHOG_API_KEY: process.env.POSTHOG_API_KEY,\n SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,\n };\n\n for (const [varName, value] of Object.entries(envVars)) {\n if (value && typeof value === 'string') {\n if (value.trim() === '') {\n warnings.push(`Environment variable ${varName} is set but empty`);\n } else if (value.includes('your-') || value.includes('paste-')) {\n warnings.push(`Environment variable ${varName} appears to contain placeholder text`);\n }\n }\n }\n\n // Warn about development environment\n if (\n process.env.NODE_ENV === 'development' &&\n !envVars.SEGMENT_WRITE_KEY &&\n !envVars.POSTHOG_API_KEY\n ) {\n warnings.push(\n 'No analytics environment variables detected in development. Consider using console provider for debugging.',\n );\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\n/**\n * Validates Segment write key format.\n *\n * Checks that the write key matches Segment's expected format:\n * - Exactly 32 alphanumeric characters\n * - Not a placeholder value\n *\n * @param {string} writeKey - Write key to validate\n * @returns {boolean} `true` if valid, `false` otherwise\n *\n * @internal\n */\nfunction isValidSegmentWriteKey(writeKey: string): boolean {\n // Segment write keys are exactly 32 characters, alphanumeric\n if (!/^[\\dA-Za-z]{32}$/.test(writeKey)) {\n return false;\n }\n\n // Check for common placeholder patterns\n const placeholders = ['your-write-key', 'paste-key-here', 'xxxxxxxx', 'example'];\n if (placeholders.some(p => writeKey.toLowerCase().includes(p.toLowerCase()))) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Validates PostHog API key format.\n *\n * Checks that the API key matches PostHog's expected format:\n * - Starts with `phc_` prefix\n * - Followed by 43 alphanumeric/dash/underscore characters\n * - Not a placeholder value\n *\n * @param {string} apiKey - API key to validate\n * @returns {boolean} `true` if valid, `false` otherwise\n *\n * @internal\n */\nfunction isValidPostHogApiKey(apiKey: string): boolean {\n // PostHog keys: phc_ prefix + 43 alphanumeric/dash/underscore characters\n if (!/^phc_[\\w-]{43}$/.test(apiKey)) {\n return false;\n }\n\n // Check for placeholder patterns\n const placeholders = ['your-api-key', 'paste-key-here', 'xxxxxxxx', 'example'];\n if (placeholders.some(p => apiKey.toLowerCase().includes(p.toLowerCase()))) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Utility to throw validation errors (for strict validation).\n *\n * Validates the configuration and throws an error if validation fails.\n * Useful for ensuring configuration is valid before using analytics.\n *\n * @param {AnalyticsConfig} config - Analytics configuration to validate\n * @throws {Error} If configuration validation fails\n *\n * @example\n * ```typescript\n * try {\n * validateConfigOrThrow(config);\n * // Configuration is valid, proceed\n * } catch (error) {\n * console.error('Invalid config:', error.message);\n * }\n * ```\n */\nexport function validateConfigOrThrow(config: AnalyticsConfig): void {\n const result = validateAnalyticsConfig(config);\n\n if (!result.isValid) {\n const errorMessages = result.errors\n .map(error => `${error.provider}.${error.field}: ${error.message}`)\n .join('\\n');\n\n throw new Error(`Analytics configuration validation failed:\\n${errorMessages}`);\n }\n\n // Log warnings but don't throw\n if (result.warnings.length > 0 && config.onError) {\n config.onError(new Error('Analytics configuration warnings'), {\n provider: 'analytics',\n method: 'validateConfig',\n warnings: result.warnings,\n });\n }\n}\n\n/**\n * Development helper to check configuration.\n *\n * Logs configuration details and validation results for debugging.\n * Only useful in development environments.\n *\n * @param {AnalyticsConfig} config - Analytics configuration to debug\n * @returns {Promise<void>} Promise that resolves when debugging is complete\n *\n * @example\n * ```typescript\n * if (process.env.NODE_ENV === 'development') {\n * await debugConfig(config);\n * }\n * ```\n */\nexport async function debugConfig(config: AnalyticsConfig): Promise<void> {\n const result = validateAnalyticsConfig(config);\n\n logInfo('Analytics Configuration Debug', {\n config,\n validationResult: result,\n });\n\n if (result.errors.length > 0) {\n logError('Analytics configuration errors: Validation failed', {\n errors: result.errors,\n });\n }\n\n if (result.warnings.length > 0) {\n logWarn('Analytics configuration warnings', { warnings: result.warnings });\n }\n}\n","/**\n * @fileoverview Event Ingestion Schemas and Validation\n *\n * Defines Zod schemas for validating event ingestion payloads. These schemas\n * ensure type-safe, secure event ingestion with proper validation.\n *\n * **Key Features**:\n * - CloudEvents-style fields for future interoperability\n * - Support for single events and batched arrays\n * - Strict validation of required fields\n * - Typed extensions per event category\n *\n * @module @od-oneapp/analytics/shared/ingestion/schemas\n */\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Base Schemas\n// =============================================================================\n\n/**\n * Property value schema - safe, serializable values only.\n */\nexport const PropertyValueSchema = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.date(), // Accept Date objects only; ISO strings are already handled as strings\n]);\n\n/**\n * Property object schema with nested structure support.\n */\nexport const PropertyObjectSchema: z.ZodType<Record<string, unknown>> = z.record(\n z.string(),\n z.lazy(() =>\n z.union([PropertyValueSchema, z.array(PropertyValueSchema), z.record(z.string(), z.unknown())]),\n ),\n);\n\n/**\n * Emitter context schema - contextual information about the environment.\n */\nexport const EmitterContextSchema = z\n .object({\n /** Application information */\n app: z\n .object({\n name: z.string().optional(),\n version: z.string().optional(),\n build: z.string().optional(),\n namespace: z.string().optional(),\n })\n .optional(),\n\n /** Campaign/UTM information */\n campaign: z\n .object({\n name: z.string().optional(),\n source: z.string().optional(),\n medium: z.string().optional(),\n term: z.string().optional(),\n content: z.string().optional(),\n })\n .passthrough()\n .optional(),\n\n /** Device information */\n device: z\n .object({\n id: z.string().optional(),\n manufacturer: z.string().optional(),\n model: z.string().optional(),\n name: z.string().optional(),\n type: z.string().optional(),\n version: z.string().optional(),\n })\n .optional(),\n\n /** User's IP address */\n ip: z.string().optional(),\n\n /** Library making the request */\n library: z\n .object({\n name: z.string(),\n version: z.string(),\n })\n .optional(),\n\n /** User's locale (e.g., 'en-US') */\n locale: z.string().optional(),\n\n /** Network information */\n network: z\n .object({\n bluetooth: z.boolean().optional(),\n carrier: z.string().optional(),\n cellular: z.boolean().optional(),\n wifi: z.boolean().optional(),\n })\n .optional(),\n\n /** OS information */\n os: z\n .object({\n name: z.string().optional(),\n version: z.string().optional(),\n })\n .optional(),\n\n /** Page information */\n page: z\n .object({\n path: z.string().optional(),\n // Use permissive string validation for referrer/url since real-world values\n // may be malformed, truncated, or non-standard (e.g., data: URIs, about:blank)\n referrer: z.string().optional(),\n search: z.string().optional(),\n title: z.string().optional(),\n url: z.string().optional(),\n })\n .optional(),\n\n /** Screen information */\n screen: z\n .object({\n density: z.number().optional(),\n height: z.number().int().positive().optional(),\n width: z.number().int().positive().optional(),\n })\n .optional(),\n\n /** User's timezone (tzdata string) */\n timezone: z.string().optional(),\n\n /** Group/account ID */\n groupId: z.string().optional(),\n\n /** User agent string */\n userAgent: z.string().optional(),\n\n /** Channel where request originated */\n channel: z.enum(['server', 'browser', 'mobile', 'api']).optional(),\n\n /** Location context */\n location: z\n .object({\n city: z.string().optional(),\n country: z.string().optional(),\n latitude: z.number().optional(),\n longitude: z.number().optional(),\n region: z.string().optional(),\n })\n .optional(),\n })\n .passthrough();\n\n// =============================================================================\n// Event Type Schemas\n// =============================================================================\n\n/**\n * Base payload schema - common fields for all event types.\n */\nconst BasePayloadSchema = z\n .object({\n /** Anonymous ID if user is not identified */\n anonymousId: z.string().max(255).optional(),\n\n /** User ID (at least one of userId or anonymousId required) */\n userId: z.string().max(255).optional(),\n\n /** Timestamp when the event occurred */\n timestamp: z.union([z.string().datetime(), z.date()]).optional(),\n\n /** Original timestamp before processing */\n originalTimestamp: z.union([z.string().datetime(), z.date()]).optional(),\n\n /** Context and control fields */\n context: EmitterContextSchema.optional(),\n\n /** Message ID for deduplication */\n messageId: z.string().uuid().optional(),\n })\n .refine(data => Boolean(data.anonymousId) || Boolean(data.userId), {\n message: 'Either anonymousId or userId must be provided',\n });\n\n/**\n * Track event payload schema.\n * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).\n */\nexport const TrackEventSchema = BasePayloadSchema.safeExtend({\n type: z.literal('track'),\n event: z.string().min(1).max(255),\n properties: PropertyObjectSchema.optional(),\n});\n\n/**\n * Identify event payload schema.\n * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).\n */\nexport const IdentifyEventSchema = BasePayloadSchema.safeExtend({\n type: z.literal('identify'),\n userId: z.string().min(1).max(255),\n traits: PropertyObjectSchema.optional(),\n});\n\n/**\n * Page event payload schema.\n * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).\n */\nexport const PageEventSchema = BasePayloadSchema.safeExtend({\n type: z.literal('page'),\n name: z.string().max(255).optional(),\n category: z.string().max(255).optional(),\n properties: PropertyObjectSchema.optional(),\n});\n\n/**\n * Screen event payload schema.\n * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).\n */\nexport const ScreenEventSchema = BasePayloadSchema.safeExtend({\n type: z.literal('screen'),\n name: z.string().max(255).optional(),\n category: z.string().max(255).optional(),\n properties: PropertyObjectSchema.optional(),\n});\n\n/**\n * Group event payload schema.\n * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).\n */\nexport const GroupEventSchema = BasePayloadSchema.safeExtend({\n type: z.literal('group'),\n groupId: z.string().min(1).max(255),\n traits: PropertyObjectSchema.optional(),\n});\n\n/**\n * Alias event payload schema.\n * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).\n */\nexport const AliasEventSchema = BasePayloadSchema.safeExtend({\n type: z.literal('alias'),\n userId: z.string().min(1).max(255),\n previousId: z.string().min(1).max(255),\n});\n\n/**\n * Union of all event payload schemas.\n */\nexport const EventPayloadSchema = z.discriminatedUnion('type', [\n TrackEventSchema,\n IdentifyEventSchema,\n PageEventSchema,\n ScreenEventSchema,\n GroupEventSchema,\n AliasEventSchema,\n]);\n\n// =============================================================================\n// Ingestion Request Schemas\n// =============================================================================\n\n/**\n * Single event ingestion request schema.\n */\nexport const SingleEventRequestSchema = EventPayloadSchema;\n\n/**\n * Batch event ingestion request schema.\n *\n * The batch size limit is enforced at the service level via `maxBatchSize` config\n * (default: 100) rather than in the schema, allowing for flexible configuration.\n */\nexport const BatchEventRequestSchema = z.object({\n batch: z.array(EventPayloadSchema).min(1),\n});\n\n/**\n * Combined ingestion request schema - accepts single event or batch.\n */\nexport const IngestionRequestSchema = z.union([BatchEventRequestSchema, SingleEventRequestSchema]);\n\n// =============================================================================\n// Ingestion Response Schemas\n// =============================================================================\n\n/**\n * Event processing result.\n */\nexport const EventResultSchema = z.object({\n /** Server-generated event ID */\n id: z.string().uuid(),\n\n /** Original message ID if provided */\n messageId: z.string().uuid().optional(),\n\n /** Event type */\n type: z.enum(['track', 'identify', 'page', 'screen', 'group', 'alias']),\n\n /** Processing status */\n status: z.enum(['accepted', 'rejected']),\n\n /** Error message if rejected */\n error: z.string().optional(),\n});\n\n/**\n * Successful ingestion response schema.\n */\nexport const IngestionSuccessResponseSchema = z.object({\n /** Whether the request was successful */\n success: z.literal(true),\n\n /** Number of events accepted */\n accepted: z.number().int().nonnegative(),\n\n /** Number of events rejected */\n rejected: z.number().int().nonnegative(),\n\n /** Per-event results */\n results: z.array(EventResultSchema),\n\n /** Server timestamp when events were received */\n receivedAt: z.string().datetime(),\n});\n\n/**\n * Error response schema.\n */\nexport const IngestionErrorResponseSchema = z.object({\n /** Whether the request was successful */\n success: z.literal(false),\n\n /** Error code */\n code: z.string(),\n\n /** Error message */\n error: z.string(),\n\n /** Field-level validation errors */\n fieldErrors: z\n .array(\n z.object({\n path: z.array(z.union([z.string(), z.number()])),\n message: z.string(),\n }),\n )\n .optional(),\n});\n\n/**\n * Combined response schema.\n */\nexport const IngestionResponseSchema = z.union([\n IngestionSuccessResponseSchema,\n IngestionErrorResponseSchema,\n]);\n\n// =============================================================================\n// Type Exports\n// =============================================================================\n\nexport type PropertyValue = z.infer<typeof PropertyValueSchema>;\nexport type PropertyObject = z.infer<typeof PropertyObjectSchema>;\nexport type EmitterContext = z.infer<typeof EmitterContextSchema>;\nexport type TrackEvent = z.infer<typeof TrackEventSchema>;\nexport type IdentifyEvent = z.infer<typeof IdentifyEventSchema>;\nexport type PageEvent = z.infer<typeof PageEventSchema>;\nexport type ScreenEvent = z.infer<typeof ScreenEventSchema>;\nexport type GroupEvent = z.infer<typeof GroupEventSchema>;\nexport type AliasEvent = z.infer<typeof AliasEventSchema>;\nexport type EventPayload = z.infer<typeof EventPayloadSchema>;\nexport type SingleEventRequest = z.infer<typeof SingleEventRequestSchema>;\nexport type BatchEventRequest = z.infer<typeof BatchEventRequestSchema>;\nexport type IngestionRequest = z.infer<typeof IngestionRequestSchema>;\nexport type EventResult = z.infer<typeof EventResultSchema>;\nexport type IngestionSuccessResponse = z.infer<typeof IngestionSuccessResponseSchema>;\nexport type IngestionErrorResponse = z.infer<typeof IngestionErrorResponseSchema>;\nexport type IngestionResponse = z.infer<typeof IngestionResponseSchema>;\n","/**\n * @fileoverview Event Ingestion Service\n *\n * Provides server-side event ingestion functionality. Handles validation,\n * normalization, and forwarding of events to the analytics system.\n *\n * **Key Features**:\n * - Validates incoming events against Zod schemas\n * - Normalizes event payloads with defaults\n * - Forwards events to AnalyticsManager\n * - Tracks ingestion metrics\n * - Handles batch processing efficiently\n *\n * @module @od-oneapp/analytics/shared/ingestion/service\n */\n\nimport { logDebug, logError, logInfo, logWarn } from '@repo/shared/logs';\n\nimport { sanitizeProperties, validateEventName } from '../utils/security';\n\nimport { BatchEventRequestSchema, EventPayloadSchema, IngestionRequestSchema } from './schemas';\n\nimport type {\n BatchEventRequest,\n EmitterContext,\n EventPayload,\n EventResult,\n IngestionErrorResponse,\n IngestionRequest,\n IngestionSuccessResponse,\n} from './schemas';\nimport type { EmitterPayload } from '../emitters/emitter-types';\nimport type { AnalyticsManager } from '../types/types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Ingestion context provided by the caller (usually the API route handler).\n */\nexport interface IngestionContext {\n /** Source identifier (app/module/emitter id) */\n source: string;\n\n /** Tenant/project ID for multi-tenancy */\n tenantId?: string;\n\n /** User ID from auth context */\n userId?: string;\n\n /** Account/organization ID */\n accountId?: string;\n\n /** Client IP address */\n ip?: string;\n\n /** Client user agent */\n userAgent?: string;\n\n /** Environment (dev/stage/prod) */\n environment?: string;\n\n /** Trace ID for observability */\n traceId?: string;\n\n /** Correlation ID for request tracing */\n correlationId?: string;\n\n /** API version */\n apiVersion?: string;\n\n /** SDK version from client */\n sdkVersion?: string;\n}\n\n/**\n * Ingestion service configuration.\n */\nexport interface IngestionServiceConfig {\n /** Maximum events per batch (default: 100) */\n maxBatchSize?: number;\n\n /** Maximum payload size in bytes (default: 1MB) */\n maxPayloadSize?: number;\n\n /** Whether to strip PII from properties (default: true in production) */\n stripPII?: boolean;\n\n /** Whether to strip HTML from properties (default: true) */\n stripHTML?: boolean;\n\n /** Timeout for processing each event in ms (default: 5000) */\n eventTimeout?: number;\n\n /** Concurrency for batch processing (default: 10) */\n batchConcurrency?: number;\n}\n\n/**\n * Ingestion metrics for observability.\n */\nexport interface IngestionMetrics {\n /** Total events received */\n totalReceived: number;\n\n /** Events accepted */\n accepted: number;\n\n /** Events rejected */\n rejected: number;\n\n /** Events by type */\n byType: Record<string, number>;\n\n /** Processing time in ms */\n processingTimeMs: number;\n}\n\n// =============================================================================\n// Default Configuration\n// =============================================================================\n\nconst DEFAULT_CONFIG: Required<IngestionServiceConfig> = {\n maxBatchSize: 100,\n maxPayloadSize: 1024 * 1024, // 1MB\n stripPII: true, // Default to safe; callers can override\n stripHTML: true,\n eventTimeout: 5000,\n batchConcurrency: 10,\n};\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Generate a UUID v4.\n */\nfunction generateUUID(): string {\n return crypto.randomUUID();\n}\n\n/**\n * Get current ISO timestamp.\n */\nfunction getCurrentTimestamp(): string {\n return new Date().toISOString();\n}\n\n/**\n * Check if request is a batch request.\n */\nfunction isBatchRequest(request: IngestionRequest): request is BatchEventRequest {\n return 'batch' in request && Array.isArray(request.batch);\n}\n\n/**\n * Normalize an event payload with defaults and server metadata.\n */\nfunction normalizeEvent(\n event: EventPayload,\n context: IngestionContext,\n receivedAt: string,\n): EventPayload {\n const normalized = { ...event };\n\n // Generate message ID if not provided\n if (!normalized.messageId) {\n normalized.messageId = generateUUID();\n }\n\n // Set timestamp if not provided\n if (!normalized.timestamp) {\n normalized.timestamp = receivedAt;\n }\n\n // Preserve original timestamp\n if (!normalized.originalTimestamp) {\n normalized.originalTimestamp = normalized.timestamp;\n }\n\n // Merge context with server-provided context\n const mergedContext: EmitterContext = {\n ...normalized.context,\n channel: normalized.context?.channel ?? 'api',\n ip: context.ip ?? normalized.context?.ip,\n userAgent: context.userAgent ?? normalized.context?.userAgent,\n library: normalized.context?.library ?? {\n name: context.source,\n version: context.sdkVersion ?? 'unknown',\n },\n };\n\n normalized.context = mergedContext;\n\n // Set userId from auth context if not provided\n if (!normalized.userId && context.userId) {\n normalized.userId = context.userId;\n }\n\n return normalized;\n}\n\n/**\n * Convert EventPayload to EmitterPayload format for AnalyticsManager.\n * Explicitly maps fields to ensure type safety.\n */\nfunction toEmitterPayload(event: EventPayload): EmitterPayload {\n // Explicitly construct the EmitterPayload from EventPayload fields\n // to maintain type safety and avoid unsafe double casts\n const basePayload = {\n userId: event.userId,\n anonymousId: event.anonymousId,\n timestamp: event.timestamp,\n context: event.context,\n messageId: event.messageId,\n };\n\n switch (event.type) {\n case 'track':\n return {\n type: 'track',\n event: event.event,\n properties: event.properties,\n ...basePayload,\n } as EmitterPayload;\n case 'identify':\n return {\n type: 'identify',\n traits: event.traits,\n ...basePayload,\n } as EmitterPayload;\n case 'page':\n return {\n type: 'page',\n name: event.name,\n category: event.category,\n properties: event.properties,\n ...basePayload,\n } as EmitterPayload;\n case 'screen':\n return {\n type: 'screen',\n name: event.name,\n category: event.category,\n properties: event.properties,\n ...basePayload,\n } as EmitterPayload;\n case 'group':\n return {\n type: 'group',\n groupId: event.groupId,\n traits: event.traits,\n ...basePayload,\n } as EmitterPayload;\n case 'alias':\n return {\n type: 'alias',\n previousId: event.previousId,\n ...basePayload,\n } as EmitterPayload;\n default:\n // This should never happen due to discriminated union validation\n throw new Error(`Unknown event type: ${(event as { type: string }).type}`);\n }\n}\n\n// =============================================================================\n// Ingestion Service Class\n// =============================================================================\n\n/**\n * Event Ingestion Service.\n *\n * Handles validation, normalization, and forwarding of analytics events.\n * Designed for high-volume ingestion with batching and rate limiting support.\n *\n * @example\n * ```typescript\n * const service = new IngestionService(analyticsManager);\n *\n * const result = await service.ingest(requestBody, {\n * source: 'web-app',\n * tenantId: 'tenant-123',\n * userId: 'user-456',\n * });\n *\n * if (result.success) {\n * console.log(`Accepted ${result.accepted} events`);\n * }\n * ```\n */\nexport class IngestionService {\n private readonly config: Required<IngestionServiceConfig>;\n\n constructor(\n private readonly analyticsManager: AnalyticsManager,\n config: IngestionServiceConfig = {},\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Parse and validate the ingestion request payload.\n *\n * @param payload - Raw request payload (parsed JSON)\n * @returns Parsed and validated request, or error response\n */\n parseRequest(\n payload: unknown,\n ): { success: true; data: IngestionRequest } | { success: false; error: IngestionErrorResponse } {\n // Validate payload size (rough estimate)\n try {\n const payloadSize = JSON.stringify(payload).length;\n if (payloadSize > this.config.maxPayloadSize) {\n return {\n success: false,\n error: {\n success: false,\n code: 'PAYLOAD_TOO_LARGE',\n error: `Payload size ${payloadSize} exceeds maximum ${this.config.maxPayloadSize} bytes`,\n },\n };\n }\n } catch {\n return {\n success: false,\n error: {\n success: false,\n code: 'INVALID_JSON',\n error: 'Failed to serialize payload for size check',\n },\n };\n }\n\n // Validate against schema\n const result = IngestionRequestSchema.safeParse(payload);\n\n if (!result.success) {\n const fieldErrors = result.error.issues.map(err => ({\n path: err.path.map(p => (typeof p === 'symbol' ? String(p) : p)),\n message: err.message,\n }));\n\n return {\n success: false,\n error: {\n success: false,\n code: 'VALIDATION_ERROR',\n error: 'Request validation failed',\n fieldErrors,\n },\n };\n }\n\n // Check batch size\n if (isBatchRequest(result.data) && result.data.batch.length > this.config.maxBatchSize) {\n return {\n success: false,\n error: {\n success: false,\n code: 'BATCH_TOO_LARGE',\n error: `Batch size ${result.data.batch.length} exceeds maximum ${this.config.maxBatchSize}`,\n },\n };\n }\n\n return { success: true, data: result.data };\n }\n\n /**\n * Ingest events from a validated request.\n *\n * @param request - Validated ingestion request\n * @param context - Ingestion context from the caller\n * @returns Ingestion response with per-event results\n */\n async ingest(\n request: IngestionRequest,\n context: IngestionContext,\n ): Promise<IngestionSuccessResponse> {\n const startTime = process.hrtime.bigint();\n const receivedAt = getCurrentTimestamp();\n\n // Extract events from request (single or batch)\n const events: EventPayload[] = isBatchRequest(request) ? request.batch : [request];\n\n const results: EventResult[] = [];\n const metrics: IngestionMetrics = {\n totalReceived: events.length,\n accepted: 0,\n rejected: 0,\n byType: {},\n processingTimeMs: 0,\n };\n\n // Process events\n const processedEvents: EmitterPayload[] = [];\n\n for (const event of events) {\n const eventId = generateUUID();\n\n try {\n // Validate event name for track events\n if (event.type === 'track') {\n const validation = validateEventName(event.event);\n if (!validation.valid) {\n results.push({\n id: eventId,\n messageId: event.messageId,\n type: event.type,\n status: 'rejected',\n error: `Invalid event name: ${validation.reason}`,\n });\n metrics.rejected++;\n continue;\n }\n }\n\n // Normalize event\n const normalized = normalizeEvent(event, context, receivedAt);\n\n // Sanitize properties\n if ('properties' in normalized && normalized.properties) {\n const sanitized = sanitizeProperties(normalized.properties, {\n stripPII: this.config.stripPII,\n stripHTML: this.config.stripHTML,\n allowDangerousKeys: false,\n });\n (normalized as { properties: Record<string, unknown> }).properties = sanitized.data;\n\n if (sanitized.warnings.length > 0) {\n logDebug('Event properties sanitized', {\n eventId,\n warnings: sanitized.warnings,\n });\n }\n }\n\n if ('traits' in normalized && normalized.traits) {\n const sanitized = sanitizeProperties(normalized.traits, {\n stripPII: this.config.stripPII,\n stripHTML: this.config.stripHTML,\n allowDangerousKeys: false,\n });\n (normalized as { traits: Record<string, unknown> }).traits = sanitized.data;\n }\n\n // Convert to emitter payload and queue for processing\n const emitterPayload = toEmitterPayload(normalized);\n processedEvents.push(emitterPayload);\n\n results.push({\n id: eventId,\n messageId: normalized.messageId,\n type: event.type,\n status: 'accepted',\n });\n\n metrics.accepted++;\n metrics.byType[event.type] = (metrics.byType[event.type] ?? 0) + 1;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\n results.push({\n id: eventId,\n messageId: event.messageId,\n type: event.type,\n status: 'rejected',\n error: errorMessage,\n });\n\n metrics.rejected++;\n\n logWarn('Event processing failed', {\n eventId,\n type: event.type,\n error: errorMessage,\n });\n }\n }\n\n // Forward accepted events to analytics manager (non-blocking)\n // Note: This provides at-most-once delivery semantics. Events may be lost\n // if the analytics manager fails. For guaranteed delivery, consider using\n // a durable queue (Redis, SQS) for critical events.\n if (processedEvents.length > 0) {\n // Use emitBatch for efficient processing\n void (async () => {\n try {\n await this.analyticsManager.emitBatch(processedEvents, {\n timeout: this.config.eventTimeout,\n concurrency: this.config.batchConcurrency,\n failFast: false, // Process all events even if some fail\n });\n } catch (error) {\n logError('Failed to emit events to analytics manager', {\n error: error instanceof Error ? error.message : 'Unknown error',\n eventCount: processedEvents.length,\n traceId: context.traceId,\n });\n }\n })();\n }\n\n // Calculate processing time\n const endTime = process.hrtime.bigint();\n metrics.processingTimeMs = Number(endTime - startTime) / 1_000_000;\n\n // Log metrics\n logInfo('Event ingestion completed', {\n traceId: context.traceId,\n source: context.source,\n tenantId: context.tenantId,\n totalReceived: metrics.totalReceived,\n accepted: metrics.accepted,\n rejected: metrics.rejected,\n processingTimeMs: metrics.processingTimeMs.toFixed(2),\n byType: metrics.byType,\n });\n\n return {\n success: true,\n accepted: metrics.accepted,\n rejected: metrics.rejected,\n results,\n receivedAt,\n };\n }\n\n /**\n * Process a raw request body through parsing and ingestion.\n *\n * Convenience method that combines parseRequest and ingest.\n *\n * @param payload - Raw request payload\n * @param context - Ingestion context\n * @returns Ingestion response (success or error)\n */\n async processRequest(\n payload: unknown,\n context: IngestionContext,\n ): Promise<IngestionSuccessResponse | IngestionErrorResponse> {\n const parseResult = this.parseRequest(payload);\n\n if (!parseResult.success) {\n return parseResult.error;\n }\n\n return this.ingest(parseResult.data, context);\n }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\n/**\n * Create an ingestion service instance.\n *\n * @param analyticsManager - Initialized AnalyticsManager instance\n * @param config - Optional service configuration\n * @returns Configured IngestionService instance\n *\n * @example\n * ```typescript\n * import { createServerAnalytics } from '@od-oneapp/analytics/server';\n * import { createIngestionService } from '@od-oneapp/analytics/server';\n *\n * const analytics = await createServerAnalytics(config);\n * const ingestionService = createIngestionService(analytics);\n * ```\n */\nexport function createIngestionService(\n analyticsManager: AnalyticsManager,\n config?: IngestionServiceConfig,\n): IngestionService {\n return new IngestionService(analyticsManager, config);\n}\n\n/**\n * Validate a single event payload.\n *\n * Useful for pre-validation before queuing events.\n *\n * @param payload - Event payload to validate\n * @returns Validation result with parsed data or errors\n */\nexport function validateEventPayload(\n payload: unknown,\n): { success: true; data: EventPayload } | { success: false; errors: string[] } {\n const result = EventPayloadSchema.safeParse(payload);\n\n if (!result.success) {\n return {\n success: false,\n errors: result.error.issues.map(\n e => `${e.path.map(p => (typeof p === 'symbol' ? String(p) : p)).join('.')}: ${e.message}`,\n ),\n };\n }\n\n return { success: true, data: result.data };\n}\n\n/**\n * Validate a batch of event payloads.\n *\n * @param payloads - Array of event payloads to validate\n * @returns Validation result with parsed data or errors\n */\nexport function validateBatchPayload(\n payloads: unknown[],\n): { success: true; data: EventPayload[] } | { success: false; errors: string[] } {\n const result = BatchEventRequestSchema.safeParse({ batch: payloads });\n\n if (!result.success) {\n return {\n success: false,\n errors: result.error.issues.map(\n e => `${e.path.map(p => (typeof p === 'symbol' ? String(p) : p)).join('.')}: ${e.message}`,\n ),\n };\n }\n\n return { success: true, data: result.data.batch };\n}\n"],"mappings":"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"}
|