autotel-subscribers 10.0.0 → 12.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.
- package/README.md +68 -13
- package/dist/{event-subscriber-base-CnF3V56W.d.cts → event-subscriber-base-uT-C_zrL.d.cts} +39 -3
- package/dist/{event-subscriber-base-CnF3V56W.d.ts → event-subscriber-base-uT-C_zrL.d.ts} +39 -3
- package/dist/factories.cjs +127 -24
- package/dist/factories.cjs.map +1 -1
- package/dist/factories.d.cts +1 -1
- package/dist/factories.d.ts +1 -1
- package/dist/factories.js +127 -24
- package/dist/factories.js.map +1 -1
- package/dist/index.cjs +127 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +127 -24
- package/dist/index.js.map +1 -1
- package/dist/mixpanel.cjs +1 -1
- package/dist/mixpanel.cjs.map +1 -1
- package/dist/mixpanel.js +1 -1
- package/dist/mixpanel.js.map +1 -1
- package/dist/posthog.cjs +116 -13
- package/dist/posthog.cjs.map +1 -1
- package/dist/posthog.d.cts +91 -2
- package/dist/posthog.d.ts +91 -2
- package/dist/posthog.js +116 -13
- package/dist/posthog.js.map +1 -1
- package/dist/segment.cjs +10 -10
- package/dist/segment.cjs.map +1 -1
- package/dist/segment.js +10 -10
- package/dist/segment.js.map +1 -1
- package/dist/slack.cjs +54 -0
- package/dist/slack.cjs.map +1 -1
- package/dist/slack.d.cts +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +54 -0
- package/dist/slack.js.map +1 -1
- package/package.json +3 -3
- package/src/event-subscriber-base.ts +83 -3
- package/src/posthog.test.ts +2 -2
- package/src/posthog.ts +184 -20
package/dist/slack.cjs
CHANGED
|
@@ -30,6 +30,35 @@ var EventSubscriber = class {
|
|
|
30
30
|
payload
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Filter out undefined and null values from attributes
|
|
35
|
+
*
|
|
36
|
+
* This improves DX by allowing callers to pass objects with optional properties
|
|
37
|
+
* without having to manually filter them first.
|
|
38
|
+
*
|
|
39
|
+
* @param attributes - Input attributes (may contain undefined/null)
|
|
40
|
+
* @returns Filtered attributes with only defined values, or undefined if empty
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const filtered = this.filterAttributes({
|
|
45
|
+
* userId: user.id,
|
|
46
|
+
* email: user.email, // might be undefined
|
|
47
|
+
* plan: null, // will be filtered out
|
|
48
|
+
* });
|
|
49
|
+
* // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
filterAttributes(attributes) {
|
|
53
|
+
if (!attributes) return void 0;
|
|
54
|
+
const filtered = {};
|
|
55
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
56
|
+
if (value !== void 0 && value !== null) {
|
|
57
|
+
filtered[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return Object.keys(filtered).length > 0 ? filtered : void 0;
|
|
61
|
+
}
|
|
33
62
|
/**
|
|
34
63
|
* Track an event
|
|
35
64
|
*/
|
|
@@ -87,6 +116,31 @@ var EventSubscriber = class {
|
|
|
87
116
|
};
|
|
88
117
|
await this.send(payload);
|
|
89
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Track funnel progression with custom step names
|
|
121
|
+
*
|
|
122
|
+
* Unlike trackFunnelStep which uses FunnelStatus enum values,
|
|
123
|
+
* this method allows any string as the step name for flexible funnel tracking.
|
|
124
|
+
*
|
|
125
|
+
* @param funnelName - Name of the funnel (e.g., "checkout", "onboarding")
|
|
126
|
+
* @param stepName - Custom step name (e.g., "cart_viewed", "payment_entered")
|
|
127
|
+
* @param stepNumber - Optional numeric position in the funnel
|
|
128
|
+
* @param attributes - Optional event attributes
|
|
129
|
+
*/
|
|
130
|
+
async trackFunnelProgression(funnelName, stepName, stepNumber, attributes) {
|
|
131
|
+
if (!this.enabled) return;
|
|
132
|
+
const payload = {
|
|
133
|
+
type: "funnel",
|
|
134
|
+
name: `${funnelName}.${stepName}`,
|
|
135
|
+
funnel: funnelName,
|
|
136
|
+
step: stepName,
|
|
137
|
+
stepName,
|
|
138
|
+
stepNumber,
|
|
139
|
+
attributes,
|
|
140
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
141
|
+
};
|
|
142
|
+
await this.send(payload);
|
|
143
|
+
}
|
|
90
144
|
/**
|
|
91
145
|
* Flush pending requests and clean up
|
|
92
146
|
*
|
package/dist/slack.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/slack.ts"],"names":[],"mappings":"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","file":"slack.cjs","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\n\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status */\n step?: FunnelStatus;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Track an event\n */\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {type EventAttributes, type FunnelStatus, type OutcomeStatus} from 'autotel/event-subscriber';","/**\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 { Events } from 'autotel/events';\n * import { SlackSubscriber } from 'autotel-subscribers/slack';\n *\n * const events = new Events('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 Events('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 {\n EventSubscriber,\n type EventPayload,\n} 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<Omit<SlackSubscriberConfig, 'channel' | 'filter'>> & {\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(\n new Date(payload.timestamp).getTime() / 1000\n );\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/slack.ts"],"names":[],"mappings":";;;AAmIO,IAAe,kBAAf,MAA2D;AAAA;AAAA;AAAA;AAAA,EASvD,OAAA;AAAA;AAAA;AAAA;AAAA,EAKC,OAAA,GAAmB,IAAA;AAAA;AAAA;AAAA;AAAA,EAKrB,eAAA,uBAA0C,GAAA,EAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqB5C,WAAA,CAAY,OAAc,OAAA,EAA6B;AAC/D,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN,CAAA,CAAA,EAAI,IAAA,CAAK,IAAI,CAAA,iBAAA,EAAoB,QAAQ,IAAI,CAAA,CAAA,CAAA;AAAA,MAC7C,KAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBU,iBACR,UAAA,EAC6B;AAC7B,IAAA,IAAI,CAAC,YAAY,OAAO,MAAA;AAExB,IAAA,MAAM,WAA4B,EAAC;AACnC,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA,EAAG;AACrD,MAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,QAAA,QAAA,CAAS,GAAG,CAAA,GAAI,KAAA;AAAA,MAClB;AAAA,IACF;AAGA,IAAA,OAAO,OAAO,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,GAAS,IAAI,QAAA,GAAW,MAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAA,CAAW,IAAA,EAAc,UAAA,EAA6C;AAC1E,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AAEnB,IAAA,MAAM,OAAA,GAAwB;AAAA,MAC5B,IAAA,EAAM,OAAA;AAAA,MACN,IAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAA,CACJ,UAAA,EACA,IAAA,EACA,UAAA,EACe;AACf,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AAEnB,IAAA,MAAM,OAAA,GAAwB;AAAA,MAC5B,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAAA,MAC3B,MAAA,EAAQ,UAAA;AAAA,MACR,IAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAA,CACJ,aAAA,EACA,OAAA,EACA,UAAA,EACe;AACf,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AAEnB,IAAA,MAAM,OAAA,GAAwB;AAAA,MAC5B,IAAA,EAAM,SAAA;AAAA,MACN,IAAA,EAAM,CAAA,EAAG,aAAa,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AAAA,MACjC,SAAA,EAAW,aAAA;AAAA,MACX,OAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAA,CACJ,IAAA,EACA,KAAA,EACA,UAAA,EACe;AACf,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AAEnB,IAAA,MAAM,OAAA,GAAwB;AAAA,MAC5B,IAAA,EAAM,OAAA;AAAA,MACN,IAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,sBAAA,CACJ,UAAA,EACA,QAAA,EACA,YACA,UAAA,EACe;AACf,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AAEnB,IAAA,MAAM,OAAA,GAAwB;AAAA,MAC5B,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA;AAAA,MAC/B,MAAA,EAAQ,UAAA;AAAA,MACR,IAAA,EAAM,QAAA;AAAA,MACN,QAAA;AAAA,MACA,UAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,QAAA,GAA0B;AAE9B,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AAIf,IAAA,MAAM,gBAAA,GAAmB,EAAA;AACzB,IAAA,MAAM,eAAA,GAAkB,EAAA;AAExB,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,GAAU,gBAAA,EAAkB,OAAA,EAAA,EAAW;AAC3D,MAAA,IAAI,IAAA,CAAK,eAAA,CAAgB,IAAA,KAAS,CAAA,EAAG;AACnC,QAAA;AAAA,MACF;AAGA,MAAA,MAAM,OAAA,CAAQ,UAAA,CAAW,IAAA,CAAK,eAAe,CAAA;AAG7C,MAAA,IAAI,KAAK,eAAA,CAAgB,IAAA,GAAO,CAAA,IAAK,OAAA,GAAU,mBAAmB,CAAA,EAAG;AACnE,QAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,eAAe,CAAC,CAAA;AAAA,MACrE;AAAA,IACF;AAGA,IAAA,IAAI,IAAA,CAAK,eAAA,CAAgB,IAAA,GAAO,CAAA,EAAG;AACjC,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,IAAI,IAAA,CAAK,IAAI,CAAA,0BAAA,EAA6B,IAAA,CAAK,gBAAgB,IAAI,CAAA,2GAAA;AAAA,OAErE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,KAAK,OAAA,EAAsC;AACvD,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,qBAAA,CAAsB,OAAO,CAAA;AAClD,IAAA,IAAA,CAAK,eAAA,CAAgB,IAAI,OAAO,CAAA;AAEhC,IAAA,KAAK,OAAA,CAAQ,QAAQ,MAAM;AACzB,MAAA,IAAA,CAAK,eAAA,CAAgB,OAAO,OAAO,CAAA;AAAA,IACrC,CAAC,CAAA;AAED,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,sBACZ,OAAA,EACe;AACf,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,kBAAkB,OAAO,CAAA;AAAA,IACtC,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,WAAA,CAAY,OAAgB,OAAO,CAAA;AAAA,IAC1C;AAAA,EACF;AACF,CAAA;;;ACrRO,IAAM,eAAA,GAAN,cAA8B,eAAA,CAAgB;AAAA,EAC1C,IAAA,GAAO,iBAAA;AAAA,EACP,OAAA,GAAU,OAAA;AAAA,EAEX,MAAA;AAAA,EAKR,YAAY,MAAA,EAA+B;AACzC,IAAA,KAAA,EAAM;AAEN,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,YAAY,MAAA,CAAO,UAAA;AAAA,MACnB,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,QAAA,EAAU,OAAO,QAAA,IAAY,YAAA;AAAA,MAC7B,SAAA,EAAW,OAAO,SAAA,IAAa,4BAAA;AAAA,MAC/B,gBAAA,EAAkB,OAAO,gBAAA,IAAoB,IAAA;AAAA,MAC7C,iBAAA,EAAmB,OAAO,iBAAA,IAAqB,IAAA;AAAA,MAC/C,kBAAA,EAAoB,OAAO,kBAAA,IAAsB,EAAA;AAAA,MACjD,QAAQ,MAAA,CAAO,MAAA;AAAA,MACf,OAAA,EAAS,OAAO,OAAA,IAAW;AAAA,KAC7B;AAEA,IAAA,IAAA,CAAK,OAAA,GAAU,KAAK,MAAA,CAAO,OAAA;AAE3B,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,UAAA,EAAY;AAC3B,MAAA,OAAA,CAAQ,KAAA;AAAA,QACN;AAAA,OACF;AACA,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,MAAgB,kBAAkB,OAAA,EAAsC;AAEtE,IAAA,IAAI,IAAA,CAAK,OAAO,MAAA,EAAQ;AACtB,MAAA,MAAM,QAAA,GAAW,KAAK,MAAA,CAAO,MAAA;AAC7B,MAAA,MAAM,aAAA,GAAgB,SAAS,OAAO,CAAA;AACtC,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,kBAAA,CAAmB,OAAO,CAAA;AAE/C,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,IAAA,CAAK,OAAO,UAAA,EAAY;AAAA,MACnD,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC7B,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,sBAAA,EAAyB,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA;AAAA,OACzD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,OAAA,EAAqC;AAC9D,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,aAAA,CAAc,OAAO,CAAA;AACxC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,aAAA,CAAc,OAAO,CAAA;AACxC,IAAA,MAAM,KAAA,GAAQ,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,QAAQ,IAAI,CAAA,CAAA;AAGtC,IAAA,MAAM,MAAA,GAAuB;AAAA,MAC3B;AAAA,QACE,KAAA,EAAO,YAAA;AAAA,QACP,KAAA,EAAO,IAAA,CAAK,eAAA,CAAgB,OAAO,CAAA;AAAA,QACnC,KAAA,EAAO;AAAA;AACT,KACF;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,gBAAA,EAAkB;AAChC,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,KAAA,EAAO,WAAA;AAAA,QACP,OAAO,IAAI,IAAA,CAAK,QAAQ,SAAS,CAAA,CAAE,eAAe,OAAA,EAAS;AAAA,UACzD,KAAA,EAAO,OAAA;AAAA,UACP,GAAA,EAAK,SAAA;AAAA,UACL,IAAA,EAAM,SAAA;AAAA,UACN,MAAA,EAAQ,SAAA;AAAA,UACR,MAAA,EAAQ;AAAA,SACT,CAAA;AAAA,QACD,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,iBAAA,IAAqB,OAAA,CAAQ,UAAA,EAAY;AACvD,MAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,gBAAA,CAAiB,OAAA,CAAQ,UAAU,CAAA;AAChE,MAAA,MAAA,CAAO,IAAA,CAAK,GAAG,eAAe,CAAA;AAAA,IAChC;AAEA,IAAA,MAAM,UAAA,GAA8B;AAAA,MAClC,KAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA,EAAQ,eAAA;AAAA,MACR,WAAA,EAAa;AAAA,KACf;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,gBAAA,EAAkB;AAChC,MAAA,UAAA,CAAW,KAAK,IAAA,CAAK,KAAA;AAAA,QACnB,IAAI,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,CAAE,SAAQ,GAAI;AAAA,OAC1C;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAK,MAAA,CAAO,OAAA;AAAA,MACrB,QAAA,EAAU,KAAK,MAAA,CAAO,QAAA;AAAA,MACtB,UAAA,EAAY,KAAK,MAAA,CAAO,SAAA;AAAA,MACxB,WAAA,EAAa,CAAC,UAAU;AAAA,KAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,OAAA,EAA+B;AACnD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,SAAA,EAAW;AACd,QAAA,OAAO,OAAA,CAAQ,OAAA,KAAY,SAAA,GAAY,QAAA,GAAM,QAAA;AAAA,MAC/C;AAAA,MACA,KAAK,QAAA,EAAU;AACb,QAAA,OAAO,WAAA;AAAA,MACT;AAAA,MACA,KAAK,OAAA,EAAS;AACZ,QAAA,OAAO,WAAA;AAAA,MACT;AAAA,MACA,SAAS;AAEP,QAAA,IAAI,OAAA,CAAQ,KAAK,QAAA,CAAS,OAAO,KAAK,OAAA,CAAQ,IAAA,CAAK,SAAS,SAAS,CAAA;AACnE,UAAA,OAAO,WAAA;AACT,QAAA,IAAI,OAAA,CAAQ,KAAK,QAAA,CAAS,QAAQ,KAAK,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAM,CAAA;AACjE,UAAA,OAAO,WAAA;AACT,QAAA,IAAI,OAAA,CAAQ,KAAK,QAAA,CAAS,OAAO,KAAK,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAM,CAAA;AAChE,UAAA,OAAO,cAAA;AACT,QAAA,OAAO,WAAA;AAAA,MACT;AAAA;AACF,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,OAAA,EAA+B;AACnD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,SAAA,EAAW;AACd,QAAA,OAAO,OAAA,CAAQ,OAAA,KAAY,SAAA,GAAY,MAAA,GAAS,QAAA;AAAA,MAClD;AAAA;AAAA,MACA,KAAK,QAAA,EAAU;AACb,QAAA,OAAO,SAAA;AAAA,MACT;AAAA;AAAA,MACA,KAAK,OAAA,EAAS;AACZ,QAAA,OAAO,SAAA;AAAA,MACT;AAAA;AAAA,MACA,SAAS;AAEP,QAAA,IAAI,OAAA,CAAQ,KAAK,QAAA,CAAS,OAAO,KAAK,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAM,CAAA;AAChE,UAAA,OAAO,QAAA;AACT,QAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,QAAA,CAAS,SAAS,GAAG,OAAO,SAAA;AAC7C,QAAA,OAAO,MAAA;AAAA,MACT;AAAA;AACF,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,OAAA,EAA+B;AACrD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,OAAA,EAAS;AACZ,QAAA,OAAO,OAAA;AAAA,MACT;AAAA,MACA,KAAK,QAAA,EAAU;AACb,QAAA,OAAO,CAAA,QAAA,EAAW,OAAA,CAAQ,IAAA,IAAQ,SAAS,CAAA,CAAA;AAAA,MAC7C;AAAA,MACA,KAAK,SAAA,EAAW;AACd,QAAA,OAAO,CAAA,SAAA,EAAY,OAAA,CAAQ,OAAA,IAAW,SAAS,CAAA,CAAA;AAAA,MACjD;AAAA,MACA,KAAK,OAAA,EAAS;AACZ,QAAA,OAAO,CAAA,OAAA,EAAU,OAAA,CAAQ,KAAA,IAAS,KAAK,CAAA,CAAA;AAAA,MACzC;AAAA,MACA,SAAS;AACP,QAAA,OAAO,OAAA,CAAQ,IAAA;AAAA,MACjB;AAAA;AACF,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,UAAA,EAA+C;AACtE,IAAA,MAAM,SAAuB,EAAC;AAC9B,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA;AAGzC,IAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,QAAQ,MAAA,EAAQ,IAAA,CAAK,OAAO,kBAAkB,CAAA;AAErE,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,MAAA,MAAM,CAAC,GAAA,EAAK,KAAK,CAAA,GAAI,QAAQ,CAAC,CAAA;AAG9B,MAAA,IAAI,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,IAAK,QAAQ,WAAA,EAAa;AAEhD,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,KAAA,EAAO,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA;AAAA,QAC/B,KAAA,EAAO,IAAA,CAAK,gBAAA,CAAiB,KAAK,CAAA;AAAA,QAClC,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH;AAGA,IAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,kBAAA,EAAoB;AACnD,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,KAAA,EAAO,MAAA;AAAA,QACP,OAAO,CAAA,QAAA,EAAW,OAAA,CAAQ,MAAA,GAAS,IAAA,CAAK,OAAO,kBAAkB,CAAA,YAAA,CAAA;AAAA,QACjE,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,IAAA,EAAsB;AAC5C,IAAA,OAAO,IAAA,CACJ,UAAA,CAAW,UAAA,EAAY,KAAK,CAAA,CAC5B,OAAA,CAAQ,IAAA,EAAM,CAAC,GAAA,KAAQ,GAAA,CAAI,WAAA,EAAa,EACxC,IAAA,EAAK;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,KAAA,EAAoB;AAC3C,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW,OAAO,KAAA;AAClD,IAAA,IAAI,OAAO,KAAA,KAAU,SAAA,EAAW,OAAO,QAAQ,KAAA,GAAQ,IAAA;AACvD,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,IAAA,CAAK,UAAU,KAAK,CAAA;AAC1D,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,EAAG;AACzD,MAAA,OAAO,KAAA,CAAM,QAAQ,CAAC,CAAA;AAAA,IACxB;AACA,IAAA,OAAO,OAAO,KAAK,CAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKU,WAAA,CAAY,OAAc,OAAA,EAA6B;AAC/D,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN,CAAA,iCAAA,EAAoC,OAAA,CAAQ,IAAI,CAAA,QAAA,EAAW,QAAQ,IAAI,CAAA,EAAA,CAAA;AAAA,MACvE;AAAA,KACF;AAAA,EACF;AACF","file":"slack.cjs","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n EventAttributesInput,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\n\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status (from FunnelStatus enum) */\n step?: FunnelStatus | string;\n\n /** For funnel events: custom step name (from trackFunnelProgression) */\n stepName?: string;\n\n /** For funnel events: numeric position in funnel */\n stepNumber?: number;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Filter out undefined and null values from attributes\n *\n * This improves DX by allowing callers to pass objects with optional properties\n * without having to manually filter them first.\n *\n * @param attributes - Input attributes (may contain undefined/null)\n * @returns Filtered attributes with only defined values, or undefined if empty\n *\n * @example\n * ```typescript\n * const filtered = this.filterAttributes({\n * userId: user.id,\n * email: user.email, // might be undefined\n * plan: null, // will be filtered out\n * });\n * // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }\n * ```\n */\n protected filterAttributes(\n attributes?: EventAttributesInput,\n ): EventAttributes | undefined {\n if (!attributes) return undefined;\n\n const filtered: EventAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined && value !== null) {\n filtered[key] = value;\n }\n }\n\n // Return undefined if no attributes remain after filtering\n return Object.keys(filtered).length > 0 ? filtered : undefined;\n }\n\n /**\n * Track an event\n */\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track funnel progression with custom step names\n *\n * Unlike trackFunnelStep which uses FunnelStatus enum values,\n * this method allows any string as the step name for flexible funnel tracking.\n *\n * @param funnelName - Name of the funnel (e.g., \"checkout\", \"onboarding\")\n * @param stepName - Custom step name (e.g., \"cart_viewed\", \"payment_entered\")\n * @param stepNumber - Optional numeric position in the funnel\n * @param attributes - Optional event attributes\n */\n async trackFunnelProgression(\n funnelName: string,\n stepName: string,\n stepNumber?: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${stepName}`,\n funnel: funnelName,\n step: stepName,\n stepName,\n stepNumber,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {\n type EventAttributes,\n type EventAttributesInput,\n type FunnelStatus,\n type OutcomeStatus,\n} from 'autotel/event-subscriber';","/**\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 { Events } from 'autotel/events';\n * import { SlackSubscriber } from 'autotel-subscribers/slack';\n *\n * const events = new Events('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 Events('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 {\n EventSubscriber,\n type EventPayload,\n} 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<Omit<SlackSubscriberConfig, 'channel' | 'filter'>> & {\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(\n new Date(payload.timestamp).getTime() / 1000\n );\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"]}
|
package/dist/slack.d.cts
CHANGED
package/dist/slack.d.ts
CHANGED
package/dist/slack.js
CHANGED
|
@@ -28,6 +28,35 @@ var EventSubscriber = class {
|
|
|
28
28
|
payload
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Filter out undefined and null values from attributes
|
|
33
|
+
*
|
|
34
|
+
* This improves DX by allowing callers to pass objects with optional properties
|
|
35
|
+
* without having to manually filter them first.
|
|
36
|
+
*
|
|
37
|
+
* @param attributes - Input attributes (may contain undefined/null)
|
|
38
|
+
* @returns Filtered attributes with only defined values, or undefined if empty
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const filtered = this.filterAttributes({
|
|
43
|
+
* userId: user.id,
|
|
44
|
+
* email: user.email, // might be undefined
|
|
45
|
+
* plan: null, // will be filtered out
|
|
46
|
+
* });
|
|
47
|
+
* // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
filterAttributes(attributes) {
|
|
51
|
+
if (!attributes) return void 0;
|
|
52
|
+
const filtered = {};
|
|
53
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
54
|
+
if (value !== void 0 && value !== null) {
|
|
55
|
+
filtered[key] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Object.keys(filtered).length > 0 ? filtered : void 0;
|
|
59
|
+
}
|
|
31
60
|
/**
|
|
32
61
|
* Track an event
|
|
33
62
|
*/
|
|
@@ -85,6 +114,31 @@ var EventSubscriber = class {
|
|
|
85
114
|
};
|
|
86
115
|
await this.send(payload);
|
|
87
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Track funnel progression with custom step names
|
|
119
|
+
*
|
|
120
|
+
* Unlike trackFunnelStep which uses FunnelStatus enum values,
|
|
121
|
+
* this method allows any string as the step name for flexible funnel tracking.
|
|
122
|
+
*
|
|
123
|
+
* @param funnelName - Name of the funnel (e.g., "checkout", "onboarding")
|
|
124
|
+
* @param stepName - Custom step name (e.g., "cart_viewed", "payment_entered")
|
|
125
|
+
* @param stepNumber - Optional numeric position in the funnel
|
|
126
|
+
* @param attributes - Optional event attributes
|
|
127
|
+
*/
|
|
128
|
+
async trackFunnelProgression(funnelName, stepName, stepNumber, attributes) {
|
|
129
|
+
if (!this.enabled) return;
|
|
130
|
+
const payload = {
|
|
131
|
+
type: "funnel",
|
|
132
|
+
name: `${funnelName}.${stepName}`,
|
|
133
|
+
funnel: funnelName,
|
|
134
|
+
step: stepName,
|
|
135
|
+
stepName,
|
|
136
|
+
stepNumber,
|
|
137
|
+
attributes,
|
|
138
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
};
|
|
140
|
+
await this.send(payload);
|
|
141
|
+
}
|
|
88
142
|
/**
|
|
89
143
|
* Flush pending requests and clean up
|
|
90
144
|
*
|
package/dist/slack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/slack.ts"],"names":[],"mappings":"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","file":"slack.js","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\n\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status */\n step?: FunnelStatus;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Track an event\n */\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {type EventAttributes, type FunnelStatus, type OutcomeStatus} from 'autotel/event-subscriber';","/**\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 { Events } from 'autotel/events';\n * import { SlackSubscriber } from 'autotel-subscribers/slack';\n *\n * const events = new Events('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 Events('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 {\n EventSubscriber,\n type EventPayload,\n} 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<Omit<SlackSubscriberConfig, 'channel' | 'filter'>> & {\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(\n new Date(payload.timestamp).getTime() / 1000\n );\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/slack.ts"],"names":[],"mappings":"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","file":"slack.js","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n EventAttributesInput,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\n\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status (from FunnelStatus enum) */\n step?: FunnelStatus | string;\n\n /** For funnel events: custom step name (from trackFunnelProgression) */\n stepName?: string;\n\n /** For funnel events: numeric position in funnel */\n stepNumber?: number;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Filter out undefined and null values from attributes\n *\n * This improves DX by allowing callers to pass objects with optional properties\n * without having to manually filter them first.\n *\n * @param attributes - Input attributes (may contain undefined/null)\n * @returns Filtered attributes with only defined values, or undefined if empty\n *\n * @example\n * ```typescript\n * const filtered = this.filterAttributes({\n * userId: user.id,\n * email: user.email, // might be undefined\n * plan: null, // will be filtered out\n * });\n * // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }\n * ```\n */\n protected filterAttributes(\n attributes?: EventAttributesInput,\n ): EventAttributes | undefined {\n if (!attributes) return undefined;\n\n const filtered: EventAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined && value !== null) {\n filtered[key] = value;\n }\n }\n\n // Return undefined if no attributes remain after filtering\n return Object.keys(filtered).length > 0 ? filtered : undefined;\n }\n\n /**\n * Track an event\n */\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Track funnel progression with custom step names\n *\n * Unlike trackFunnelStep which uses FunnelStatus enum values,\n * this method allows any string as the step name for flexible funnel tracking.\n *\n * @param funnelName - Name of the funnel (e.g., \"checkout\", \"onboarding\")\n * @param stepName - Custom step name (e.g., \"cart_viewed\", \"payment_entered\")\n * @param stepNumber - Optional numeric position in the funnel\n * @param attributes - Optional event attributes\n */\n async trackFunnelProgression(\n funnelName: string,\n stepName: string,\n stepNumber?: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${stepName}`,\n funnel: funnelName,\n step: stepName,\n stepName,\n stepNumber,\n attributes,\n timestamp: new Date().toISOString(),\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {\n type EventAttributes,\n type EventAttributesInput,\n type FunnelStatus,\n type OutcomeStatus,\n} from 'autotel/event-subscriber';","/**\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 { Events } from 'autotel/events';\n * import { SlackSubscriber } from 'autotel-subscribers/slack';\n *\n * const events = new Events('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 Events('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 {\n EventSubscriber,\n type EventPayload,\n} 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<Omit<SlackSubscriberConfig, 'channel' | 'filter'>> & {\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(\n new Date(payload.timestamp).getTime() / 1000\n );\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-subscribers",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "Write Once, Observe Anywhere - Event subscribers for autotel (PostHog, Mixpanel, Amplitude, Segment)",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"author": "Jag Reehal<jag@jagreehal.com> (https://jagreehal.com)",
|
|
83
83
|
"license": "MIT",
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"autotel": "2.
|
|
85
|
+
"autotel": "2.8.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependenciesMeta": {
|
|
88
88
|
"posthog-node": {
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"typescript": "^5.9.3",
|
|
120
120
|
"typescript-eslint": "^8.48.0",
|
|
121
121
|
"vitest": "^4.0.14",
|
|
122
|
-
"autotel": "2.
|
|
122
|
+
"autotel": "2.8.0"
|
|
123
123
|
},
|
|
124
124
|
"repository": {
|
|
125
125
|
"type": "git",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
import type {
|
|
68
68
|
EventSubscriber as IEventSubscriber,
|
|
69
69
|
EventAttributes,
|
|
70
|
+
EventAttributesInput,
|
|
70
71
|
FunnelStatus,
|
|
71
72
|
OutcomeStatus,
|
|
72
73
|
} from 'autotel/event-subscriber';
|
|
@@ -90,8 +91,14 @@ export interface EventPayload {
|
|
|
90
91
|
/** For funnel events: funnel name */
|
|
91
92
|
funnel?: string;
|
|
92
93
|
|
|
93
|
-
/** For funnel events: step status */
|
|
94
|
-
step?: FunnelStatus;
|
|
94
|
+
/** For funnel events: step status (from FunnelStatus enum) */
|
|
95
|
+
step?: FunnelStatus | string;
|
|
96
|
+
|
|
97
|
+
/** For funnel events: custom step name (from trackFunnelProgression) */
|
|
98
|
+
stepName?: string;
|
|
99
|
+
|
|
100
|
+
/** For funnel events: numeric position in funnel */
|
|
101
|
+
stepNumber?: number;
|
|
95
102
|
|
|
96
103
|
/** For outcome events: operation name */
|
|
97
104
|
operation?: string;
|
|
@@ -170,6 +177,41 @@ export abstract class EventSubscriber implements IEventSubscriber {
|
|
|
170
177
|
);
|
|
171
178
|
}
|
|
172
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Filter out undefined and null values from attributes
|
|
182
|
+
*
|
|
183
|
+
* This improves DX by allowing callers to pass objects with optional properties
|
|
184
|
+
* without having to manually filter them first.
|
|
185
|
+
*
|
|
186
|
+
* @param attributes - Input attributes (may contain undefined/null)
|
|
187
|
+
* @returns Filtered attributes with only defined values, or undefined if empty
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* const filtered = this.filterAttributes({
|
|
192
|
+
* userId: user.id,
|
|
193
|
+
* email: user.email, // might be undefined
|
|
194
|
+
* plan: null, // will be filtered out
|
|
195
|
+
* });
|
|
196
|
+
* // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
protected filterAttributes(
|
|
200
|
+
attributes?: EventAttributesInput,
|
|
201
|
+
): EventAttributes | undefined {
|
|
202
|
+
if (!attributes) return undefined;
|
|
203
|
+
|
|
204
|
+
const filtered: EventAttributes = {};
|
|
205
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
206
|
+
if (value !== undefined && value !== null) {
|
|
207
|
+
filtered[key] = value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Return undefined if no attributes remain after filtering
|
|
212
|
+
return Object.keys(filtered).length > 0 ? filtered : undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
173
215
|
/**
|
|
174
216
|
* Track an event
|
|
175
217
|
*/
|
|
@@ -251,6 +293,39 @@ export abstract class EventSubscriber implements IEventSubscriber {
|
|
|
251
293
|
await this.send(payload);
|
|
252
294
|
}
|
|
253
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Track funnel progression with custom step names
|
|
298
|
+
*
|
|
299
|
+
* Unlike trackFunnelStep which uses FunnelStatus enum values,
|
|
300
|
+
* this method allows any string as the step name for flexible funnel tracking.
|
|
301
|
+
*
|
|
302
|
+
* @param funnelName - Name of the funnel (e.g., "checkout", "onboarding")
|
|
303
|
+
* @param stepName - Custom step name (e.g., "cart_viewed", "payment_entered")
|
|
304
|
+
* @param stepNumber - Optional numeric position in the funnel
|
|
305
|
+
* @param attributes - Optional event attributes
|
|
306
|
+
*/
|
|
307
|
+
async trackFunnelProgression(
|
|
308
|
+
funnelName: string,
|
|
309
|
+
stepName: string,
|
|
310
|
+
stepNumber?: number,
|
|
311
|
+
attributes?: EventAttributes,
|
|
312
|
+
): Promise<void> {
|
|
313
|
+
if (!this.enabled) return;
|
|
314
|
+
|
|
315
|
+
const payload: EventPayload = {
|
|
316
|
+
type: 'funnel',
|
|
317
|
+
name: `${funnelName}.${stepName}`,
|
|
318
|
+
funnel: funnelName,
|
|
319
|
+
step: stepName,
|
|
320
|
+
stepName,
|
|
321
|
+
stepNumber,
|
|
322
|
+
attributes,
|
|
323
|
+
timestamp: new Date().toISOString(),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
await this.send(payload);
|
|
327
|
+
}
|
|
328
|
+
|
|
254
329
|
/**
|
|
255
330
|
* Flush pending requests and clean up
|
|
256
331
|
*
|
|
@@ -322,4 +397,9 @@ export abstract class EventSubscriber implements IEventSubscriber {
|
|
|
322
397
|
}
|
|
323
398
|
}
|
|
324
399
|
|
|
325
|
-
export {
|
|
400
|
+
export {
|
|
401
|
+
type EventAttributes,
|
|
402
|
+
type EventAttributesInput,
|
|
403
|
+
type FunnelStatus,
|
|
404
|
+
type OutcomeStatus,
|
|
405
|
+
} from 'autotel/event-subscriber';
|
package/src/posthog.test.ts
CHANGED
|
@@ -92,7 +92,7 @@ describe('PostHogSubscriber', () => {
|
|
|
92
92
|
|
|
93
93
|
it('should throw if no apiKey and no client provided', () => {
|
|
94
94
|
expect(() => new PostHogSubscriber({})).toThrow(
|
|
95
|
-
'PostHogSubscriber requires either apiKey or
|
|
95
|
+
'PostHogSubscriber requires either apiKey, client, or useGlobalClient to be provided',
|
|
96
96
|
);
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -208,7 +208,7 @@ describe('PostHogSubscriber', () => {
|
|
|
208
208
|
expect(mockCapture).toHaveBeenCalledWith({
|
|
209
209
|
distinctId: 'anonymous',
|
|
210
210
|
event: 'page.viewed',
|
|
211
|
-
properties: undefined
|
|
211
|
+
properties: {}, // Empty object due to automatic undefined/null filtering
|
|
212
212
|
});
|
|
213
213
|
});
|
|
214
214
|
|