autotel
Version:
Write Once, Observe Anywhere
379 lines (375 loc) • 13 kB
TypeScript
import { Logger } from './logger.js';
import { EventSubscriber, EventAttributes, FunnelStatus, OutcomeStatus, EventAttributesInput } from './event-subscriber.js';
import { EventCollector } from './event-testing.js';
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 };