autotel-subscribers 45.0.0 → 46.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/amplitude.cjs +1 -2
  2. package/dist/amplitude.d.cts +1 -2
  3. package/dist/amplitude.d.ts +1 -2
  4. package/dist/amplitude.js +1 -2
  5. package/dist/architecture-snapshot.cjs +1 -2
  6. package/dist/architecture-snapshot.d.cts +1 -2
  7. package/dist/architecture-snapshot.d.ts +1 -2
  8. package/dist/architecture-snapshot.js +1 -2
  9. package/dist/event-subscriber-base-C_F3Ycbe.cjs +1 -2
  10. package/dist/event-subscriber-base-Day2ml6V.js +1 -2
  11. package/dist/event-subscriber-base-DpZclJM4.d.cts +1 -2
  12. package/dist/event-subscriber-base-DpZclJM4.d.ts +1 -2
  13. package/dist/factories.cjs +1 -2
  14. package/dist/factories.d.cts +1 -2
  15. package/dist/factories.d.ts +1 -2
  16. package/dist/factories.js +1 -2
  17. package/dist/file.cjs +1 -2
  18. package/dist/file.d.cts +1 -2
  19. package/dist/file.d.ts +1 -2
  20. package/dist/file.js +1 -2
  21. package/dist/index.cjs +3 -2
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +2 -2
  25. package/dist/loki.cjs +229 -0
  26. package/dist/loki.d.cts +119 -0
  27. package/dist/loki.d.ts +119 -0
  28. package/dist/loki.js +221 -0
  29. package/dist/middleware.cjs +1 -2
  30. package/dist/middleware.d.cts +1 -2
  31. package/dist/middleware.d.ts +1 -2
  32. package/dist/middleware.js +1 -2
  33. package/dist/mixpanel.cjs +1 -2
  34. package/dist/mixpanel.d.cts +1 -2
  35. package/dist/mixpanel.d.ts +1 -2
  36. package/dist/mixpanel.js +1 -2
  37. package/dist/posthog-ByP8BmHb.js +1 -2
  38. package/dist/posthog-DOOBZanH.cjs +1 -2
  39. package/dist/posthog.d.cts +1 -2
  40. package/dist/posthog.d.ts +1 -2
  41. package/dist/security.cjs +1 -2
  42. package/dist/security.d.cts +1 -2
  43. package/dist/security.d.ts +1 -2
  44. package/dist/security.js +1 -2
  45. package/dist/segment.cjs +1 -2
  46. package/dist/segment.d.cts +1 -2
  47. package/dist/segment.d.ts +1 -2
  48. package/dist/segment.js +1 -2
  49. package/dist/slack.cjs +1 -2
  50. package/dist/slack.d.cts +1 -2
  51. package/dist/slack.d.ts +1 -2
  52. package/dist/slack.js +1 -2
  53. package/dist/webhook-delivery-D3v50C81.js +1 -2
  54. package/dist/webhook-delivery-DZeUlZhi.cjs +1 -2
  55. package/dist/webhook.cjs +1 -2
  56. package/dist/webhook.d.cts +1 -2
  57. package/dist/webhook.d.ts +1 -2
  58. package/dist/webhook.js +1 -2
  59. package/package.json +17 -10
  60. package/dist/amplitude.cjs.map +0 -1
  61. package/dist/amplitude.d.cts.map +0 -1
  62. package/dist/amplitude.d.ts.map +0 -1
  63. package/dist/amplitude.js.map +0 -1
  64. package/dist/architecture-snapshot.cjs.map +0 -1
  65. package/dist/architecture-snapshot.d.cts.map +0 -1
  66. package/dist/architecture-snapshot.d.ts.map +0 -1
  67. package/dist/architecture-snapshot.js.map +0 -1
  68. package/dist/event-subscriber-base-C_F3Ycbe.cjs.map +0 -1
  69. package/dist/event-subscriber-base-Day2ml6V.js.map +0 -1
  70. package/dist/event-subscriber-base-DpZclJM4.d.cts.map +0 -1
  71. package/dist/event-subscriber-base-DpZclJM4.d.ts.map +0 -1
  72. package/dist/factories.cjs.map +0 -1
  73. package/dist/factories.d.cts.map +0 -1
  74. package/dist/factories.d.ts.map +0 -1
  75. package/dist/factories.js.map +0 -1
  76. package/dist/file.cjs.map +0 -1
  77. package/dist/file.d.cts.map +0 -1
  78. package/dist/file.d.ts.map +0 -1
  79. package/dist/file.js.map +0 -1
  80. package/dist/index.cjs.map +0 -1
  81. package/dist/index.d.cts.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/middleware.cjs.map +0 -1
  85. package/dist/middleware.d.cts.map +0 -1
  86. package/dist/middleware.d.ts.map +0 -1
  87. package/dist/middleware.js.map +0 -1
  88. package/dist/mixpanel.cjs.map +0 -1
  89. package/dist/mixpanel.d.cts.map +0 -1
  90. package/dist/mixpanel.d.ts.map +0 -1
  91. package/dist/mixpanel.js.map +0 -1
  92. package/dist/posthog-ByP8BmHb.js.map +0 -1
  93. package/dist/posthog-DOOBZanH.cjs.map +0 -1
  94. package/dist/posthog.d.cts.map +0 -1
  95. package/dist/posthog.d.ts.map +0 -1
  96. package/dist/security.cjs.map +0 -1
  97. package/dist/security.d.cts.map +0 -1
  98. package/dist/security.d.ts.map +0 -1
  99. package/dist/security.js.map +0 -1
  100. package/dist/segment.cjs.map +0 -1
  101. package/dist/segment.d.cts.map +0 -1
  102. package/dist/segment.d.ts.map +0 -1
  103. package/dist/segment.js.map +0 -1
  104. package/dist/slack.cjs.map +0 -1
  105. package/dist/slack.d.cts.map +0 -1
  106. package/dist/slack.d.ts.map +0 -1
  107. package/dist/slack.js.map +0 -1
  108. package/dist/webhook-delivery-D3v50C81.js.map +0 -1
  109. package/dist/webhook-delivery-DZeUlZhi.cjs.map +0 -1
  110. package/dist/webhook.cjs.map +0 -1
  111. package/dist/webhook.d.cts.map +0 -1
  112. package/dist/webhook.d.ts.map +0 -1
  113. package/dist/webhook.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * Subscriber middleware system for transforming, filtering, and enriching events\n *\n * Provides composable middleware for adding cross-cutting concerns:\n * - Retry logic, rate limiting, and circuit breaker patterns\n * - Event filtering, transformation, and enrichment\n * - Idempotency and batching\n * - Event logging and observability\n *\n * @example\n * ```typescript\n * import { applyMiddleware, retryMiddleware, rateLimitMiddleware } from 'autotel-subscribers/middleware'\n *\n * const subscriber = applyMiddleware(\n * createPostHogSubscriber({ apiKey: '...' }),\n * [\n * retryMiddleware({ maxRetries: 3 }),\n * rateLimitMiddleware({ requestsPerSecond: 100 })\n * ]\n * )\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\n/** Normalized event for middleware processing */\nexport type EventsEvent =\n | {\n type: 'event';\n name: string;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n }\n | {\n type: 'funnel';\n funnel: string;\n step: FunnelStatus;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n }\n | {\n type: 'outcome';\n operation: string;\n outcome: OutcomeStatus;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n }\n | {\n type: 'value';\n name: string;\n value: number;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n };\n\nexport type SendEventRecord = {\n subscriberName: string;\n eventType: EventsEvent['type'];\n eventName: string;\n status: 'success' | 'error';\n durationMs: number;\n startedAt: Date;\n endedAt: Date;\n error?: { name: string; message: string; code?: string };\n};\n\nexport type SendEventSink = {\n write(event: SendEventRecord): Promise<void>;\n};\n\nexport type RateLimitAlgorithm = 'fixed' | 'sliding';\nexport type RateLimitRecord = { count: number; resetAtMs: number };\n\nexport type RateLimitStore = {\n record(\n key: string,\n windowMs: number,\n algorithm: RateLimitAlgorithm,\n ): Promise<RateLimitRecord>;\n};\n\nexport type IdempotencyStore<TResult = unknown> = {\n get(key: string): Promise<TResult | null>;\n set(key: string, result: TResult, ttlMs: number): Promise<void>;\n};\n\nexport type SubscriberMiddleware<\n TCtxIn = Record<string, unknown>,\n TCtxOut = TCtxIn,\n> = (params: {\n event: EventsEvent;\n ctx: TCtxIn;\n subscriber: Pick<EventSubscriber, 'name' | 'version'>;\n next: (update?: {\n event?: EventsEvent;\n ctxPatch?: Partial<TCtxOut>;\n }) => Promise<void>;\n}) => Promise<void>;\n\n/** Type-safe middleware factory helper */\nexport const createMiddleware = <\n TCtxIn = Record<string, unknown>,\n TCtxOut = TCtxIn,\n>(\n fn: SubscriberMiddleware<TCtxIn, TCtxOut>,\n): SubscriberMiddleware<TCtxIn, TCtxOut> => fn;\n\nfunction eventNameOf(event: EventsEvent): string {\n switch (event.type) {\n case 'event':\n case 'value': {\n return event.name;\n }\n case 'funnel': {\n return `${event.funnel}.${event.step}`;\n }\n case 'outcome': {\n return `${event.operation}.${event.outcome}`;\n }\n }\n}\n\nfunction defaultContextFactory() {\n return {} as Record<string, unknown>;\n}\n\nasync function dispatchEvent(\n subscriber: EventSubscriber,\n event: EventsEvent,\n): Promise<void> {\n switch (event.type) {\n case 'event': {\n await subscriber.trackEvent(event.name, event.attributes, event.options);\n return;\n }\n case 'funnel': {\n await subscriber.trackFunnelStep(\n event.funnel,\n event.step,\n event.attributes,\n event.options,\n );\n return;\n }\n case 'outcome': {\n await subscriber.trackOutcome(\n event.operation,\n event.outcome,\n event.attributes,\n event.options,\n );\n return;\n }\n case 'value': {\n await subscriber.trackValue(\n event.name,\n event.value,\n event.attributes,\n event.options,\n );\n }\n }\n}\n\n/**\n * Apply middleware to a subscriber\n *\n * Chains middleware in order, each can transform events or context before passing to next\n *\n * @example\n * ```typescript\n * const enriched = applyMiddleware(\n * subscriber,\n * [enrichmentMiddleware(event => ({ ...event, timestamp: Date.now() }))]\n * )\n * ```\n */\nexport function applyMiddleware<TCtx = Record<string, unknown>>(\n subscriber: EventSubscriber,\n middlewares: Array<SubscriberMiddleware<TCtx, TCtx>>,\n options?: { initialContext?: () => TCtx },\n): EventSubscriber {\n const runChain = async (initialEvent: EventsEvent): Promise<void> => {\n const baseCtx = (\n options?.initialContext ?? defaultContextFactory\n )() as TCtx;\n\n const execute = async (\n index: number,\n event: EventsEvent,\n ctx: TCtx,\n ): Promise<void> => {\n const middleware = middlewares[index];\n if (!middleware) {\n await dispatchEvent(subscriber, event);\n return;\n }\n\n await middleware({\n event,\n ctx,\n subscriber,\n next: async (update) => {\n const nextEvent = update?.event ?? event;\n const nextCtx = update?.ctxPatch\n ? ({ ...ctx, ...update.ctxPatch } as TCtx)\n : ctx;\n await execute(index + 1, nextEvent, nextCtx);\n },\n });\n };\n\n await execute(0, initialEvent, baseCtx);\n };\n\n return {\n name: `${subscriber.name}(middleware)`,\n version: subscriber.version,\n trackEvent: async (name, attributes, options_) =>\n runChain({ type: 'event', name, attributes, options: options_ }),\n trackFunnelStep: async (funnel, step, attributes, options_) =>\n runChain({ type: 'funnel', funnel, step, attributes, options: options_ }),\n trackOutcome: async (operation, outcome, attributes, options_) =>\n runChain({\n type: 'outcome',\n operation,\n outcome,\n attributes,\n options: options_,\n }),\n trackValue: async (name, value, attributes, options_) =>\n runChain({ type: 'value', name, value, attributes, options: options_ }),\n shutdown: async () => {\n await subscriber.shutdown?.();\n },\n };\n}\n\n/**\n * Retry middleware with exponential backoff\n *\n * Automatically retries failed events with exponential backoff delay\n */\nexport function retryMiddleware(options: {\n maxRetries?: number;\n delayMs?: number;\n onRetry?: (attempt: number, error: Error, event: EventsEvent) => void;\n}): SubscriberMiddleware {\n const { maxRetries = 3, delayMs = 1000, onRetry } = options;\n\n return async ({ event, next }) => {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n await next();\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < maxRetries) {\n onRetry?.(attempt, lastError, event);\n await new Promise((resolve) =>\n setTimeout(resolve, delayMs * 2 ** (attempt - 1)),\n );\n }\n }\n }\n\n throw lastError;\n };\n}\n\n/**\n * Sampling middleware to reduce event volume\n *\n * Only passes through a percentage of events based on the rate (0.0 to 1.0)\n */\nexport function samplingMiddleware(rate: number): SubscriberMiddleware {\n if (rate < 0 || rate > 1)\n throw new Error('Sample rate must be between 0 and 1');\n\n return async ({ next }) => {\n if (Math.random() < rate) await next();\n };\n}\n\n/**\n * Enrichment middleware to add or modify event data\n *\n * Apply a transformation function to each event before sending\n */\nexport function enrichmentMiddleware(\n enricher: (event: EventsEvent) => EventsEvent,\n): SubscriberMiddleware {\n return async ({ event, next }) => {\n await next({ event: enricher(event) });\n };\n}\n\n/**\n * Logging middleware for debugging event flow\n *\n * Logs event type and name (optionally full event) to console\n */\nexport function loggingMiddleware(\n options: { prefix?: string; logAttributes?: boolean } = {},\n): SubscriberMiddleware {\n const { prefix = '[Events]', logAttributes = false } = options;\n\n return async ({ event, next }) => {\n if (logAttributes) {\n console.log(prefix, event.type, event);\n } else {\n console.log(prefix, event.type, eventNameOf(event));\n }\n await next();\n };\n}\n\n/**\n * Filter middleware to selectively process events\n *\n * Only forwards events that match the predicate\n */\nexport function filterMiddleware(\n predicate: (event: EventsEvent) => boolean,\n): SubscriberMiddleware {\n return async ({ event, next }) => {\n if (predicate(event)) await next();\n };\n}\n\n/**\n * Transform middleware to modify event structure\n *\n * Similar to enrichment but replaces entire event\n */\nexport function transformMiddleware(\n transformer: (event: EventsEvent) => EventsEvent,\n): SubscriberMiddleware {\n return async ({ event, next }) => {\n await next({ event: transformer(event) });\n };\n}\n\n/**\n * Batching middleware to group events for bulk sending\n *\n * Collects events into batches before forwarding\n */\nexport function batchingMiddleware(options: {\n batchSize?: number;\n flushInterval?: number;\n}): SubscriberMiddleware {\n const { batchSize = 100, flushInterval = 5000 } = options;\n const queue: Array<() => Promise<void>> = [];\n let timer: NodeJS.Timeout | null = null;\n\n const flush = async () => {\n const pending = queue.splice(0);\n await Promise.all(pending.map((run) => run()));\n };\n\n return async ({ next }) => {\n await new Promise<void>((resolve, reject) => {\n queue.push(async () => {\n try {\n await next();\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n\n if (queue.length >= batchSize) {\n if (timer) clearTimeout(timer);\n timer = null;\n void flush().catch(reject);\n return;\n }\n\n if (!timer) {\n timer = setTimeout(() => {\n timer = null;\n void flush().catch(reject);\n }, flushInterval);\n }\n });\n };\n}\n\n/**\n * Rate limit middleware to control event sending rate\n *\n * Enforces a maximum rate of events sent per second\n */\nexport function rateLimitMiddleware(options: {\n requestsPerSecond: number;\n}): SubscriberMiddleware {\n const intervalMs = 1000 / options.requestsPerSecond;\n let lastAt = 0;\n\n return async ({ next }) => {\n const now = Date.now();\n const waitMs = Math.max(0, intervalMs - (now - lastAt));\n if (waitMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n lastAt = Date.now();\n await next();\n };\n}\n\n/**\n * Circuit breaker middleware for fault tolerance\n *\n * Prevents cascading failures by stopping requests when error rate exceeds threshold\n */\nexport function circuitBreakerMiddleware(options: {\n failureThreshold?: number;\n timeout?: number;\n onOpen?: () => void;\n onClose?: () => void;\n}): SubscriberMiddleware {\n const { failureThreshold = 5, timeout = 60_000, onOpen, onClose } = options;\n let failureCount = 0;\n let lastFailureAt = 0;\n let open = false;\n\n return async ({ next }) => {\n if (open) {\n if (Date.now() - lastFailureAt > timeout) {\n open = false;\n failureCount = 0;\n onClose?.();\n } else {\n throw new Error('Circuit breaker is open');\n }\n }\n\n try {\n await next();\n failureCount = 0;\n } catch (error) {\n failureCount += 1;\n lastFailureAt = Date.now();\n if (failureCount >= failureThreshold) {\n open = true;\n onOpen?.();\n }\n throw error;\n }\n };\n}\n\n/**\n * Timeout middleware to prevent hanging requests\n *\n * Rejects the event if processing exceeds the specified timeout\n */\nexport function timeoutMiddleware(options: {\n timeoutMs: number;\n}): SubscriberMiddleware {\n return async ({ next }) => {\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(\n () => reject(new Error(`Timeout after ${options.timeoutMs}ms`)),\n options.timeoutMs,\n );\n });\n\n await Promise.race([next(), timeoutPromise]);\n };\n}\n\n/**\n * Create an idempotency store with custom get/set operations\n *\n * Used with withIdempotency middleware to prevent duplicate event processing\n */\nexport function createIdempotencyStore<TResult = unknown>(options: {\n get: (key: string) => Promise<TResult | null>;\n set: (key: string, result: TResult, ttlMs: number) => Promise<void>;\n}): IdempotencyStore<TResult> {\n return { get: options.get, set: options.set };\n}\n\n/**\n * In-memory idempotency store\n *\n * Useful for testing or single-process applications\n */\nexport function inMemoryIdempotencyStore<\n TResult = unknown,\n>(): IdempotencyStore<TResult> {\n type Entry = { result: TResult; expiresAtMs: number };\n const map = new Map<string, Entry>();\n\n return createIdempotencyStore<TResult>({\n get: async (key) => {\n const entry = map.get(key);\n if (!entry) return null;\n if (entry.expiresAtMs <= Date.now()) {\n map.delete(key);\n return null;\n }\n return entry.result;\n },\n set: async (key, result, ttlMs) => {\n map.set(key, { result, expiresAtMs: Date.now() + ttlMs });\n },\n });\n}\n\n/**\n * Create a rate limit store with custom record operation\n *\n * Used with withRateLimit middleware for distributed rate limiting\n */\nexport function createRateLimitStore(options: {\n record: (\n key: string,\n windowMs: number,\n algorithm: RateLimitAlgorithm,\n ) => Promise<RateLimitRecord>;\n}): RateLimitStore {\n return { record: options.record };\n}\n\n/**\n * In-memory rate limit store\n *\n * Supports fixed and sliding window algorithms\n */\nexport function inMemoryRateLimitStore(): RateLimitStore {\n type FixedEntry = { count: number; expireAtMs: number };\n const fixed = new Map<string, FixedEntry>();\n const sliding = new Map<string, number[]>();\n\n return {\n async record(key, windowMs, algorithm) {\n const now = Date.now();\n if (algorithm === 'fixed') {\n const entry = fixed.get(key);\n if (!entry || entry.expireAtMs <= now) {\n const next = { count: 1, expireAtMs: now + windowMs };\n fixed.set(key, next);\n return { count: 1, resetAtMs: next.expireAtMs };\n }\n entry.count += 1;\n return { count: entry.count, resetAtMs: entry.expireAtMs };\n }\n\n const cutoff = now - windowMs;\n const points = (sliding.get(key) ?? []).filter((ts) => ts > cutoff);\n points.push(now);\n sliding.set(key, points);\n return { count: points.length, resetAtMs: points[0]! + windowMs };\n },\n };\n}\n\n/**\n * Idempotency middleware to prevent duplicate processing\n *\n * Skips processing if the same event (by key) was processed within TTL\n *\n * @example\n * ```typescript\n * const middleware = withIdempotency({\n * store: inMemoryIdempotencyStore(),\n * key: event => event.name,\n * ttlMs: 60000\n * })\n * ```\n */\nexport function withIdempotency(options: {\n store: IdempotencyStore<boolean>;\n key: string | ((event: EventsEvent) => string);\n ttlMs: number;\n}): SubscriberMiddleware {\n return async ({ event, next }) => {\n const key =\n typeof options.key === 'function' ? options.key(event) : options.key;\n const cached = await options.store.get(key);\n if (cached) return;\n\n await next();\n await options.store.set(key, true, options.ttlMs);\n };\n}\n\n/**\n * Rate limiting middleware with store-based tracking\n *\n * Enforces rate limits across distributed systems using a store backend\n *\n * @example\n * ```typescript\n * const middleware = withRateLimit({\n * store: inMemoryRateLimitStore(),\n * key: event => 'global',\n * max: 1000,\n * windowMs: 60000\n * })\n * ```\n */\nexport function withRateLimit(options: {\n store: RateLimitStore;\n key: string | ((event: EventsEvent) => string);\n max: number;\n windowMs: number;\n algorithm?: RateLimitAlgorithm;\n}): SubscriberMiddleware {\n const algorithm = options.algorithm ?? 'fixed';\n\n return async ({ event, next }) => {\n const key =\n typeof options.key === 'function' ? options.key(event) : options.key;\n const { count, resetAtMs } = await options.store.record(\n key,\n options.windowMs,\n algorithm,\n );\n if (count > options.max) {\n const retryAfterMs = Math.max(0, resetAtMs - Date.now());\n throw new Error(\n `Rate limited for key \"${key}\". Retry after ${retryAfterMs}ms.`,\n );\n }\n await next();\n };\n}\n\n/**\n * Event logger middleware for observability\n *\n * Writes event records to a sink for monitoring, audit logging, or analytics\n *\n * @example\n * ```typescript\n * const middleware = withEventLogger({\n * sink: {\n * write: async (record) => {\n * await db.eventLogs.insert(record)\n * }\n * }\n * })\n * ```\n */\nexport function withEventLogger(options: {\n sink: SendEventSink;\n}): SubscriberMiddleware {\n return async ({ event, next, subscriber }) => {\n const startedAt = new Date();\n const start = performance.now();\n\n try {\n await next();\n const endedAt = new Date();\n await options.sink.write({\n subscriberName: subscriber.name ?? 'unknown',\n eventType: event.type,\n eventName: eventNameOf(event),\n status: 'success',\n durationMs: performance.now() - start,\n startedAt,\n endedAt,\n });\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n const endedAt = new Date();\n const maybeCode = (err as { code?: unknown }).code;\n\n await options.sink.write({\n subscriberName: subscriber.name ?? 'unknown',\n eventType: event.type,\n eventName: eventNameOf(event),\n status: 'error',\n durationMs: performance.now() - start,\n startedAt,\n endedAt,\n error: {\n name: err.name,\n message: err.message,\n code: typeof maybeCode === 'string' ? maybeCode : undefined,\n },\n });\n throw err;\n }\n };\n}\n"],"mappings":";;AA0GA,MAAa,oBAIX,OAC0C;AAE5C,SAAS,YAAY,OAA4B;CAC/C,QAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK,SACH,OAAO,MAAM;EAEf,KAAK,UACH,OAAO,GAAG,MAAM,OAAO,GAAG,MAAM;EAElC,KAAK,WACH,OAAO,GAAG,MAAM,UAAU,GAAG,MAAM;CAEvC;AACF;AAEA,SAAS,wBAAwB;CAC/B,OAAO,CAAC;AACV;AAEA,eAAe,cACb,YACA,OACe;CACf,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,YAAY,MAAM,OAAO;GACvE;EAEF,KAAK;GACH,MAAM,WAAW,gBACf,MAAM,QACN,MAAM,MACN,MAAM,YACN,MAAM,OACR;GACA;EAEF,KAAK;GACH,MAAM,WAAW,aACf,MAAM,WACN,MAAM,SACN,MAAM,YACN,MAAM,OACR;GACA;EAEF,KAAK,SACH,MAAM,WAAW,WACf,MAAM,MACN,MAAM,OACN,MAAM,YACN,MAAM,OACR;CAEJ;AACF;;;;;;;;;;;;;;AAeA,SAAgB,gBACd,YACA,aACA,SACiB;CACjB,MAAM,WAAW,OAAO,iBAA6C;EACnE,MAAM,WACJ,SAAS,kBAAkB,sBAAqB,CAChD;EAEF,MAAM,UAAU,OACd,OACA,OACA,QACkB;GAClB,MAAM,aAAa,YAAY;GAC/B,IAAI,CAAC,YAAY;IACf,MAAM,cAAc,YAAY,KAAK;IACrC;GACF;GAEA,MAAM,WAAW;IACf;IACA;IACA;IACA,MAAM,OAAO,WAAW;KACtB,MAAM,YAAY,QAAQ,SAAS;KACnC,MAAM,UAAU,QAAQ,WACnB;MAAE,GAAG;MAAK,GAAG,OAAO;KAAS,IAC9B;KACJ,MAAM,QAAQ,QAAQ,GAAG,WAAW,OAAO;IAC7C;GACF,CAAC;EACH;EAEA,MAAM,QAAQ,GAAG,cAAc,OAAO;CACxC;CAEA,OAAO;EACL,MAAM,GAAG,WAAW,KAAK;EACzB,SAAS,WAAW;EACpB,YAAY,OAAO,MAAM,YAAY,aACnC,SAAS;GAAE,MAAM;GAAS;GAAM;GAAY,SAAS;EAAS,CAAC;EACjE,iBAAiB,OAAO,QAAQ,MAAM,YAAY,aAChD,SAAS;GAAE,MAAM;GAAU;GAAQ;GAAM;GAAY,SAAS;EAAS,CAAC;EAC1E,cAAc,OAAO,WAAW,SAAS,YAAY,aACnD,SAAS;GACP,MAAM;GACN;GACA;GACA;GACA,SAAS;EACX,CAAC;EACH,YAAY,OAAO,MAAM,OAAO,YAAY,aAC1C,SAAS;GAAE,MAAM;GAAS;GAAM;GAAO;GAAY,SAAS;EAAS,CAAC;EACxE,UAAU,YAAY;GACpB,MAAM,WAAW,WAAW;EAC9B;CACF;AACF;;;;;;AAOA,SAAgB,gBAAgB,SAIP;CACvB,MAAM,EAAE,aAAa,GAAG,UAAU,KAAM,YAAY;CAEpD,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,YAAY,WAC3C,IAAI;GACF,MAAM,KAAK;GACX;EACF,SAAS,OAAO;GACd,YAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GACpE,IAAI,UAAU,YAAY;IACxB,UAAU,SAAS,WAAW,KAAK;IACnC,MAAM,IAAI,SAAS,YACjB,WAAW,SAAS,UAAU,MAAM,UAAU,EAAE,CAClD;GACF;EACF;EAGF,MAAM;CACR;AACF;;;;;;AAOA,SAAgB,mBAAmB,MAAoC;CACrE,IAAI,OAAO,KAAK,OAAO,GACrB,MAAM,IAAI,MAAM,qCAAqC;CAEvD,OAAO,OAAO,EAAE,WAAW;EACzB,IAAI,KAAK,OAAO,IAAI,MAAM,MAAM,KAAK;CACvC;AACF;;;;;;AAOA,SAAgB,qBACd,UACsB;CACtB,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,MAAM,KAAK,EAAE,OAAO,SAAS,KAAK,EAAE,CAAC;CACvC;AACF;;;;;;AAOA,SAAgB,kBACd,UAAwD,CAAC,GACnC;CACtB,MAAM,EAAE,SAAS,YAAY,gBAAgB,UAAU;CAEvD,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,IAAI,eACF,QAAQ,IAAI,QAAQ,MAAM,MAAM,KAAK;OAErC,QAAQ,IAAI,QAAQ,MAAM,MAAM,YAAY,KAAK,CAAC;EAEpD,MAAM,KAAK;CACb;AACF;;;;;;AAOA,SAAgB,iBACd,WACsB;CACtB,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,IAAI,UAAU,KAAK,GAAG,MAAM,KAAK;CACnC;AACF;;;;;;AAOA,SAAgB,oBACd,aACsB;CACtB,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,MAAM,KAAK,EAAE,OAAO,YAAY,KAAK,EAAE,CAAC;CAC1C;AACF;;;;;;AAOA,SAAgB,mBAAmB,SAGV;CACvB,MAAM,EAAE,YAAY,KAAK,gBAAgB,QAAS;CAClD,MAAM,QAAoC,CAAC;CAC3C,IAAI,QAA+B;CAEnC,MAAM,QAAQ,YAAY;EACxB,MAAM,UAAU,MAAM,OAAO,CAAC;EAC9B,MAAM,QAAQ,IAAI,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC;CAC/C;CAEA,OAAO,OAAO,EAAE,WAAW;EACzB,MAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,KAAK,YAAY;IACrB,IAAI;KACF,MAAM,KAAK;KACX,QAAQ;IACV,SAAS,OAAO;KACd,OAAO,KAAK;IACd;GACF,CAAC;GAED,IAAI,MAAM,UAAU,WAAW;IAC7B,IAAI,OAAO,aAAa,KAAK;IAC7B,QAAQ;IACR,AAAK,MAAM,CAAC,CAAC,MAAM,MAAM;IACzB;GACF;GAEA,IAAI,CAAC,OACH,QAAQ,iBAAiB;IACvB,QAAQ;IACR,AAAK,MAAM,CAAC,CAAC,MAAM,MAAM;GAC3B,GAAG,aAAa;EAEpB,CAAC;CACH;AACF;;;;;;AAOA,SAAgB,oBAAoB,SAEX;CACvB,MAAM,aAAa,MAAO,QAAQ;CAClC,IAAI,SAAS;CAEb,OAAO,OAAO,EAAE,WAAW;EAEzB,MAAM,SAAS,KAAK,IAAI,GAAG,cADf,KAAK,IAC0B,IAAI,OAAO;EACtD,IAAI,SAAS,GACX,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;EAE5D,SAAS,KAAK,IAAI;EAClB,MAAM,KAAK;CACb;AACF;;;;;;AAOA,SAAgB,yBAAyB,SAKhB;CACvB,MAAM,EAAE,mBAAmB,GAAG,UAAU,KAAQ,QAAQ,YAAY;CACpE,IAAI,eAAe;CACnB,IAAI,gBAAgB;CACpB,IAAI,OAAO;CAEX,OAAO,OAAO,EAAE,WAAW;EACzB,IAAI,MACF,IAAI,KAAK,IAAI,IAAI,gBAAgB,SAAS;GACxC,OAAO;GACP,eAAe;GACf,UAAU;EACZ,OACE,MAAM,IAAI,MAAM,yBAAyB;EAI7C,IAAI;GACF,MAAM,KAAK;GACX,eAAe;EACjB,SAAS,OAAO;GACd,gBAAgB;GAChB,gBAAgB,KAAK,IAAI;GACzB,IAAI,gBAAgB,kBAAkB;IACpC,OAAO;IACP,SAAS;GACX;GACA,MAAM;EACR;CACF;AACF;;;;;;AAOA,SAAgB,kBAAkB,SAET;CACvB,OAAO,OAAO,EAAE,WAAW;EACzB,MAAM,iBAAiB,IAAI,SAAgB,GAAG,WAAW;GACvD,iBACQ,uBAAO,IAAI,MAAM,iBAAiB,QAAQ,UAAU,GAAG,CAAC,GAC9D,QAAQ,SACV;EACF,CAAC;EAED,MAAM,QAAQ,KAAK,CAAC,KAAK,GAAG,cAAc,CAAC;CAC7C;AACF;;;;;;AAOA,SAAgB,uBAA0C,SAG5B;CAC5B,OAAO;EAAE,KAAK,QAAQ;EAAK,KAAK,QAAQ;CAAI;AAC9C;;;;;;AAOA,SAAgB,2BAEe;CAE7B,MAAM,sBAAM,IAAI,IAAmB;CAEnC,OAAO,uBAAgC;EACrC,KAAK,OAAO,QAAQ;GAClB,MAAM,QAAQ,IAAI,IAAI,GAAG;GACzB,IAAI,CAAC,OAAO,OAAO;GACnB,IAAI,MAAM,eAAe,KAAK,IAAI,GAAG;IACnC,IAAI,OAAO,GAAG;IACd,OAAO;GACT;GACA,OAAO,MAAM;EACf;EACA,KAAK,OAAO,KAAK,QAAQ,UAAU;GACjC,IAAI,IAAI,KAAK;IAAE;IAAQ,aAAa,KAAK,IAAI,IAAI;GAAM,CAAC;EAC1D;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,qBAAqB,SAMlB;CACjB,OAAO,EAAE,QAAQ,QAAQ,OAAO;AAClC;;;;;;AAOA,SAAgB,yBAAyC;CAEvD,MAAM,wBAAQ,IAAI,IAAwB;CAC1C,MAAM,0BAAU,IAAI,IAAsB;CAE1C,OAAO,EACL,MAAM,OAAO,KAAK,UAAU,WAAW;EACrC,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,cAAc,SAAS;GACzB,MAAM,QAAQ,MAAM,IAAI,GAAG;GAC3B,IAAI,CAAC,SAAS,MAAM,cAAc,KAAK;IACrC,MAAM,OAAO;KAAE,OAAO;KAAG,YAAY,MAAM;IAAS;IACpD,MAAM,IAAI,KAAK,IAAI;IACnB,OAAO;KAAE,OAAO;KAAG,WAAW,KAAK;IAAW;GAChD;GACA,MAAM,SAAS;GACf,OAAO;IAAE,OAAO,MAAM;IAAO,WAAW,MAAM;GAAW;EAC3D;EAEA,MAAM,SAAS,MAAM;EACrB,MAAM,UAAU,QAAQ,IAAI,GAAG,KAAK,CAAC,EAAC,CAAE,QAAQ,OAAO,KAAK,MAAM;EAClE,OAAO,KAAK,GAAG;EACf,QAAQ,IAAI,KAAK,MAAM;EACvB,OAAO;GAAE,OAAO,OAAO;GAAQ,WAAW,OAAO,KAAM;EAAS;CAClE,EACF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,SAIP;CACvB,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,MAAM,MACJ,OAAO,QAAQ,QAAQ,aAAa,QAAQ,IAAI,KAAK,IAAI,QAAQ;EAEnE,IAAI,MADiB,QAAQ,MAAM,IAAI,GAAG,GAC9B;EAEZ,MAAM,KAAK;EACX,MAAM,QAAQ,MAAM,IAAI,KAAK,MAAM,QAAQ,KAAK;CAClD;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,cAAc,SAML;CACvB,MAAM,YAAY,QAAQ,aAAa;CAEvC,OAAO,OAAO,EAAE,OAAO,WAAW;EAChC,MAAM,MACJ,OAAO,QAAQ,QAAQ,aAAa,QAAQ,IAAI,KAAK,IAAI,QAAQ;EACnE,MAAM,EAAE,OAAO,cAAc,MAAM,QAAQ,MAAM,OAC/C,KACA,QAAQ,UACR,SACF;EACA,IAAI,QAAQ,QAAQ,KAAK;GACvB,MAAM,eAAe,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC;GACvD,MAAM,IAAI,MACR,yBAAyB,IAAI,iBAAiB,aAAa,IAC7D;EACF;EACA,MAAM,KAAK;CACb;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,gBAAgB,SAEP;CACvB,OAAO,OAAO,EAAE,OAAO,MAAM,iBAAiB;EAC5C,MAAM,4BAAY,IAAI,KAAK;EAC3B,MAAM,QAAQ,YAAY,IAAI;EAE9B,IAAI;GACF,MAAM,KAAK;GACX,MAAM,0BAAU,IAAI,KAAK;GACzB,MAAM,QAAQ,KAAK,MAAM;IACvB,gBAAgB,WAAW,QAAQ;IACnC,WAAW,MAAM;IACjB,WAAW,YAAY,KAAK;IAC5B,QAAQ;IACR,YAAY,YAAY,IAAI,IAAI;IAChC;IACA;GACF,CAAC;EACH,SAAS,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GACpE,MAAM,0BAAU,IAAI,KAAK;GACzB,MAAM,YAAa,IAA2B;GAE9C,MAAM,QAAQ,KAAK,MAAM;IACvB,gBAAgB,WAAW,QAAQ;IACnC,WAAW,MAAM;IACjB,WAAW,YAAY,KAAK;IAC5B,QAAQ;IACR,YAAY,YAAY,IAAI,IAAI;IAChC;IACA;IACA,OAAO;KACL,MAAM,IAAI;KACV,SAAS,IAAI;KACb,MAAM,OAAO,cAAc,WAAW,YAAY;IACpD;GACF,CAAC;GACD,MAAM;EACR;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mixpanel.cjs","names":[],"sources":["../src/mixpanel.ts"],"sourcesContent":["/**\n * Mixpanel Subscriber for autotel\n *\n * Send events to Mixpanel for product events.\n *\n * @example\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { MixpanelSubscriber } from 'autotel-subscribers/mixpanel';\n *\n * const events = new Event('checkout', {\n * subscribers: [\n * new MixpanelSubscriber({\n * token: process.env.MIXPANEL_TOKEN!\n * })\n * ]\n * });\n *\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n EventTrackingOptions,\n AutotelEventContext,\n} from 'autotel/event-subscriber';\n\nexport interface MixpanelConfig {\n /** Mixpanel project token */\n token: string;\n /** Enable/disable the subscriber */\n enabled?: boolean;\n}\n\nexport class MixpanelSubscriber implements EventSubscriber {\n readonly name = 'MixpanelSubscriber';\n readonly version = '1.0.0';\n\n private mixpanel: any;\n private enabled: boolean;\n private config: MixpanelConfig;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: MixpanelConfig) {\n this.enabled = config.enabled ?? true;\n this.config = config;\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Dynamic import to avoid adding mixpanel as a hard dependency\n const Mixpanel = await import('mixpanel');\n this.mixpanel = Mixpanel.default.init(this.config.token);\n } catch (error) {\n console.error(\n 'Mixpanel subscriber failed to initialize. Install mixpanel: pnpm add mixpanel',\n error,\n );\n this.enabled = false;\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n /**\n * Map autotel context to Mixpanel properties\n *\n * Mixpanel uses standard snake_case field names:\n * - autotel.trace_id → trace_id\n * - autotel.span_id → span_id\n * - autotel.correlation_id → correlation_id\n */\n private mapAutotelContext(\n autotel?: AutotelEventContext,\n ): Record<string, unknown> {\n if (!autotel) return {};\n\n const mapped: Record<string, unknown> = {};\n\n if (autotel.trace_id) {\n mapped.trace_id = autotel.trace_id;\n }\n if (autotel.span_id) {\n mapped.span_id = autotel.span_id;\n }\n if (autotel.correlation_id) {\n mapped.correlation_id = autotel.correlation_id;\n }\n if (autotel.trace_flags) {\n mapped.trace_flags = autotel.trace_flags;\n }\n if (autotel.trace_state) {\n mapped.trace_state = autotel.trace_state;\n }\n if (autotel.trace_url) {\n mapped.trace_url = autotel.trace_url;\n }\n // Batch/fan-in context\n if (autotel.linked_trace_id_count !== undefined) {\n mapped.linked_trace_id_count = autotel.linked_trace_id_count;\n }\n if (autotel.linked_trace_id_hash) {\n mapped.linked_trace_id_hash = autotel.linked_trace_id_hash;\n }\n if (autotel.linked_trace_ids) {\n mapped.linked_trace_ids = autotel.linked_trace_ids;\n }\n\n return mapped;\n }\n\n async trackEvent(\n name: string,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(name, {\n distinct_id: distinctId,\n ...attributes,\n ...autotelProps,\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(`${funnelName}.${step}`, {\n distinct_id: distinctId,\n funnel: funnelName,\n step,\n ...attributes,\n ...autotelProps,\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(`${operationName}.${outcome}`, {\n distinct_id: distinctId,\n operation: operationName,\n outcome,\n ...attributes,\n ...autotelProps,\n });\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(name, {\n distinct_id: distinctId,\n value,\n ...attributes,\n ...autotelProps,\n });\n }\n}\n"],"mappings":";;;AAsCA,IAAa,qBAAb,MAA2D;CACzD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,cAAoC;CAE5C,YAAY,QAAwB;EAClC,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS;EAEd,IAAI,KAAK,SAEP,KAAK,cAAc,KAAK,WAAW;CAEvC;CAEA,MAAc,aAA4B;EACxC,IAAI;GAEF,MAAM,WAAW,MAAM,OAAO;GAC9B,KAAK,WAAW,SAAS,QAAQ,KAAK,KAAK,OAAO,KAAK;EACzD,SAAS,OAAO;GACd,QAAQ,MACN,iFACA,KACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAc,oBAAmC;EAC/C,IAAI,KAAK,aAAa;GACpB,MAAM,KAAK;GACX,KAAK,cAAc;EACrB;CACF;;;;;;;;;CAUA,AAAQ,kBACN,SACyB;EACzB,IAAI,CAAC,SAAS,OAAO,CAAC;EAEtB,MAAM,SAAkC,CAAC;EAEzC,IAAI,QAAQ,UACV,OAAO,WAAW,QAAQ;EAE5B,IAAI,QAAQ,SACV,OAAO,UAAU,QAAQ;EAE3B,IAAI,QAAQ,gBACV,OAAO,iBAAiB,QAAQ;EAElC,IAAI,QAAQ,aACV,OAAO,cAAc,QAAQ;EAE/B,IAAI,QAAQ,aACV,OAAO,cAAc,QAAQ;EAE/B,IAAI,QAAQ,WACV,OAAO,YAAY,QAAQ;EAG7B,IAAI,QAAQ,0BAA0B,QACpC,OAAO,wBAAwB,QAAQ;EAEzC,IAAI,QAAQ,sBACV,OAAO,uBAAuB,QAAQ;EAExC,IAAI,QAAQ,kBACV,OAAO,mBAAmB,QAAQ;EAGpC,OAAO;CACT;CAEA,MAAM,WACJ,MACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,MAAM;GACzB,aAAa;GACb,GAAG;GACH,GAAG;EACL,CAAC;CACH;CAEA,MAAM,gBACJ,YACA,MACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,GAAG,WAAW,GAAG,QAAQ;GAC5C,aAAa;GACb,QAAQ;GACR;GACA,GAAG;GACH,GAAG;EACL,CAAC;CACH;CAEA,MAAM,aACJ,eACA,SACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,GAAG,cAAc,GAAG,WAAW;GAClD,aAAa;GACb,WAAW;GACX;GACA,GAAG;GACH,GAAG;EACL,CAAC;CACH;CAEA,MAAM,WACJ,MACA,OACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,MAAM;GACzB,aAAa;GACb;GACA,GAAG;GACH,GAAG;EACL,CAAC;CACH;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mixpanel.d.cts","names":[],"sources":["../src/mixpanel.ts"],"mappings":";;UA+BiB;;EAEf;;EAEA;;cAGW,8BAA8B;WAChC;WACA;UAED;UACA;UACA;UACA;EAEI,YAAA,QAAQ;UAUN;UAcA;;;;;;;;;UAeN;EAuCF,WACJ,cACA,aAAa,iBACb,UAAU,uBACT;EAcG,gBACJ,oBACA,MAAM,cACN,aAAa,iBACb,UAAU,uBACT;EAgBG,aACJ,uBACA,SAAS,eACT,aAAa,iBACb,UAAU,uBACT;EAgBG,WACJ,cACA,eACA,aAAa,iBACb,UAAU,uBACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mixpanel.d.ts","names":[],"sources":["../src/mixpanel.ts"],"mappings":";;UA+BiB;;EAEf;;EAEA;;cAGW,8BAA8B;WAChC;WACA;UAED;UACA;UACA;UACA;EAEI,YAAA,QAAQ;UAUN;UAcA;;;;;;;;;UAeN;EAuCF,WACJ,cACA,aAAa,iBACb,UAAU,uBACT;EAcG,gBACJ,oBACA,MAAM,cACN,aAAa,iBACb,UAAU,uBACT;EAgBG,aACJ,uBACA,SAAS,eACT,aAAa,iBACb,UAAU,uBACT;EAgBG,WACJ,cACA,eACA,aAAa,iBACb,UAAU,uBACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mixpanel.js","names":[],"sources":["../src/mixpanel.ts"],"sourcesContent":["/**\n * Mixpanel Subscriber for autotel\n *\n * Send events to Mixpanel for product events.\n *\n * @example\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { MixpanelSubscriber } from 'autotel-subscribers/mixpanel';\n *\n * const events = new Event('checkout', {\n * subscribers: [\n * new MixpanelSubscriber({\n * token: process.env.MIXPANEL_TOKEN!\n * })\n * ]\n * });\n *\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n EventTrackingOptions,\n AutotelEventContext,\n} from 'autotel/event-subscriber';\n\nexport interface MixpanelConfig {\n /** Mixpanel project token */\n token: string;\n /** Enable/disable the subscriber */\n enabled?: boolean;\n}\n\nexport class MixpanelSubscriber implements EventSubscriber {\n readonly name = 'MixpanelSubscriber';\n readonly version = '1.0.0';\n\n private mixpanel: any;\n private enabled: boolean;\n private config: MixpanelConfig;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: MixpanelConfig) {\n this.enabled = config.enabled ?? true;\n this.config = config;\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Dynamic import to avoid adding mixpanel as a hard dependency\n const Mixpanel = await import('mixpanel');\n this.mixpanel = Mixpanel.default.init(this.config.token);\n } catch (error) {\n console.error(\n 'Mixpanel subscriber failed to initialize. Install mixpanel: pnpm add mixpanel',\n error,\n );\n this.enabled = false;\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n /**\n * Map autotel context to Mixpanel properties\n *\n * Mixpanel uses standard snake_case field names:\n * - autotel.trace_id → trace_id\n * - autotel.span_id → span_id\n * - autotel.correlation_id → correlation_id\n */\n private mapAutotelContext(\n autotel?: AutotelEventContext,\n ): Record<string, unknown> {\n if (!autotel) return {};\n\n const mapped: Record<string, unknown> = {};\n\n if (autotel.trace_id) {\n mapped.trace_id = autotel.trace_id;\n }\n if (autotel.span_id) {\n mapped.span_id = autotel.span_id;\n }\n if (autotel.correlation_id) {\n mapped.correlation_id = autotel.correlation_id;\n }\n if (autotel.trace_flags) {\n mapped.trace_flags = autotel.trace_flags;\n }\n if (autotel.trace_state) {\n mapped.trace_state = autotel.trace_state;\n }\n if (autotel.trace_url) {\n mapped.trace_url = autotel.trace_url;\n }\n // Batch/fan-in context\n if (autotel.linked_trace_id_count !== undefined) {\n mapped.linked_trace_id_count = autotel.linked_trace_id_count;\n }\n if (autotel.linked_trace_id_hash) {\n mapped.linked_trace_id_hash = autotel.linked_trace_id_hash;\n }\n if (autotel.linked_trace_ids) {\n mapped.linked_trace_ids = autotel.linked_trace_ids;\n }\n\n return mapped;\n }\n\n async trackEvent(\n name: string,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(name, {\n distinct_id: distinctId,\n ...attributes,\n ...autotelProps,\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(`${funnelName}.${step}`, {\n distinct_id: distinctId,\n funnel: funnelName,\n step,\n ...attributes,\n ...autotelProps,\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(`${operationName}.${outcome}`, {\n distinct_id: distinctId,\n operation: operationName,\n outcome,\n ...attributes,\n ...autotelProps,\n });\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n const distinctId = attributes?.userId || attributes?.user_id || 'anonymous';\n const autotelProps = this.mapAutotelContext(options?.autotel);\n\n this.mixpanel?.track(name, {\n distinct_id: distinctId,\n value,\n ...attributes,\n ...autotelProps,\n });\n }\n}\n"],"mappings":";AAsCA,IAAa,qBAAb,MAA2D;CACzD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,cAAoC;CAE5C,YAAY,QAAwB;EAClC,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS;EAEd,IAAI,KAAK,SAEP,KAAK,cAAc,KAAK,WAAW;CAEvC;CAEA,MAAc,aAA4B;EACxC,IAAI;GAEF,MAAM,WAAW,MAAM,OAAO;GAC9B,KAAK,WAAW,SAAS,QAAQ,KAAK,KAAK,OAAO,KAAK;EACzD,SAAS,OAAO;GACd,QAAQ,MACN,iFACA,KACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAc,oBAAmC;EAC/C,IAAI,KAAK,aAAa;GACpB,MAAM,KAAK;GACX,KAAK,cAAc;EACrB;CACF;;;;;;;;;CAUA,AAAQ,kBACN,SACyB;EACzB,IAAI,CAAC,SAAS,OAAO,CAAC;EAEtB,MAAM,SAAkC,CAAC;EAEzC,IAAI,QAAQ,UACV,OAAO,WAAW,QAAQ;EAE5B,IAAI,QAAQ,SACV,OAAO,UAAU,QAAQ;EAE3B,IAAI,QAAQ,gBACV,OAAO,iBAAiB,QAAQ;EAElC,IAAI,QAAQ,aACV,OAAO,cAAc,QAAQ;EAE/B,IAAI,QAAQ,aACV,OAAO,cAAc,QAAQ;EAE/B,IAAI,QAAQ,WACV,OAAO,YAAY,QAAQ;EAG7B,IAAI,QAAQ,0BAA0B,QACpC,OAAO,wBAAwB,QAAQ;EAEzC,IAAI,QAAQ,sBACV,OAAO,uBAAuB,QAAQ;EAExC,IAAI,QAAQ,kBACV,OAAO,mBAAmB,QAAQ;EAGpC,OAAO;CACT;CAEA,MAAM,WACJ,MACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,MAAM;GACzB,aAAa;GACb,GAAG;GACH,GAAG;EACL,CAAC;CACH;CAEA,MAAM,gBACJ,YACA,MACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,GAAG,WAAW,GAAG,QAAQ;GAC5C,aAAa;GACb,QAAQ;GACR;GACA,GAAG;GACH,GAAG;EACL,CAAC;CACH;CAEA,MAAM,aACJ,eACA,SACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,GAAG,cAAc,GAAG,WAAW;GAClD,aAAa;GACb,WAAW;GACX;GACA,GAAG;GACH,GAAG;EACL,CAAC;CACH;CAEA,MAAM,WACJ,MACA,OACA,YACA,SACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,MAAM,aAAa,YAAY,UAAU,YAAY,WAAW;EAChE,MAAM,eAAe,KAAK,kBAAkB,SAAS,OAAO;EAE5D,KAAK,UAAU,MAAM,MAAM;GACzB,aAAa;GACb;GACA,GAAG;GACH,GAAG;EACL,CAAC;CACH;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"posthog-ByP8BmHb.js","names":[],"sources":["../src/posthog-error-formatter.ts","../src/posthog.ts"],"sourcesContent":["/**\n * Format errors for PostHog's $exception event format.\n * Compatible with autotel-web's ExceptionList type.\n */\n\ntype StringRedactor = (value: string) => string;\n\ninterface StackFrame {\n filename?: string;\n function?: string;\n lineno?: number;\n colno?: number;\n abs_path?: string;\n in_app?: boolean;\n platform?: string;\n}\n\ninterface ExceptionRecord {\n type: string;\n value: string;\n mechanism: { type: string; handled: boolean };\n stacktrace?: { frames: StackFrame[] };\n}\n\ninterface PostHogExceptionProperties {\n $exception_list: Array<{\n type: string;\n value: string;\n mechanism: { type: string; handled: boolean };\n stacktrace: { frames: Array<StackFrame & { platform: string }> };\n }>;\n}\n\nconst MAX_CAUSE_DEPTH = 5;\n\nexport function formatExceptionForPostHog(\n exceptionList: ExceptionRecord[],\n platform: string = 'web:javascript',\n redactor?: StringRedactor,\n): PostHogExceptionProperties {\n return {\n $exception_list: exceptionList.map((ex) => ({\n type: ex.type,\n value: redactor ? redactor(ex.value) : ex.value,\n mechanism: ex.mechanism,\n stacktrace: {\n frames: (ex.stacktrace?.frames || []).map((frame) => ({\n ...frame,\n abs_path:\n frame.abs_path && redactor\n ? redactor(frame.abs_path)\n : frame.abs_path,\n platform,\n })),\n },\n })),\n };\n}\n\nexport function errorToExceptionList(\n input: unknown,\n redactor?: StringRedactor,\n): ExceptionRecord[] {\n const error =\n input instanceof Error\n ? input\n : new Error(\n input === null || input === undefined\n ? 'Unknown error'\n : String(input),\n );\n\n const records: ExceptionRecord[] = [];\n let current: Error | undefined = error;\n let depth = 0;\n\n while (current && depth < MAX_CAUSE_DEPTH) {\n const value = current.message || 'Unknown error';\n const frames = current.stack ? parseStackBasic(current.stack) : undefined;\n records.push({\n type: current.name || 'Error',\n value: redactor ? redactor(value) : value,\n mechanism: { type: 'manual', handled: true },\n stacktrace: frames\n ? {\n frames: redactor\n ? frames.map((f) => ({\n ...f,\n abs_path: f.abs_path ? redactor(f.abs_path) : f.abs_path,\n }))\n : frames,\n }\n : undefined,\n });\n current = current.cause instanceof Error ? current.cause : undefined;\n depth++;\n }\n\n return records.toReversed();\n}\n\nfunction parseStackBasic(stack: string): StackFrame[] {\n const lines = stack.split('\\n');\n const frames: StackFrame[] = [];\n const re = /^\\s*at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\n\n for (const line of lines) {\n const match = line.trim().match(re);\n if (match) {\n const [, fn, absPath, lineStr, colStr] = match;\n frames.push({\n function: fn || undefined,\n abs_path: absPath,\n filename: absPath.split('/').pop() || absPath,\n lineno: Number.parseInt(lineStr, 10),\n colno: Number.parseInt(colStr, 10),\n in_app: !absPath.includes('node_modules'),\n });\n }\n }\n\n return frames;\n}\n","/**\n * PostHog Subscriber for autotel\n *\n * Send events to PostHog for product events, feature flags, and A/B testing.\n *\n * @example Basic usage\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * const events = new Event('checkout', {\n * subscribers: [\n * new PostHogSubscriber({\n * apiKey: process.env.POSTHOG_API_KEY!,\n * host: 'https://us.i.posthog.com' // optional, defaults to US cloud\n * })\n * ]\n * });\n *\n * // Events go to both OpenTelemetry AND PostHog\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n *\n * @example Feature flags\n * ```typescript\n * const subscriber = new PostHogSubscriber({ apiKey: 'phc_...' });\n *\n * // Check if feature is enabled\n * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');\n *\n * // Get feature flag value (string, boolean, number)\n * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');\n *\n * // Get all flags for a user\n * const allFlags = await subscriber.getAllFlags('user-123');\n * ```\n *\n * @example Person and group events\n * ```typescript\n * // Identify user and set properties\n * await subscriber.identify('user-123', {\n * email: 'user@example.com',\n * plan: 'premium'\n * });\n *\n * // Identify a group (e.g., organization)\n * await subscriber.groupIdentify('company', 'acme-corp', {\n * industry: 'saas',\n * employees: 500\n * });\n * ```\n *\n * @example Serverless configuration\n * ```typescript\n * // Optimized for AWS Lambda / Vercel Functions\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * flushAt: 1, // Send immediately (don't batch)\n * flushInterval: 0, // Disable interval-based flushing\n * });\n * ```\n *\n * @example Custom PostHog client\n * ```typescript\n * import { PostHog } from 'posthog-node';\n *\n * const customClient = new PostHog('phc_...', {\n * host: 'https://eu.i.posthog.com',\n * // ... other PostHog options\n * });\n *\n * const subscriber = new PostHogSubscriber({\n * client: customClient\n * });\n * ```\n *\n * @example Error handling\n * ```typescript\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * onError: (error) => {\n * console.error('PostHog error:', error);\n * // Send to error tracking service\n * }\n * });\n * ```\n */\n\nimport type {\n EventAttributes,\n EventAttributesInput,\n} from 'autotel/event-subscriber';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\nimport {\n formatExceptionForPostHog,\n errorToExceptionList,\n} from './posthog-error-formatter';\nimport slowRedact from 'slow-redact';\n\n// Type-only import to avoid runtime dependency\nimport type { PostHog } from 'posthog-node';\n\n/**\n * Error context for enhanced error handling\n *\n * Provides detailed context about the event that caused an error.\n */\nexport interface ErrorContext {\n /** The error that occurred */\n error: Error;\n\n /** Event name (if applicable) */\n eventName?: string;\n\n /** Event type (event, funnel, outcome, value) */\n eventType?: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event attributes (filtered) */\n attributes?: EventAttributes;\n\n /** Subscriber name */\n subscriberName: string;\n}\n\ntype StringRedactor = (value: string) => string;\n\nexport interface PostHogConfig {\n /** PostHog API key (starts with phc_) - required if not providing custom client */\n apiKey?: string;\n\n /** PostHog host (defaults to US cloud) */\n host?: string;\n\n /** Enable/disable the subscriber */\n enabled?: boolean;\n\n /** Custom PostHog client instance (bypasses apiKey/host) */\n client?: PostHog;\n\n /**\n * Use global browser client (window.posthog)\n *\n * When true, uses the PostHog client already loaded on the page via script tag.\n * This is useful for Next.js apps that initialize PostHog in _app.tsx.\n *\n * @example\n * ```typescript\n * // Browser - uses window.posthog\n * const subscriber = new PostHogSubscriber({\n * useGlobalClient: true,\n * });\n * ```\n */\n useGlobalClient?: boolean;\n\n /**\n * Serverless mode preset (AWS Lambda, Vercel Functions, Next.js API routes)\n *\n * When true, auto-configures for serverless environments:\n * - flushAt: 1 (send immediately, don't batch)\n * - flushInterval: 0 (disable interval-based flushing)\n * - requestTimeout: 3000 (shorter timeout for fast responses)\n *\n * @example\n * ```typescript\n * // Vercel / Next.js API route\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * serverless: true,\n * });\n * ```\n */\n serverless?: boolean;\n\n // Serverless optimizations\n /** Flush batch when it reaches this size (default: 20, set to 1 for immediate send) */\n flushAt?: number;\n\n /** Flush interval in milliseconds (default: 10000, set to 0 to disable) */\n flushInterval?: number;\n\n // Performance tuning\n /** Disable geoip lookup to reduce request size (default: false) */\n disableGeoip?: boolean;\n\n /** Request timeout in milliseconds (default: 10000) */\n requestTimeout?: number;\n\n /** Send feature flag evaluation events (default: true) */\n sendFeatureFlags?: boolean;\n\n /**\n * Automatically filter out undefined and null values from attributes\n *\n * When true (default), undefined and null values are removed before sending.\n * This improves DX when passing objects with optional properties.\n *\n * @default true\n *\n * @example\n * ```typescript\n * // With filterUndefinedValues: true (default)\n * subscriber.trackEvent('user.action', {\n * userId: user.id,\n * email: user.email, // might be undefined - will be filtered\n * plan: user.subscription, // might be null - will be filtered\n * });\n * ```\n */\n filterUndefinedValues?: boolean;\n\n // Error handling\n /** Error callback for debugging and monitoring */\n onError?: (error: Error) => void;\n\n /**\n * Enhanced error callback with event context\n *\n * Provides detailed context about the event that caused the error.\n * If both onError and onErrorWithContext are provided, both are called.\n *\n * @example\n * ```typescript\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * onErrorWithContext: (ctx) => {\n * console.error(`Failed to track ${ctx.eventType}: ${ctx.eventName}`, ctx.error);\n * Sentry.captureException(ctx.error, { extra: ctx });\n * }\n * });\n * ```\n */\n onErrorWithContext?: (context: ErrorContext) => void;\n\n /** Known attribute paths to redact using slow-redact (path-based, immutable). */\n redactPaths?: string[];\n\n /** String redactor for value-based PII scanning. Applied after path-based redaction. */\n stringRedactor?: StringRedactor;\n\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * PostHog feature flag options\n */\nexport interface FeatureFlagOptions {\n /** Group context for group-based feature flags */\n groups?: Record<string, string | number>;\n\n /** Group properties for feature flag evaluation */\n groupProperties?: Record<string, Record<string, any>>;\n\n /** Person properties for feature flag evaluation */\n personProperties?: Record<string, any>;\n\n /** Only evaluate locally, don't send $feature_flag_called event */\n onlyEvaluateLocally?: boolean;\n\n /** Send feature flag events even if disabled globally */\n sendFeatureFlagEvents?: boolean;\n}\n\n/**\n * Person properties for identify calls\n */\nexport interface PersonProperties {\n /** Set properties (will update existing values) */\n $set?: Record<string, any>;\n\n /** Set properties only if they don't exist */\n $set_once?: Record<string, any>;\n\n /** Any custom properties */\n [key: string]: any;\n}\n\nexport class PostHogSubscriber extends EventSubscriber {\n readonly name = 'PostHogSubscriber';\n readonly version = '2.0.0';\n\n private posthog: PostHog | null = null;\n private config: PostHogConfig;\n private initPromise: Promise<void> | null = null;\n /** True when using browser's window.posthog (different API signature) */\n private isBrowserClient = false;\n private pathRedactor:\n ((obj: Record<string, unknown>) => Record<string, unknown>) | null = null;\n private stringRedactor: StringRedactor | null = null;\n\n constructor(config: PostHogConfig) {\n super();\n\n // Apply serverless preset first (can be overridden by explicit config)\n if (config.serverless) {\n config = {\n flushAt: 1,\n flushInterval: 0,\n requestTimeout: 3000,\n ...config, // User config overrides serverless defaults\n };\n }\n\n // Validate: need either apiKey, client, or useGlobalClient\n if (!config.apiKey && !config.client && !config.useGlobalClient) {\n throw new Error(\n 'PostHogSubscriber requires either apiKey, client, or useGlobalClient to be provided',\n );\n }\n\n this.enabled = config.enabled ?? true;\n // Default filterUndefinedValues to true\n this.config = {\n filterUndefinedValues: true,\n ...config,\n };\n\n if (this.config.redactPaths && this.config.redactPaths.length > 0) {\n this.pathRedactor = slowRedact({\n paths: this.config.redactPaths,\n serialize: false,\n }) as any;\n }\n if (this.config.stringRedactor) {\n this.stringRedactor = this.config.stringRedactor;\n }\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Option 1: Use global browser client (window.posthog)\n if (this.config.useGlobalClient) {\n const globalWindow =\n typeof globalThis === 'undefined'\n ? undefined\n : (globalThis as Record<string, unknown>);\n if (globalWindow?.posthog) {\n this.posthog = globalWindow.posthog as PostHog;\n this.isBrowserClient = true;\n this.setupErrorHandling();\n return;\n }\n throw new Error(\n 'useGlobalClient enabled but window.posthog not found. ' +\n 'Ensure PostHog script is loaded before initializing the subscriber.',\n );\n }\n\n // Option 2: Use custom client if provided\n if (this.config.client) {\n this.posthog = this.config.client;\n this.setupErrorHandling();\n return;\n }\n\n // Option 3: Create new PostHog client with dynamic import\n const { PostHog } = await import('posthog-node');\n\n this.posthog = new PostHog(this.config.apiKey!, {\n host: this.config.host || 'https://us.i.posthog.com',\n flushAt: this.config.flushAt,\n flushInterval: this.config.flushInterval,\n requestTimeout: this.config.requestTimeout,\n disableGeoip: this.config.disableGeoip,\n sendFeatureFlagEvent: this.config.sendFeatureFlags,\n });\n\n this.setupErrorHandling();\n } catch (error) {\n console.error(\n 'PostHog subscriber failed to initialize. Install posthog-node: pnpm add posthog-node',\n error,\n );\n this.enabled = false;\n this.config.onError?.(error as Error);\n }\n }\n\n private setupErrorHandling(): void {\n if (this.config.debug) {\n this.posthog?.debug();\n }\n\n if (this.config.onError && this.posthog?.on) {\n this.posthog.on('error', this.config.onError);\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n private extractDistinctId(attributes?: EventAttributes): string {\n return (attributes?.userId || attributes?.user_id || 'anonymous') as string;\n }\n\n private redactProperties(\n properties: Record<string, unknown>,\n ): Record<string, unknown> {\n let result = properties;\n if (this.pathRedactor) {\n result = this.pathRedactor(properties) as Record<string, unknown>;\n }\n if (this.stringRedactor) {\n result = this.redactStringValues(result);\n }\n return result;\n }\n\n private redactStringValues(\n obj: Record<string, unknown>,\n ): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === 'string') {\n result[key] = this.stringRedactor!(value);\n } else if (Array.isArray(value)) {\n result[key] = this.redactArray(value);\n } else if (value !== null && typeof value === 'object') {\n result[key] = this.redactStringValues(value as Record<string, unknown>);\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n private redactArray(arr: unknown[]): unknown[] {\n return arr.map((item) => {\n if (typeof item === 'string') {\n return this.stringRedactor!(item);\n } else if (Array.isArray(item)) {\n return this.redactArray(item);\n } else if (item !== null && typeof item === 'object') {\n return this.redactStringValues(item as Record<string, unknown>);\n }\n return item;\n });\n }\n\n /**\n * Set the string redactor. Called by autotel init() when attributeRedactor is configured.\n * Can also be called manually.\n */\n setStringRedactor(redactor: StringRedactor): void {\n this.stringRedactor = redactor;\n }\n\n /**\n * Send payload to PostHog\n *\n * Maps autotel context to PostHog-specific field names:\n * - autotel.trace_id → $trace_id\n * - autotel.span_id → $span_id\n * - autotel.correlation_id → $correlation_id\n * - autotel.trace_url → $trace_url\n */\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n await this.ensureInitialized();\n\n // Filter attributes if enabled (default: true)\n const filteredAttributes =\n this.config.filterUndefinedValues === false\n ? payload.attributes\n : this.filterAttributes(payload.attributes as EventAttributesInput);\n\n // Build properties object, including value and funnel metadata if present\n const properties: Record<string, unknown> = { ...filteredAttributes };\n if (payload.value !== undefined) {\n properties.value = payload.value;\n }\n // Add funnel progression metadata\n if (payload.stepNumber !== undefined) {\n properties.step_number = payload.stepNumber;\n }\n if (payload.stepName !== undefined) {\n properties.step_name = payload.stepName;\n }\n\n // Map autotel context to PostHog-specific field names\n if (payload.autotel) {\n if (payload.autotel.trace_id) {\n properties.$trace_id = payload.autotel.trace_id;\n }\n if (payload.autotel.span_id) {\n properties.$span_id = payload.autotel.span_id;\n }\n if (payload.autotel.correlation_id) {\n properties.$correlation_id = payload.autotel.correlation_id;\n }\n if (payload.autotel.trace_flags) {\n properties.$trace_flags = payload.autotel.trace_flags;\n }\n if (payload.autotel.trace_state) {\n properties.$trace_state = payload.autotel.trace_state;\n }\n if (payload.autotel.trace_url) {\n properties.$trace_url = payload.autotel.trace_url;\n }\n // Batch/fan-in context\n if (payload.autotel.linked_trace_id_count !== undefined) {\n properties.$linked_trace_id_count =\n payload.autotel.linked_trace_id_count;\n }\n if (payload.autotel.linked_trace_id_hash) {\n properties.$linked_trace_id_hash = payload.autotel.linked_trace_id_hash;\n }\n if (payload.autotel.linked_trace_ids) {\n properties.$linked_trace_ids = payload.autotel.linked_trace_ids;\n }\n }\n\n const redactedProperties = this.redactProperties(properties);\n\n if (payload.attributes?.['exception.list']) {\n try {\n const exceptionList = JSON.parse(\n payload.attributes['exception.list'] as string,\n );\n const formatted = formatExceptionForPostHog(\n exceptionList,\n undefined,\n this.stringRedactor ?? undefined,\n );\n const exceptionProperties = {\n ...redactedProperties,\n ...formatted,\n };\n\n if (this.isBrowserClient) {\n (this.posthog as any)?.capture('$exception', exceptionProperties);\n } else {\n this.posthog?.capture({\n distinctId: this.extractDistinctId(filteredAttributes),\n event: '$exception',\n properties: exceptionProperties,\n });\n }\n } catch {\n // exception.list parsing failed, continue with normal event\n }\n }\n\n const distinctId = this.extractDistinctId(filteredAttributes);\n\n // Browser client has different API signature\n if (this.isBrowserClient) {\n // Browser API: capture(eventName, properties)\n (this.posthog as any)?.capture(payload.name, redactedProperties);\n } else {\n // Server API: capture({ distinctId, event, properties, groups })\n const capturePayload: any = {\n distinctId,\n event: payload.name,\n properties: redactedProperties,\n };\n\n // Add groups if present in attributes\n if (filteredAttributes?.groups) {\n capturePayload.groups = filteredAttributes.groups;\n }\n\n this.posthog?.capture(capturePayload);\n }\n }\n\n // Feature Flag Methods\n\n /**\n * Check if a feature flag is enabled for a user\n *\n * @param flagKey - Feature flag key\n * @param distinctId - User ID or anonymous ID\n * @param options - Feature flag evaluation options\n * @returns true if enabled, false otherwise\n *\n * @example\n * ```typescript\n * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');\n *\n * // With groups\n * const isEnabled = await subscriber.isFeatureEnabled('beta-features', 'user-123', {\n * groups: { company: 'acme-corp' }\n * });\n * ```\n */\n async isFeatureEnabled(\n flagKey: string,\n distinctId: string,\n options?: FeatureFlagOptions,\n ): Promise<boolean> {\n if (!this.enabled) return false;\n await this.ensureInitialized();\n\n try {\n return (\n (await this.posthog?.isFeatureEnabled(\n flagKey,\n distinctId,\n options as any,\n )) ?? false\n );\n } catch (error) {\n this.config.onError?.(error as Error);\n return false;\n }\n }\n\n /**\n * Get feature flag value for a user\n *\n * @param flagKey - Feature flag key\n * @param distinctId - User ID or anonymous ID\n * @param options - Feature flag evaluation options\n * @returns Flag value (string, boolean, or undefined)\n *\n * @example\n * ```typescript\n * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');\n * // Returns: 'control' | 'test' | 'test-2' | undefined\n *\n * // With person properties\n * const variant = await subscriber.getFeatureFlag('premium-feature', 'user-123', {\n * personProperties: { plan: 'premium' }\n * });\n * ```\n */\n async getFeatureFlag(\n flagKey: string,\n distinctId: string,\n options?: FeatureFlagOptions,\n ): Promise<string | boolean | undefined> {\n if (!this.enabled) return undefined;\n await this.ensureInitialized();\n\n try {\n return await this.posthog?.getFeatureFlag(\n flagKey,\n distinctId,\n options as any,\n );\n } catch (error) {\n this.config.onError?.(error as Error);\n return undefined;\n }\n }\n\n /**\n * Get all feature flags for a user\n *\n * @param distinctId - User ID or anonymous ID\n * @param options - Feature flag evaluation options\n * @returns Object mapping flag keys to their values\n *\n * @example\n * ```typescript\n * const flags = await subscriber.getAllFlags('user-123');\n * // Returns: { 'new-checkout': true, 'experiment-variant': 'test', ... }\n * ```\n */\n async getAllFlags(\n distinctId: string,\n options?: FeatureFlagOptions,\n ): Promise<Record<string, string | number | boolean>> {\n if (!this.enabled) return {};\n await this.ensureInitialized();\n\n try {\n const flags = await this.posthog?.getAllFlags(distinctId, options as any);\n return flags ?? {};\n } catch (error) {\n this.config.onError?.(error as Error);\n return {};\n }\n }\n\n /**\n * Reload feature flags from PostHog server\n *\n * Call this to refresh feature flag definitions without restarting.\n *\n * @example\n * ```typescript\n * await subscriber.reloadFeatureFlags();\n * ```\n */\n async reloadFeatureFlags(): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n await this.posthog?.reloadFeatureFlags();\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n\n // Person and Group Events\n\n /**\n * Identify a user and set their properties\n *\n * @param distinctId - User ID\n * @param properties - Person properties ($set, $set_once, or custom properties)\n *\n * @example\n * ```typescript\n * // Set properties (will update existing values)\n * await subscriber.identify('user-123', {\n * $set: {\n * email: 'user@example.com',\n * plan: 'premium'\n * }\n * });\n *\n * // Set properties only once (won't update if already exists)\n * await subscriber.identify('user-123', {\n * $set_once: {\n * signup_date: '2025-01-17'\n * }\n * });\n * ```\n */\n async identify(\n distinctId: string,\n properties?: PersonProperties,\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n this.posthog?.identify({\n distinctId,\n properties,\n });\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n\n /**\n * Identify a group and set its properties\n *\n * Groups are useful for B2B SaaS to track organizations, teams, or accounts.\n *\n * @param groupType - Type of group (e.g., 'company', 'organization', 'team')\n * @param groupKey - Unique identifier for the group\n * @param properties - Group properties\n *\n * @example\n * ```typescript\n * await subscriber.groupIdentify('company', 'acme-corp', {\n * $set: {\n * name: 'Acme Corporation',\n * industry: 'saas',\n * employees: 500,\n * plan: 'enterprise'\n * }\n * });\n * ```\n */\n async groupIdentify(\n groupType: string,\n groupKey: string | number,\n properties?: Record<string, any>,\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n this.posthog?.groupIdentify({\n groupType,\n groupKey: String(groupKey), // Convert to string for PostHog SDK\n properties,\n });\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n\n /**\n * Track an event with group context\n *\n * Use this to associate events with groups (e.g., organizations).\n *\n * @param name - Event name\n * @param attributes - Event attributes\n * @param groups - Group context (e.g., { company: 'acme-corp' })\n *\n * @example\n * ```typescript\n * await subscriber.trackEventWithGroups('feature.used', {\n * userId: 'user-123',\n * feature: 'advanced-events'\n * }, {\n * company: 'acme-corp'\n * });\n * ```\n */\n async trackEventWithGroups(\n name: string,\n attributes?: EventAttributes,\n groups?: Record<string, string | number>,\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n const eventAttributes: EventAttributes = {\n ...attributes,\n } as EventAttributes;\n if (groups) {\n (eventAttributes as any).groups = groups;\n }\n\n await this.trackEvent(name, eventAttributes);\n }\n\n /**\n * Capture an exception and send to PostHog error tracking.\n *\n * If using browser client (window.posthog), delegates to its captureException.\n * Otherwise, formats and sends via posthog-node capture API.\n */\n async captureException(\n error: unknown,\n options?: {\n distinctId?: string;\n additionalProperties?: Record<string, unknown>;\n },\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n if (this.isBrowserClient) {\n const browserProps = options?.additionalProperties\n ? this.redactProperties(options.additionalProperties)\n : undefined;\n (this.posthog as any)?.captureException?.(error, browserProps);\n return;\n }\n\n const exceptionList = errorToExceptionList(\n error,\n this.stringRedactor ?? undefined,\n );\n const formatted = formatExceptionForPostHog(\n exceptionList,\n 'node:javascript',\n this.stringRedactor ?? undefined,\n );\n\n const properties = {\n ...formatted,\n ...options?.additionalProperties,\n };\n\n this.posthog?.capture({\n distinctId: options?.distinctId || 'anonymous',\n event: '$exception',\n properties: this.redactProperties(properties),\n });\n } catch (error_) {\n this.config.onError?.(error_ as Error);\n }\n }\n\n /**\n * Flush pending events and clean up resources\n */\n async shutdown(): Promise<void> {\n await super.shutdown(); // Drain pending requests first\n await this.ensureInitialized();\n\n if (this.posthog) {\n try {\n await this.posthog.shutdown();\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n }\n\n /**\n * Handle errors with custom error handler\n */\n protected handleError(error: Error, payload: EventPayload): void {\n // Call basic onError if provided\n this.config.onError?.(error);\n\n // Call enhanced onErrorWithContext if provided\n if (this.config.onErrorWithContext) {\n this.config.onErrorWithContext({\n error,\n eventName: payload.name,\n eventType: payload.type,\n attributes: payload.attributes,\n subscriberName: this.name,\n });\n }\n\n super.handleError(error, payload);\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"posthog-DOOBZanH.cjs","names":["EventSubscriber"],"sources":["../src/posthog-error-formatter.ts","../src/posthog.ts"],"sourcesContent":["/**\n * Format errors for PostHog's $exception event format.\n * Compatible with autotel-web's ExceptionList type.\n */\n\ntype StringRedactor = (value: string) => string;\n\ninterface StackFrame {\n filename?: string;\n function?: string;\n lineno?: number;\n colno?: number;\n abs_path?: string;\n in_app?: boolean;\n platform?: string;\n}\n\ninterface ExceptionRecord {\n type: string;\n value: string;\n mechanism: { type: string; handled: boolean };\n stacktrace?: { frames: StackFrame[] };\n}\n\ninterface PostHogExceptionProperties {\n $exception_list: Array<{\n type: string;\n value: string;\n mechanism: { type: string; handled: boolean };\n stacktrace: { frames: Array<StackFrame & { platform: string }> };\n }>;\n}\n\nconst MAX_CAUSE_DEPTH = 5;\n\nexport function formatExceptionForPostHog(\n exceptionList: ExceptionRecord[],\n platform: string = 'web:javascript',\n redactor?: StringRedactor,\n): PostHogExceptionProperties {\n return {\n $exception_list: exceptionList.map((ex) => ({\n type: ex.type,\n value: redactor ? redactor(ex.value) : ex.value,\n mechanism: ex.mechanism,\n stacktrace: {\n frames: (ex.stacktrace?.frames || []).map((frame) => ({\n ...frame,\n abs_path:\n frame.abs_path && redactor\n ? redactor(frame.abs_path)\n : frame.abs_path,\n platform,\n })),\n },\n })),\n };\n}\n\nexport function errorToExceptionList(\n input: unknown,\n redactor?: StringRedactor,\n): ExceptionRecord[] {\n const error =\n input instanceof Error\n ? input\n : new Error(\n input === null || input === undefined\n ? 'Unknown error'\n : String(input),\n );\n\n const records: ExceptionRecord[] = [];\n let current: Error | undefined = error;\n let depth = 0;\n\n while (current && depth < MAX_CAUSE_DEPTH) {\n const value = current.message || 'Unknown error';\n const frames = current.stack ? parseStackBasic(current.stack) : undefined;\n records.push({\n type: current.name || 'Error',\n value: redactor ? redactor(value) : value,\n mechanism: { type: 'manual', handled: true },\n stacktrace: frames\n ? {\n frames: redactor\n ? frames.map((f) => ({\n ...f,\n abs_path: f.abs_path ? redactor(f.abs_path) : f.abs_path,\n }))\n : frames,\n }\n : undefined,\n });\n current = current.cause instanceof Error ? current.cause : undefined;\n depth++;\n }\n\n return records.toReversed();\n}\n\nfunction parseStackBasic(stack: string): StackFrame[] {\n const lines = stack.split('\\n');\n const frames: StackFrame[] = [];\n const re = /^\\s*at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\n\n for (const line of lines) {\n const match = line.trim().match(re);\n if (match) {\n const [, fn, absPath, lineStr, colStr] = match;\n frames.push({\n function: fn || undefined,\n abs_path: absPath,\n filename: absPath.split('/').pop() || absPath,\n lineno: Number.parseInt(lineStr, 10),\n colno: Number.parseInt(colStr, 10),\n in_app: !absPath.includes('node_modules'),\n });\n }\n }\n\n return frames;\n}\n","/**\n * PostHog Subscriber for autotel\n *\n * Send events to PostHog for product events, feature flags, and A/B testing.\n *\n * @example Basic usage\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * const events = new Event('checkout', {\n * subscribers: [\n * new PostHogSubscriber({\n * apiKey: process.env.POSTHOG_API_KEY!,\n * host: 'https://us.i.posthog.com' // optional, defaults to US cloud\n * })\n * ]\n * });\n *\n * // Events go to both OpenTelemetry AND PostHog\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n *\n * @example Feature flags\n * ```typescript\n * const subscriber = new PostHogSubscriber({ apiKey: 'phc_...' });\n *\n * // Check if feature is enabled\n * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');\n *\n * // Get feature flag value (string, boolean, number)\n * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');\n *\n * // Get all flags for a user\n * const allFlags = await subscriber.getAllFlags('user-123');\n * ```\n *\n * @example Person and group events\n * ```typescript\n * // Identify user and set properties\n * await subscriber.identify('user-123', {\n * email: 'user@example.com',\n * plan: 'premium'\n * });\n *\n * // Identify a group (e.g., organization)\n * await subscriber.groupIdentify('company', 'acme-corp', {\n * industry: 'saas',\n * employees: 500\n * });\n * ```\n *\n * @example Serverless configuration\n * ```typescript\n * // Optimized for AWS Lambda / Vercel Functions\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * flushAt: 1, // Send immediately (don't batch)\n * flushInterval: 0, // Disable interval-based flushing\n * });\n * ```\n *\n * @example Custom PostHog client\n * ```typescript\n * import { PostHog } from 'posthog-node';\n *\n * const customClient = new PostHog('phc_...', {\n * host: 'https://eu.i.posthog.com',\n * // ... other PostHog options\n * });\n *\n * const subscriber = new PostHogSubscriber({\n * client: customClient\n * });\n * ```\n *\n * @example Error handling\n * ```typescript\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * onError: (error) => {\n * console.error('PostHog error:', error);\n * // Send to error tracking service\n * }\n * });\n * ```\n */\n\nimport type {\n EventAttributes,\n EventAttributesInput,\n} from 'autotel/event-subscriber';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\nimport {\n formatExceptionForPostHog,\n errorToExceptionList,\n} from './posthog-error-formatter';\nimport slowRedact from 'slow-redact';\n\n// Type-only import to avoid runtime dependency\nimport type { PostHog } from 'posthog-node';\n\n/**\n * Error context for enhanced error handling\n *\n * Provides detailed context about the event that caused an error.\n */\nexport interface ErrorContext {\n /** The error that occurred */\n error: Error;\n\n /** Event name (if applicable) */\n eventName?: string;\n\n /** Event type (event, funnel, outcome, value) */\n eventType?: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event attributes (filtered) */\n attributes?: EventAttributes;\n\n /** Subscriber name */\n subscriberName: string;\n}\n\ntype StringRedactor = (value: string) => string;\n\nexport interface PostHogConfig {\n /** PostHog API key (starts with phc_) - required if not providing custom client */\n apiKey?: string;\n\n /** PostHog host (defaults to US cloud) */\n host?: string;\n\n /** Enable/disable the subscriber */\n enabled?: boolean;\n\n /** Custom PostHog client instance (bypasses apiKey/host) */\n client?: PostHog;\n\n /**\n * Use global browser client (window.posthog)\n *\n * When true, uses the PostHog client already loaded on the page via script tag.\n * This is useful for Next.js apps that initialize PostHog in _app.tsx.\n *\n * @example\n * ```typescript\n * // Browser - uses window.posthog\n * const subscriber = new PostHogSubscriber({\n * useGlobalClient: true,\n * });\n * ```\n */\n useGlobalClient?: boolean;\n\n /**\n * Serverless mode preset (AWS Lambda, Vercel Functions, Next.js API routes)\n *\n * When true, auto-configures for serverless environments:\n * - flushAt: 1 (send immediately, don't batch)\n * - flushInterval: 0 (disable interval-based flushing)\n * - requestTimeout: 3000 (shorter timeout for fast responses)\n *\n * @example\n * ```typescript\n * // Vercel / Next.js API route\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * serverless: true,\n * });\n * ```\n */\n serverless?: boolean;\n\n // Serverless optimizations\n /** Flush batch when it reaches this size (default: 20, set to 1 for immediate send) */\n flushAt?: number;\n\n /** Flush interval in milliseconds (default: 10000, set to 0 to disable) */\n flushInterval?: number;\n\n // Performance tuning\n /** Disable geoip lookup to reduce request size (default: false) */\n disableGeoip?: boolean;\n\n /** Request timeout in milliseconds (default: 10000) */\n requestTimeout?: number;\n\n /** Send feature flag evaluation events (default: true) */\n sendFeatureFlags?: boolean;\n\n /**\n * Automatically filter out undefined and null values from attributes\n *\n * When true (default), undefined and null values are removed before sending.\n * This improves DX when passing objects with optional properties.\n *\n * @default true\n *\n * @example\n * ```typescript\n * // With filterUndefinedValues: true (default)\n * subscriber.trackEvent('user.action', {\n * userId: user.id,\n * email: user.email, // might be undefined - will be filtered\n * plan: user.subscription, // might be null - will be filtered\n * });\n * ```\n */\n filterUndefinedValues?: boolean;\n\n // Error handling\n /** Error callback for debugging and monitoring */\n onError?: (error: Error) => void;\n\n /**\n * Enhanced error callback with event context\n *\n * Provides detailed context about the event that caused the error.\n * If both onError and onErrorWithContext are provided, both are called.\n *\n * @example\n * ```typescript\n * const subscriber = new PostHogSubscriber({\n * apiKey: 'phc_...',\n * onErrorWithContext: (ctx) => {\n * console.error(`Failed to track ${ctx.eventType}: ${ctx.eventName}`, ctx.error);\n * Sentry.captureException(ctx.error, { extra: ctx });\n * }\n * });\n * ```\n */\n onErrorWithContext?: (context: ErrorContext) => void;\n\n /** Known attribute paths to redact using slow-redact (path-based, immutable). */\n redactPaths?: string[];\n\n /** String redactor for value-based PII scanning. Applied after path-based redaction. */\n stringRedactor?: StringRedactor;\n\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * PostHog feature flag options\n */\nexport interface FeatureFlagOptions {\n /** Group context for group-based feature flags */\n groups?: Record<string, string | number>;\n\n /** Group properties for feature flag evaluation */\n groupProperties?: Record<string, Record<string, any>>;\n\n /** Person properties for feature flag evaluation */\n personProperties?: Record<string, any>;\n\n /** Only evaluate locally, don't send $feature_flag_called event */\n onlyEvaluateLocally?: boolean;\n\n /** Send feature flag events even if disabled globally */\n sendFeatureFlagEvents?: boolean;\n}\n\n/**\n * Person properties for identify calls\n */\nexport interface PersonProperties {\n /** Set properties (will update existing values) */\n $set?: Record<string, any>;\n\n /** Set properties only if they don't exist */\n $set_once?: Record<string, any>;\n\n /** Any custom properties */\n [key: string]: any;\n}\n\nexport class PostHogSubscriber extends EventSubscriber {\n readonly name = 'PostHogSubscriber';\n readonly version = '2.0.0';\n\n private posthog: PostHog | null = null;\n private config: PostHogConfig;\n private initPromise: Promise<void> | null = null;\n /** True when using browser's window.posthog (different API signature) */\n private isBrowserClient = false;\n private pathRedactor:\n ((obj: Record<string, unknown>) => Record<string, unknown>) | null = null;\n private stringRedactor: StringRedactor | null = null;\n\n constructor(config: PostHogConfig) {\n super();\n\n // Apply serverless preset first (can be overridden by explicit config)\n if (config.serverless) {\n config = {\n flushAt: 1,\n flushInterval: 0,\n requestTimeout: 3000,\n ...config, // User config overrides serverless defaults\n };\n }\n\n // Validate: need either apiKey, client, or useGlobalClient\n if (!config.apiKey && !config.client && !config.useGlobalClient) {\n throw new Error(\n 'PostHogSubscriber requires either apiKey, client, or useGlobalClient to be provided',\n );\n }\n\n this.enabled = config.enabled ?? true;\n // Default filterUndefinedValues to true\n this.config = {\n filterUndefinedValues: true,\n ...config,\n };\n\n if (this.config.redactPaths && this.config.redactPaths.length > 0) {\n this.pathRedactor = slowRedact({\n paths: this.config.redactPaths,\n serialize: false,\n }) as any;\n }\n if (this.config.stringRedactor) {\n this.stringRedactor = this.config.stringRedactor;\n }\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Option 1: Use global browser client (window.posthog)\n if (this.config.useGlobalClient) {\n const globalWindow =\n typeof globalThis === 'undefined'\n ? undefined\n : (globalThis as Record<string, unknown>);\n if (globalWindow?.posthog) {\n this.posthog = globalWindow.posthog as PostHog;\n this.isBrowserClient = true;\n this.setupErrorHandling();\n return;\n }\n throw new Error(\n 'useGlobalClient enabled but window.posthog not found. ' +\n 'Ensure PostHog script is loaded before initializing the subscriber.',\n );\n }\n\n // Option 2: Use custom client if provided\n if (this.config.client) {\n this.posthog = this.config.client;\n this.setupErrorHandling();\n return;\n }\n\n // Option 3: Create new PostHog client with dynamic import\n const { PostHog } = await import('posthog-node');\n\n this.posthog = new PostHog(this.config.apiKey!, {\n host: this.config.host || 'https://us.i.posthog.com',\n flushAt: this.config.flushAt,\n flushInterval: this.config.flushInterval,\n requestTimeout: this.config.requestTimeout,\n disableGeoip: this.config.disableGeoip,\n sendFeatureFlagEvent: this.config.sendFeatureFlags,\n });\n\n this.setupErrorHandling();\n } catch (error) {\n console.error(\n 'PostHog subscriber failed to initialize. Install posthog-node: pnpm add posthog-node',\n error,\n );\n this.enabled = false;\n this.config.onError?.(error as Error);\n }\n }\n\n private setupErrorHandling(): void {\n if (this.config.debug) {\n this.posthog?.debug();\n }\n\n if (this.config.onError && this.posthog?.on) {\n this.posthog.on('error', this.config.onError);\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n private extractDistinctId(attributes?: EventAttributes): string {\n return (attributes?.userId || attributes?.user_id || 'anonymous') as string;\n }\n\n private redactProperties(\n properties: Record<string, unknown>,\n ): Record<string, unknown> {\n let result = properties;\n if (this.pathRedactor) {\n result = this.pathRedactor(properties) as Record<string, unknown>;\n }\n if (this.stringRedactor) {\n result = this.redactStringValues(result);\n }\n return result;\n }\n\n private redactStringValues(\n obj: Record<string, unknown>,\n ): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === 'string') {\n result[key] = this.stringRedactor!(value);\n } else if (Array.isArray(value)) {\n result[key] = this.redactArray(value);\n } else if (value !== null && typeof value === 'object') {\n result[key] = this.redactStringValues(value as Record<string, unknown>);\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n private redactArray(arr: unknown[]): unknown[] {\n return arr.map((item) => {\n if (typeof item === 'string') {\n return this.stringRedactor!(item);\n } else if (Array.isArray(item)) {\n return this.redactArray(item);\n } else if (item !== null && typeof item === 'object') {\n return this.redactStringValues(item as Record<string, unknown>);\n }\n return item;\n });\n }\n\n /**\n * Set the string redactor. Called by autotel init() when attributeRedactor is configured.\n * Can also be called manually.\n */\n setStringRedactor(redactor: StringRedactor): void {\n this.stringRedactor = redactor;\n }\n\n /**\n * Send payload to PostHog\n *\n * Maps autotel context to PostHog-specific field names:\n * - autotel.trace_id → $trace_id\n * - autotel.span_id → $span_id\n * - autotel.correlation_id → $correlation_id\n * - autotel.trace_url → $trace_url\n */\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n await this.ensureInitialized();\n\n // Filter attributes if enabled (default: true)\n const filteredAttributes =\n this.config.filterUndefinedValues === false\n ? payload.attributes\n : this.filterAttributes(payload.attributes as EventAttributesInput);\n\n // Build properties object, including value and funnel metadata if present\n const properties: Record<string, unknown> = { ...filteredAttributes };\n if (payload.value !== undefined) {\n properties.value = payload.value;\n }\n // Add funnel progression metadata\n if (payload.stepNumber !== undefined) {\n properties.step_number = payload.stepNumber;\n }\n if (payload.stepName !== undefined) {\n properties.step_name = payload.stepName;\n }\n\n // Map autotel context to PostHog-specific field names\n if (payload.autotel) {\n if (payload.autotel.trace_id) {\n properties.$trace_id = payload.autotel.trace_id;\n }\n if (payload.autotel.span_id) {\n properties.$span_id = payload.autotel.span_id;\n }\n if (payload.autotel.correlation_id) {\n properties.$correlation_id = payload.autotel.correlation_id;\n }\n if (payload.autotel.trace_flags) {\n properties.$trace_flags = payload.autotel.trace_flags;\n }\n if (payload.autotel.trace_state) {\n properties.$trace_state = payload.autotel.trace_state;\n }\n if (payload.autotel.trace_url) {\n properties.$trace_url = payload.autotel.trace_url;\n }\n // Batch/fan-in context\n if (payload.autotel.linked_trace_id_count !== undefined) {\n properties.$linked_trace_id_count =\n payload.autotel.linked_trace_id_count;\n }\n if (payload.autotel.linked_trace_id_hash) {\n properties.$linked_trace_id_hash = payload.autotel.linked_trace_id_hash;\n }\n if (payload.autotel.linked_trace_ids) {\n properties.$linked_trace_ids = payload.autotel.linked_trace_ids;\n }\n }\n\n const redactedProperties = this.redactProperties(properties);\n\n if (payload.attributes?.['exception.list']) {\n try {\n const exceptionList = JSON.parse(\n payload.attributes['exception.list'] as string,\n );\n const formatted = formatExceptionForPostHog(\n exceptionList,\n undefined,\n this.stringRedactor ?? undefined,\n );\n const exceptionProperties = {\n ...redactedProperties,\n ...formatted,\n };\n\n if (this.isBrowserClient) {\n (this.posthog as any)?.capture('$exception', exceptionProperties);\n } else {\n this.posthog?.capture({\n distinctId: this.extractDistinctId(filteredAttributes),\n event: '$exception',\n properties: exceptionProperties,\n });\n }\n } catch {\n // exception.list parsing failed, continue with normal event\n }\n }\n\n const distinctId = this.extractDistinctId(filteredAttributes);\n\n // Browser client has different API signature\n if (this.isBrowserClient) {\n // Browser API: capture(eventName, properties)\n (this.posthog as any)?.capture(payload.name, redactedProperties);\n } else {\n // Server API: capture({ distinctId, event, properties, groups })\n const capturePayload: any = {\n distinctId,\n event: payload.name,\n properties: redactedProperties,\n };\n\n // Add groups if present in attributes\n if (filteredAttributes?.groups) {\n capturePayload.groups = filteredAttributes.groups;\n }\n\n this.posthog?.capture(capturePayload);\n }\n }\n\n // Feature Flag Methods\n\n /**\n * Check if a feature flag is enabled for a user\n *\n * @param flagKey - Feature flag key\n * @param distinctId - User ID or anonymous ID\n * @param options - Feature flag evaluation options\n * @returns true if enabled, false otherwise\n *\n * @example\n * ```typescript\n * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');\n *\n * // With groups\n * const isEnabled = await subscriber.isFeatureEnabled('beta-features', 'user-123', {\n * groups: { company: 'acme-corp' }\n * });\n * ```\n */\n async isFeatureEnabled(\n flagKey: string,\n distinctId: string,\n options?: FeatureFlagOptions,\n ): Promise<boolean> {\n if (!this.enabled) return false;\n await this.ensureInitialized();\n\n try {\n return (\n (await this.posthog?.isFeatureEnabled(\n flagKey,\n distinctId,\n options as any,\n )) ?? false\n );\n } catch (error) {\n this.config.onError?.(error as Error);\n return false;\n }\n }\n\n /**\n * Get feature flag value for a user\n *\n * @param flagKey - Feature flag key\n * @param distinctId - User ID or anonymous ID\n * @param options - Feature flag evaluation options\n * @returns Flag value (string, boolean, or undefined)\n *\n * @example\n * ```typescript\n * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');\n * // Returns: 'control' | 'test' | 'test-2' | undefined\n *\n * // With person properties\n * const variant = await subscriber.getFeatureFlag('premium-feature', 'user-123', {\n * personProperties: { plan: 'premium' }\n * });\n * ```\n */\n async getFeatureFlag(\n flagKey: string,\n distinctId: string,\n options?: FeatureFlagOptions,\n ): Promise<string | boolean | undefined> {\n if (!this.enabled) return undefined;\n await this.ensureInitialized();\n\n try {\n return await this.posthog?.getFeatureFlag(\n flagKey,\n distinctId,\n options as any,\n );\n } catch (error) {\n this.config.onError?.(error as Error);\n return undefined;\n }\n }\n\n /**\n * Get all feature flags for a user\n *\n * @param distinctId - User ID or anonymous ID\n * @param options - Feature flag evaluation options\n * @returns Object mapping flag keys to their values\n *\n * @example\n * ```typescript\n * const flags = await subscriber.getAllFlags('user-123');\n * // Returns: { 'new-checkout': true, 'experiment-variant': 'test', ... }\n * ```\n */\n async getAllFlags(\n distinctId: string,\n options?: FeatureFlagOptions,\n ): Promise<Record<string, string | number | boolean>> {\n if (!this.enabled) return {};\n await this.ensureInitialized();\n\n try {\n const flags = await this.posthog?.getAllFlags(distinctId, options as any);\n return flags ?? {};\n } catch (error) {\n this.config.onError?.(error as Error);\n return {};\n }\n }\n\n /**\n * Reload feature flags from PostHog server\n *\n * Call this to refresh feature flag definitions without restarting.\n *\n * @example\n * ```typescript\n * await subscriber.reloadFeatureFlags();\n * ```\n */\n async reloadFeatureFlags(): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n await this.posthog?.reloadFeatureFlags();\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n\n // Person and Group Events\n\n /**\n * Identify a user and set their properties\n *\n * @param distinctId - User ID\n * @param properties - Person properties ($set, $set_once, or custom properties)\n *\n * @example\n * ```typescript\n * // Set properties (will update existing values)\n * await subscriber.identify('user-123', {\n * $set: {\n * email: 'user@example.com',\n * plan: 'premium'\n * }\n * });\n *\n * // Set properties only once (won't update if already exists)\n * await subscriber.identify('user-123', {\n * $set_once: {\n * signup_date: '2025-01-17'\n * }\n * });\n * ```\n */\n async identify(\n distinctId: string,\n properties?: PersonProperties,\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n this.posthog?.identify({\n distinctId,\n properties,\n });\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n\n /**\n * Identify a group and set its properties\n *\n * Groups are useful for B2B SaaS to track organizations, teams, or accounts.\n *\n * @param groupType - Type of group (e.g., 'company', 'organization', 'team')\n * @param groupKey - Unique identifier for the group\n * @param properties - Group properties\n *\n * @example\n * ```typescript\n * await subscriber.groupIdentify('company', 'acme-corp', {\n * $set: {\n * name: 'Acme Corporation',\n * industry: 'saas',\n * employees: 500,\n * plan: 'enterprise'\n * }\n * });\n * ```\n */\n async groupIdentify(\n groupType: string,\n groupKey: string | number,\n properties?: Record<string, any>,\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n this.posthog?.groupIdentify({\n groupType,\n groupKey: String(groupKey), // Convert to string for PostHog SDK\n properties,\n });\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n\n /**\n * Track an event with group context\n *\n * Use this to associate events with groups (e.g., organizations).\n *\n * @param name - Event name\n * @param attributes - Event attributes\n * @param groups - Group context (e.g., { company: 'acme-corp' })\n *\n * @example\n * ```typescript\n * await subscriber.trackEventWithGroups('feature.used', {\n * userId: 'user-123',\n * feature: 'advanced-events'\n * }, {\n * company: 'acme-corp'\n * });\n * ```\n */\n async trackEventWithGroups(\n name: string,\n attributes?: EventAttributes,\n groups?: Record<string, string | number>,\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n const eventAttributes: EventAttributes = {\n ...attributes,\n } as EventAttributes;\n if (groups) {\n (eventAttributes as any).groups = groups;\n }\n\n await this.trackEvent(name, eventAttributes);\n }\n\n /**\n * Capture an exception and send to PostHog error tracking.\n *\n * If using browser client (window.posthog), delegates to its captureException.\n * Otherwise, formats and sends via posthog-node capture API.\n */\n async captureException(\n error: unknown,\n options?: {\n distinctId?: string;\n additionalProperties?: Record<string, unknown>;\n },\n ): Promise<void> {\n if (!this.enabled) return;\n await this.ensureInitialized();\n\n try {\n if (this.isBrowserClient) {\n const browserProps = options?.additionalProperties\n ? this.redactProperties(options.additionalProperties)\n : undefined;\n (this.posthog as any)?.captureException?.(error, browserProps);\n return;\n }\n\n const exceptionList = errorToExceptionList(\n error,\n this.stringRedactor ?? undefined,\n );\n const formatted = formatExceptionForPostHog(\n exceptionList,\n 'node:javascript',\n this.stringRedactor ?? undefined,\n );\n\n const properties = {\n ...formatted,\n ...options?.additionalProperties,\n };\n\n this.posthog?.capture({\n distinctId: options?.distinctId || 'anonymous',\n event: '$exception',\n properties: this.redactProperties(properties),\n });\n } catch (error_) {\n this.config.onError?.(error_ as Error);\n }\n }\n\n /**\n * Flush pending events and clean up resources\n */\n async shutdown(): Promise<void> {\n await super.shutdown(); // Drain pending requests first\n await this.ensureInitialized();\n\n if (this.posthog) {\n try {\n await this.posthog.shutdown();\n } catch (error) {\n this.config.onError?.(error as Error);\n }\n }\n }\n\n /**\n * Handle errors with custom error handler\n */\n protected handleError(error: Error, payload: EventPayload): void {\n // Call basic onError if provided\n this.config.onError?.(error);\n\n // Call enhanced onErrorWithContext if provided\n if (this.config.onErrorWithContext) {\n this.config.onErrorWithContext({\n error,\n eventName: payload.name,\n eventType: payload.type,\n attributes: payload.attributes,\n subscriberName: this.name,\n });\n }\n\n super.handleError(error, payload);\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"posthog.d.cts","names":[],"sources":["../src/posthog.ts"],"mappings":";;;;;;;;;UA2GiB;;EAEf,OAAO;;EAGP;;EAGA;;EAGA,aAAa;;EAGb;;KAGG,kBAAkB;UAEN;;EAEf;;EAGA;;EAGA;;EAGA,SAAS;;;;;;;;;;;;;;;EAgBT;;;;;;;;;;;;;;;;;;EAmBA;;EAIA;;EAGA;;EAIA;;EAGA;;EAGA;;;;;;;;;;;;;;;;;;;EAoBA;;EAIA,WAAW,OAAO;;;;;;;;;;;;;;;;;;EAmBlB,sBAAsB,SAAS;;EAG/B;;EAGA,iBAAiB;;EAGjB;;;;;UAMe;;EAEf,SAAS;;EAGT,kBAAkB,eAAe;;EAGjC,mBAAmB;;EAGnB;;EAGA;;;;;UAMe;;EAEf,OAAO;;EAGP,YAAY;;GAGX;;cAGU,0BAA0B;WAC5B;WACA;UAED;UACA;UACA;;UAEA;UACA;UAEA;EAEI,YAAA,QAAQ;UA2CN;UAkDN;UAUM;UAON;UAIA;UAaA;UAkBA;;;;;EAiBR,kBAAkB,UAAU;;;;;;;;;;YAaZ,kBAAkB,SAAS,eAAe;;;;;;;;;;;;;;;;;;;EAiIpD,iBACJ,iBACA,oBACA,UAAU,qBACT;;;;;;;;;;;;;;;;;;;;EAqCG,eACJ,iBACA,oBACA,UAAU,qBACT;;;;;;;;;;;;;;EA6BG,YACJ,oBACA,UAAU,qBACT,QAAQ;;;;;;;;;;;EAuBL,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;EAqCtB,SACJ,oBACA,aAAa,mBACZ;;;;;;;;;;;;;;;;;;;;;;EAmCG,cACJ,mBACA,2BACA,aAAa,sBACZ;;;;;;;;;;;;;;;;;;;;EAkCG,qBACJ,cACA,aAAa,iBACb,SAAS,kCACR;;;;;;;EAoBG,iBACJ,gBACA;IACE;IACA,uBAAuB;MAExB;;;;EAyCG,YAAY;;;;YAgBR,YAAY,OAAO,OAAO,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"posthog.d.ts","names":[],"sources":["../src/posthog.ts"],"mappings":";;;;;;;;;UA2GiB;;EAEf,OAAO;;EAGP;;EAGA;;EAGA,aAAa;;EAGb;;KAGG,kBAAkB;UAEN;;EAEf;;EAGA;;EAGA;;EAGA,SAAS;;;;;;;;;;;;;;;EAgBT;;;;;;;;;;;;;;;;;;EAmBA;;EAIA;;EAGA;;EAIA;;EAGA;;EAGA;;;;;;;;;;;;;;;;;;;EAoBA;;EAIA,WAAW,OAAO;;;;;;;;;;;;;;;;;;EAmBlB,sBAAsB,SAAS;;EAG/B;;EAGA,iBAAiB;;EAGjB;;;;;UAMe;;EAEf,SAAS;;EAGT,kBAAkB,eAAe;;EAGjC,mBAAmB;;EAGnB;;EAGA;;;;;UAMe;;EAEf,OAAO;;EAGP,YAAY;;GAGX;;cAGU,0BAA0B;WAC5B;WACA;UAED;UACA;UACA;;UAEA;UACA;UAEA;EAEI,YAAA,QAAQ;UA2CN;UAkDN;UAUM;UAON;UAIA;UAaA;UAkBA;;;;;EAiBR,kBAAkB,UAAU;;;;;;;;;;YAaZ,kBAAkB,SAAS,eAAe;;;;;;;;;;;;;;;;;;;EAiIpD,iBACJ,iBACA,oBACA,UAAU,qBACT;;;;;;;;;;;;;;;;;;;;EAqCG,eACJ,iBACA,oBACA,UAAU,qBACT;;;;;;;;;;;;;;EA6BG,YACJ,oBACA,UAAU,qBACT,QAAQ;;;;;;;;;;;EAuBL,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;EAqCtB,SACJ,oBACA,aAAa,mBACZ;;;;;;;;;;;;;;;;;;;;;;EAmCG,cACJ,mBACA,2BACA,aAAa,sBACZ;;;;;;;;;;;;;;;;;;;;EAkCG,qBACJ,cACA,aAAa,iBACb,SAAS,kCACR;;;;;;;EAoBG,iBACJ,gBACA;IACE;IACA,uBAAuB;MAExB;;;;EAyCG,YAAY;;;;YAgBR,YAAY,OAAO,OAAO,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"security.cjs","names":["EventSubscriber","createHttpClient","SECURITY_SEVERITY_RANK","postJsonWithRetry"],"sources":["../src/security.ts"],"sourcesContent":["/**\n * SecuritySubscriber - Forward security events to a webhook, SIEM, or pager\n *\n * Pairs with `autotel-audit`'s security-event schema: track security events\n * through the Events API and this subscriber forwards the ones that matter\n * (severity-gated) to your alerting destination.\n *\n * @example Webhook (SIEM / incident channel)\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { SecuritySubscriber } from 'autotel-subscribers/security';\n *\n * const events = new Event('api', {\n * subscribers: [\n * new SecuritySubscriber({\n * webhookUrl: process.env.SECURITY_WEBHOOK_URL!,\n * minSeverity: 'error',\n * }),\n * ],\n * });\n *\n * events.trackEvent('security.access.tenant.violation', {\n * category: 'authorization',\n * outcome: 'denied',\n * severity: 'critical',\n * actorId: 'user-1',\n * });\n * ```\n *\n * @example Custom handler (PagerDuty, OpsGenie, internal bus)\n * ```typescript\n * new SecuritySubscriber({\n * handler: async (alert) => pagerduty.trigger(alert),\n * minSeverity: 'critical',\n * });\n * ```\n */\n\nimport {\n EventSubscriber,\n type EventPayload,\n type AutotelEventContext,\n} from './event-subscriber-base';\nimport type { EventAttributes } from 'autotel/event-subscriber';\nimport {\n SECURITY_SEVERITY_RANK,\n parseSecuritySeverity,\n} from 'autotel/security-schema';\nimport type { SecuritySeverity } from 'autotel/security-schema';\nimport { createHttpClient } from './http-client';\nimport { postJsonWithRetry } from './webhook-delivery';\n\nexport type SecurityAlertSeverity = SecuritySeverity;\n\n/** Normalized alert passed to handlers and POSTed to webhooks. */\nexport interface SecurityAlert {\n /** Full event name, e.g. `security.auth.login.failed`. */\n event: string;\n severity: SecurityAlertSeverity;\n category?: string;\n outcome?: string;\n reason?: string;\n /** Remaining event attributes (severity/category/outcome/reason lifted out). */\n attributes?: EventAttributes;\n /** ISO 8601. */\n timestamp: string;\n /** Trace correlation, when the Events pipeline includes it. */\n trace?: AutotelEventContext;\n}\n\nexport interface SecuritySubscriberConfig {\n /** POST alerts as JSON to this URL. Required unless `handler` is set. */\n webhookUrl?: string;\n /** Extra headers for the webhook request (e.g. auth). */\n headers?: Record<string, string>;\n /** Custom destination — takes precedence over `webhookUrl`. */\n handler?: (alert: SecurityAlert) => void | Promise<void>;\n /** Forward events at or above this severity. Default `warning`. */\n minSeverity?: SecurityAlertSeverity;\n /**\n * Events are recognized as security events when their name starts with\n * this prefix. Default `security.`.\n */\n eventPrefix?: string;\n /** Extra predicate applied after the prefix/severity gates. */\n filter?: (payload: EventPayload) => boolean;\n /** Enable/disable subscriber. Default true. */\n enabled?: boolean;\n /** Webhook delivery attempts including the first. Default 3. */\n maxRetries?: number;\n /** Webhook request timeout in milliseconds. Default 30_000. */\n timeoutMs?: number;\n /** Base webhook retry backoff; doubles per attempt. Default 1000. */\n retryDelayMs?: number;\n}\n\nexport class SecuritySubscriber extends EventSubscriber {\n readonly name = 'SecuritySubscriber';\n readonly version = '1.0.0';\n\n private config: {\n webhookUrl?: string;\n headers: Record<string, string>;\n handler?: (alert: SecurityAlert) => void | Promise<void>;\n minSeverity: SecurityAlertSeverity;\n eventPrefix: string;\n filter?: (payload: EventPayload) => boolean;\n maxRetries?: number;\n retryDelayMs?: number;\n };\n\n private readonly httpClient;\n\n constructor(config: SecuritySubscriberConfig) {\n super();\n\n this.config = {\n webhookUrl: config.webhookUrl,\n headers: config.headers ?? {},\n handler: config.handler,\n minSeverity: config.minSeverity ?? 'warning',\n eventPrefix: config.eventPrefix ?? 'security.',\n filter: config.filter,\n maxRetries: config.maxRetries,\n retryDelayMs: config.retryDelayMs,\n };\n this.httpClient = createHttpClient({ timeoutMs: config.timeoutMs });\n\n this.enabled = config.enabled ?? true;\n\n if (!this.config.webhookUrl && !this.config.handler) {\n console.error(\n '[SecuritySubscriber] No webhookUrl or handler provided - subscriber disabled',\n );\n this.enabled = false;\n }\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n if (!payload.name.startsWith(this.config.eventPrefix)) {\n return; // Not a security event\n }\n\n const severity = parseSecuritySeverity(payload.attributes?.severity);\n if (\n SECURITY_SEVERITY_RANK[severity] <\n SECURITY_SEVERITY_RANK[this.config.minSeverity]\n ) {\n return; // Below the alerting bar\n }\n\n const filterFn = this.config.filter;\n if (filterFn && !filterFn(payload)) {\n return;\n }\n\n const alert = this.toAlert(payload, severity);\n\n if (this.config.handler) {\n await this.config.handler(alert);\n return;\n }\n\n await postJsonWithRetry(\n this.httpClient,\n this.config.webhookUrl as string,\n alert,\n {\n headers: this.config.headers,\n maxRetries: this.config.maxRetries,\n retryDelayMs: this.config.retryDelayMs,\n label: 'Security webhook',\n },\n );\n }\n\n private toAlert(\n payload: EventPayload,\n severity: SecurityAlertSeverity,\n ): SecurityAlert {\n const {\n severity: _severity,\n category,\n outcome,\n reason,\n ...rest\n } = payload.attributes ?? {};\n\n return {\n event: payload.name,\n severity,\n ...(typeof category === 'string' && { category }),\n ...(typeof outcome === 'string' && { outcome }),\n ...(typeof reason === 'string' && { reason }),\n ...(Object.keys(rest).length > 0 && { attributes: rest }),\n timestamp: payload.timestamp,\n ...(payload.autotel && { trace: payload.autotel }),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,IAAa,qBAAb,cAAwCA,8CAAgB;CACtD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CAWR,AAAiB;CAEjB,YAAY,QAAkC;EAC5C,MAAM;EAEN,KAAK,SAAS;GACZ,YAAY,OAAO;GACnB,SAAS,OAAO,WAAW,CAAC;GAC5B,SAAS,OAAO;GAChB,aAAa,OAAO,eAAe;GACnC,aAAa,OAAO,eAAe;GACnC,QAAQ,OAAO;GACf,YAAY,OAAO;GACnB,cAAc,OAAO;EACvB;EACA,KAAK,aAAaC,0CAAiB,EAAE,WAAW,OAAO,UAAU,CAAC;EAElE,KAAK,UAAU,OAAO,WAAW;EAEjC,IAAI,CAAC,KAAK,OAAO,cAAc,CAAC,KAAK,OAAO,SAAS;GACnD,QAAQ,MACN,8EACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAgB,kBAAkB,SAAsC;EACtE,IAAI,CAAC,QAAQ,KAAK,WAAW,KAAK,OAAO,WAAW,GAClD;EAGF,MAAM,8DAAiC,QAAQ,YAAY,QAAQ;EACnE,IACEC,+CAAuB,YACvBA,+CAAuB,KAAK,OAAO,cAEnC;EAGF,MAAM,WAAW,KAAK,OAAO;EAC7B,IAAI,YAAY,CAAC,SAAS,OAAO,GAC/B;EAGF,MAAM,QAAQ,KAAK,QAAQ,SAAS,QAAQ;EAE5C,IAAI,KAAK,OAAO,SAAS;GACvB,MAAM,KAAK,OAAO,QAAQ,KAAK;GAC/B;EACF;EAEA,MAAMC,2CACJ,KAAK,YACL,KAAK,OAAO,YACZ,OACA;GACE,SAAS,KAAK,OAAO;GACrB,YAAY,KAAK,OAAO;GACxB,cAAc,KAAK,OAAO;GAC1B,OAAO;EACT,CACF;CACF;CAEA,AAAQ,QACN,SACA,UACe;EACf,MAAM,EACJ,UAAU,WACV,UACA,SACA,QACA,GAAG,SACD,QAAQ,cAAc,CAAC;EAE3B,OAAO;GACL,OAAO,QAAQ;GACf;GACA,GAAI,OAAO,aAAa,YAAY,EAAE,SAAS;GAC/C,GAAI,OAAO,YAAY,YAAY,EAAE,QAAQ;GAC7C,GAAI,OAAO,WAAW,YAAY,EAAE,OAAO;GAC3C,GAAI,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,KAAK,EAAE,YAAY,KAAK;GACvD,WAAW,QAAQ;GACnB,GAAI,QAAQ,WAAW,EAAE,OAAO,QAAQ,QAAQ;EAClD;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"security.d.cts","names":[],"sources":["../src/security.ts"],"mappings":";;;;KAoDY,wBAAwB;;UAGnB;;EAEf;EACA,UAAU;EACV;EACA;EACA;;EAEA,aAAa;;EAEb;;EAEA,QAAQ;;UAGO;;EAEf;;EAEA,UAAU;;EAEV,WAAW,OAAO,yBAAyB;;EAE3C,cAAc;;;;;EAKd;;EAEA,UAAU,SAAS;;EAEnB;;EAEA;;EAEA;;EAEA;;cAGW,2BAA2B;WAC7B;WACA;UAED;mBAWS;EAEL,YAAA,QAAQ;YAyBJ,kBAAkB,SAAS,eAAe;UAsClD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"security.d.ts","names":[],"sources":["../src/security.ts"],"mappings":";;;;KAoDY,wBAAwB;;UAGnB;;EAEf;EACA,UAAU;EACV;EACA;EACA;;EAEA,aAAa;;EAEb;;EAEA,QAAQ;;UAGO;;EAEf;;EAEA,UAAU;;EAEV,WAAW,OAAO,yBAAyB;;EAE3C,cAAc;;;;;EAKd;;EAEA,UAAU,SAAS;;EAEnB;;EAEA;;EAEA;;EAEA;;cAGW,2BAA2B;WAC7B;WACA;UAED;mBAWS;EAEL,YAAA,QAAQ;YAyBJ,kBAAkB,SAAS,eAAe;UAsClD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"security.js","names":[],"sources":["../src/security.ts"],"sourcesContent":["/**\n * SecuritySubscriber - Forward security events to a webhook, SIEM, or pager\n *\n * Pairs with `autotel-audit`'s security-event schema: track security events\n * through the Events API and this subscriber forwards the ones that matter\n * (severity-gated) to your alerting destination.\n *\n * @example Webhook (SIEM / incident channel)\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { SecuritySubscriber } from 'autotel-subscribers/security';\n *\n * const events = new Event('api', {\n * subscribers: [\n * new SecuritySubscriber({\n * webhookUrl: process.env.SECURITY_WEBHOOK_URL!,\n * minSeverity: 'error',\n * }),\n * ],\n * });\n *\n * events.trackEvent('security.access.tenant.violation', {\n * category: 'authorization',\n * outcome: 'denied',\n * severity: 'critical',\n * actorId: 'user-1',\n * });\n * ```\n *\n * @example Custom handler (PagerDuty, OpsGenie, internal bus)\n * ```typescript\n * new SecuritySubscriber({\n * handler: async (alert) => pagerduty.trigger(alert),\n * minSeverity: 'critical',\n * });\n * ```\n */\n\nimport {\n EventSubscriber,\n type EventPayload,\n type AutotelEventContext,\n} from './event-subscriber-base';\nimport type { EventAttributes } from 'autotel/event-subscriber';\nimport {\n SECURITY_SEVERITY_RANK,\n parseSecuritySeverity,\n} from 'autotel/security-schema';\nimport type { SecuritySeverity } from 'autotel/security-schema';\nimport { createHttpClient } from './http-client';\nimport { postJsonWithRetry } from './webhook-delivery';\n\nexport type SecurityAlertSeverity = SecuritySeverity;\n\n/** Normalized alert passed to handlers and POSTed to webhooks. */\nexport interface SecurityAlert {\n /** Full event name, e.g. `security.auth.login.failed`. */\n event: string;\n severity: SecurityAlertSeverity;\n category?: string;\n outcome?: string;\n reason?: string;\n /** Remaining event attributes (severity/category/outcome/reason lifted out). */\n attributes?: EventAttributes;\n /** ISO 8601. */\n timestamp: string;\n /** Trace correlation, when the Events pipeline includes it. */\n trace?: AutotelEventContext;\n}\n\nexport interface SecuritySubscriberConfig {\n /** POST alerts as JSON to this URL. Required unless `handler` is set. */\n webhookUrl?: string;\n /** Extra headers for the webhook request (e.g. auth). */\n headers?: Record<string, string>;\n /** Custom destination — takes precedence over `webhookUrl`. */\n handler?: (alert: SecurityAlert) => void | Promise<void>;\n /** Forward events at or above this severity. Default `warning`. */\n minSeverity?: SecurityAlertSeverity;\n /**\n * Events are recognized as security events when their name starts with\n * this prefix. Default `security.`.\n */\n eventPrefix?: string;\n /** Extra predicate applied after the prefix/severity gates. */\n filter?: (payload: EventPayload) => boolean;\n /** Enable/disable subscriber. Default true. */\n enabled?: boolean;\n /** Webhook delivery attempts including the first. Default 3. */\n maxRetries?: number;\n /** Webhook request timeout in milliseconds. Default 30_000. */\n timeoutMs?: number;\n /** Base webhook retry backoff; doubles per attempt. Default 1000. */\n retryDelayMs?: number;\n}\n\nexport class SecuritySubscriber extends EventSubscriber {\n readonly name = 'SecuritySubscriber';\n readonly version = '1.0.0';\n\n private config: {\n webhookUrl?: string;\n headers: Record<string, string>;\n handler?: (alert: SecurityAlert) => void | Promise<void>;\n minSeverity: SecurityAlertSeverity;\n eventPrefix: string;\n filter?: (payload: EventPayload) => boolean;\n maxRetries?: number;\n retryDelayMs?: number;\n };\n\n private readonly httpClient;\n\n constructor(config: SecuritySubscriberConfig) {\n super();\n\n this.config = {\n webhookUrl: config.webhookUrl,\n headers: config.headers ?? {},\n handler: config.handler,\n minSeverity: config.minSeverity ?? 'warning',\n eventPrefix: config.eventPrefix ?? 'security.',\n filter: config.filter,\n maxRetries: config.maxRetries,\n retryDelayMs: config.retryDelayMs,\n };\n this.httpClient = createHttpClient({ timeoutMs: config.timeoutMs });\n\n this.enabled = config.enabled ?? true;\n\n if (!this.config.webhookUrl && !this.config.handler) {\n console.error(\n '[SecuritySubscriber] No webhookUrl or handler provided - subscriber disabled',\n );\n this.enabled = false;\n }\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n if (!payload.name.startsWith(this.config.eventPrefix)) {\n return; // Not a security event\n }\n\n const severity = parseSecuritySeverity(payload.attributes?.severity);\n if (\n SECURITY_SEVERITY_RANK[severity] <\n SECURITY_SEVERITY_RANK[this.config.minSeverity]\n ) {\n return; // Below the alerting bar\n }\n\n const filterFn = this.config.filter;\n if (filterFn && !filterFn(payload)) {\n return;\n }\n\n const alert = this.toAlert(payload, severity);\n\n if (this.config.handler) {\n await this.config.handler(alert);\n return;\n }\n\n await postJsonWithRetry(\n this.httpClient,\n this.config.webhookUrl as string,\n alert,\n {\n headers: this.config.headers,\n maxRetries: this.config.maxRetries,\n retryDelayMs: this.config.retryDelayMs,\n label: 'Security webhook',\n },\n );\n }\n\n private toAlert(\n payload: EventPayload,\n severity: SecurityAlertSeverity,\n ): SecurityAlert {\n const {\n severity: _severity,\n category,\n outcome,\n reason,\n ...rest\n } = payload.attributes ?? {};\n\n return {\n event: payload.name,\n severity,\n ...(typeof category === 'string' && { category }),\n ...(typeof outcome === 'string' && { outcome }),\n ...(typeof reason === 'string' && { reason }),\n ...(Object.keys(rest).length > 0 && { attributes: rest }),\n timestamp: payload.timestamp,\n ...(payload.autotel && { trace: payload.autotel }),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,IAAa,qBAAb,cAAwC,gBAAgB;CACtD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CAWR,AAAiB;CAEjB,YAAY,QAAkC;EAC5C,MAAM;EAEN,KAAK,SAAS;GACZ,YAAY,OAAO;GACnB,SAAS,OAAO,WAAW,CAAC;GAC5B,SAAS,OAAO;GAChB,aAAa,OAAO,eAAe;GACnC,aAAa,OAAO,eAAe;GACnC,QAAQ,OAAO;GACf,YAAY,OAAO;GACnB,cAAc,OAAO;EACvB;EACA,KAAK,aAAa,iBAAiB,EAAE,WAAW,OAAO,UAAU,CAAC;EAElE,KAAK,UAAU,OAAO,WAAW;EAEjC,IAAI,CAAC,KAAK,OAAO,cAAc,CAAC,KAAK,OAAO,SAAS;GACnD,QAAQ,MACN,8EACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAgB,kBAAkB,SAAsC;EACtE,IAAI,CAAC,QAAQ,KAAK,WAAW,KAAK,OAAO,WAAW,GAClD;EAGF,MAAM,WAAW,sBAAsB,QAAQ,YAAY,QAAQ;EACnE,IACE,uBAAuB,YACvB,uBAAuB,KAAK,OAAO,cAEnC;EAGF,MAAM,WAAW,KAAK,OAAO;EAC7B,IAAI,YAAY,CAAC,SAAS,OAAO,GAC/B;EAGF,MAAM,QAAQ,KAAK,QAAQ,SAAS,QAAQ;EAE5C,IAAI,KAAK,OAAO,SAAS;GACvB,MAAM,KAAK,OAAO,QAAQ,KAAK;GAC/B;EACF;EAEA,MAAM,kBACJ,KAAK,YACL,KAAK,OAAO,YACZ,OACA;GACE,SAAS,KAAK,OAAO;GACrB,YAAY,KAAK,OAAO;GACxB,cAAc,KAAK,OAAO;GAC1B,OAAO;EACT,CACF;CACF;CAEA,AAAQ,QACN,SACA,UACe;EACf,MAAM,EACJ,UAAU,WACV,UACA,SACA,QACA,GAAG,SACD,QAAQ,cAAc,CAAC;EAE3B,OAAO;GACL,OAAO,QAAQ;GACf;GACA,GAAI,OAAO,aAAa,YAAY,EAAE,SAAS;GAC/C,GAAI,OAAO,YAAY,YAAY,EAAE,QAAQ;GAC7C,GAAI,OAAO,WAAW,YAAY,EAAE,OAAO;GAC3C,GAAI,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,KAAK,EAAE,YAAY,KAAK;GACvD,WAAW,QAAQ;GACnB,GAAI,QAAQ,WAAW,EAAE,OAAO,QAAQ,QAAQ;EAClD;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"segment.cjs","names":[],"sources":["../src/segment.ts"],"sourcesContent":["/**\n * Segment Subscriber for autotel\n *\n * Send events to Segment (customer data platform).\n *\n * @example\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { SegmentSubscriber } from 'autotel-subscribers/segment';\n *\n * const events = new Event('checkout', {\n * subscribers: [\n * new SegmentSubscriber({\n * writeKey: process.env.SEGMENT_WRITE_KEY!\n * })\n * ]\n * });\n *\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\nexport interface SegmentConfig {\n /** Segment write key */\n writeKey: string;\n /** Enable/disable the subscriber */\n enabled?: boolean;\n}\n\nexport class SegmentSubscriber implements EventSubscriber {\n readonly name = 'SegmentSubscriber';\n readonly version = '1.0.0';\n\n private events: any;\n private enabled: boolean;\n private config: SegmentConfig;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: SegmentConfig) {\n this.enabled = config.enabled ?? true;\n this.config = config;\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Dynamic import to avoid adding @segment/events-node as a hard dependency\n const { Analytics } = await import('@segment/analytics-node');\n this.events = new Analytics({ writeKey: this.config.writeKey });\n } catch (error) {\n console.error(\n 'Segment subscriber failed to initialize. Install @segment/events-node: pnpm add @segment/events-node',\n error,\n );\n this.enabled = false;\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: name,\n properties: attributes,\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: `${funnelName}.${step}`,\n properties: {\n funnel: funnelName,\n step,\n ...attributes,\n },\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: `${operationName}.${outcome}`,\n properties: {\n operation: operationName,\n outcome,\n ...attributes,\n },\n });\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: name,\n properties: {\n value,\n ...attributes,\n },\n });\n }\n\n /** Flush pending events before shutdown */\n async shutdown(): Promise<void> {\n await this.ensureInitialized();\n if (this.events) {\n await this.events.closeAndFlush();\n }\n }\n}\n"],"mappings":";;;AAoCA,IAAa,oBAAb,MAA0D;CACxD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,cAAoC;CAE5C,YAAY,QAAuB;EACjC,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS;EAEd,IAAI,KAAK,SAEP,KAAK,cAAc,KAAK,WAAW;CAEvC;CAEA,MAAc,aAA4B;EACxC,IAAI;GAEF,MAAM,EAAE,cAAc,MAAM,OAAO;GACnC,KAAK,SAAS,IAAI,UAAU,EAAE,UAAU,KAAK,OAAO,SAAS,CAAC;EAChE,SAAS,OAAO;GACd,QAAQ,MACN,wGACA,KACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAc,oBAAmC;EAC/C,IAAI,KAAK,aAAa;GACpB,MAAM,KAAK;GACX,KAAK,cAAc;EACrB;CACF;CAEA,MAAM,WAAW,MAAc,YAA6C;EAC1E,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO;GACP,YAAY;EACd,CAAC;CACH;CAEA,MAAM,gBACJ,YACA,MACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO,GAAG,WAAW,GAAG;GACxB,YAAY;IACV,QAAQ;IACR;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,aACJ,eACA,SACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO,GAAG,cAAc,GAAG;GAC3B,YAAY;IACV,WAAW;IACX;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,WACJ,MACA,OACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO;GACP,YAAY;IACV;IACA,GAAG;GACL;EACF,CAAC;CACH;;CAGA,MAAM,WAA0B;EAC9B,MAAM,KAAK,kBAAkB;EAC7B,IAAI,KAAK,QACP,MAAM,KAAK,OAAO,cAAc;CAEpC;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"segment.d.cts","names":[],"sources":["../src/segment.ts"],"mappings":";;UA6BiB;;EAEf;;EAEA;;cAGW,6BAA6B;WAC/B;WACA;UAED;UACA;UACA;UACA;EAEI,YAAA,QAAQ;UAUN;UAcA;EAOR,WAAW,cAAc,aAAa,kBAAkB;EAWxD,gBACJ,oBACA,MAAM,cACN,aAAa,kBACZ;EAeG,aACJ,uBACA,SAAS,eACT,aAAa,kBACZ;EAeG,WACJ,cACA,eACA,aAAa,kBACZ;;EAeG,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"segment.d.ts","names":[],"sources":["../src/segment.ts"],"mappings":";;UA6BiB;;EAEf;;EAEA;;cAGW,6BAA6B;WAC/B;WACA;UAED;UACA;UACA;UACA;EAEI,YAAA,QAAQ;UAUN;UAcA;EAOR,WAAW,cAAc,aAAa,kBAAkB;EAWxD,gBACJ,oBACA,MAAM,cACN,aAAa,kBACZ;EAeG,aACJ,uBACA,SAAS,eACT,aAAa,kBACZ;EAeG,WACJ,cACA,eACA,aAAa,kBACZ;;EAeG,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"segment.js","names":[],"sources":["../src/segment.ts"],"sourcesContent":["/**\n * Segment Subscriber for autotel\n *\n * Send events to Segment (customer data platform).\n *\n * @example\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { SegmentSubscriber } from 'autotel-subscribers/segment';\n *\n * const events = new Event('checkout', {\n * subscribers: [\n * new SegmentSubscriber({\n * writeKey: process.env.SEGMENT_WRITE_KEY!\n * })\n * ]\n * });\n *\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\nexport interface SegmentConfig {\n /** Segment write key */\n writeKey: string;\n /** Enable/disable the subscriber */\n enabled?: boolean;\n}\n\nexport class SegmentSubscriber implements EventSubscriber {\n readonly name = 'SegmentSubscriber';\n readonly version = '1.0.0';\n\n private events: any;\n private enabled: boolean;\n private config: SegmentConfig;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: SegmentConfig) {\n this.enabled = config.enabled ?? true;\n this.config = config;\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Dynamic import to avoid adding @segment/events-node as a hard dependency\n const { Analytics } = await import('@segment/analytics-node');\n this.events = new Analytics({ writeKey: this.config.writeKey });\n } catch (error) {\n console.error(\n 'Segment subscriber failed to initialize. Install @segment/events-node: pnpm add @segment/events-node',\n error,\n );\n this.enabled = false;\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: name,\n properties: attributes,\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: `${funnelName}.${step}`,\n properties: {\n funnel: funnelName,\n step,\n ...attributes,\n },\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: `${operationName}.${outcome}`,\n properties: {\n operation: operationName,\n outcome,\n ...attributes,\n },\n });\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.events?.track({\n userId: attributes?.userId || attributes?.user_id || 'anonymous',\n event: name,\n properties: {\n value,\n ...attributes,\n },\n });\n }\n\n /** Flush pending events before shutdown */\n async shutdown(): Promise<void> {\n await this.ensureInitialized();\n if (this.events) {\n await this.events.closeAndFlush();\n }\n }\n}\n"],"mappings":";AAoCA,IAAa,oBAAb,MAA0D;CACxD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,cAAoC;CAE5C,YAAY,QAAuB;EACjC,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS;EAEd,IAAI,KAAK,SAEP,KAAK,cAAc,KAAK,WAAW;CAEvC;CAEA,MAAc,aAA4B;EACxC,IAAI;GAEF,MAAM,EAAE,cAAc,MAAM,OAAO;GACnC,KAAK,SAAS,IAAI,UAAU,EAAE,UAAU,KAAK,OAAO,SAAS,CAAC;EAChE,SAAS,OAAO;GACd,QAAQ,MACN,wGACA,KACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAc,oBAAmC;EAC/C,IAAI,KAAK,aAAa;GACpB,MAAM,KAAK;GACX,KAAK,cAAc;EACrB;CACF;CAEA,MAAM,WAAW,MAAc,YAA6C;EAC1E,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO;GACP,YAAY;EACd,CAAC;CACH;CAEA,MAAM,gBACJ,YACA,MACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO,GAAG,WAAW,GAAG;GACxB,YAAY;IACV,QAAQ;IACR;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,aACJ,eACA,SACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO,GAAG,cAAc,GAAG;GAC3B,YAAY;IACV,WAAW;IACX;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,WACJ,MACA,OACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,QAAQ,MAAM;GACjB,QAAQ,YAAY,UAAU,YAAY,WAAW;GACrD,OAAO;GACP,YAAY;IACV;IACA,GAAG;GACL;EACF,CAAC;CACH;;CAGA,MAAM,WAA0B;EAC9B,MAAM,KAAK,kBAAkB;EAC7B,IAAI,KAAK,QACP,MAAM,KAAK,OAAO,cAAc;CAEpC;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"slack.cjs","names":["EventSubscriber"],"sources":["../src/slack.ts"],"sourcesContent":["/**\n * Slack Subscriber for autotel\n *\n * Send events events as notifications to Slack channels via webhooks.\n *\n * Perfect for:\n * - Critical business events (orders, payments, signups)\n * - Real-time alerts for failures\n * - Team notifications for important milestones\n * - Monitoring funnel completions\n *\n * @example Basic usage\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { SlackSubscriber } from 'autotel-subscribers/slack';\n *\n * const events = new Event('app', {\n * subscribers: [\n * new SlackSubscriber({\n * webhookUrl: process.env.SLACK_WEBHOOK_URL!,\n * channel: '#order-events'\n * })\n * ]\n * });\n *\n * // Sends to Slack\n * events.trackEvent('order.completed', {\n * orderId: 'ord_123',\n * userId: 'user_456',\n * amount: 99.99\n * });\n * ```\n *\n * @example Filter critical events only\n * ```typescript\n * const events = new Event('app', {\n * subscribers: [\n * new SlackSubscriber({\n * webhookUrl: process.env.SLACK_WEBHOOK_URL!,\n * channel: '#alerts',\n * filter: (payload) => {\n * // Only send failures and high-value orders\n * if (payload.type === 'outcome' && payload.outcome === 'failure') {\n * return true;\n * }\n * if (payload.name === 'order.completed' && payload.attributes?.amount > 1000) {\n * return true;\n * }\n * return false;\n * }\n * })\n * ]\n * });\n * ```\n *\n * Setup:\n * 1. Create Slack App: https://api.slack.com/apps\n * 2. Enable Incoming Webhooks\n * 3. Add webhook to workspace\n * 4. Copy webhook URL (https://hooks.slack.com/services/...)\n */\n\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\nexport interface SlackSubscriberConfig {\n /** Slack webhook URL (https://hooks.slack.com/services/...) */\n webhookUrl: string;\n\n /** Default channel to post to (optional, overrides webhook default) */\n channel?: string;\n\n /** Custom username for bot (default: 'Events Bot') */\n username?: string;\n\n /** Custom emoji icon (default: ':chart_with_upwards_trend:') */\n iconEmoji?: string;\n\n /** Include timestamp in messages (default: true) */\n includeTimestamp?: boolean;\n\n /** Include event attributes as fields (default: true) */\n includeAttributes?: boolean;\n\n /** Maximum attributes to show (default: 10) */\n maxAttributeFields?: number;\n\n /** Filter function - return true to send, false to skip */\n filter?: (payload: EventPayload) => boolean;\n\n /** Enable/disable subscriber */\n enabled?: boolean;\n}\n\ninterface SlackMessage {\n channel?: string;\n username?: string;\n icon_emoji?: string;\n text?: string;\n attachments: SlackAttachment[];\n}\n\ninterface SlackAttachment {\n color?: string;\n title?: string;\n text?: string;\n fields?: SlackField[];\n footer?: string;\n footer_icon?: string;\n ts?: number;\n}\n\ninterface SlackField {\n title: string;\n value: string;\n short: boolean;\n}\n\nexport class SlackSubscriber extends EventSubscriber {\n readonly name = 'SlackSubscriber';\n readonly version = '1.0.0';\n\n private config: Required<\n Omit<SlackSubscriberConfig, 'channel' | 'filter'>\n > & {\n channel?: string;\n filter?: (payload: EventPayload) => boolean;\n };\n\n constructor(config: SlackSubscriberConfig) {\n super();\n\n this.config = {\n webhookUrl: config.webhookUrl,\n channel: config.channel,\n username: config.username ?? 'Events Bot',\n iconEmoji: config.iconEmoji ?? ':chart_with_upwards_trend:',\n includeTimestamp: config.includeTimestamp ?? true,\n includeAttributes: config.includeAttributes ?? true,\n maxAttributeFields: config.maxAttributeFields ?? 10,\n filter: config.filter,\n enabled: config.enabled ?? true,\n };\n\n this.enabled = this.config.enabled;\n\n if (!this.config.webhookUrl) {\n console.error(\n '[SlackSubscriber] No webhook URL provided - subscriber disabled',\n );\n this.enabled = false;\n }\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n // Apply filter if provided\n if (this.config.filter) {\n const filterFn = this.config.filter;\n const shouldInclude = filterFn(payload);\n if (!shouldInclude) {\n return; // Skip this event\n }\n }\n\n const message = this.formatSlackMessage(payload);\n\n const response = await fetch(this.config.webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(message),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Slack webhook failed (${response.status}): ${errorText}`,\n );\n }\n }\n\n /**\n * Format events payload as Slack message\n */\n private formatSlackMessage(payload: EventPayload): SlackMessage {\n const emoji = this.getEventEmoji(payload);\n const color = this.getEventColor(payload);\n const title = `${emoji} ${payload.name}`;\n\n // Add event type\n const fields: SlackField[] = [\n {\n title: 'Event Type',\n value: this.formatEventType(payload),\n short: true,\n },\n ];\n\n // Add timestamp if enabled\n if (this.config.includeTimestamp) {\n fields.push({\n title: 'Timestamp',\n value: new Date(payload.timestamp).toLocaleString('en-US', {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }),\n short: true,\n });\n }\n\n // Add attributes as fields\n if (this.config.includeAttributes && payload.attributes) {\n const attributeFields = this.formatAttributes(payload.attributes);\n fields.push(...attributeFields);\n }\n\n const attachment: SlackAttachment = {\n color,\n title,\n fields,\n footer: 'Events Events',\n footer_icon: 'https://i.imgur.com/QpCKbNL.png',\n };\n\n // Add Unix timestamp for Slack\n if (this.config.includeTimestamp) {\n attachment.ts = Math.floor(new Date(payload.timestamp).getTime() / 1000);\n }\n\n return {\n channel: this.config.channel,\n username: this.config.username,\n icon_emoji: this.config.iconEmoji,\n attachments: [attachment],\n };\n }\n\n /**\n * Get emoji for event type\n */\n private getEventEmoji(payload: EventPayload): string {\n switch (payload.type) {\n case 'outcome': {\n return payload.outcome === 'success' ? '✅' : '❌';\n }\n case 'funnel': {\n return '🔄';\n }\n case 'value': {\n return '📊';\n }\n default: {\n // Use custom emoji for common event patterns\n if (payload.name.includes('order') || payload.name.includes('payment'))\n return '💰';\n if (payload.name.includes('signup') || payload.name.includes('user'))\n return '👤';\n if (payload.name.includes('error') || payload.name.includes('fail'))\n return '⚠️';\n return '📌';\n }\n }\n }\n\n /**\n * Get Slack attachment color for event type\n */\n private getEventColor(payload: EventPayload): string {\n switch (payload.type) {\n case 'outcome': {\n return payload.outcome === 'success' ? 'good' : 'danger';\n } // Green or red\n case 'funnel': {\n return '#3AA3E3';\n } // Blue\n case 'value': {\n return '#764FA5';\n } // Purple\n default: {\n // Custom colors for patterns\n if (payload.name.includes('error') || payload.name.includes('fail'))\n return 'danger';\n if (payload.name.includes('warning')) return 'warning';\n return 'good';\n } // Default green\n }\n }\n\n /**\n * Format event type for display\n */\n private formatEventType(payload: EventPayload): string {\n switch (payload.type) {\n case 'event': {\n return 'Event';\n }\n case 'funnel': {\n return `Funnel: ${payload.step || 'unknown'}`;\n }\n case 'outcome': {\n return `Outcome: ${payload.outcome || 'unknown'}`;\n }\n case 'value': {\n return `Value: ${payload.value ?? 'N/A'}`;\n }\n default: {\n return payload.type;\n }\n }\n }\n\n /**\n * Format attributes as Slack fields\n */\n private formatAttributes(attributes: Record<string, any>): SlackField[] {\n const fields: SlackField[] = [];\n const entries = Object.entries(attributes);\n\n // Limit number of fields\n const limit = Math.min(entries.length, this.config.maxAttributeFields);\n\n for (let i = 0; i < limit; i++) {\n const [key, value] = entries[i];\n\n // Skip internal/system fields\n if (key.startsWith('_') || key === 'timestamp') continue;\n\n fields.push({\n title: this.formatFieldName(key),\n value: this.formatFieldValue(value),\n short: true,\n });\n }\n\n // Add truncation notice if needed\n if (entries.length > this.config.maxAttributeFields) {\n fields.push({\n title: 'Note',\n value: `... and ${entries.length - this.config.maxAttributeFields} more fields`,\n short: false,\n });\n }\n\n return fields;\n }\n\n /**\n * Format field name (convert camelCase to Title Case)\n */\n private formatFieldName(name: string): string {\n return name\n .replaceAll(/([A-Z])/g, ' $1') // Add space before capitals\n .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter\n .trim();\n }\n\n /**\n * Format field value\n */\n private formatFieldValue(value: any): string {\n if (value === null || value === undefined) return 'N/A';\n if (typeof value === 'boolean') return value ? 'Yes' : 'No';\n if (typeof value === 'object') return JSON.stringify(value);\n if (typeof value === 'number' && !Number.isInteger(value)) {\n return value.toFixed(2);\n }\n return String(value);\n }\n\n /**\n * Handle errors (override from EventSubscriber)\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[SlackSubscriber] Failed to send ${payload.type} event \"${payload.name}\":`,\n error,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqHA,IAAa,kBAAb,cAAqCA,8CAAgB;CACnD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CAOR,YAAY,QAA+B;EACzC,MAAM;EAEN,KAAK,SAAS;GACZ,YAAY,OAAO;GACnB,SAAS,OAAO;GAChB,UAAU,OAAO,YAAY;GAC7B,WAAW,OAAO,aAAa;GAC/B,kBAAkB,OAAO,oBAAoB;GAC7C,mBAAmB,OAAO,qBAAqB;GAC/C,oBAAoB,OAAO,sBAAsB;GACjD,QAAQ,OAAO;GACf,SAAS,OAAO,WAAW;EAC7B;EAEA,KAAK,UAAU,KAAK,OAAO;EAE3B,IAAI,CAAC,KAAK,OAAO,YAAY;GAC3B,QAAQ,MACN,iEACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAgB,kBAAkB,SAAsC;EAEtE,IAAI,KAAK,OAAO,QAAQ;GACtB,MAAM,WAAW,KAAK,OAAO;GAE7B,IAAI,CADkB,SAAS,OACd,GACf;EAEJ;EAEA,MAAM,UAAU,KAAK,mBAAmB,OAAO;EAE/C,MAAM,WAAW,MAAM,MAAM,KAAK,OAAO,YAAY;GACnD,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,YAAY,MAAM,SAAS,KAAK;GACtC,MAAM,IAAI,MACR,yBAAyB,SAAS,OAAO,KAAK,WAChD;EACF;CACF;;;;CAKA,AAAQ,mBAAmB,SAAqC;EAC9D,MAAM,QAAQ,KAAK,cAAc,OAAO;EACxC,MAAM,QAAQ,KAAK,cAAc,OAAO;EACxC,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ;EAGlC,MAAM,SAAuB,CAC3B;GACE,OAAO;GACP,OAAO,KAAK,gBAAgB,OAAO;GACnC,OAAO;EACT,CACF;EAGA,IAAI,KAAK,OAAO,kBACd,OAAO,KAAK;GACV,OAAO;GACP,OAAO,IAAI,KAAK,QAAQ,SAAS,CAAC,CAAC,eAAe,SAAS;IACzD,OAAO;IACP,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;GACV,CAAC;GACD,OAAO;EACT,CAAC;EAIH,IAAI,KAAK,OAAO,qBAAqB,QAAQ,YAAY;GACvD,MAAM,kBAAkB,KAAK,iBAAiB,QAAQ,UAAU;GAChE,OAAO,KAAK,GAAG,eAAe;EAChC;EAEA,MAAM,aAA8B;GAClC;GACA;GACA;GACA,QAAQ;GACR,aAAa;EACf;EAGA,IAAI,KAAK,OAAO,kBACd,WAAW,KAAK,KAAK,MAAM,IAAI,KAAK,QAAQ,SAAS,CAAC,CAAC,QAAQ,IAAI,GAAI;EAGzE,OAAO;GACL,SAAS,KAAK,OAAO;GACrB,UAAU,KAAK,OAAO;GACtB,YAAY,KAAK,OAAO;GACxB,aAAa,CAAC,UAAU;EAC1B;CACF;;;;CAKA,AAAQ,cAAc,SAA+B;EACnD,QAAQ,QAAQ,MAAhB;GACE,KAAK,WACH,OAAO,QAAQ,YAAY,YAAY,MAAM;GAE/C,KAAK,UACH,OAAO;GAET,KAAK,SACH,OAAO;GAET;IAEE,IAAI,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,KAAK,SAAS,SAAS,GACnE,OAAO;IACT,IAAI,QAAQ,KAAK,SAAS,QAAQ,KAAK,QAAQ,KAAK,SAAS,MAAM,GACjE,OAAO;IACT,IAAI,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,KAAK,SAAS,MAAM,GAChE,OAAO;IACT,OAAO;EAEX;CACF;;;;CAKA,AAAQ,cAAc,SAA+B;EACnD,QAAQ,QAAQ,MAAhB;GACE,KAAK,WACH,OAAO,QAAQ,YAAY,YAAY,SAAS;GAElD,KAAK,UACH,OAAO;GAET,KAAK,SACH,OAAO;GAET;IAEE,IAAI,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,KAAK,SAAS,MAAM,GAChE,OAAO;IACT,IAAI,QAAQ,KAAK,SAAS,SAAS,GAAG,OAAO;IAC7C,OAAO;EAEX;CACF;;;;CAKA,AAAQ,gBAAgB,SAA+B;EACrD,QAAQ,QAAQ,MAAhB;GACE,KAAK,SACH,OAAO;GAET,KAAK,UACH,OAAO,WAAW,QAAQ,QAAQ;GAEpC,KAAK,WACH,OAAO,YAAY,QAAQ,WAAW;GAExC,KAAK,SACH,OAAO,UAAU,QAAQ,SAAS;GAEpC,SACE,OAAO,QAAQ;EAEnB;CACF;;;;CAKA,AAAQ,iBAAiB,YAA+C;EACtE,MAAM,SAAuB,CAAC;EAC9B,MAAM,UAAU,OAAO,QAAQ,UAAU;EAGzC,MAAM,QAAQ,KAAK,IAAI,QAAQ,QAAQ,KAAK,OAAO,kBAAkB;EAErE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,CAAC,KAAK,SAAS,QAAQ;GAG7B,IAAI,IAAI,WAAW,GAAG,KAAK,QAAQ,aAAa;GAEhD,OAAO,KAAK;IACV,OAAO,KAAK,gBAAgB,GAAG;IAC/B,OAAO,KAAK,iBAAiB,KAAK;IAClC,OAAO;GACT,CAAC;EACH;EAGA,IAAI,QAAQ,SAAS,KAAK,OAAO,oBAC/B,OAAO,KAAK;GACV,OAAO;GACP,OAAO,WAAW,QAAQ,SAAS,KAAK,OAAO,mBAAmB;GAClE,OAAO;EACT,CAAC;EAGH,OAAO;CACT;;;;CAKA,AAAQ,gBAAgB,MAAsB;EAC5C,OAAO,KACJ,WAAW,YAAY,KAAK,CAAC,CAC7B,QAAQ,OAAO,QAAQ,IAAI,YAAY,CAAC,CAAC,CACzC,KAAK;CACV;;;;CAKA,AAAQ,iBAAiB,OAAoB;EAC3C,IAAI,UAAU,QAAQ,UAAU,QAAW,OAAO;EAClD,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,QAAQ;EACvD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;EAC1D,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,GACtD,OAAO,MAAM,QAAQ,CAAC;EAExB,OAAO,OAAO,KAAK;CACrB;;;;CAKA,AAAU,YAAY,OAAc,SAA6B;EAC/D,QAAQ,MACN,oCAAoC,QAAQ,KAAK,UAAU,QAAQ,KAAK,KACxE,KACF;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"slack.d.cts","names":[],"sources":["../src/slack.ts"],"mappings":";;UAgEiB;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA,UAAU,SAAS;;EAGnB;;cA2BW,wBAAwB;WAC1B;WACA;UAED;EAOI,YAAA,QAAQ;YAyBJ,kBAAkB,SAAS,eAAe;;;;UA6BlD;;;;UA2DA;;;;UA2BA;;;;UAwBA;;;;UAuBA;;;;UAmCA;;;;UAUA;;;;YAaE,YAAY,OAAO,OAAO,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"slack.d.ts","names":[],"sources":["../src/slack.ts"],"mappings":";;UAgEiB;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA,UAAU,SAAS;;EAGnB;;cA2BW,wBAAwB;WAC1B;WACA;UAED;EAOI,YAAA,QAAQ;YAyBJ,kBAAkB,SAAS,eAAe;;;;UA6BlD;;;;UA2DA;;;;UA2BA;;;;UAwBA;;;;UAuBA;;;;UAmCA;;;;UAUA;;;;YAaE,YAAY,OAAO,OAAO,SAAS"}
package/dist/slack.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"slack.js","names":[],"sources":["../src/slack.ts"],"sourcesContent":["/**\n * Slack Subscriber for autotel\n *\n * Send events events as notifications to Slack channels via webhooks.\n *\n * Perfect for:\n * - Critical business events (orders, payments, signups)\n * - Real-time alerts for failures\n * - Team notifications for important milestones\n * - Monitoring funnel completions\n *\n * @example Basic usage\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { SlackSubscriber } from 'autotel-subscribers/slack';\n *\n * const events = new Event('app', {\n * subscribers: [\n * new SlackSubscriber({\n * webhookUrl: process.env.SLACK_WEBHOOK_URL!,\n * channel: '#order-events'\n * })\n * ]\n * });\n *\n * // Sends to Slack\n * events.trackEvent('order.completed', {\n * orderId: 'ord_123',\n * userId: 'user_456',\n * amount: 99.99\n * });\n * ```\n *\n * @example Filter critical events only\n * ```typescript\n * const events = new Event('app', {\n * subscribers: [\n * new SlackSubscriber({\n * webhookUrl: process.env.SLACK_WEBHOOK_URL!,\n * channel: '#alerts',\n * filter: (payload) => {\n * // Only send failures and high-value orders\n * if (payload.type === 'outcome' && payload.outcome === 'failure') {\n * return true;\n * }\n * if (payload.name === 'order.completed' && payload.attributes?.amount > 1000) {\n * return true;\n * }\n * return false;\n * }\n * })\n * ]\n * });\n * ```\n *\n * Setup:\n * 1. Create Slack App: https://api.slack.com/apps\n * 2. Enable Incoming Webhooks\n * 3. Add webhook to workspace\n * 4. Copy webhook URL (https://hooks.slack.com/services/...)\n */\n\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\nexport interface SlackSubscriberConfig {\n /** Slack webhook URL (https://hooks.slack.com/services/...) */\n webhookUrl: string;\n\n /** Default channel to post to (optional, overrides webhook default) */\n channel?: string;\n\n /** Custom username for bot (default: 'Events Bot') */\n username?: string;\n\n /** Custom emoji icon (default: ':chart_with_upwards_trend:') */\n iconEmoji?: string;\n\n /** Include timestamp in messages (default: true) */\n includeTimestamp?: boolean;\n\n /** Include event attributes as fields (default: true) */\n includeAttributes?: boolean;\n\n /** Maximum attributes to show (default: 10) */\n maxAttributeFields?: number;\n\n /** Filter function - return true to send, false to skip */\n filter?: (payload: EventPayload) => boolean;\n\n /** Enable/disable subscriber */\n enabled?: boolean;\n}\n\ninterface SlackMessage {\n channel?: string;\n username?: string;\n icon_emoji?: string;\n text?: string;\n attachments: SlackAttachment[];\n}\n\ninterface SlackAttachment {\n color?: string;\n title?: string;\n text?: string;\n fields?: SlackField[];\n footer?: string;\n footer_icon?: string;\n ts?: number;\n}\n\ninterface SlackField {\n title: string;\n value: string;\n short: boolean;\n}\n\nexport class SlackSubscriber extends EventSubscriber {\n readonly name = 'SlackSubscriber';\n readonly version = '1.0.0';\n\n private config: Required<\n Omit<SlackSubscriberConfig, 'channel' | 'filter'>\n > & {\n channel?: string;\n filter?: (payload: EventPayload) => boolean;\n };\n\n constructor(config: SlackSubscriberConfig) {\n super();\n\n this.config = {\n webhookUrl: config.webhookUrl,\n channel: config.channel,\n username: config.username ?? 'Events Bot',\n iconEmoji: config.iconEmoji ?? ':chart_with_upwards_trend:',\n includeTimestamp: config.includeTimestamp ?? true,\n includeAttributes: config.includeAttributes ?? true,\n maxAttributeFields: config.maxAttributeFields ?? 10,\n filter: config.filter,\n enabled: config.enabled ?? true,\n };\n\n this.enabled = this.config.enabled;\n\n if (!this.config.webhookUrl) {\n console.error(\n '[SlackSubscriber] No webhook URL provided - subscriber disabled',\n );\n this.enabled = false;\n }\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n // Apply filter if provided\n if (this.config.filter) {\n const filterFn = this.config.filter;\n const shouldInclude = filterFn(payload);\n if (!shouldInclude) {\n return; // Skip this event\n }\n }\n\n const message = this.formatSlackMessage(payload);\n\n const response = await fetch(this.config.webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(message),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Slack webhook failed (${response.status}): ${errorText}`,\n );\n }\n }\n\n /**\n * Format events payload as Slack message\n */\n private formatSlackMessage(payload: EventPayload): SlackMessage {\n const emoji = this.getEventEmoji(payload);\n const color = this.getEventColor(payload);\n const title = `${emoji} ${payload.name}`;\n\n // Add event type\n const fields: SlackField[] = [\n {\n title: 'Event Type',\n value: this.formatEventType(payload),\n short: true,\n },\n ];\n\n // Add timestamp if enabled\n if (this.config.includeTimestamp) {\n fields.push({\n title: 'Timestamp',\n value: new Date(payload.timestamp).toLocaleString('en-US', {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }),\n short: true,\n });\n }\n\n // Add attributes as fields\n if (this.config.includeAttributes && payload.attributes) {\n const attributeFields = this.formatAttributes(payload.attributes);\n fields.push(...attributeFields);\n }\n\n const attachment: SlackAttachment = {\n color,\n title,\n fields,\n footer: 'Events Events',\n footer_icon: 'https://i.imgur.com/QpCKbNL.png',\n };\n\n // Add Unix timestamp for Slack\n if (this.config.includeTimestamp) {\n attachment.ts = Math.floor(new Date(payload.timestamp).getTime() / 1000);\n }\n\n return {\n channel: this.config.channel,\n username: this.config.username,\n icon_emoji: this.config.iconEmoji,\n attachments: [attachment],\n };\n }\n\n /**\n * Get emoji for event type\n */\n private getEventEmoji(payload: EventPayload): string {\n switch (payload.type) {\n case 'outcome': {\n return payload.outcome === 'success' ? '✅' : '❌';\n }\n case 'funnel': {\n return '🔄';\n }\n case 'value': {\n return '📊';\n }\n default: {\n // Use custom emoji for common event patterns\n if (payload.name.includes('order') || payload.name.includes('payment'))\n return '💰';\n if (payload.name.includes('signup') || payload.name.includes('user'))\n return '👤';\n if (payload.name.includes('error') || payload.name.includes('fail'))\n return '⚠️';\n return '📌';\n }\n }\n }\n\n /**\n * Get Slack attachment color for event type\n */\n private getEventColor(payload: EventPayload): string {\n switch (payload.type) {\n case 'outcome': {\n return payload.outcome === 'success' ? 'good' : 'danger';\n } // Green or red\n case 'funnel': {\n return '#3AA3E3';\n } // Blue\n case 'value': {\n return '#764FA5';\n } // Purple\n default: {\n // Custom colors for patterns\n if (payload.name.includes('error') || payload.name.includes('fail'))\n return 'danger';\n if (payload.name.includes('warning')) return 'warning';\n return 'good';\n } // Default green\n }\n }\n\n /**\n * Format event type for display\n */\n private formatEventType(payload: EventPayload): string {\n switch (payload.type) {\n case 'event': {\n return 'Event';\n }\n case 'funnel': {\n return `Funnel: ${payload.step || 'unknown'}`;\n }\n case 'outcome': {\n return `Outcome: ${payload.outcome || 'unknown'}`;\n }\n case 'value': {\n return `Value: ${payload.value ?? 'N/A'}`;\n }\n default: {\n return payload.type;\n }\n }\n }\n\n /**\n * Format attributes as Slack fields\n */\n private formatAttributes(attributes: Record<string, any>): SlackField[] {\n const fields: SlackField[] = [];\n const entries = Object.entries(attributes);\n\n // Limit number of fields\n const limit = Math.min(entries.length, this.config.maxAttributeFields);\n\n for (let i = 0; i < limit; i++) {\n const [key, value] = entries[i];\n\n // Skip internal/system fields\n if (key.startsWith('_') || key === 'timestamp') continue;\n\n fields.push({\n title: this.formatFieldName(key),\n value: this.formatFieldValue(value),\n short: true,\n });\n }\n\n // Add truncation notice if needed\n if (entries.length > this.config.maxAttributeFields) {\n fields.push({\n title: 'Note',\n value: `... and ${entries.length - this.config.maxAttributeFields} more fields`,\n short: false,\n });\n }\n\n return fields;\n }\n\n /**\n * Format field name (convert camelCase to Title Case)\n */\n private formatFieldName(name: string): string {\n return name\n .replaceAll(/([A-Z])/g, ' $1') // Add space before capitals\n .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter\n .trim();\n }\n\n /**\n * Format field value\n */\n private formatFieldValue(value: any): string {\n if (value === null || value === undefined) return 'N/A';\n if (typeof value === 'boolean') return value ? 'Yes' : 'No';\n if (typeof value === 'object') return JSON.stringify(value);\n if (typeof value === 'number' && !Number.isInteger(value)) {\n return value.toFixed(2);\n }\n return String(value);\n }\n\n /**\n * Handle errors (override from EventSubscriber)\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[SlackSubscriber] Failed to send ${payload.type} event \"${payload.name}\":`,\n error,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqHA,IAAa,kBAAb,cAAqC,gBAAgB;CACnD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ;CAOR,YAAY,QAA+B;EACzC,MAAM;EAEN,KAAK,SAAS;GACZ,YAAY,OAAO;GACnB,SAAS,OAAO;GAChB,UAAU,OAAO,YAAY;GAC7B,WAAW,OAAO,aAAa;GAC/B,kBAAkB,OAAO,oBAAoB;GAC7C,mBAAmB,OAAO,qBAAqB;GAC/C,oBAAoB,OAAO,sBAAsB;GACjD,QAAQ,OAAO;GACf,SAAS,OAAO,WAAW;EAC7B;EAEA,KAAK,UAAU,KAAK,OAAO;EAE3B,IAAI,CAAC,KAAK,OAAO,YAAY;GAC3B,QAAQ,MACN,iEACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAgB,kBAAkB,SAAsC;EAEtE,IAAI,KAAK,OAAO,QAAQ;GACtB,MAAM,WAAW,KAAK,OAAO;GAE7B,IAAI,CADkB,SAAS,OACd,GACf;EAEJ;EAEA,MAAM,UAAU,KAAK,mBAAmB,OAAO;EAE/C,MAAM,WAAW,MAAM,MAAM,KAAK,OAAO,YAAY;GACnD,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,YAAY,MAAM,SAAS,KAAK;GACtC,MAAM,IAAI,MACR,yBAAyB,SAAS,OAAO,KAAK,WAChD;EACF;CACF;;;;CAKA,AAAQ,mBAAmB,SAAqC;EAC9D,MAAM,QAAQ,KAAK,cAAc,OAAO;EACxC,MAAM,QAAQ,KAAK,cAAc,OAAO;EACxC,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ;EAGlC,MAAM,SAAuB,CAC3B;GACE,OAAO;GACP,OAAO,KAAK,gBAAgB,OAAO;GACnC,OAAO;EACT,CACF;EAGA,IAAI,KAAK,OAAO,kBACd,OAAO,KAAK;GACV,OAAO;GACP,OAAO,IAAI,KAAK,QAAQ,SAAS,CAAC,CAAC,eAAe,SAAS;IACzD,OAAO;IACP,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;GACV,CAAC;GACD,OAAO;EACT,CAAC;EAIH,IAAI,KAAK,OAAO,qBAAqB,QAAQ,YAAY;GACvD,MAAM,kBAAkB,KAAK,iBAAiB,QAAQ,UAAU;GAChE,OAAO,KAAK,GAAG,eAAe;EAChC;EAEA,MAAM,aAA8B;GAClC;GACA;GACA;GACA,QAAQ;GACR,aAAa;EACf;EAGA,IAAI,KAAK,OAAO,kBACd,WAAW,KAAK,KAAK,MAAM,IAAI,KAAK,QAAQ,SAAS,CAAC,CAAC,QAAQ,IAAI,GAAI;EAGzE,OAAO;GACL,SAAS,KAAK,OAAO;GACrB,UAAU,KAAK,OAAO;GACtB,YAAY,KAAK,OAAO;GACxB,aAAa,CAAC,UAAU;EAC1B;CACF;;;;CAKA,AAAQ,cAAc,SAA+B;EACnD,QAAQ,QAAQ,MAAhB;GACE,KAAK,WACH,OAAO,QAAQ,YAAY,YAAY,MAAM;GAE/C,KAAK,UACH,OAAO;GAET,KAAK,SACH,OAAO;GAET;IAEE,IAAI,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,KAAK,SAAS,SAAS,GACnE,OAAO;IACT,IAAI,QAAQ,KAAK,SAAS,QAAQ,KAAK,QAAQ,KAAK,SAAS,MAAM,GACjE,OAAO;IACT,IAAI,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,KAAK,SAAS,MAAM,GAChE,OAAO;IACT,OAAO;EAEX;CACF;;;;CAKA,AAAQ,cAAc,SAA+B;EACnD,QAAQ,QAAQ,MAAhB;GACE,KAAK,WACH,OAAO,QAAQ,YAAY,YAAY,SAAS;GAElD,KAAK,UACH,OAAO;GAET,KAAK,SACH,OAAO;GAET;IAEE,IAAI,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,KAAK,SAAS,MAAM,GAChE,OAAO;IACT,IAAI,QAAQ,KAAK,SAAS,SAAS,GAAG,OAAO;IAC7C,OAAO;EAEX;CACF;;;;CAKA,AAAQ,gBAAgB,SAA+B;EACrD,QAAQ,QAAQ,MAAhB;GACE,KAAK,SACH,OAAO;GAET,KAAK,UACH,OAAO,WAAW,QAAQ,QAAQ;GAEpC,KAAK,WACH,OAAO,YAAY,QAAQ,WAAW;GAExC,KAAK,SACH,OAAO,UAAU,QAAQ,SAAS;GAEpC,SACE,OAAO,QAAQ;EAEnB;CACF;;;;CAKA,AAAQ,iBAAiB,YAA+C;EACtE,MAAM,SAAuB,CAAC;EAC9B,MAAM,UAAU,OAAO,QAAQ,UAAU;EAGzC,MAAM,QAAQ,KAAK,IAAI,QAAQ,QAAQ,KAAK,OAAO,kBAAkB;EAErE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,CAAC,KAAK,SAAS,QAAQ;GAG7B,IAAI,IAAI,WAAW,GAAG,KAAK,QAAQ,aAAa;GAEhD,OAAO,KAAK;IACV,OAAO,KAAK,gBAAgB,GAAG;IAC/B,OAAO,KAAK,iBAAiB,KAAK;IAClC,OAAO;GACT,CAAC;EACH;EAGA,IAAI,QAAQ,SAAS,KAAK,OAAO,oBAC/B,OAAO,KAAK;GACV,OAAO;GACP,OAAO,WAAW,QAAQ,SAAS,KAAK,OAAO,mBAAmB;GAClE,OAAO;EACT,CAAC;EAGH,OAAO;CACT;;;;CAKA,AAAQ,gBAAgB,MAAsB;EAC5C,OAAO,KACJ,WAAW,YAAY,KAAK,CAAC,CAC7B,QAAQ,OAAO,QAAQ,IAAI,YAAY,CAAC,CAAC,CACzC,KAAK;CACV;;;;CAKA,AAAQ,iBAAiB,OAAoB;EAC3C,IAAI,UAAU,QAAQ,UAAU,QAAW,OAAO;EAClD,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,QAAQ;EACvD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;EAC1D,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,GACtD,OAAO,MAAM,QAAQ,CAAC;EAExB,OAAO,OAAO,KAAK;CACrB;;;;CAKA,AAAU,YAAY,OAAc,SAA6B;EAC/D,QAAQ,MACN,oCAAoC,QAAQ,KAAK,UAAU,QAAQ,KAAK,KACxE,KACF;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-delivery-D3v50C81.js","names":[],"sources":["../src/http-client.ts","../src/retry-classification.ts","../src/webhook-delivery.ts"],"sourcesContent":["/**\n * HTTP client for sending webhook events\n *\n * Provides proper error handling, timeout support, and response parsing\n */\n\nexport type HttpRetryOptions = {\n retries?: number;\n delayMs?: number;\n};\n\nexport type HttpClientOptions = {\n timeoutMs?: number;\n retry?: HttpRetryOptions;\n};\n\nexport type HttpSuccess<T = unknown> = {\n ok: true;\n status: number;\n data: T | null;\n};\n\nexport type HttpNetworkError = {\n ok: false;\n kind: 'network';\n timedOut: boolean;\n cause: Error;\n};\n\nexport type HttpStatusError<E = unknown> = {\n ok: false;\n kind: 'http';\n status: number;\n statusText: string;\n body: E;\n};\n\nexport type HttpResult<T = unknown, E = unknown> =\n HttpSuccess<T> | HttpNetworkError | HttpStatusError<E>;\n\nexport type HttpRequestOptions = {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeoutMs?: number;\n};\n\nasync function parseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (text.trim().length === 0) return null;\n\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n return error.name === 'AbortError' || error.name === 'TimeoutError';\n}\n\n/**\n * Create an HTTP client with timeout and error handling\n *\n * @param options Configuration for timeout and retry behavior\n * @returns HTTP client with request method\n *\n * @example\n * ```typescript\n * const client = createHttpClient({ timeoutMs: 5000 })\n * const result = await client.request('https://example.com', {\n * method: 'POST',\n * body: JSON.stringify({ event: 'test' })\n * })\n * ```\n */\nexport function createHttpClient(options: HttpClientOptions = {}) {\n const defaultTimeoutMs = options.timeoutMs ?? 30_000;\n\n return {\n async request<T = unknown, E = unknown>(\n url: string,\n requestOptions: HttpRequestOptions = {},\n ): Promise<HttpResult<T, E>> {\n const timeoutMs = requestOptions.timeoutMs ?? defaultTimeoutMs;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: requestOptions.method ?? 'GET',\n headers: requestOptions.headers,\n body: requestOptions.body,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const body = (await parseBody(response)) as E;\n return {\n ok: false,\n kind: 'http',\n status: response.status,\n statusText: response.statusText,\n body,\n };\n }\n\n const data = (await parseBody(response)) as T;\n return { ok: true, status: response.status, data };\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return {\n ok: false,\n kind: 'network',\n timedOut: isTimeoutError(error),\n cause,\n };\n } finally {\n clearTimeout(timeoutHandle);\n }\n },\n };\n}\n","/**\n * Error classification and retry logic for webhook providers\n *\n * Intelligently maps HTTP status codes to retriable vs non-retriable errors\n */\n\nexport type ProviderErrorCode =\n 'VALIDATION' | 'CONFIG' | 'RATE_LIMITED' | 'PROVIDER' | 'NETWORK';\n\n/**\n * Structured error for event subscriber failures\n *\n * Includes error code, retriability flag, and optional details from the provider\n */\nexport class SubscriberProviderError extends Error {\n readonly code: ProviderErrorCode;\n readonly retriable: boolean;\n readonly details?: unknown;\n\n constructor(options: {\n message: string;\n code: ProviderErrorCode;\n retriable: boolean;\n details?: unknown;\n cause?: unknown;\n }) {\n super(\n options.message,\n options.cause ? { cause: options.cause } : undefined,\n );\n this.name = 'SubscriberProviderError';\n this.code = options.code;\n this.retriable = options.retriable;\n this.details = options.details;\n }\n}\n\nexport type MappedHttpError = { code: ProviderErrorCode; retriable: boolean };\n\n/**\n * Map HTTP status codes to error classification\n *\n * - 400, 422: VALIDATION (not retriable)\n * - 401, 403, 404: CONFIG (not retriable)\n * - 429: RATE_LIMITED (retriable)\n * - 5xx: PROVIDER (retriable)\n * - 2xx-3xx: Success (not error)\n */\nexport function mapHttpStatus(status: number): MappedHttpError {\n switch (status) {\n case 400:\n case 422: {\n return { code: 'VALIDATION', retriable: false };\n }\n case 401:\n case 403:\n case 404: {\n return { code: 'CONFIG', retriable: false };\n }\n case 429: {\n return { code: 'RATE_LIMITED', retriable: true };\n }\n default: {\n return { code: 'PROVIDER', retriable: status >= 500 };\n }\n }\n}\n\n/**\n * Check if an error is retriable\n *\n * Returns the retriable flag from SubscriberProviderError, or true for unknown errors\n */\nexport function isProviderRetriable(error: unknown): boolean {\n if (error instanceof SubscriberProviderError) return error.retriable;\n return true;\n}\n","/**\n * Shared JSON webhook delivery: timeout-bounded POST with classified\n * errors and exponential-backoff retries. Used by `WebhookSubscriber`\n * and `SecuritySubscriber` so delivery semantics stay identical.\n */\n\nimport type { createHttpClient } from './http-client';\nimport {\n mapHttpStatus,\n SubscriberProviderError,\n isProviderRetriable,\n} from './retry-classification';\n\nexport type HttpClient = ReturnType<typeof createHttpClient>;\n\nexport type JsonDeliveryOptions = {\n method?: 'POST' | 'PUT';\n headers?: Record<string, string>;\n /** Attempts including the first. Default 3. */\n maxRetries?: number;\n /** Base backoff delay; doubles per attempt. Default 1000. */\n retryDelayMs?: number;\n /** Prefix for error messages, e.g. `Webhook` or `Security webhook`. */\n label?: string;\n};\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * POST `payload` as JSON, retrying retriable failures with exponential\n * backoff. Throws a classified `SubscriberProviderError` once retries are\n * exhausted — callers rely on the `EventSubscriber` base class to route\n * that through `handleError`.\n */\nexport async function postJsonWithRetry(\n client: HttpClient,\n url: string,\n payload: unknown,\n options: JsonDeliveryOptions = {},\n): Promise<void> {\n const maxRetries = options.maxRetries ?? 3;\n const retryDelayMs = options.retryDelayMs ?? 1000;\n const method = options.method ?? 'POST';\n const label = options.label ?? 'Webhook';\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n const response = await client.request<unknown, unknown>(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) return;\n\n if (response.kind === 'network') {\n lastError = new SubscriberProviderError({\n message: response.timedOut\n ? `${label} request timed out`\n : `${label} network request failed`,\n code: 'NETWORK',\n retriable: true,\n details: response.cause,\n cause: response.cause,\n });\n } else {\n const mapped = mapHttpStatus(response.status);\n lastError = new SubscriberProviderError({\n message: `${label} returned ${response.status}: ${response.statusText}`,\n code: mapped.code,\n retriable: mapped.retriable,\n details: response.body,\n });\n }\n\n const canRetry = isProviderRetriable(lastError) && attempt < maxRetries;\n if (!canRetry) break;\n\n await delay(retryDelayMs * 2 ** (attempt - 1));\n }\n\n throw lastError ?? new Error(`${label} send failed`);\n}\n"],"mappings":";AA+CA,eAAe,UAAU,UAAsC;CAC7D,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;CAErC,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAAyB;CAC/C,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,OAAO,MAAM,SAAS,gBAAgB,MAAM,SAAS;AACvD;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,UAA6B,CAAC,GAAG;CAChE,MAAM,mBAAmB,QAAQ,aAAa;CAE9C,OAAO,EACL,MAAM,QACJ,KACA,iBAAqC,CAAC,GACX;EAC3B,MAAM,YAAY,eAAe,aAAa;EAC9C,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,gBAAgB,iBAAiB,WAAW,MAAM,GAAG,SAAS;EAEpE,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ,eAAe,UAAU;IACjC,SAAS,eAAe;IACxB,MAAM,eAAe;IACrB,QAAQ,WAAW;GACrB,CAAC;GAED,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAQ,MAAM,UAAU,QAAQ;IACtC,OAAO;KACL,IAAI;KACJ,MAAM;KACN,QAAQ,SAAS;KACjB,YAAY,SAAS;KACrB;IACF;GACF;GAEA,MAAM,OAAQ,MAAM,UAAU,QAAQ;GACtC,OAAO;IAAE,IAAI;IAAM,QAAQ,SAAS;IAAQ;GAAK;EACnD,SAAS,OAAO;GACd,MAAM,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GACtE,OAAO;IACL,IAAI;IACJ,MAAM;IACN,UAAU,eAAe,KAAK;IAC9B;GACF;EACF,UAAU;GACR,aAAa,aAAa;EAC5B;CACF,EACF;AACF;;;;;;;;;AC9GA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,SAMT;EACD,MACE,QAAQ,SACR,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,MAC7C;EACA,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;EACpB,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,QAAQ;CACzB;AACF;;;;;;;;;;AAaA,SAAgB,cAAc,QAAiC;CAC7D,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,KACH,OAAO;GAAE,MAAM;GAAc,WAAW;EAAM;EAEhD,KAAK;EACL,KAAK;EACL,KAAK,KACH,OAAO;GAAE,MAAM;GAAU,WAAW;EAAM;EAE5C,KAAK,KACH,OAAO;GAAE,MAAM;GAAgB,WAAW;EAAK;EAEjD,SACE,OAAO;GAAE,MAAM;GAAY,WAAW,UAAU;EAAI;CAExD;AACF;;;;;;AAOA,SAAgB,oBAAoB,OAAyB;CAC3D,IAAI,iBAAiB,yBAAyB,OAAO,MAAM;CAC3D,OAAO;AACT;;;;AClDA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;;;;;AAQA,eAAsB,kBACpB,QACA,KACA,SACA,UAA+B,CAAC,GACjB;CACf,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI;CAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,YAAY,WAAW;EACtD,MAAM,WAAW,MAAM,OAAO,QAA0B,KAAK;GAC3D;GACA,SAAS;IACP,gBAAgB;IAChB,GAAG,QAAQ;GACb;GACA,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;EAED,IAAI,SAAS,IAAI;EAEjB,IAAI,SAAS,SAAS,WACpB,YAAY,IAAI,wBAAwB;GACtC,SAAS,SAAS,WACd,GAAG,MAAM,sBACT,GAAG,MAAM;GACb,MAAM;GACN,WAAW;GACX,SAAS,SAAS;GAClB,OAAO,SAAS;EAClB,CAAC;OACI;GACL,MAAM,SAAS,cAAc,SAAS,MAAM;GAC5C,YAAY,IAAI,wBAAwB;IACtC,SAAS,GAAG,MAAM,YAAY,SAAS,OAAO,IAAI,SAAS;IAC3D,MAAM,OAAO;IACb,WAAW,OAAO;IAClB,SAAS,SAAS;GACpB,CAAC;EACH;EAGA,IAAI,EADa,oBAAoB,SAAS,KAAK,UAAU,aAC9C;EAEf,MAAM,MAAM,eAAe,MAAM,UAAU,EAAE;CAC/C;CAEA,MAAM,6BAAa,IAAI,MAAM,GAAG,MAAM,aAAa;AACrD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-delivery-DZeUlZhi.cjs","names":[],"sources":["../src/http-client.ts","../src/retry-classification.ts","../src/webhook-delivery.ts"],"sourcesContent":["/**\n * HTTP client for sending webhook events\n *\n * Provides proper error handling, timeout support, and response parsing\n */\n\nexport type HttpRetryOptions = {\n retries?: number;\n delayMs?: number;\n};\n\nexport type HttpClientOptions = {\n timeoutMs?: number;\n retry?: HttpRetryOptions;\n};\n\nexport type HttpSuccess<T = unknown> = {\n ok: true;\n status: number;\n data: T | null;\n};\n\nexport type HttpNetworkError = {\n ok: false;\n kind: 'network';\n timedOut: boolean;\n cause: Error;\n};\n\nexport type HttpStatusError<E = unknown> = {\n ok: false;\n kind: 'http';\n status: number;\n statusText: string;\n body: E;\n};\n\nexport type HttpResult<T = unknown, E = unknown> =\n HttpSuccess<T> | HttpNetworkError | HttpStatusError<E>;\n\nexport type HttpRequestOptions = {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeoutMs?: number;\n};\n\nasync function parseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (text.trim().length === 0) return null;\n\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n return error.name === 'AbortError' || error.name === 'TimeoutError';\n}\n\n/**\n * Create an HTTP client with timeout and error handling\n *\n * @param options Configuration for timeout and retry behavior\n * @returns HTTP client with request method\n *\n * @example\n * ```typescript\n * const client = createHttpClient({ timeoutMs: 5000 })\n * const result = await client.request('https://example.com', {\n * method: 'POST',\n * body: JSON.stringify({ event: 'test' })\n * })\n * ```\n */\nexport function createHttpClient(options: HttpClientOptions = {}) {\n const defaultTimeoutMs = options.timeoutMs ?? 30_000;\n\n return {\n async request<T = unknown, E = unknown>(\n url: string,\n requestOptions: HttpRequestOptions = {},\n ): Promise<HttpResult<T, E>> {\n const timeoutMs = requestOptions.timeoutMs ?? defaultTimeoutMs;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: requestOptions.method ?? 'GET',\n headers: requestOptions.headers,\n body: requestOptions.body,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const body = (await parseBody(response)) as E;\n return {\n ok: false,\n kind: 'http',\n status: response.status,\n statusText: response.statusText,\n body,\n };\n }\n\n const data = (await parseBody(response)) as T;\n return { ok: true, status: response.status, data };\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return {\n ok: false,\n kind: 'network',\n timedOut: isTimeoutError(error),\n cause,\n };\n } finally {\n clearTimeout(timeoutHandle);\n }\n },\n };\n}\n","/**\n * Error classification and retry logic for webhook providers\n *\n * Intelligently maps HTTP status codes to retriable vs non-retriable errors\n */\n\nexport type ProviderErrorCode =\n 'VALIDATION' | 'CONFIG' | 'RATE_LIMITED' | 'PROVIDER' | 'NETWORK';\n\n/**\n * Structured error for event subscriber failures\n *\n * Includes error code, retriability flag, and optional details from the provider\n */\nexport class SubscriberProviderError extends Error {\n readonly code: ProviderErrorCode;\n readonly retriable: boolean;\n readonly details?: unknown;\n\n constructor(options: {\n message: string;\n code: ProviderErrorCode;\n retriable: boolean;\n details?: unknown;\n cause?: unknown;\n }) {\n super(\n options.message,\n options.cause ? { cause: options.cause } : undefined,\n );\n this.name = 'SubscriberProviderError';\n this.code = options.code;\n this.retriable = options.retriable;\n this.details = options.details;\n }\n}\n\nexport type MappedHttpError = { code: ProviderErrorCode; retriable: boolean };\n\n/**\n * Map HTTP status codes to error classification\n *\n * - 400, 422: VALIDATION (not retriable)\n * - 401, 403, 404: CONFIG (not retriable)\n * - 429: RATE_LIMITED (retriable)\n * - 5xx: PROVIDER (retriable)\n * - 2xx-3xx: Success (not error)\n */\nexport function mapHttpStatus(status: number): MappedHttpError {\n switch (status) {\n case 400:\n case 422: {\n return { code: 'VALIDATION', retriable: false };\n }\n case 401:\n case 403:\n case 404: {\n return { code: 'CONFIG', retriable: false };\n }\n case 429: {\n return { code: 'RATE_LIMITED', retriable: true };\n }\n default: {\n return { code: 'PROVIDER', retriable: status >= 500 };\n }\n }\n}\n\n/**\n * Check if an error is retriable\n *\n * Returns the retriable flag from SubscriberProviderError, or true for unknown errors\n */\nexport function isProviderRetriable(error: unknown): boolean {\n if (error instanceof SubscriberProviderError) return error.retriable;\n return true;\n}\n","/**\n * Shared JSON webhook delivery: timeout-bounded POST with classified\n * errors and exponential-backoff retries. Used by `WebhookSubscriber`\n * and `SecuritySubscriber` so delivery semantics stay identical.\n */\n\nimport type { createHttpClient } from './http-client';\nimport {\n mapHttpStatus,\n SubscriberProviderError,\n isProviderRetriable,\n} from './retry-classification';\n\nexport type HttpClient = ReturnType<typeof createHttpClient>;\n\nexport type JsonDeliveryOptions = {\n method?: 'POST' | 'PUT';\n headers?: Record<string, string>;\n /** Attempts including the first. Default 3. */\n maxRetries?: number;\n /** Base backoff delay; doubles per attempt. Default 1000. */\n retryDelayMs?: number;\n /** Prefix for error messages, e.g. `Webhook` or `Security webhook`. */\n label?: string;\n};\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * POST `payload` as JSON, retrying retriable failures with exponential\n * backoff. Throws a classified `SubscriberProviderError` once retries are\n * exhausted — callers rely on the `EventSubscriber` base class to route\n * that through `handleError`.\n */\nexport async function postJsonWithRetry(\n client: HttpClient,\n url: string,\n payload: unknown,\n options: JsonDeliveryOptions = {},\n): Promise<void> {\n const maxRetries = options.maxRetries ?? 3;\n const retryDelayMs = options.retryDelayMs ?? 1000;\n const method = options.method ?? 'POST';\n const label = options.label ?? 'Webhook';\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n const response = await client.request<unknown, unknown>(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) return;\n\n if (response.kind === 'network') {\n lastError = new SubscriberProviderError({\n message: response.timedOut\n ? `${label} request timed out`\n : `${label} network request failed`,\n code: 'NETWORK',\n retriable: true,\n details: response.cause,\n cause: response.cause,\n });\n } else {\n const mapped = mapHttpStatus(response.status);\n lastError = new SubscriberProviderError({\n message: `${label} returned ${response.status}: ${response.statusText}`,\n code: mapped.code,\n retriable: mapped.retriable,\n details: response.body,\n });\n }\n\n const canRetry = isProviderRetriable(lastError) && attempt < maxRetries;\n if (!canRetry) break;\n\n await delay(retryDelayMs * 2 ** (attempt - 1));\n }\n\n throw lastError ?? new Error(`${label} send failed`);\n}\n"],"mappings":";;AA+CA,eAAe,UAAU,UAAsC;CAC7D,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;CAErC,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAAyB;CAC/C,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,OAAO,MAAM,SAAS,gBAAgB,MAAM,SAAS;AACvD;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,UAA6B,CAAC,GAAG;CAChE,MAAM,mBAAmB,QAAQ,aAAa;CAE9C,OAAO,EACL,MAAM,QACJ,KACA,iBAAqC,CAAC,GACX;EAC3B,MAAM,YAAY,eAAe,aAAa;EAC9C,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,gBAAgB,iBAAiB,WAAW,MAAM,GAAG,SAAS;EAEpE,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ,eAAe,UAAU;IACjC,SAAS,eAAe;IACxB,MAAM,eAAe;IACrB,QAAQ,WAAW;GACrB,CAAC;GAED,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAQ,MAAM,UAAU,QAAQ;IACtC,OAAO;KACL,IAAI;KACJ,MAAM;KACN,QAAQ,SAAS;KACjB,YAAY,SAAS;KACrB;IACF;GACF;GAEA,MAAM,OAAQ,MAAM,UAAU,QAAQ;GACtC,OAAO;IAAE,IAAI;IAAM,QAAQ,SAAS;IAAQ;GAAK;EACnD,SAAS,OAAO;GACd,MAAM,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GACtE,OAAO;IACL,IAAI;IACJ,MAAM;IACN,UAAU,eAAe,KAAK;IAC9B;GACF;EACF,UAAU;GACR,aAAa,aAAa;EAC5B;CACF,EACF;AACF;;;;;;;;;AC9GA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,SAMT;EACD,MACE,QAAQ,SACR,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,MAC7C;EACA,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;EACpB,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,QAAQ;CACzB;AACF;;;;;;;;;;AAaA,SAAgB,cAAc,QAAiC;CAC7D,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,KACH,OAAO;GAAE,MAAM;GAAc,WAAW;EAAM;EAEhD,KAAK;EACL,KAAK;EACL,KAAK,KACH,OAAO;GAAE,MAAM;GAAU,WAAW;EAAM;EAE5C,KAAK,KACH,OAAO;GAAE,MAAM;GAAgB,WAAW;EAAK;EAEjD,SACE,OAAO;GAAE,MAAM;GAAY,WAAW,UAAU;EAAI;CAExD;AACF;;;;;;AAOA,SAAgB,oBAAoB,OAAyB;CAC3D,IAAI,iBAAiB,yBAAyB,OAAO,MAAM;CAC3D,OAAO;AACT;;;;AClDA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;;;;;AAQA,eAAsB,kBACpB,QACA,KACA,SACA,UAA+B,CAAC,GACjB;CACf,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI;CAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,YAAY,WAAW;EACtD,MAAM,WAAW,MAAM,OAAO,QAA0B,KAAK;GAC3D;GACA,SAAS;IACP,gBAAgB;IAChB,GAAG,QAAQ;GACb;GACA,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;EAED,IAAI,SAAS,IAAI;EAEjB,IAAI,SAAS,SAAS,WACpB,YAAY,IAAI,wBAAwB;GACtC,SAAS,SAAS,WACd,GAAG,MAAM,sBACT,GAAG,MAAM;GACb,MAAM;GACN,WAAW;GACX,SAAS,SAAS;GAClB,OAAO,SAAS;EAClB,CAAC;OACI;GACL,MAAM,SAAS,cAAc,SAAS,MAAM;GAC5C,YAAY,IAAI,wBAAwB;IACtC,SAAS,GAAG,MAAM,YAAY,SAAS,OAAO,IAAI,SAAS;IAC3D,MAAM,OAAO;IACb,WAAW,OAAO;IAClB,SAAS,SAAS;GACpB,CAAC;EACH;EAGA,IAAI,EADa,oBAAoB,SAAS,KAAK,UAAU,aAC9C;EAEf,MAAM,MAAM,eAAe,MAAM,UAAU,EAAE;CAC/C;CAEA,MAAM,6BAAa,IAAI,MAAM,GAAG,MAAM,aAAa;AACrD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook.cjs","names":["createHttpClient","postJsonWithRetry"],"sources":["../src/webhook.ts"],"sourcesContent":["/**\n * Webhook Subscriber for autotel\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n EventTrackingOptions,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\nimport { createHttpClient } from './http-client';\nimport { postJsonWithRetry } from './webhook-delivery';\n\nexport interface WebhookConfig {\n url: string;\n headers?: Record<string, string>;\n enabled?: boolean;\n maxRetries?: number;\n method?: 'POST' | 'PUT';\n timeoutMs?: number;\n retryDelayMs?: number;\n}\n\nexport class WebhookSubscriber implements EventSubscriber {\n readonly name = 'WebhookSubscriber';\n readonly version = '1.1.0';\n\n private readonly config: WebhookConfig;\n private enabled: boolean;\n private readonly pendingRequests: Set<Promise<void>> = new Set();\n private readonly httpClient;\n\n constructor(config: WebhookConfig) {\n this.config = config;\n this.enabled = config.enabled ?? true;\n this.httpClient = createHttpClient({ timeoutMs: config.timeoutMs });\n }\n\n private async send(payload: unknown): Promise<void> {\n if (!this.enabled) return;\n\n await postJsonWithRetry(this.httpClient, this.config.url, payload, {\n method: this.config.method,\n headers: this.config.headers,\n maxRetries: this.config.maxRetries,\n retryDelayMs: this.config.retryDelayMs,\n label: 'Webhook',\n });\n }\n\n async trackEvent(\n name: string,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n const request = this.send({\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n });\n this.trackRequest(request);\n await request;\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n const request = this.send({\n type: 'funnel',\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n });\n this.trackRequest(request);\n await request;\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n const request = this.send({\n type: 'outcome',\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n });\n this.trackRequest(request);\n await request;\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n const request = this.send({\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n });\n this.trackRequest(request);\n await request;\n }\n\n private trackRequest(request: Promise<void>): void {\n this.pendingRequests.add(request);\n void request\n .catch(() => {})\n .finally(() => {\n this.pendingRequests.delete(request);\n });\n }\n\n async shutdown(): Promise<void> {\n if (this.pendingRequests.size > 0) {\n await Promise.allSettled(this.pendingRequests);\n }\n }\n}\n"],"mappings":";;;;AAwBA,IAAa,oBAAb,MAA0D;CACxD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAiB;CACjB,AAAQ;CACR,AAAiB,kCAAsC,IAAI,IAAI;CAC/D,AAAiB;CAEjB,YAAY,QAAuB;EACjC,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,aAAaA,0CAAiB,EAAE,WAAW,OAAO,UAAU,CAAC;CACpE;CAEA,MAAc,KAAK,SAAiC;EAClD,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAMC,2CAAkB,KAAK,YAAY,KAAK,OAAO,KAAK,SAAS;GACjE,QAAQ,KAAK,OAAO;GACpB,SAAS,KAAK,OAAO;GACrB,YAAY,KAAK,OAAO;GACxB,cAAc,KAAK,OAAO;GAC1B,OAAO;EACT,CAAC;CACH;CAEA,MAAM,WACJ,MACA,YACA,SACe;EACf,MAAM,UAAU,KAAK,KAAK;GACxB,MAAM;GACN;GACA;GACA,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;GAClC,SAAS,SAAS;EACpB,CAAC;EACD,KAAK,aAAa,OAAO;EACzB,MAAM;CACR;CAEA,MAAM,gBACJ,YACA,MACA,YACA,SACe;EACf,MAAM,UAAU,KAAK,KAAK;GACxB,MAAM;GACN,QAAQ;GACR;GACA;GACA,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;GAClC,SAAS,SAAS;EACpB,CAAC;EACD,KAAK,aAAa,OAAO;EACzB,MAAM;CACR;CAEA,MAAM,aACJ,eACA,SACA,YACA,SACe;EACf,MAAM,UAAU,KAAK,KAAK;GACxB,MAAM;GACN,WAAW;GACX;GACA;GACA,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;GAClC,SAAS,SAAS;EACpB,CAAC;EACD,KAAK,aAAa,OAAO;EACzB,MAAM;CACR;CAEA,MAAM,WACJ,MACA,OACA,YACA,SACe;EACf,MAAM,UAAU,KAAK,KAAK;GACxB,MAAM;GACN;GACA;GACA;GACA,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;GAClC,SAAS,SAAS;EACpB,CAAC;EACD,KAAK,aAAa,OAAO;EACzB,MAAM;CACR;CAEA,AAAQ,aAAa,SAA8B;EACjD,KAAK,gBAAgB,IAAI,OAAO;EAChC,AAAK,QACF,YAAY,CAAC,CAAC,CAAC,CACf,cAAc;GACb,KAAK,gBAAgB,OAAO,OAAO;EACrC,CAAC;CACL;CAEA,MAAM,WAA0B;EAC9B,IAAI,KAAK,gBAAgB,OAAO,GAC9B,MAAM,QAAQ,WAAW,KAAK,eAAe;CAEjD;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook.d.cts","names":[],"sources":["../src/webhook.ts"],"mappings":";;UAciB;EACf;EACA,UAAU;EACV;EACA;EACA;EACA;EACA;;cAGW,6BAA6B;WAC/B;WACA;mBAEQ;UACT;mBACS;mBACA;EAEL,YAAA,QAAQ;UAMN;EAYR,WACJ,cACA,aAAa,iBACb,UAAU,uBACT;EAYG,gBACJ,oBACA,MAAM,cACN,aAAa,iBACb,UAAU,uBACT;EAaG,aACJ,uBACA,SAAS,eACT,aAAa,iBACb,UAAU,uBACT;EAaG,WACJ,cACA,eACA,aAAa,iBACb,UAAU,uBACT;UAaK;EASF,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook.d.ts","names":[],"sources":["../src/webhook.ts"],"mappings":";;UAciB;EACf;EACA,UAAU;EACV;EACA;EACA;EACA;EACA;;cAGW,6BAA6B;WAC/B;WACA;mBAEQ;UACT;mBACS;mBACA;EAEL,YAAA,QAAQ;UAMN;EAYR,WACJ,cACA,aAAa,iBACb,UAAU,uBACT;EAYG,gBACJ,oBACA,MAAM,cACN,aAAa,iBACb,UAAU,uBACT;EAaG,aACJ,uBACA,SAAS,eACT,aAAa,iBACb,UAAU,uBACT;EAaG,WACJ,cACA,eACA,aAAa,iBACb,UAAU,uBACT;UAaK;EASF,YAAY"}