UNPKG

autotel

Version:
379 lines (375 loc) 13 kB
import { Logger } from './logger.cjs'; import { EventSubscriber, EventAttributes, FunnelStatus, OutcomeStatus, EventAttributesInput } from './event-subscriber.cjs'; import { EventCollector } from './event-testing.cjs'; import 'pino'; /** * Events API for product events platforms * * Track user behavior, business events, and critical actions. * Sends to product events platforms (PostHog, Mixpanel, Amplitude) via subscribers. * For business people who think in events/funnels. * * For OpenTelemetry metrics (Prometheus/Grafana), use the Metrics class instead. * * @example Recommended: Configure subscribers in init(), use track() function * ```typescript * import { init, track } from 'autotel'; * import { PostHogSubscriber } from 'autotel-subscribers/posthog'; * * init({ * service: 'my-app', * subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })] * }); * * // Track events - uses subscribers from init() * track('application.submitted', { jobId: '123', userId: '456' }); * ``` * * @example Create Event instance (inherits subscribers from init) * ```typescript * import { Event } from 'autotel/event'; * * // Uses subscribers configured in init() * const event = new Event('job-application'); * event.trackEvent('application.submitted', { jobId: '123' }); * ``` * * @example Override subscribers for specific Event instance * ```typescript * import { Event } from 'autotel/event'; * import { PostHogSubscriber } from 'autotel-subscribers/posthog'; * * // Override: use different subscribers for this instance * const event = new Event('job-application', { * subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })] * }); * * event.trackEvent('application.submitted', { jobId: '123' }); * ``` */ /** * Events class for tracking user behavior and product events * * Track critical indicators such as: * - User events (signups, purchases, feature usage) * - Conversion funnels (signup → activation → purchase) * - Business outcomes (success/failure rates) * - Product metrics (revenue, engagement, retention) * * All events are sent to events platforms via subscribers (PostHog, Mixpanel, etc.). * For OpenTelemetry metrics, use the Metrics class instead. */ /** * Events options */ interface EventsOptions { /** Optional logger for audit trail */ logger?: Logger; /** Optional collector for testing (captures events in memory) */ collector?: EventCollector; /** * Optional subscribers to send events to other platforms * (e.g., PostHog, Mixpanel, Amplitude) * * **Subscriber Resolution**: * - If provided → uses these subscribers (instance override) * - If not provided → falls back to subscribers from `init()` (global config) * - If neither → no subscribers (events logged only) * * Install `autotel-subscribers` package for ready-made subscribers */ subscribers?: EventSubscriber[]; } declare class Event { private serviceName; private logger?; private collector?; private subscribers; private hasSubscribers; private circuitBreakers; /** * Create a new Event instance * * **Note**: Most users should use `init()` + `track()` instead of creating Event instances directly. * * **Subscriber Resolution**: * - If `subscribers` provided in options → uses those (instance override) * - If `subscribers` not provided → falls back to subscribers from `init()` (global config) * - If neither → no subscribers (events logged only) * * @param serviceName - Service name for identifying events * @param options - Optional configuration (logger, collector, subscribers) * * @example Recommended: Use track() with init() * ```typescript * import { init, track } from 'autotel'; * import { PostHogSubscriber } from 'autotel-subscribers/posthog'; * * init({ * service: 'checkout', * subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })] * }); * * track('purchase.completed', { amount: 99.99 }); * ``` * * @example Inherit subscribers from init() * ```typescript * // Uses subscribers configured in init() * const event = new Event('checkout'); * event.trackEvent('purchase.completed', { amount: 99.99 }); * ``` * * @example Override subscribers for this instance * ```typescript * import { Event } from 'autotel/event'; * import { PostHogSubscriber } from 'autotel-subscribers/posthog'; * * // Override: use different subscribers for this instance only * const event = new Event('checkout', { * subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })] * }); * ``` */ constructor(serviceName: string, options?: EventsOptions); /** * Automatically enrich attributes with all available telemetry context * * Auto-captures: * - Resource attributes: service.version, deployment.environment * - Trace context: traceId, spanId, correlationId * - Operation context: operation.name */ private enrichWithTelemetryContext; /** * Build autotel event context for trace correlation * * Works in 4 contexts: * 1. Inside a span → use current span's trace_id + span_id * 2. Outside span but in AsyncLocalStorage context → use trace_id + correlation_id * 3. Totally standalone → use correlation_id + service/env/version * 4. Batch/fan-in (multiple linked parents) → use count + hash or full array * * @returns AutotelEventContext or undefined if trace context is disabled */ private buildAutotelContext; /** * Enrich event attributes from baggage with guardrails * * @param attributes - Current event attributes * @returns Enriched attributes with baggage values */ private enrichFromBaggage; /** * Check if a baggage key is allowed based on config */ private isBaggageKeyAllowed; /** * Check if a key matches a baggage pattern * Supports exact matches and wildcard patterns (e.g., 'tenant.*') */ private matchesBaggagePattern; /** * Track a business event * * Use this for tracking user actions, business events, product usage: * - "user.signup" * - "order.completed" * - "feature.used" * * Events are sent to configured subscribers (PostHog, Mixpanel, etc.). * * @example * ```typescript * // Track user signup * events.trackEvent('user.signup', { * userId: '123', * plan: 'pro' * }) * * // Track order * events.trackEvent('order.completed', { * orderId: 'ord_123', * amount: 99.99 * }) * ``` */ trackEvent(eventName: string, attributes?: EventAttributes): void; /** * Notify all subscribers concurrently without blocking * Uses circuit breakers to protect against failing subscribers * Uses Promise.allSettled to prevent subscriber errors from affecting other subscribers */ private notifySubscribers; /** * Track conversion funnel steps * * Monitor where users drop off in multi-step processes. * * @example * ```typescript * // Track signup funnel * events.trackFunnelStep('signup', 'started', { userId: '123' }) * events.trackFunnelStep('signup', 'email_verified', { userId: '123' }) * events.trackFunnelStep('signup', 'completed', { userId: '123' }) * * // Track checkout flow * events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 }) * events.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 }) * events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 }) * ``` */ trackFunnelStep(funnelName: string, status: FunnelStatus, attributes?: EventAttributes): void; /** * Track outcomes (success/failure/partial) * * Monitor success rates of critical operations. * * @example * ```typescript * // Track email delivery * events.trackOutcome('email.delivery', 'success', { * recipientType: 'user', * emailType: 'welcome' * }) * * events.trackOutcome('email.delivery', 'failure', { * recipientType: 'user', * errorCode: 'invalid_email' * }) * * // Track payment processing * events.trackOutcome('payment.process', 'success', { amount: 99.99 }) * events.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' }) * ``` */ trackOutcome(operationName: string, status: OutcomeStatus, attributes?: EventAttributes): void; /** * Track value metrics * * Record numerical values like revenue, transaction amounts, * item counts, processing times, engagement scores, etc. * * @example * ```typescript * // Track revenue * events.trackValue('order.revenue', 149.99, { * currency: 'USD', * productCategory: 'electronics' * }) * * // Track items per cart * events.trackValue('cart.item_count', 5, { * userId: '123' * }) * * // Track processing time * events.trackValue('api.response_time', 250, { * unit: 'ms', * endpoint: '/api/checkout' * }) * ``` */ trackValue(metricName: string, value: number, attributes?: EventAttributes): void; /** * Flush all subscribers and wait for pending events * * Call this before shutdown to ensure all events are delivered. * * @example * ```typescript * const event =new Event('app', { subscribers: [...] }); * * // Before shutdown * await events.flush(); * ``` */ flush(): Promise<void>; /** * Shutdown the Event instance and all subscribers * * Unlike `flush()`, this method: * - Shuts down all subscribers * - Prevents further event tracking (hasSubscribers becomes false) * - Should only be called once at application shutdown * * @example * ```typescript * // In Next.js API route with after() * import { after } from 'next/server'; * * export async function POST(req: Request) { * const event = new Event('checkout', { subscribers: [...] }); * event.trackEvent('order.completed', { orderId: '123' }); * * after(async () => { * await event.shutdown(); * }); * * return Response.json({ success: true }); * } * ``` */ shutdown(): Promise<void>; /** * Track funnel progression with custom step names * * Unlike trackFunnelStep which uses FunnelStatus enum values, * this method allows any string as the step name for flexible funnel tracking. * * @param funnelName - Name of the funnel (e.g., "checkout", "onboarding") * @param stepName - Custom step name (e.g., "cart_viewed", "payment_entered") * @param stepNumber - Optional numeric position in the funnel * @param attributes - Optional event attributes * * @example * ```typescript * // Track custom checkout steps * event.trackFunnelProgression('checkout', 'cart_viewed', 1); * event.trackFunnelProgression('checkout', 'shipping_selected', 2); * event.trackFunnelProgression('checkout', 'payment_entered', 3); * event.trackFunnelProgression('checkout', 'order_confirmed', 4); * ``` */ trackFunnelProgression(funnelName: string, stepName: string, stepNumber?: number, attributes?: EventAttributes): void; /** * Track multiple events in a batch * * Useful for bulk event tracking with consistent timestamps. * Events are sent to subscribers individually but processed together. * * @param events - Array of events to track * * @example * ```typescript * event.trackBatch([ * { name: 'item.viewed', attributes: { itemId: '1' } }, * { name: 'item.viewed', attributes: { itemId: '2' } }, * { name: 'cart.updated', attributes: { itemCount: 2 } }, * ]); * ``` */ trackBatch(events: Array<{ name: string; attributes?: EventAttributesInput; }>): void; } /** * Get or create an Events instance for a service * * @param serviceName - Service name for identifying events * @param logger - Optional logger * @returns Events instance * * @example * ```typescript * const event =getEvents('job-application') * events.trackEvent('application.submitted', { jobId: '123' }) * ``` */ declare function getEvents(serviceName: string, logger?: Logger): Event; /** * Reset all events instances (mainly for testing) */ declare function resetEvents(): void; export { Event, EventAttributes, EventAttributesInput, type EventsOptions, FunnelStatus, OutcomeStatus, getEvents, resetEvents };