autotel-subscribers 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +669 -0
  3. package/dist/amplitude.cjs +2486 -0
  4. package/dist/amplitude.cjs.map +1 -0
  5. package/dist/amplitude.d.cts +49 -0
  6. package/dist/amplitude.d.ts +49 -0
  7. package/dist/amplitude.js +2463 -0
  8. package/dist/amplitude.js.map +1 -0
  9. package/dist/event-subscriber-base-CnF3V56W.d.cts +182 -0
  10. package/dist/event-subscriber-base-CnF3V56W.d.ts +182 -0
  11. package/dist/factories.cjs +16660 -0
  12. package/dist/factories.cjs.map +1 -0
  13. package/dist/factories.d.cts +304 -0
  14. package/dist/factories.d.ts +304 -0
  15. package/dist/factories.js +16624 -0
  16. package/dist/factories.js.map +1 -0
  17. package/dist/index.cjs +16575 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.d.cts +179 -0
  20. package/dist/index.d.ts +179 -0
  21. package/dist/index.js +16539 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/middleware.cjs +220 -0
  24. package/dist/middleware.cjs.map +1 -0
  25. package/dist/middleware.d.cts +227 -0
  26. package/dist/middleware.d.ts +227 -0
  27. package/dist/middleware.js +208 -0
  28. package/dist/middleware.js.map +1 -0
  29. package/dist/mixpanel.cjs +2940 -0
  30. package/dist/mixpanel.cjs.map +1 -0
  31. package/dist/mixpanel.d.cts +47 -0
  32. package/dist/mixpanel.d.ts +47 -0
  33. package/dist/mixpanel.js +2932 -0
  34. package/dist/mixpanel.js.map +1 -0
  35. package/dist/posthog.cjs +4115 -0
  36. package/dist/posthog.cjs.map +1 -0
  37. package/dist/posthog.d.cts +299 -0
  38. package/dist/posthog.d.ts +299 -0
  39. package/dist/posthog.js +4113 -0
  40. package/dist/posthog.js.map +1 -0
  41. package/dist/segment.cjs +6822 -0
  42. package/dist/segment.cjs.map +1 -0
  43. package/dist/segment.d.cts +49 -0
  44. package/dist/segment.d.ts +49 -0
  45. package/dist/segment.js +6794 -0
  46. package/dist/segment.js.map +1 -0
  47. package/dist/slack.cjs +368 -0
  48. package/dist/slack.cjs.map +1 -0
  49. package/dist/slack.d.cts +126 -0
  50. package/dist/slack.d.ts +126 -0
  51. package/dist/slack.js +366 -0
  52. package/dist/slack.js.map +1 -0
  53. package/dist/webhook.cjs +100 -0
  54. package/dist/webhook.cjs.map +1 -0
  55. package/dist/webhook.d.cts +53 -0
  56. package/dist/webhook.d.ts +53 -0
  57. package/dist/webhook.js +98 -0
  58. package/dist/webhook.js.map +1 -0
  59. package/examples/quickstart-custom-subscriber.ts +144 -0
  60. package/examples/subscriber-bigquery.ts +219 -0
  61. package/examples/subscriber-databricks.ts +280 -0
  62. package/examples/subscriber-kafka.ts +326 -0
  63. package/examples/subscriber-kinesis.ts +307 -0
  64. package/examples/subscriber-posthog.ts +421 -0
  65. package/examples/subscriber-pubsub.ts +336 -0
  66. package/examples/subscriber-snowflake.ts +232 -0
  67. package/package.json +141 -0
  68. package/src/amplitude.test.ts +231 -0
  69. package/src/amplitude.ts +148 -0
  70. package/src/event-subscriber-base.ts +325 -0
  71. package/src/factories.ts +197 -0
  72. package/src/index.ts +50 -0
  73. package/src/middleware.ts +489 -0
  74. package/src/mixpanel.test.ts +194 -0
  75. package/src/mixpanel.ts +134 -0
  76. package/src/mock-event-subscriber.ts +333 -0
  77. package/src/posthog.test.ts +629 -0
  78. package/src/posthog.ts +530 -0
  79. package/src/segment.test.ts +228 -0
  80. package/src/segment.ts +148 -0
  81. package/src/slack.ts +383 -0
  82. package/src/streaming-event-subscriber.ts +323 -0
  83. package/src/testing/index.ts +37 -0
  84. package/src/testing/mock-webhook-server.ts +242 -0
  85. package/src/testing/subscriber-test-harness.ts +365 -0
  86. package/src/webhook.test.ts +264 -0
  87. package/src/webhook.ts +158 -0
package/dist/slack.cjs ADDED
@@ -0,0 +1,368 @@
1
+ 'use strict';
2
+
3
+ // src/event-subscriber-base.ts
4
+ var EventSubscriber = class {
5
+ /**
6
+ * Subscriber version (optional)
7
+ */
8
+ version;
9
+ /**
10
+ * Enable/disable the subscriber (default: true)
11
+ */
12
+ enabled = true;
13
+ /**
14
+ * Track pending requests for graceful shutdown
15
+ */
16
+ pendingRequests = /* @__PURE__ */ new Set();
17
+ /**
18
+ * Optional: Handle errors
19
+ *
20
+ * Override this to customize error handling (logging, retries, etc.).
21
+ * Default behavior: log to console.error
22
+ *
23
+ * @param error - Error that occurred
24
+ * @param payload - Event payload that failed
25
+ */
26
+ handleError(error, payload) {
27
+ console.error(
28
+ `[${this.name}] Failed to send ${payload.type}:`,
29
+ error,
30
+ payload
31
+ );
32
+ }
33
+ /**
34
+ * Track an event
35
+ */
36
+ async trackEvent(name, attributes) {
37
+ if (!this.enabled) return;
38
+ const payload = {
39
+ type: "event",
40
+ name,
41
+ attributes,
42
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
43
+ };
44
+ await this.send(payload);
45
+ }
46
+ /**
47
+ * Track a funnel step
48
+ */
49
+ async trackFunnelStep(funnelName, step, attributes) {
50
+ if (!this.enabled) return;
51
+ const payload = {
52
+ type: "funnel",
53
+ name: `${funnelName}.${step}`,
54
+ funnel: funnelName,
55
+ step,
56
+ attributes,
57
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
58
+ };
59
+ await this.send(payload);
60
+ }
61
+ /**
62
+ * Track an outcome
63
+ */
64
+ async trackOutcome(operationName, outcome, attributes) {
65
+ if (!this.enabled) return;
66
+ const payload = {
67
+ type: "outcome",
68
+ name: `${operationName}.${outcome}`,
69
+ operation: operationName,
70
+ outcome,
71
+ attributes,
72
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
73
+ };
74
+ await this.send(payload);
75
+ }
76
+ /**
77
+ * Track a value/metric
78
+ */
79
+ async trackValue(name, value, attributes) {
80
+ if (!this.enabled) return;
81
+ const payload = {
82
+ type: "value",
83
+ name,
84
+ value,
85
+ attributes,
86
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
87
+ };
88
+ await this.send(payload);
89
+ }
90
+ /**
91
+ * Flush pending requests and clean up
92
+ *
93
+ * CRITICAL: Prevents race condition during shutdown
94
+ * 1. Disables subscriber to stop new events
95
+ * 2. Drains all pending requests (with retry logic)
96
+ * 3. Ensures flush guarantee
97
+ *
98
+ * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),
99
+ * but ALWAYS call super.shutdown() first to drain pending requests.
100
+ */
101
+ async shutdown() {
102
+ this.enabled = false;
103
+ const maxDrainAttempts = 10;
104
+ const drainIntervalMs = 50;
105
+ for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {
106
+ if (this.pendingRequests.size === 0) {
107
+ break;
108
+ }
109
+ await Promise.allSettled(this.pendingRequests);
110
+ if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {
111
+ await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));
112
+ }
113
+ }
114
+ if (this.pendingRequests.size > 0) {
115
+ console.warn(
116
+ `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. This may indicate a bug in the subscriber or extremely slow destination.`
117
+ );
118
+ }
119
+ }
120
+ /**
121
+ * Internal: Send payload and track request
122
+ */
123
+ async send(payload) {
124
+ const request = this.sendWithErrorHandling(payload);
125
+ this.pendingRequests.add(request);
126
+ void request.finally(() => {
127
+ this.pendingRequests.delete(request);
128
+ });
129
+ return request;
130
+ }
131
+ /**
132
+ * Internal: Send with error handling
133
+ */
134
+ async sendWithErrorHandling(payload) {
135
+ try {
136
+ await this.sendToDestination(payload);
137
+ } catch (error) {
138
+ this.handleError(error, payload);
139
+ }
140
+ }
141
+ };
142
+
143
+ // src/slack.ts
144
+ var SlackSubscriber = class extends EventSubscriber {
145
+ name = "SlackSubscriber";
146
+ version = "1.0.0";
147
+ config;
148
+ constructor(config) {
149
+ super();
150
+ this.config = {
151
+ webhookUrl: config.webhookUrl,
152
+ channel: config.channel,
153
+ username: config.username ?? "Events Bot",
154
+ iconEmoji: config.iconEmoji ?? ":chart_with_upwards_trend:",
155
+ includeTimestamp: config.includeTimestamp ?? true,
156
+ includeAttributes: config.includeAttributes ?? true,
157
+ maxAttributeFields: config.maxAttributeFields ?? 10,
158
+ filter: config.filter,
159
+ enabled: config.enabled ?? true
160
+ };
161
+ this.enabled = this.config.enabled;
162
+ if (!this.config.webhookUrl) {
163
+ console.error(
164
+ "[SlackSubscriber] No webhook URL provided - subscriber disabled"
165
+ );
166
+ this.enabled = false;
167
+ }
168
+ }
169
+ async sendToDestination(payload) {
170
+ if (this.config.filter) {
171
+ const filterFn = this.config.filter;
172
+ const shouldInclude = filterFn(payload);
173
+ if (!shouldInclude) {
174
+ return;
175
+ }
176
+ }
177
+ const message = this.formatSlackMessage(payload);
178
+ const response = await fetch(this.config.webhookUrl, {
179
+ method: "POST",
180
+ headers: { "Content-Type": "application/json" },
181
+ body: JSON.stringify(message)
182
+ });
183
+ if (!response.ok) {
184
+ const errorText = await response.text();
185
+ throw new Error(
186
+ `Slack webhook failed (${response.status}): ${errorText}`
187
+ );
188
+ }
189
+ }
190
+ /**
191
+ * Format events payload as Slack message
192
+ */
193
+ formatSlackMessage(payload) {
194
+ const emoji = this.getEventEmoji(payload);
195
+ const color = this.getEventColor(payload);
196
+ const title = `${emoji} ${payload.name}`;
197
+ const fields = [
198
+ {
199
+ title: "Event Type",
200
+ value: this.formatEventType(payload),
201
+ short: true
202
+ }
203
+ ];
204
+ if (this.config.includeTimestamp) {
205
+ fields.push({
206
+ title: "Timestamp",
207
+ value: new Date(payload.timestamp).toLocaleString("en-US", {
208
+ month: "short",
209
+ day: "numeric",
210
+ hour: "2-digit",
211
+ minute: "2-digit",
212
+ second: "2-digit"
213
+ }),
214
+ short: true
215
+ });
216
+ }
217
+ if (this.config.includeAttributes && payload.attributes) {
218
+ const attributeFields = this.formatAttributes(payload.attributes);
219
+ fields.push(...attributeFields);
220
+ }
221
+ const attachment = {
222
+ color,
223
+ title,
224
+ fields,
225
+ footer: "Events Events",
226
+ footer_icon: "https://i.imgur.com/QpCKbNL.png"
227
+ };
228
+ if (this.config.includeTimestamp) {
229
+ attachment.ts = Math.floor(
230
+ new Date(payload.timestamp).getTime() / 1e3
231
+ );
232
+ }
233
+ return {
234
+ channel: this.config.channel,
235
+ username: this.config.username,
236
+ icon_emoji: this.config.iconEmoji,
237
+ attachments: [attachment]
238
+ };
239
+ }
240
+ /**
241
+ * Get emoji for event type
242
+ */
243
+ getEventEmoji(payload) {
244
+ switch (payload.type) {
245
+ case "outcome": {
246
+ return payload.outcome === "success" ? "\u2705" : "\u274C";
247
+ }
248
+ case "funnel": {
249
+ return "\u{1F504}";
250
+ }
251
+ case "value": {
252
+ return "\u{1F4CA}";
253
+ }
254
+ default: {
255
+ if (payload.name.includes("order") || payload.name.includes("payment"))
256
+ return "\u{1F4B0}";
257
+ if (payload.name.includes("signup") || payload.name.includes("user"))
258
+ return "\u{1F464}";
259
+ if (payload.name.includes("error") || payload.name.includes("fail"))
260
+ return "\u26A0\uFE0F";
261
+ return "\u{1F4CC}";
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Get Slack attachment color for event type
267
+ */
268
+ getEventColor(payload) {
269
+ switch (payload.type) {
270
+ case "outcome": {
271
+ return payload.outcome === "success" ? "good" : "danger";
272
+ }
273
+ // Green or red
274
+ case "funnel": {
275
+ return "#3AA3E3";
276
+ }
277
+ // Blue
278
+ case "value": {
279
+ return "#764FA5";
280
+ }
281
+ // Purple
282
+ default: {
283
+ if (payload.name.includes("error") || payload.name.includes("fail"))
284
+ return "danger";
285
+ if (payload.name.includes("warning")) return "warning";
286
+ return "good";
287
+ }
288
+ }
289
+ }
290
+ /**
291
+ * Format event type for display
292
+ */
293
+ formatEventType(payload) {
294
+ switch (payload.type) {
295
+ case "event": {
296
+ return "Event";
297
+ }
298
+ case "funnel": {
299
+ return `Funnel: ${payload.step || "unknown"}`;
300
+ }
301
+ case "outcome": {
302
+ return `Outcome: ${payload.outcome || "unknown"}`;
303
+ }
304
+ case "value": {
305
+ return `Value: ${payload.value ?? "N/A"}`;
306
+ }
307
+ default: {
308
+ return payload.type;
309
+ }
310
+ }
311
+ }
312
+ /**
313
+ * Format attributes as Slack fields
314
+ */
315
+ formatAttributes(attributes) {
316
+ const fields = [];
317
+ const entries = Object.entries(attributes);
318
+ const limit = Math.min(entries.length, this.config.maxAttributeFields);
319
+ for (let i = 0; i < limit; i++) {
320
+ const [key, value] = entries[i];
321
+ if (key.startsWith("_") || key === "timestamp") continue;
322
+ fields.push({
323
+ title: this.formatFieldName(key),
324
+ value: this.formatFieldValue(value),
325
+ short: true
326
+ });
327
+ }
328
+ if (entries.length > this.config.maxAttributeFields) {
329
+ fields.push({
330
+ title: "Note",
331
+ value: `... and ${entries.length - this.config.maxAttributeFields} more fields`,
332
+ short: false
333
+ });
334
+ }
335
+ return fields;
336
+ }
337
+ /**
338
+ * Format field name (convert camelCase to Title Case)
339
+ */
340
+ formatFieldName(name) {
341
+ return name.replaceAll(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
342
+ }
343
+ /**
344
+ * Format field value
345
+ */
346
+ formatFieldValue(value) {
347
+ if (value === null || value === void 0) return "N/A";
348
+ if (typeof value === "boolean") return value ? "Yes" : "No";
349
+ if (typeof value === "object") return JSON.stringify(value);
350
+ if (typeof value === "number" && !Number.isInteger(value)) {
351
+ return value.toFixed(2);
352
+ }
353
+ return String(value);
354
+ }
355
+ /**
356
+ * Handle errors (override from EventSubscriber)
357
+ */
358
+ handleError(error, payload) {
359
+ console.error(
360
+ `[SlackSubscriber] Failed to send ${payload.type} event "${payload.name}":`,
361
+ error
362
+ );
363
+ }
364
+ };
365
+
366
+ exports.SlackSubscriber = SlackSubscriber;
367
+ //# sourceMappingURL=slack.cjs.map
368
+ //# sourceMappingURL=slack.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,126 @@
1
+ import { E as EventSubscriber, a as EventPayload } from './event-subscriber-base-CnF3V56W.cjs';
2
+ import 'autotel/event-subscriber';
3
+
4
+ /**
5
+ * Slack Subscriber for autotel
6
+ *
7
+ * Send events events as notifications to Slack channels via webhooks.
8
+ *
9
+ * Perfect for:
10
+ * - Critical business events (orders, payments, signups)
11
+ * - Real-time alerts for failures
12
+ * - Team notifications for important milestones
13
+ * - Monitoring funnel completions
14
+ *
15
+ * @example Basic usage
16
+ * ```typescript
17
+ * import { Events } from 'autotel/events';
18
+ * import { SlackSubscriber } from 'autotel-subscribers/slack';
19
+ *
20
+ * const events = new Events('app', {
21
+ * subscribers: [
22
+ * new SlackSubscriber({
23
+ * webhookUrl: process.env.SLACK_WEBHOOK_URL!,
24
+ * channel: '#order-events'
25
+ * })
26
+ * ]
27
+ * });
28
+ *
29
+ * // Sends to Slack
30
+ * events.trackEvent('order.completed', {
31
+ * orderId: 'ord_123',
32
+ * userId: 'user_456',
33
+ * amount: 99.99
34
+ * });
35
+ * ```
36
+ *
37
+ * @example Filter critical events only
38
+ * ```typescript
39
+ * const events = new Events('app', {
40
+ * subscribers: [
41
+ * new SlackSubscriber({
42
+ * webhookUrl: process.env.SLACK_WEBHOOK_URL!,
43
+ * channel: '#alerts',
44
+ * filter: (payload) => {
45
+ * // Only send failures and high-value orders
46
+ * if (payload.type === 'outcome' && payload.outcome === 'failure') {
47
+ * return true;
48
+ * }
49
+ * if (payload.name === 'order.completed' && payload.attributes?.amount > 1000) {
50
+ * return true;
51
+ * }
52
+ * return false;
53
+ * }
54
+ * })
55
+ * ]
56
+ * });
57
+ * ```
58
+ *
59
+ * Setup:
60
+ * 1. Create Slack App: https://api.slack.com/apps
61
+ * 2. Enable Incoming Webhooks
62
+ * 3. Add webhook to workspace
63
+ * 4. Copy webhook URL (https://hooks.slack.com/services/...)
64
+ */
65
+
66
+ interface SlackSubscriberConfig {
67
+ /** Slack webhook URL (https://hooks.slack.com/services/...) */
68
+ webhookUrl: string;
69
+ /** Default channel to post to (optional, overrides webhook default) */
70
+ channel?: string;
71
+ /** Custom username for bot (default: 'Events Bot') */
72
+ username?: string;
73
+ /** Custom emoji icon (default: ':chart_with_upwards_trend:') */
74
+ iconEmoji?: string;
75
+ /** Include timestamp in messages (default: true) */
76
+ includeTimestamp?: boolean;
77
+ /** Include event attributes as fields (default: true) */
78
+ includeAttributes?: boolean;
79
+ /** Maximum attributes to show (default: 10) */
80
+ maxAttributeFields?: number;
81
+ /** Filter function - return true to send, false to skip */
82
+ filter?: (payload: EventPayload) => boolean;
83
+ /** Enable/disable subscriber */
84
+ enabled?: boolean;
85
+ }
86
+ declare class SlackSubscriber extends EventSubscriber {
87
+ readonly name = "SlackSubscriber";
88
+ readonly version = "1.0.0";
89
+ private config;
90
+ constructor(config: SlackSubscriberConfig);
91
+ protected sendToDestination(payload: EventPayload): Promise<void>;
92
+ /**
93
+ * Format events payload as Slack message
94
+ */
95
+ private formatSlackMessage;
96
+ /**
97
+ * Get emoji for event type
98
+ */
99
+ private getEventEmoji;
100
+ /**
101
+ * Get Slack attachment color for event type
102
+ */
103
+ private getEventColor;
104
+ /**
105
+ * Format event type for display
106
+ */
107
+ private formatEventType;
108
+ /**
109
+ * Format attributes as Slack fields
110
+ */
111
+ private formatAttributes;
112
+ /**
113
+ * Format field name (convert camelCase to Title Case)
114
+ */
115
+ private formatFieldName;
116
+ /**
117
+ * Format field value
118
+ */
119
+ private formatFieldValue;
120
+ /**
121
+ * Handle errors (override from EventSubscriber)
122
+ */
123
+ protected handleError(error: Error, payload: EventPayload): void;
124
+ }
125
+
126
+ export { SlackSubscriber, type SlackSubscriberConfig };
@@ -0,0 +1,126 @@
1
+ import { E as EventSubscriber, a as EventPayload } from './event-subscriber-base-CnF3V56W.js';
2
+ import 'autotel/event-subscriber';
3
+
4
+ /**
5
+ * Slack Subscriber for autotel
6
+ *
7
+ * Send events events as notifications to Slack channels via webhooks.
8
+ *
9
+ * Perfect for:
10
+ * - Critical business events (orders, payments, signups)
11
+ * - Real-time alerts for failures
12
+ * - Team notifications for important milestones
13
+ * - Monitoring funnel completions
14
+ *
15
+ * @example Basic usage
16
+ * ```typescript
17
+ * import { Events } from 'autotel/events';
18
+ * import { SlackSubscriber } from 'autotel-subscribers/slack';
19
+ *
20
+ * const events = new Events('app', {
21
+ * subscribers: [
22
+ * new SlackSubscriber({
23
+ * webhookUrl: process.env.SLACK_WEBHOOK_URL!,
24
+ * channel: '#order-events'
25
+ * })
26
+ * ]
27
+ * });
28
+ *
29
+ * // Sends to Slack
30
+ * events.trackEvent('order.completed', {
31
+ * orderId: 'ord_123',
32
+ * userId: 'user_456',
33
+ * amount: 99.99
34
+ * });
35
+ * ```
36
+ *
37
+ * @example Filter critical events only
38
+ * ```typescript
39
+ * const events = new Events('app', {
40
+ * subscribers: [
41
+ * new SlackSubscriber({
42
+ * webhookUrl: process.env.SLACK_WEBHOOK_URL!,
43
+ * channel: '#alerts',
44
+ * filter: (payload) => {
45
+ * // Only send failures and high-value orders
46
+ * if (payload.type === 'outcome' && payload.outcome === 'failure') {
47
+ * return true;
48
+ * }
49
+ * if (payload.name === 'order.completed' && payload.attributes?.amount > 1000) {
50
+ * return true;
51
+ * }
52
+ * return false;
53
+ * }
54
+ * })
55
+ * ]
56
+ * });
57
+ * ```
58
+ *
59
+ * Setup:
60
+ * 1. Create Slack App: https://api.slack.com/apps
61
+ * 2. Enable Incoming Webhooks
62
+ * 3. Add webhook to workspace
63
+ * 4. Copy webhook URL (https://hooks.slack.com/services/...)
64
+ */
65
+
66
+ interface SlackSubscriberConfig {
67
+ /** Slack webhook URL (https://hooks.slack.com/services/...) */
68
+ webhookUrl: string;
69
+ /** Default channel to post to (optional, overrides webhook default) */
70
+ channel?: string;
71
+ /** Custom username for bot (default: 'Events Bot') */
72
+ username?: string;
73
+ /** Custom emoji icon (default: ':chart_with_upwards_trend:') */
74
+ iconEmoji?: string;
75
+ /** Include timestamp in messages (default: true) */
76
+ includeTimestamp?: boolean;
77
+ /** Include event attributes as fields (default: true) */
78
+ includeAttributes?: boolean;
79
+ /** Maximum attributes to show (default: 10) */
80
+ maxAttributeFields?: number;
81
+ /** Filter function - return true to send, false to skip */
82
+ filter?: (payload: EventPayload) => boolean;
83
+ /** Enable/disable subscriber */
84
+ enabled?: boolean;
85
+ }
86
+ declare class SlackSubscriber extends EventSubscriber {
87
+ readonly name = "SlackSubscriber";
88
+ readonly version = "1.0.0";
89
+ private config;
90
+ constructor(config: SlackSubscriberConfig);
91
+ protected sendToDestination(payload: EventPayload): Promise<void>;
92
+ /**
93
+ * Format events payload as Slack message
94
+ */
95
+ private formatSlackMessage;
96
+ /**
97
+ * Get emoji for event type
98
+ */
99
+ private getEventEmoji;
100
+ /**
101
+ * Get Slack attachment color for event type
102
+ */
103
+ private getEventColor;
104
+ /**
105
+ * Format event type for display
106
+ */
107
+ private formatEventType;
108
+ /**
109
+ * Format attributes as Slack fields
110
+ */
111
+ private formatAttributes;
112
+ /**
113
+ * Format field name (convert camelCase to Title Case)
114
+ */
115
+ private formatFieldName;
116
+ /**
117
+ * Format field value
118
+ */
119
+ private formatFieldValue;
120
+ /**
121
+ * Handle errors (override from EventSubscriber)
122
+ */
123
+ protected handleError(error: Error, payload: EventPayload): void;
124
+ }
125
+
126
+ export { SlackSubscriber, type SlackSubscriberConfig };