autotel
Version:
Write Once, Observe Anywhere
1 lines • 51.9 kB
Source Map (JSON)
{"version":3,"file":"event-CcZYwp50.cjs","names":["getConfig","trace","getOperationContext","getEventsConfig","getOrCreateCorrelationId","context","propagation","validateEvent","getValidationConfig"],"sources":["../src/circuit-breaker.ts","../src/events-config.ts","../src/event.ts"],"sourcesContent":["/**\n * Circuit breaker for event subscribers\n *\n * Prevents cascading failures by fast-failing when an (subscriber) is unhealthy.\n * Uses the circuit breaker pattern with three states:\n * - CLOSED: Normal operation ((subscriber) working)\n * - OPEN: Fast-fail mode ((subscriber) down)\n * - HALF_OPEN: Testing if (subscriber) recovered\n */\n\nexport interface CircuitBreakerConfig {\n /** Number of failures before opening circuit (default: 5) */\n failureThreshold: number;\n /** Time to wait before trying again in ms (default: 30000 = 30s) */\n resetTimeout: number;\n /** Time window for counting failures in ms (default: 60000 = 1min) */\n windowSize: number;\n}\n\nconst DEFAULT_CONFIG: CircuitBreakerConfig = {\n failureThreshold: 5,\n resetTimeout: 30_000, // 30 seconds\n windowSize: 60_000, // 1 minute\n};\n\nexport type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';\n\nexport const CircuitState = {\n CLOSED: 'CLOSED' as const, // Normal operation\n OPEN: 'OPEN' as const, // Fast-fail mode\n HALF_OPEN: 'HALF_OPEN' as const, // Testing recovery\n} as const;\n\ninterface FailureRecord {\n timestamp: number;\n error: string;\n}\n\n/**\n * Circuit breaker implementation\n *\n * Tracks failures and automatically opens the circuit to prevent\n * overwhelming failing subscribers.\n */\nexport class CircuitBreaker {\n private state: CircuitState = CircuitState.CLOSED;\n private failures: FailureRecord[] = [];\n private lastFailureTime: number = 0;\n private readonly config: CircuitBreakerConfig;\n private readonly name: string;\n\n constructor(name: string, config?: Partial<CircuitBreakerConfig>) {\n this.name = name;\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Execute a function with circuit breaker protection\n * Throws CircuitOpenError if circuit is open\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n // Check if circuit is open\n if (this.state === CircuitState.OPEN) {\n // Check if we should transition to half-open\n const now = Date.now();\n if (now - this.lastFailureTime >= this.config.resetTimeout) {\n this.state = CircuitState.HALF_OPEN;\n } else {\n throw new CircuitOpenError(\n `Circuit breaker is OPEN for ${this.name}. ` +\n `Will retry in ${Math.ceil((this.config.resetTimeout - (now - this.lastFailureTime)) / 1000)}s`,\n );\n }\n }\n\n try {\n const result = await fn();\n\n // Success! Close circuit if it was half-open\n if (this.state === CircuitState.HALF_OPEN) {\n this.reset();\n }\n\n return result;\n } catch (error) {\n this.recordFailure(error);\n throw error;\n }\n }\n\n /**\n * Record a failure and potentially open the circuit\n */\n private recordFailure(error: unknown): void {\n const now = Date.now();\n\n // Remove old failures outside the time window\n this.failures = this.failures.filter(\n (f) => now - f.timestamp < this.config.windowSize,\n );\n\n // Record new failure\n this.failures.push({\n timestamp: now,\n error: error instanceof Error ? error.message : String(error),\n });\n\n this.lastFailureTime = now;\n\n // Check if we should open the circuit\n if (this.failures.length >= this.config.failureThreshold) {\n if (this.state === CircuitState.HALF_OPEN) {\n // Failed during test - reopen circuit\n this.state = CircuitState.OPEN;\n } else if (this.state === CircuitState.CLOSED) {\n // Too many failures - open circuit\n this.state = CircuitState.OPEN;\n }\n }\n }\n\n /**\n * Reset the circuit breaker (on success)\n */\n private reset(): void {\n this.state = CircuitState.CLOSED;\n this.failures = [];\n this.lastFailureTime = 0;\n }\n\n /**\n * Get current state (for monitoring)\n */\n getState(): CircuitState {\n return this.state;\n }\n\n /**\n * Get failure count in current window\n */\n getFailureCount(): number {\n const now = Date.now();\n // Clean up old failures\n this.failures = this.failures.filter(\n (f) => now - f.timestamp < this.config.windowSize,\n );\n return this.failures.length;\n }\n\n /**\n * Get recent failures (for debugging)\n */\n getRecentFailures(): FailureRecord[] {\n const now = Date.now();\n return this.failures.filter(\n (f) => now - f.timestamp < this.config.windowSize,\n );\n }\n\n /**\n * Manually reset the circuit breaker (for testing or manual intervention)\n */\n forceReset(): void {\n this.reset();\n }\n\n /**\n * Manually open the circuit (for testing or manual intervention)\n */\n forceOpen(): void {\n this.state = CircuitState.OPEN;\n this.lastFailureTime = Date.now();\n }\n}\n\n/**\n * Error thrown when circuit is open\n */\nexport class CircuitOpenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CircuitOpenError';\n }\n}\n","/**\n * Events configuration types for trace context, correlation IDs, and enrichment\n *\n * @example Basic usage\n * ```typescript\n * import { init } from 'autotel';\n *\n * init({\n * service: 'my-app',\n * events: {\n * includeTraceContext: true,\n * traceUrl: (ctx) => `https://grafana.internal/explore?traceId=${ctx.traceId}`\n * }\n * });\n * ```\n */\n\n/**\n * Context passed to the traceUrl function for generating clickable trace URLs\n */\nexport interface TraceUrlContext {\n /** Trace ID (32 hex chars) - may be undefined outside a trace */\n traceId?: string;\n /** Span ID (16 hex chars) - may be undefined outside a trace */\n spanId?: string;\n /** Correlation ID (always present, 16 hex chars) */\n correlationId: string;\n /** Service name from init config */\n serviceName: string;\n /** Environment from init config */\n environment?: string;\n}\n\n/**\n * Per-key transform options for baggage enrichment\n */\nexport type BaggageTransform = 'plain' | 'hash' | ((value: string) => string);\n\n/**\n * Baggage enrichment configuration with guardrails\n */\nexport interface EnrichFromBaggageConfig {\n /**\n * Allowlist of baggage keys to include in events\n * Supports exact matches and patterns (e.g., 'tenant.*')\n */\n allow: string[];\n\n /**\n * Optional denylist of baggage keys to exclude\n * Takes precedence over allow list\n */\n deny?: string[];\n\n /**\n * Optional prefix to add to all enriched keys\n * @example 'ctx.' results in 'ctx.tenant.id'\n */\n prefix?: string;\n\n /**\n * Maximum number of keys to include (default: 10)\n * Prevents payload bloat from excessive baggage\n */\n maxKeys?: number;\n\n /**\n * Maximum total bytes for enriched values (default: 1024)\n * Prevents payload bloat from large baggage values\n */\n maxBytes?: number;\n\n /**\n * Per-key transform options\n * - 'plain': Include value as-is\n * - 'hash': Hash the value (for PII protection)\n * - function: Custom transform function\n *\n * @example\n * ```typescript\n * transform: {\n * 'user.id': 'hash', // Hash user ID for privacy\n * 'tenant.id': 'plain', // Include tenant ID as-is\n * 'session.id': (v) => v.slice(0, 8) // Custom truncation\n * }\n * ```\n */\n transform?: Record<string, BaggageTransform>;\n}\n\n/**\n * Events configuration for trace context and enrichment\n */\nexport interface EventsConfig {\n /**\n * Include trace context in events (default: false)\n *\n * When enabled, events automatically include:\n * - autotel.trace_id (32 hex chars)\n * - autotel.span_id (16 hex chars)\n * - autotel.trace_flags (2 hex chars)\n * - autotel.trace_state (raw tracestate string, if present)\n * - autotel.correlation_id (always present, 16 hex chars)\n *\n * Subscribers map these to platform-specific names:\n * - PostHog: $trace_id, $span_id\n * - Mixpanel: trace_id, span_id\n */\n includeTraceContext?: boolean;\n\n /**\n * Include full array of linked trace IDs for batch/fan-in scenarios (default: false)\n *\n * When false (default), batch/fan-in events include:\n * - autotel.linked_trace_id_count: Number of linked parents\n * - autotel.linked_trace_id_hash: Stable hash of sorted IDs (keeps payload lean)\n *\n * When true, events also include:\n * - autotel.linked_trace_ids: Full array of linked trace IDs\n */\n includeLinkedTraceIds?: boolean;\n\n /**\n * Generate clickable trace URL from context\n *\n * @param ctx - Trace context with traceId, spanId, correlationId, serviceName, environment\n * @returns URL string or undefined to skip\n *\n * @example Grafana Tempo\n * ```typescript\n * traceUrl: (ctx) => ctx.traceId\n * ? `https://grafana.internal/explore?traceId=${ctx.traceId}`\n * : undefined\n * ```\n *\n * @example Datadog\n * ```typescript\n * traceUrl: (ctx) => ctx.traceId\n * ? `https://app.datadoghq.com/apm/traces?traceId=${ctx.traceId}`\n * : undefined\n * ```\n *\n * @example Jaeger\n * ```typescript\n * traceUrl: (ctx) => ctx.traceId\n * ? `https://jaeger.internal/trace/${ctx.traceId}`\n * : undefined\n * ```\n */\n traceUrl?: (ctx: TraceUrlContext) => string | undefined;\n\n /**\n * Auto-enrich events from baggage with guardrails\n *\n * Automatically includes baggage entries in events without manual code.\n * Apply allow/deny lists and per-key transforms for PII protection.\n *\n * @example Basic allowlist\n * ```typescript\n * enrichFromBaggage: {\n * allow: ['tenant.id', 'user.id', 'request.id']\n * }\n * // Events include: tenant.id, user.id, request.id from baggage\n * ```\n *\n * @example With prefix and transforms\n * ```typescript\n * enrichFromBaggage: {\n * allow: ['tenant.id', 'user.id', 'user.email'],\n * deny: ['user.ssn'],\n * prefix: 'ctx.',\n * transform: {\n * 'user.id': 'hash',\n * 'user.email': 'hash'\n * }\n * }\n * // Events include: ctx.tenant.id, ctx.user.id (hashed), ctx.user.email (hashed)\n * ```\n */\n enrichFromBaggage?: EnrichFromBaggageConfig;\n}\n\n/**\n * Autotel context object attached to event envelopes\n *\n * This structured object is attached to events and subscribers\n * decide how to map/flatten for their platform.\n */\nexport interface AutotelEventContext {\n /** Trace ID (32 hex chars) - present when inside a trace */\n trace_id?: string;\n /** Span ID (16 hex chars) - present when inside a span */\n span_id?: string;\n /** Trace flags (2 hex chars, e.g., '01' for sampled) */\n trace_flags?: string;\n /** Raw tracestate string - present if tracestate exists */\n trace_state?: string;\n /** Clickable trace URL - present if traceUrl config is set */\n trace_url?: string;\n /** Correlation ID (always present, 16 hex chars) */\n correlation_id: string;\n /** Number of linked parent traces (batch/fan-in scenarios) */\n linked_trace_id_count?: number;\n /** Stable hash of linked trace IDs (default for batch/fan-in) */\n linked_trace_id_hash?: string;\n /** Full array of linked trace IDs (only if includeLinkedTraceIds: true) */\n linked_trace_ids?: string[];\n}\n\n/**\n * Hash a string value for PII protection\n *\n * Uses a simple, fast hash function suitable for correlation.\n * NOT cryptographically secure - use for PII masking, not security.\n */\nexport function hashValue(value: string): string {\n let hash = 0;\n for (let i = 0; i < value.length; i++) {\n const char = value.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n // Convert to positive hex string\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Create a stable hash of an array of trace IDs\n *\n * Sorts the array first to ensure deterministic output regardless of order.\n */\nexport function hashLinkedTraceIds(traceIds: string[]): string {\n const sorted = [...traceIds].sort();\n return hashValue(sorted.join(','));\n}\n","/**\n * Events API for product events platforms\n *\n * Track user behavior, business events, and critical actions.\n * Sends to product events platforms (PostHog, Mixpanel, Amplitude) via subscribers.\n * For business people who think in events/funnels.\n *\n * For OpenTelemetry metrics (Prometheus/Grafana), use the Metrics class instead.\n *\n * @example Recommended: Configure subscribers in init(), use track() function\n * ```typescript\n * import { init, track } from 'autotel';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * init({\n * service: 'my-app',\n * subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]\n * });\n *\n * // Track events - uses subscribers from init()\n * track('application.submitted', { jobId: '123', userId: '456' });\n * ```\n *\n * @example Create Event instance (inherits subscribers from init)\n * ```typescript\n * import { Event } from 'autotel/event';\n *\n * // Uses subscribers configured in init()\n * const event = new Event('job-application');\n * event.trackEvent('application.submitted', { jobId: '123' });\n * ```\n *\n * @example Override subscribers for specific Event instance\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * // Override: use different subscribers for this instance\n * const event = new Event('job-application', {\n * subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]\n * });\n *\n * event.trackEvent('application.submitted', { jobId: '123' });\n * ```\n */\n\nimport { trace, propagation, context, TraceFlags } from '@opentelemetry/api';\nimport { type Logger } from './logger';\nimport {\n getLogger,\n getValidationConfig,\n getConfig,\n getEventsConfig,\n} from './init';\nimport {\n type EventSubscriber,\n type EventAttributes,\n type EventAttributesInput,\n type FunnelStatus,\n type OutcomeStatus,\n type AutotelEventContext,\n} from './event-subscriber';\nimport { type EventCollector } from './event-testing';\nimport { CircuitBreaker, CircuitOpenError } from './circuit-breaker';\nimport { validateEvent } from './validation';\nimport { getOperationContext } from './operation-context';\nimport {\n type EnrichFromBaggageConfig,\n hashValue,\n hashLinkedTraceIds,\n} from './events-config';\nimport { getOrCreateCorrelationId } from './correlation-id';\n\n// Re-export types for convenience\nexport type {\n EventAttributes,\n EventAttributesInput,\n FunnelStatus,\n OutcomeStatus,\n} from './event-subscriber';\n\n/**\n * Events class for tracking user behavior and product events\n *\n * Track critical indicators such as:\n * - User events (signups, purchases, feature usage)\n * - Conversion funnels (signup → activation → purchase)\n * - Business outcomes (success/failure rates)\n * - Product metrics (revenue, engagement, retention)\n *\n * All events are sent to events platforms via subscribers (PostHog, Mixpanel, etc.).\n * For OpenTelemetry metrics, use the Metrics class instead.\n */\n/**\n * Events options\n */\nexport interface EventsOptions {\n /** Optional logger for audit trail */\n logger?: Logger;\n /** Optional collector for testing (captures events in memory) */\n collector?: EventCollector;\n /**\n * Optional subscribers to send events to other platforms\n * (e.g., PostHog, Mixpanel, Amplitude)\n *\n * **Subscriber Resolution**:\n * - If provided → uses these subscribers (instance override)\n * - If not provided → falls back to subscribers from `init()` (global config)\n * - If neither → no subscribers (events logged only)\n *\n * Install `autotel-subscribers` package for ready-made subscribers\n */\n subscribers?: EventSubscriber[];\n}\n\nexport class Event {\n private serviceName: string;\n private logger?: Logger;\n private collector?: EventCollector;\n private subscribers: EventSubscriber[];\n private hasSubscribers: boolean; // Cached for performance\n private circuitBreakers: Map<EventSubscriber, CircuitBreaker>; // One per subscriber\n\n /**\n * Create a new Event instance\n *\n * **Note**: Most users should use `init()` + `track()` instead of creating Event instances directly.\n *\n * **Subscriber Resolution**:\n * - If `subscribers` provided in options → uses those (instance override)\n * - If `subscribers` not provided → falls back to subscribers from `init()` (global config)\n * - If neither → no subscribers (events logged only)\n *\n * @param serviceName - Service name for identifying events\n * @param options - Optional configuration (logger, collector, subscribers)\n *\n * @example Recommended: Use track() with init()\n * ```typescript\n * import { init, track } from 'autotel';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * init({\n * service: 'checkout',\n * subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]\n * });\n *\n * track('purchase.completed', { amount: 99.99 });\n * ```\n *\n * @example Inherit subscribers from init()\n * ```typescript\n * // Uses subscribers configured in init()\n * const event = new Event('checkout');\n * event.trackEvent('purchase.completed', { amount: 99.99 });\n * ```\n *\n * @example Override subscribers for this instance\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * // Override: use different subscribers for this instance only\n * const event = new Event('checkout', {\n * subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]\n * });\n * ```\n */\n constructor(serviceName: string, options: EventsOptions = {}) {\n this.serviceName = serviceName;\n this.logger = options.logger;\n this.collector = options.collector;\n\n // Subscriber resolution: instance-level overrides global init() config\n // If subscribers provided to constructor, use those\n // Otherwise, fall back to subscribers from init()\n this.subscribers =\n options.subscribers === undefined\n ? getConfig()?.subscribers || []\n : options.subscribers;\n\n this.hasSubscribers = this.subscribers.length > 0; // Cache for hot path\n\n // Create circuit breaker for each subscriber\n this.circuitBreakers = new Map();\n for (const subscriber of this.subscribers) {\n const subscriberName = subscriber.name || 'Unknown';\n this.circuitBreakers.set(\n subscriber,\n new CircuitBreaker(subscriberName, {\n failureThreshold: 5,\n resetTimeout: 30_000, // 30s\n windowSize: 60_000, // 1min\n }),\n );\n }\n }\n\n /**\n * Automatically enrich attributes with all available telemetry context\n *\n * Auto-captures:\n * - Resource attributes: service.version, deployment.environment\n * - Trace context: traceId, spanId, correlationId\n * - Operation context: operation.name\n */\n private enrichWithTelemetryContext(\n attributes: EventAttributes = {},\n ): EventAttributes {\n const enriched: EventAttributes = {\n service: this.serviceName,\n ...attributes,\n };\n\n // 1. Resource attributes (service-level context)\n const config = getConfig();\n if (config) {\n if (config.version) {\n enriched['service.version'] = config.version;\n }\n if (config.environment) {\n enriched['deployment.environment'] = config.environment;\n }\n }\n\n // 2. Trace context (if inside a traced operation)\n const span = trace.getActiveSpan();\n const spanContext = span?.spanContext();\n if (spanContext) {\n enriched.traceId = spanContext.traceId;\n enriched.spanId = spanContext.spanId;\n // Add correlation ID (first 16 chars of trace ID) for easier log grouping\n enriched.correlationId = spanContext.traceId.slice(0, 16);\n }\n\n // 3. Operation context (if inside a trace/span)\n const operationContext = getOperationContext();\n if (operationContext) {\n enriched['operation.name'] = operationContext.name;\n }\n\n return enriched;\n }\n\n /**\n * Build autotel event context for trace correlation\n *\n * Works in 4 contexts:\n * 1. Inside a span → use current span's trace_id + span_id\n * 2. Outside span but in AsyncLocalStorage context → use trace_id + correlation_id\n * 3. Totally standalone → use correlation_id + service/env/version\n * 4. Batch/fan-in (multiple linked parents) → use count + hash or full array\n *\n * @returns AutotelEventContext or undefined if trace context is disabled\n */\n private buildAutotelContext(): AutotelEventContext | undefined {\n const eventsConfig = getEventsConfig();\n\n // Return undefined if trace context is not enabled\n if (!eventsConfig?.includeTraceContext) {\n // Still generate correlation_id even without full trace context\n // This provides a stable join key across events/logs/spans\n return {\n correlation_id: getOrCreateCorrelationId(),\n };\n }\n\n const config = getConfig();\n const span = trace.getActiveSpan();\n const spanContext = span?.spanContext();\n\n // Always generate a correlation_id\n const correlationId = getOrCreateCorrelationId();\n\n // Build base context\n const autotelContext: AutotelEventContext = {\n correlation_id: correlationId,\n };\n\n // Add trace context if inside a span\n if (spanContext) {\n autotelContext.trace_id = spanContext.traceId;\n autotelContext.span_id = spanContext.spanId;\n\n // Trace flags as 2-char hex string (canonical format)\n autotelContext.trace_flags = spanContext.traceFlags\n .toString(16)\n .padStart(2, '0');\n\n // Tracestate if present\n const traceState = spanContext.traceState;\n if (traceState) {\n // Convert TraceState to string representation safely\n let traceStateStr = '';\n try {\n if (typeof traceState.serialize === 'function') {\n traceStateStr = traceState.serialize();\n }\n } catch {\n // Silently ignore serialization errors - traceState is optional metadata\n }\n if (traceStateStr) {\n autotelContext.trace_state = traceStateStr;\n }\n }\n\n // Generate trace URL if configured\n if (eventsConfig.traceUrl) {\n const traceUrl = eventsConfig.traceUrl({\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n correlationId,\n serviceName: config?.service || this.serviceName,\n environment: config?.environment,\n });\n if (traceUrl) {\n autotelContext.trace_url = traceUrl;\n }\n }\n\n // Handle linked spans (batch/fan-in scenarios)\n // Note: This would require access to span links which are not easily accessible\n // from the public OpenTelemetry API. For now, we skip this unless we have\n // explicit linked trace IDs passed in.\n } else {\n // Outside span but may still have trace URL generator\n if (eventsConfig.traceUrl && config) {\n const traceUrl = eventsConfig.traceUrl({\n correlationId,\n serviceName: config.service,\n environment: config.environment,\n });\n if (traceUrl) {\n autotelContext.trace_url = traceUrl;\n }\n }\n }\n\n return autotelContext;\n }\n\n /**\n * Enrich event attributes from baggage with guardrails\n *\n * @param attributes - Current event attributes\n * @returns Enriched attributes with baggage values\n */\n private enrichFromBaggage(attributes: EventAttributes): EventAttributes {\n const eventsConfig = getEventsConfig();\n const enrichConfig = eventsConfig?.enrichFromBaggage;\n\n if (!enrichConfig) {\n return attributes;\n }\n\n const enriched = { ...attributes };\n const activeContext = context.active();\n const baggage = propagation.getBaggage(activeContext);\n\n if (!baggage) {\n return enriched;\n }\n\n let keyCount = 0;\n let byteCount = 0;\n const maxKeys = enrichConfig.maxKeys ?? 10;\n const maxBytes = enrichConfig.maxBytes ?? 1024;\n const prefix = enrichConfig.prefix ?? '';\n\n // Get all baggage entries\n for (const [key, entry] of baggage.getAllEntries()) {\n // Check if key is allowed\n if (!this.isBaggageKeyAllowed(key, enrichConfig)) {\n continue;\n }\n\n // Check limits\n if (keyCount >= maxKeys) {\n break;\n }\n\n const value = entry.value;\n\n // Apply transform first so maxBytes is checked against transformed size (e.g. hash output)\n const transform = enrichConfig.transform?.[key];\n let transformedValue: string;\n\n if (transform === 'hash') {\n transformedValue = hashValue(value);\n } else if (transform === 'plain' || !transform) {\n transformedValue = value;\n } else if (typeof transform === 'function') {\n transformedValue = transform(value);\n } else {\n transformedValue = value;\n }\n\n const valueBytes = new TextEncoder().encode(transformedValue).length;\n\n if (byteCount + valueBytes > maxBytes) {\n continue; // Skip this entry if transformed value would exceed byte limit\n }\n\n // Add to enriched attributes with prefix\n const enrichedKey = `${prefix}${key}`;\n enriched[enrichedKey] = transformedValue;\n\n keyCount++;\n byteCount += valueBytes;\n }\n\n return enriched;\n }\n\n /**\n * Check if a baggage key is allowed based on config\n */\n private isBaggageKeyAllowed(\n key: string,\n config: EnrichFromBaggageConfig,\n ): boolean {\n // Check deny list first (takes precedence)\n if (config.deny) {\n for (const pattern of config.deny) {\n if (this.matchesBaggagePattern(key, pattern)) {\n return false;\n }\n }\n }\n\n // Check allow list\n for (const pattern of config.allow) {\n if (this.matchesBaggagePattern(key, pattern)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Check if a key matches a baggage pattern\n * Supports exact matches and wildcard patterns (e.g., 'tenant.*')\n */\n private matchesBaggagePattern(key: string, pattern: string): boolean {\n if (pattern.endsWith('.*')) {\n const prefix = pattern.slice(0, -2);\n return key.startsWith(prefix + '.');\n }\n return key === pattern;\n }\n\n /**\n * Track a business event\n *\n * Use this for tracking user actions, business events, product usage:\n * - \"user.signup\"\n * - \"order.completed\"\n * - \"feature.used\"\n *\n * Events are sent to configured subscribers (PostHog, Mixpanel, etc.).\n *\n * @example\n * ```typescript\n * // Track user signup\n * events.trackEvent('user.signup', {\n * userId: '123',\n * plan: 'pro'\n * })\n *\n * // Track order\n * events.trackEvent('order.completed', {\n * orderId: 'ord_123',\n * amount: 99.99\n * })\n * ```\n */\n trackEvent(eventName: string, attributes?: EventAttributes): void {\n // Validate and sanitize input (with custom config if provided)\n const validationConfig = getValidationConfig();\n const validated = validateEvent(\n eventName,\n attributes,\n validationConfig || undefined,\n );\n\n // Auto-attach all available telemetry context\n const enrichedAttributes = this.enrichWithTelemetryContext(\n validated.attributes,\n );\n\n this.logger?.info(\n {\n event: validated.eventName,\n attributes: enrichedAttributes,\n },\n 'Event tracked',\n );\n\n // Record for testing\n this.collector?.recordEvent({\n event: validated.eventName,\n attributes: enrichedAttributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n\n // Notify subscribers (zero overhead if no subscribers)\n // Run in background - don't block event recording\n if (this.hasSubscribers) {\n // Build autotel context for trace correlation\n const autotelContext = this.buildAutotelContext();\n\n // Enrich from baggage if configured\n const finalAttributes = this.enrichFromBaggage(enrichedAttributes);\n\n void this.notifySubscribers((subscriber) =>\n subscriber.trackEvent(validated.eventName, finalAttributes, {\n autotel: autotelContext,\n }),\n );\n }\n }\n\n /**\n * Notify all subscribers concurrently without blocking\n * Uses circuit breakers to protect against failing subscribers\n * Uses Promise.allSettled to prevent subscriber errors from affecting other subscribers\n */\n private async notifySubscribers(\n fn: (subscriber: EventSubscriber) => Promise<void>,\n ): Promise<void> {\n const promises = this.subscribers.map(async (subscriber) => {\n const circuitBreaker = this.circuitBreakers.get(subscriber);\n if (!circuitBreaker) return; // Should never happen\n\n try {\n // Execute with circuit breaker protection\n await circuitBreaker.execute(() => fn(subscriber));\n } catch (error) {\n // Handle circuit open errors (expected behavior when subscriber is down)\n if (error instanceof CircuitOpenError) {\n // Circuit is open - subscriber is down, log at warn level for visibility (same behavior in all environments)\n getLogger().warn(\n {\n subscriberName: subscriber.name || 'Unknown',\n },\n `[Events] ${error.message}`,\n );\n return;\n }\n\n // Log other subscriber errors but don't throw - event failures shouldn't break business logic\n getLogger().error(\n {\n err: error instanceof Error ? error : undefined,\n subscriberName: subscriber.name || 'Unknown',\n },\n `[Events] Subscriber ${subscriber.name || 'Unknown'} failed`,\n );\n }\n });\n\n // Wait for all subscribers (success or failure)\n await Promise.allSettled(promises);\n }\n\n /**\n * Track conversion funnel steps\n *\n * Monitor where users drop off in multi-step processes.\n *\n * @example\n * ```typescript\n * // Track signup funnel\n * events.trackFunnelStep('signup', 'started', { userId: '123' })\n * events.trackFunnelStep('signup', 'email_verified', { userId: '123' })\n * events.trackFunnelStep('signup', 'completed', { userId: '123' })\n *\n * // Track checkout flow\n * events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })\n * events.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })\n * events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })\n * ```\n */\n trackFunnelStep(\n funnelName: string,\n status: FunnelStatus,\n attributes?: EventAttributes,\n ): void {\n // Auto-attach all available telemetry context\n const enrichedAttributes = this.enrichWithTelemetryContext(attributes);\n\n this.logger?.info(\n {\n funnel: funnelName,\n status,\n attributes: enrichedAttributes,\n },\n 'Funnel step tracked',\n );\n\n // Record for testing\n this.collector?.recordFunnelStep({\n funnel: funnelName,\n status,\n attributes: enrichedAttributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n\n // Notify subscribers\n if (this.hasSubscribers) {\n const autotelContext = this.buildAutotelContext();\n const finalAttributes = this.enrichFromBaggage(enrichedAttributes);\n\n void this.notifySubscribers((subscriber) =>\n subscriber.trackFunnelStep(funnelName, status, finalAttributes, {\n autotel: autotelContext,\n }),\n );\n }\n }\n\n /**\n * Track outcomes (success/failure/partial)\n *\n * Monitor success rates of critical operations.\n *\n * @example\n * ```typescript\n * // Track email delivery\n * events.trackOutcome('email.delivery', 'success', {\n * recipientType: 'user',\n * emailType: 'welcome'\n * })\n *\n * events.trackOutcome('email.delivery', 'failure', {\n * recipientType: 'user',\n * errorCode: 'invalid_email'\n * })\n *\n * // Track payment processing\n * events.trackOutcome('payment.process', 'success', { amount: 99.99 })\n * events.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })\n * ```\n */\n trackOutcome(\n operationName: string,\n status: OutcomeStatus,\n attributes?: EventAttributes,\n ): void {\n // Auto-attach all available telemetry context\n const enrichedAttributes = this.enrichWithTelemetryContext(attributes);\n\n this.logger?.info(\n {\n operation: operationName,\n status,\n attributes: enrichedAttributes,\n },\n 'Outcome tracked',\n );\n\n // Record for testing\n this.collector?.recordOutcome({\n operation: operationName,\n status,\n attributes: enrichedAttributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n\n // Notify subscribers\n if (this.hasSubscribers) {\n const autotelContext = this.buildAutotelContext();\n const finalAttributes = this.enrichFromBaggage(enrichedAttributes);\n\n void this.notifySubscribers((subscriber) =>\n subscriber.trackOutcome(operationName, status, finalAttributes, {\n autotel: autotelContext,\n }),\n );\n }\n }\n\n /**\n * Track value metrics\n *\n * Record numerical values like revenue, transaction amounts,\n * item counts, processing times, engagement scores, etc.\n *\n * @example\n * ```typescript\n * // Track revenue\n * events.trackValue('order.revenue', 149.99, {\n * currency: 'USD',\n * productCategory: 'electronics'\n * })\n *\n * // Track items per cart\n * events.trackValue('cart.item_count', 5, {\n * userId: '123'\n * })\n *\n * // Track processing time\n * events.trackValue('api.response_time', 250, {\n * unit: 'ms',\n * endpoint: '/api/checkout'\n * })\n * ```\n */\n trackValue(\n metricName: string,\n value: number,\n attributes?: EventAttributes,\n ): void {\n // Auto-attach all available telemetry context\n const enrichedAttributes = this.enrichWithTelemetryContext({\n metric: metricName,\n ...attributes,\n });\n\n this.logger?.debug(\n {\n metric: metricName,\n value,\n attributes: enrichedAttributes,\n },\n 'Value tracked',\n );\n\n // Record for testing\n this.collector?.recordValue({\n metric: metricName,\n value,\n attributes: enrichedAttributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n\n // Notify subscribers\n if (this.hasSubscribers) {\n const autotelContext = this.buildAutotelContext();\n const finalAttributes = this.enrichFromBaggage(enrichedAttributes);\n\n void this.notifySubscribers((subscriber) =>\n subscriber.trackValue(metricName, value, finalAttributes, {\n autotel: autotelContext,\n }),\n );\n }\n }\n\n /**\n * Flush all subscribers and wait for pending events\n *\n * Call this before shutdown to ensure all events are delivered.\n *\n * @example\n * ```typescript\n * const event =new Event('app', { subscribers: [...] });\n *\n * // Before shutdown\n * await events.flush();\n * ```\n */\n async flush(): Promise<void> {\n if (!this.hasSubscribers) return;\n\n const shutdownPromises = this.subscribers.map(async (subscriber) => {\n if (subscriber.shutdown) {\n try {\n await subscriber.shutdown();\n } catch (error) {\n getLogger().error(\n {\n err: error instanceof Error ? error : undefined,\n subscriberName: subscriber.name || 'Unknown',\n },\n `[Events] Failed to shutdown subscriber ${subscriber.name || 'Unknown'}`,\n );\n }\n }\n });\n\n await Promise.allSettled(shutdownPromises);\n }\n\n /**\n * Shutdown the Event instance and all subscribers\n *\n * Unlike `flush()`, this method:\n * - Shuts down all subscribers\n * - Prevents further event tracking (hasSubscribers becomes false)\n * - Should only be called once at application shutdown\n *\n * @example\n * ```typescript\n * // In Next.js API route with after()\n * import { after } from 'next/server';\n *\n * export async function POST(req: Request) {\n * const event = new Event('checkout', { subscribers: [...] });\n * event.trackEvent('order.completed', { orderId: '123' });\n *\n * after(async () => {\n * await event.shutdown();\n * });\n *\n * return Response.json({ success: true });\n * }\n * ```\n */\n async shutdown(): Promise<void> {\n if (!this.hasSubscribers) return;\n\n await Promise.allSettled(\n this.subscribers.map(async (subscriber) => {\n if (subscriber.shutdown) {\n try {\n await subscriber.shutdown();\n } catch (error) {\n getLogger().error(\n {\n err: error instanceof Error ? error : undefined,\n subscriberName: subscriber.name || 'Unknown',\n },\n `[Events] Failed to shutdown subscriber ${subscriber.name || 'Unknown'}`,\n );\n }\n }\n }),\n );\n\n // Prevent further tracking after shutdown\n this.hasSubscribers = false;\n }\n\n /**\n * Track funnel progression with custom step names\n *\n * Unlike trackFunnelStep which uses FunnelStatus enum values,\n * this method allows any string as the step name for flexible funnel tracking.\n *\n * @param funnelName - Name of the funnel (e.g., \"checkout\", \"onboarding\")\n * @param stepName - Custom step name (e.g., \"cart_viewed\", \"payment_entered\")\n * @param stepNumber - Optional numeric position in the funnel\n * @param attributes - Optional event attributes\n *\n * @example\n * ```typescript\n * // Track custom checkout steps\n * event.trackFunnelProgression('checkout', 'cart_viewed', 1);\n * event.trackFunnelProgression('checkout', 'shipping_selected', 2);\n * event.trackFunnelProgression('checkout', 'payment_entered', 3);\n * event.trackFunnelProgression('checkout', 'order_confirmed', 4);\n * ```\n */\n trackFunnelProgression(\n funnelName: string,\n stepName: string,\n stepNumber?: number,\n attributes?: EventAttributes,\n ): void {\n // Auto-attach all available telemetry context\n const enrichedAttributes = this.enrichWithTelemetryContext(attributes);\n\n this.logger?.info(\n {\n funnel: funnelName,\n stepName,\n stepNumber,\n attributes: enrichedAttributes,\n },\n 'Funnel progression tracked',\n );\n\n // Record for testing (as funnel step with custom name)\n this.collector?.recordFunnelStep({\n funnel: funnelName,\n status: stepName as FunnelStatus, // Cast for testing collector\n attributes: {\n ...enrichedAttributes,\n step_name: stepName,\n ...(stepNumber === undefined ? {} : { step_number: stepNumber }),\n },\n service: this.serviceName,\n timestamp: Date.now(),\n });\n\n // Notify subscribers that support trackFunnelProgression\n if (this.hasSubscribers) {\n const autotelContext = this.buildAutotelContext();\n const finalAttributes = this.enrichFromBaggage(enrichedAttributes);\n\n void this.notifySubscribers(async (subscriber) => {\n await (subscriber.trackFunnelProgression\n ? subscriber.trackFunnelProgression(\n funnelName,\n stepName,\n stepNumber,\n finalAttributes,\n { autotel: autotelContext },\n )\n : // Fall back to trackFunnelStep with step as custom name (cast)\n subscriber.trackFunnelStep(\n funnelName,\n stepName as FunnelStatus,\n {\n ...finalAttributes,\n step_name: stepName,\n ...(stepNumber === undefined\n ? {}\n : { step_number: stepNumber }),\n },\n { autotel: autotelContext },\n ));\n });\n }\n }\n\n /**\n * Track multiple events in a batch\n *\n * Useful for bulk event tracking with consistent timestamps.\n * Events are sent to subscribers individually but processed together.\n *\n * @param events - Array of events to track\n *\n * @example\n * ```typescript\n * event.trackBatch([\n * { name: 'item.viewed', attributes: { itemId: '1' } },\n * { name: 'item.viewed', attributes: { itemId: '2' } },\n * { name: 'cart.updated', attributes: { itemCount: 2 } },\n * ]);\n * ```\n */\n trackBatch(\n events: Array<{ name: string; attributes?: EventAttributesInput }>,\n ): void {\n // Filter attributes and track each event\n for (const event of events) {\n // Filter undefined/null values from attributes\n const filteredAttributes = event.attributes\n ? (Object.fromEntries(\n Object.entries(event.attributes).filter(\n ([, v]) => v !== undefined && v !== null,\n ),\n ) as EventAttributes)\n : undefined;\n\n this.trackEvent(event.name, filteredAttributes);\n }\n }\n}\n\n/**\n * Global events instances (singleton pattern)\n */\nconst eventsInstances = new Map<string, Event>();\n\n/**\n * Get or create an Events instance for a service\n *\n * @param serviceName - Service name for identifying events\n * @param logger - Optional logger\n * @returns Events instance\n *\n * @example\n * ```typescript\n * const event =getEvents('job-application')\n * events.trackEvent('application.submitted', { jobId: '123' })\n * ```\n */\nexport function getEvents(serviceName: string, logger?: Logger): Event {\n if (!eventsInstances.has(serviceName)) {\n eventsInstances.set(serviceName, new Event(serviceName, { logger }));\n }\n return eventsInstances.get(serviceName)!;\n}\n\n/**\n * Reset all events instances (mainly for testing)\n */\nexport function resetEvents(): void {\n eventsInstances.clear();\n}\n"],"mappings":";;;;;;AAmBA,MAAM,iBAAuC;CAC3C,kBAAkB;CAClB,cAAc;CACd,YAAY;AACd;AAIA,MAAa,eAAe;CAC1B,QAAQ;CACR,MAAM;CACN,WAAW;AACb;;;;;;;AAaA,IAAa,iBAAb,MAA4B;CAC1B,AAAQ,QAAsB,aAAa;CAC3C,AAAQ,WAA4B,CAAC;CACrC,AAAQ,kBAA0B;CAClC,AAAiB;CACjB,AAAiB;CAEjB,YAAY,MAAc,QAAwC;EAChE,KAAK,OAAO;EACZ,KAAK,SAAS;GAAE,GAAG;GAAgB,GAAG;EAAO;CAC/C;;;;;CAMA,MAAM,QAAW,IAAkC;EAEjD,IAAI,KAAK,UAAU,aAAa,MAAM;GAEpC,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,MAAM,KAAK,mBAAmB,KAAK,OAAO,cAC5C,KAAK,QAAQ,aAAa;QAE1B,MAAM,IAAI,iBACR,+BAA+B,KAAK,KAAK,kBACtB,KAAK,MAAM,KAAK,OAAO,gBAAgB,MAAM,KAAK,oBAAoB,GAAI,EAAE,EACjG;EAEJ;EAEA,IAAI;GACF,MAAM,SAAS,MAAM,GAAG;GAGxB,IAAI,KAAK,UAAU,aAAa,WAC9B,KAAK,MAAM;GAGb,OAAO;EACT,SAAS,OAAO;GACd,KAAK,cAAc,KAAK;GACxB,MAAM;EACR;CACF;;;;CAKA,AAAQ,cAAc,OAAsB;EAC1C,MAAM,MAAM,KAAK,IAAI;EAGrB,KAAK,WAAW,KAAK,SAAS,QAC3B,MAAM,MAAM,EAAE,YAAY,KAAK,OAAO,UACzC;EAGA,KAAK,SAAS,KAAK;GACjB,WAAW;GACX,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D,CAAC;EAED,KAAK,kBAAkB;EAGvB,IAAI,KAAK,SAAS,UAAU,KAAK,OAAO,kBACtC;OAAI,KAAK,UAAU,aAAa,WAE9B,KAAK,QAAQ,aAAa;QACrB,IAAI,KAAK,UAAU,aAAa,QAErC,KAAK,QAAQ,aAAa;EAC5B;CAEJ;;;;CAKA,AAAQ,QAAc;EACpB,KAAK,QAAQ,aAAa;EAC1B,KAAK,WAAW,CAAC;EACjB,KAAK,kBAAkB;CACzB;;;;CAKA,WAAyB;EACvB,OAAO,KAAK;CACd;;;;CAKA,kBAA0B;EACxB,MAAM,MAAM,KAAK,IAAI;EAErB,KAAK,WAAW,KAAK,SAAS,QAC3B,MAAM,MAAM,EAAE,YAAY,KAAK,OAAO,UACzC;EACA,OAAO,KAAK,SAAS;CACvB;;;;CAKA,oBAAqC;EACnC,MAAM,MAAM,KAAK,IAAI;EACrB,OAAO,KAAK,SAAS,QAClB,MAAM,MAAM,EAAE,YAAY,KAAK,OAAO,UACzC;CACF;;;;CAKA,aAAmB;EACjB,KAAK,MAAM;CACb;;;;CAKA,YAAkB;EAChB,KAAK,QAAQ,aAAa;EAC1B,KAAK,kBAAkB,KAAK,IAAI;CAClC;AACF;;;;AAKA,IAAa,mBAAb,cAAsC,MAAM;CAC1C,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;;;;;;;;ACgCA,SAAgB,UAAU,OAAuB;CAC/C,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM,WAAW,CAAC;EAC/B,QAAQ,QAAQ,KAAK,OAAO;EAC5B,OAAO,OAAO;CAChB;CAEA,QAAQ,SAAS,EAAC,CAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;AAClD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7GA,IAAa,QAAb,MAAmB;CACjB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CR,YAAY,aAAqB,UAAyB,CAAC,GAAG;EAC5D,KAAK,cAAc;EACnB,KAAK,SAAS,QAAQ;EACtB,KAAK,YAAY,QAAQ;EAKzB,KAAK,cACH,QAAQ,gBAAgB,SACpBA,uBAAU,CAAC,EAAE,eAAe,CAAC,IAC7B,QAAQ;EAEd,KAAK,iBAAiB,KAAK,YAAY,SAAS;EAGhD,KAAK,kCAAkB,IAAI,IAAI;EAC/B,KAAK,MAAM,cAAc,KAAK,aAAa;GACzC,MAAM,iBAAiB,WAAW,QAAQ;GAC1C,KAAK,gBAAgB,IACnB,YACA,IAAI,eAAe,gBAAgB;IACjC,kBAAkB;IAClB,cAAc;IACd,YAAY;GACd,CAAC,CACH;EACF;CACF;;;;;;;;;CAUA,AAAQ,2BACN,aAA8B,CAAC,GACd;EACjB,MAAM,WAA4B;GAChC,SAAS,KAAK;GACd,GAAG;EACL;EAGA,MAAM,SAASA,uBAAU;EACzB,IAAI,QAAQ;GACV,IAAI,OAAO,SACT,SAAS,qBAAqB,OAAO;GAEvC,IAAI,OAAO,aACT,SAAS,4BAA4B,OAAO;EAEhD;EAIA,MAAM,cADOC,yBAAM,cACI,CAAC,EAAE,YAAY;EACtC,IAAI,aAAa;GACf,SAAS,UAAU,YAAY;GAC/B,SAAS,SAAS,YAAY;GAE9B,SAAS,gBAAgB,YAAY,QAAQ,MAAM,GAAG,EAAE;EAC1D;EAGA,MAAM,mBAAmBC,8CAAoB;EAC7C,IAAI,kBACF,SAAS,oBAAoB,iBAAiB;EAGhD,OAAO;CACT;;;;;;;;;;;;CAaA,AAAQ,sBAAuD;EAC7D,MAAM,eAAeC,6BAAgB;EAGrC,IAAI,CAAC,cAAc,qBAGjB,OAAO,EACL,gBAAgBC,uCAAyB,EAC3C;EAGF,MAAM,SAASJ,uBAAU;EAEzB,MAAM,cADOC,yBAAM,cACI,CAAC,EAAE,YAAY;EAGtC,MAAM,gBAAgBG,uCAAyB;EAG/C,MAAM,iBAAsC,EAC1C,gBAAgB,cAClB;EAGA,IAAI,aAAa;GACf,eAAe,WAAW,YAAY;GACtC,eAAe,UAAU,YAAY;GAGrC,eAAe,cAAc,YAAY,WACtC,SAAS,EAAE,CAAC,CACZ,SAAS,GAAG,GAAG;GAGlB,MAAM,aAAa,YAAY;GAC/B,IAAI,YAAY;IAEd,IAAI,gBAAgB;IACpB,IAAI;KACF,IAAI,OAAO,WAAW,cAAc,YAClC,gBAAgB,WAAW,UAAU;IAEzC,QAAQ,CAER;IACA,IAAI,eACF,eAAe,cAAc;GAEjC;GAGA,IAAI,aAAa,UAAU;IACzB,MAAM,WAAW,aAAa,SAAS;KACrC,SAAS,YAAY;KACrB,QAAQ,YAAY;KACpB;KACA,aAAa,QAAQ,WAAW,KAAK;KACrC,aAAa,QAAQ;IACvB,CAAC;IACD,IAAI,UACF,eAAe,YAAY;GAE/B;EAMF,OAEE,IAAI,aAAa,YAAY,QAAQ;GACnC,MAAM,WAAW,aAAa,SAAS;IACrC;IACA,aAAa,OAAO;IACpB,aAAa,OAAO;GACtB,CAAC;GACD,IAAI,UACF,eAAe,YAAY;EAE/B;EAGF,OAAO;CACT;;;;;;;CAQA,AAAQ,kBAAkB,YAA8C;EAEtE,MAAM,eADeD,6BACW,CAAC,EAAE;EAEnC,IAAI,CAAC,cACH,OAAO;EAGT,MAAM,WAAW,EAAE,GAAG,WAAW;EACjC,MAAM,gBAAgBE,2BAAQ,OAAO;EACrC,MAAM,UAAUC,+BAAY,WAAW,aAAa;EAEpD,IAAI,CAAC,SACH,OAAO;EAGT,IAAI,WAAW;EACf,IAAI,YAAY;EAChB,MAAM,UAAU,aAAa,WAAW;EACxC,MAAM,WAAW,aAAa,YAAY;EAC1C,MAAM,SAAS,aAAa,UAAU;EAGtC,KAAK,MAAM,CAAC,KAAK,UAAU,QAAQ,cAAc,GAAG;GAElD,IAAI,CAAC,KAAK,oBAAoB,KAAK,YAAY,GAC7C;GAIF,IAAI,YAAY,SACd;GAGF,MAAM,QAAQ,MAAM;GAGpB,MAAM,YAAY,aAAa,YAAY;GAC3C,IAAI;GAEJ,IAAI,cAAc,QAChB,mBAAmB,UAAU,KAAK;QAC7B,IAAI,cAAc,WAAW,CAAC,WACnC,mBAAmB;QACd,IAAI,OAAO,cAAc,YAC9B,mBAAmB,UAAU,KAAK;QAElC,mBAAmB;GAGrB,MAAM,aAAa,IAAI,YAAY,CAAC,CAAC,OAAO,gBAAgB,CAAC,CAAC;GAE9D,IAAI,YAAY,aAAa,UAC3B;GAIF,MAAM,cAAc,GAAG,SAAS;GAChC,SAAS,eAAe;GAExB;GACA,aAAa;EACf;EAEA,OAAO;CACT;;;;CAKA,AAAQ,oBACN,KACA,QACS;EAET,IAAI,OAAO,MACT;QAAK,MAAM,WAAW,OAAO,MAC3B,IAAI,KAAK,sBAAsB,KAAK,OAAO,GACzC,OAAO;EAEX;EAIF,KAAK,MAAM,WAAW,OAAO,OAC3B,IAAI,KAAK,sBAAsB,KAAK,OAAO,GACzC,OAAO;EAIX,OAAO;CACT;;;;;CAMA,AAAQ,sBAAsB,KAAa,SAA0B;EACnE,IAAI,QAAQ,SAAS,IAAI,GAAG;GAC1B,MAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;GAClC,OAAO,IAAI,WAAW,SAAS,GAAG;EACpC;EACA,OAAO,QAAQ;CACjB;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BA,WAAW,WAAmB,YAAoC;EAGhE,MAAM,YAAYC,4BAChB,WACA,YAHuBC,iCAIR,KAAK,MACtB;EAGA,MAAM,qBAAqB,KAAK,2BAC9B,UAAU,UACZ;EAEA,KAAK,QAAQ,KACX;GACE,OAAO,UAAU;GACjB,YAAY;EACd,GACA,eACF;EAGA,KAAK,WAAW,YAAY;GAC1B,OAAO,UAAU;GACjB,YAAY;GACZ,SAAS,KAAK;GACd,WAAW,KAAK,IAAI;EACtB,CAAC;EAID,IAAI,KAAK,gBAAgB;GAEvB,MAAM,iBAAiB,KAAK,oBAAoB;GAGhD,MAAM,kBAAkB,KAAK,kBAAkB,kBAAkB;GAEjE,AAAK,KAAK,mBAAmB,eAC3B,WAAW,WAAW,UAAU,WAAW,iBAAiB,EAC1D,SAAS,eACX,CAAC,CACH;EACF;CACF;;;;;;CAOA,MAAc,kBACZ,IACe;EACf,MAAM,WAAW,KAAK,YAAY,IAAI,OAAO,eAAe;GAC1D,MAAM,iBAAiB,KAAK,gBAAgB,IAAI,UAAU;GAC1D,IAAI,CAAC,gBAAgB;GAErB,IAAI;IAEF,MAAM,eAAe,cAAc,GAAG,UAAU,CAAC;GACnD,SAAS,OAAO;IAEd,IAAI,iBAAiB,kBAAkB;KAErC,uBAAU,CAAC,CAAC,KACV,EACE,gBAAgB,WAAW,QAAQ,UACrC,GACA,YAAY,MAAM,SACpB;KACA;IACF;IAGA,uBAAU,CAAC,CAAC,MACV;KACE,KAAK,iBAAiB,QAAQ,QAAQ;KACtC,gBAAgB,WAAW,QAAQ;IACrC,GACA,uBAAuB,WAAW,QAAQ,UAAU,QACtD;GACF;EACF,CAAC;EAGD,MAAM,QAAQ,WAAW,QAAQ;CACnC;;;;;;;;;;;;;;;;;;;CAoBA,gBACE,YACA,QACA,YACM;EAEN,MAAM,qBAAqB,KAAK,2BAA2B,UAAU;EAErE,KAAK,QAAQ,KACX;GACE,QAAQ;GACR;GACA,YAAY;EACd,GACA,qBACF;EAGA,KAAK,WAAW,iBAAiB;GAC/B,QAAQ;GACR;GACA,YAAY;GACZ,SAAS,KAAK;GACd,WAAW,KAAK,IAAI;EACtB,CAAC;EAGD,IAAI,KAAK,gBAAgB;GACvB,MAAM,iBAAiB,KAAK,oBAAoB;GAChD,MAAM,kBAAkB,KAAK,kBAAkB,kBAAkB;GAEjE,AAAK,KAAK,mBAAmB,eAC3B,WAAW,gBAAgB,YAAY,QAAQ,iBAAiB,EAC9D,SAAS,eACX,CAAC,CACH;EACF;CACF;;;;;;;;;;;;;;;;;;;;;;;;CAyBA,aACE,eACA,QACA,YACM;EAEN,MAAM,qBAAqB,KAAK,2BAA2B,UAAU;EAErE,KAAK,QAAQ,KACX;GACE,WAAW;GACX;GACA,YAAY;EACd,GACA,iBACF;EAGA,KAAK,WAAW,cAAc;GAC5B,WAAW;GACX;GACA,YAAY;GACZ,SAAS,KAAK;GACd,WAAW,KAAK,IAAI;EACtB,CAAC;EAGD,IAAI,KAAK,gBAAgB;GACvB,MAAM,iBAAiB,KAAK,oBAAoB;GAChD,MAAM,kBAAkB,KAAK,kBAAkB,kBAAkB;GAEjE,AAAK,KAAK,mBAAmB,eAC3B,WAAW,aAAa,eAAe,QAAQ,iBAAiB,EAC9D,SAAS,eACX,CAAC,CACH;EACF;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BA,WACE,YACA,OACA,YACM;EAEN,MAAM,qBAAqB,KAAK,2BAA2B;GACzD,QAAQ;GACR,GAAG;EACL,CAAC;EAED,KAAK,QAAQ,MACX;GACE,QAAQ;GACR;GACA,YAAY;EACd,GACA,eACF;EAGA,KAAK,WAAW,YAAY;GAC1B,QAAQ;GACR;GACA,YAAY;GACZ,SAAS,KAAK;GACd,WAAW,KAAK,IAAI;EACtB,CAAC;EAGD,IAAI,KAAK,gBAAgB;GACvB,MAAM,iBAAiB,KAAK,oBAAoB;GAChD,MAAM,kBAAkB,KAAK,kBAAkB,kBAAkB;GAEjE,AAAK,