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
@@ -0,0 +1,307 @@
1
+ /**
2
+ * AWS Kinesis Streaming Subscriber Example
3
+ *
4
+ * Production-ready Kinesis subscriber for AWS-native event streaming.
5
+ *
6
+ * Installation:
7
+ * ```bash
8
+ * pnpm add @aws-sdk/client-kinesis
9
+ * ```
10
+ *
11
+ * Features:
12
+ * - Shard-based partitioning for ordered delivery
13
+ * - Automatic retries with exponential backoff
14
+ * - Backpressure handling (ProvisionedThroughputExceeded)
15
+ * - Batch sending (up to 500 records per PutRecords call)
16
+ * - CloudWatch metrics integration ready
17
+ * - Graceful shutdown
18
+ *
19
+ * Setup:
20
+ * ```bash
21
+ * # Create Kinesis stream
22
+ * aws kinesis create-stream \
23
+ * --stream-name events-events \
24
+ * --shard-count 10
25
+ *
26
+ * # Wait for stream to become active
27
+ * aws kinesis wait stream-exists \
28
+ * --stream-name events-events
29
+ * ```
30
+ *
31
+ * Usage:
32
+ * ```typescript
33
+ * import { Events } from 'autotel/events';
34
+ * import { KinesisSubscriber } from './adapter-kinesis';
35
+ *
36
+ * const events = new Events('app', {
37
+ * subscribers: [
38
+ * new KinesisSubscriber({
39
+ * streamName: 'events-events',
40
+ * region: 'us-east-1',
41
+ * partitionStrategy: 'userId',
42
+ * maxBufferSize: 10000,
43
+ * maxBatchSize: 500, // Kinesis max per PutRecords
44
+ * bufferOverflowStrategy: 'block'
45
+ * })
46
+ * ]
47
+ * });
48
+ *
49
+ * // Events partitioned by userId
50
+ * await events.trackEvent('order.completed', {
51
+ * userId: 'user_123',
52
+ * amount: 99.99
53
+ * });
54
+ * ```
55
+ */
56
+
57
+ import {
58
+ StreamingEventSubscriber,
59
+ type BufferOverflowStrategy,
60
+ } from '../src/streaming-event-subscriber';
61
+ import type { EventPayload } from '../src/event-subscriber-base';
62
+ import {
63
+ KinesisClient,
64
+ PutRecordsCommand,
65
+ type PutRecordsRequestEntry,
66
+ type PutRecordsCommandOutput,
67
+ } from '@aws-sdk/client-kinesis';
68
+
69
+ type PartitionStrategy = 'userId' | 'tenantId' | 'eventType' | 'random';
70
+
71
+ export interface KinesisSubscriberConfig {
72
+ /** Kinesis stream name */
73
+ streamName: string;
74
+
75
+ /** AWS region (default: 'us-east-1') */
76
+ region?: string;
77
+
78
+ /** Partitioning strategy (default: 'userId') */
79
+ partitionStrategy?: PartitionStrategy;
80
+
81
+ /** Enable/disable subscriber */
82
+ enabled?: boolean;
83
+
84
+ /** Maximum buffer size (default: 10000) */
85
+ maxBufferSize?: number;
86
+
87
+ /** Maximum batch size - Kinesis allows max 500 per PutRecords (default: 500) */
88
+ maxBatchSize?: number;
89
+
90
+ /** Buffer overflow strategy (default: 'block') */
91
+ bufferOverflowStrategy?: BufferOverflowStrategy;
92
+
93
+ /** Flush interval in ms (default: 1000) */
94
+ flushIntervalMs?: number;
95
+
96
+ /** AWS credentials (optional, uses default credential chain if not provided) */
97
+ credentials?: {
98
+ accessKeyId: string;
99
+ secretAccessKey: string;
100
+ };
101
+
102
+ /** Max retries for throttling (default: 5) */
103
+ maxRetries?: number;
104
+ }
105
+
106
+ export class KinesisSubscriber extends StreamingEventSubscriber {
107
+ readonly name = 'KinesisSubscriber';
108
+ readonly version = '1.0.0';
109
+
110
+ private client: KinesisClient;
111
+ private subscriberConfig: Required<Omit<KinesisSubscriberConfig, 'credentials'>> & {
112
+ credentials?: KinesisSubscriberConfig['credentials'];
113
+ };
114
+
115
+ constructor(config: KinesisSubscriberConfig) {
116
+ super({
117
+ maxBufferSize: config.maxBufferSize ?? 10_000,
118
+ maxBatchSize: Math.min(config.maxBatchSize ?? 500, 500), // Kinesis max is 500
119
+ bufferOverflowStrategy: config.bufferOverflowStrategy ?? 'block',
120
+ flushIntervalMs: config.flushIntervalMs ?? 1000,
121
+ });
122
+
123
+ // Set config defaults
124
+ this.adapterConfig = {
125
+ streamName: config.streamName,
126
+ region: config.region ?? 'us-east-1',
127
+ partitionStrategy: config.partitionStrategy ?? 'userId',
128
+ enabled: config.enabled ?? true,
129
+ maxBufferSize: config.maxBufferSize ?? 10_000,
130
+ maxBatchSize: Math.min(config.maxBatchSize ?? 500, 500),
131
+ bufferOverflowStrategy: config.bufferOverflowStrategy ?? 'block',
132
+ flushIntervalMs: config.flushIntervalMs ?? 1000,
133
+ maxRetries: config.maxRetries ?? 5,
134
+ credentials: config.credentials,
135
+ };
136
+
137
+ this.enabled = this.adapterConfig.enabled;
138
+
139
+ if (this.enabled) {
140
+ this.initializeKinesis();
141
+ }
142
+ }
143
+
144
+ private initializeKinesis(): void {
145
+ try {
146
+ this.client = new KinesisClient({
147
+ region: this.adapterConfig.region,
148
+ credentials: this.adapterConfig.credentials,
149
+ maxAttempts: this.adapterConfig.maxRetries,
150
+ });
151
+
152
+ console.log('[KinesisSubscriber] Initialized successfully');
153
+ } catch (error) {
154
+ console.error('[KinesisSubscriber] Failed to initialize:', error);
155
+ this.enabled = false;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Get partition key based on configured strategy
161
+ *
162
+ * Kinesis uses partition key for shard assignment and ordering.
163
+ * Events with same partition key go to same shard (ordered).
164
+ */
165
+ protected getPartitionKey(payload: EventPayload): string {
166
+ switch (this.adapterConfig.partitionStrategy) {
167
+ case 'userId': {
168
+ return payload.attributes?.userId?.toString() || 'default';
169
+ }
170
+
171
+ case 'tenantId': {
172
+ return payload.attributes?.tenantId?.toString() || 'default';
173
+ }
174
+
175
+ case 'eventType': {
176
+ return payload.type;
177
+ } // 'event', 'funnel', 'outcome', 'value'
178
+
179
+ case 'random': {
180
+ // Random partition key for even distribution across shards
181
+ return Math.random().toString(36).slice(7);
182
+ }
183
+
184
+ default: {
185
+ return 'default';
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Send batch of events to Kinesis
192
+ */
193
+ protected async sendBatch(events: EventPayload[]): Promise<void> {
194
+ // Build Kinesis records
195
+ const records: PutRecordsRequestEntry[] = events.map((event) => ({
196
+ Data: Buffer.from(JSON.stringify(event)),
197
+ PartitionKey: this.getPartitionKey(event),
198
+ }));
199
+
200
+ // Send to Kinesis with retry logic
201
+ await this.sendWithRetry(records);
202
+ }
203
+
204
+ /**
205
+ * Send records to Kinesis with exponential backoff retry
206
+ */
207
+ private async sendWithRetry(
208
+ records: PutRecordsRequestEntry[],
209
+ attempt = 1
210
+ ): Promise<void> {
211
+ try {
212
+ const command = new PutRecordsCommand({
213
+ StreamName: this.adapterConfig.streamName,
214
+ Records: records,
215
+ });
216
+
217
+ const result: PutRecordsCommandOutput = await this.client.send(command);
218
+
219
+ // Check for failed records
220
+ if (result.FailedRecordCount && result.FailedRecordCount > 0) {
221
+ const failedRecords: PutRecordsRequestEntry[] = [];
222
+
223
+ if (result.Records) for (const [index, record] of result.Records.entries()) {
224
+ if (record.ErrorCode) {
225
+ console.error(
226
+ `[KinesisSubscriber] Record ${index} failed: ${record.ErrorCode} - ${record.ErrorMessage}`
227
+ );
228
+ failedRecords.push(records[index]);
229
+ }
230
+ }
231
+
232
+ // Retry failed records
233
+ if (failedRecords.length > 0 && attempt < this.adapterConfig.maxRetries) {
234
+ const backoffMs = Math.min(1000 * Math.pow(2, attempt), 30_000);
235
+ console.warn(
236
+ `[KinesisSubscriber] Retrying ${failedRecords.length} failed records (attempt ${attempt}) after ${backoffMs}ms`
237
+ );
238
+
239
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
240
+ await this.sendWithRetry(failedRecords, attempt + 1);
241
+ } else if (failedRecords.length > 0) {
242
+ throw new Error(
243
+ `Failed to send ${failedRecords.length} records after ${this.adapterConfig.maxRetries} attempts`
244
+ );
245
+ }
246
+ }
247
+
248
+ // Success - log metrics
249
+ if (process.env.DEBUG) {
250
+ console.log(
251
+ `[KinesisSubscriber] Sent ${records.length} records successfully`
252
+ );
253
+ }
254
+ } catch (error: any) {
255
+ // Handle specific Kinesis errors
256
+ if (error.name === 'ProvisionedThroughputExceededException') {
257
+ console.error(
258
+ '[KinesisSubscriber] Provisioned throughput exceeded - consider increasing shard count or reducing rate'
259
+ );
260
+
261
+ // Backpressure: Wait before retry
262
+ if (attempt < this.adapterConfig.maxRetries) {
263
+ const backoffMs = Math.min(1000 * Math.pow(2, attempt), 30_000);
264
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
265
+ await this.sendWithRetry(records, attempt + 1);
266
+ return;
267
+ }
268
+ }
269
+
270
+ if (error.name === 'ResourceNotFoundException') {
271
+ console.error(
272
+ `[KinesisSubscriber] Stream not found: ${this.adapterConfig.streamName}`
273
+ );
274
+ }
275
+
276
+ throw error;
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Handle errors (override from EventSubscriber)
282
+ */
283
+ protected handleError(error: Error, payload: EventPayload): void {
284
+ console.error(
285
+ `[KinesisSubscriber] Failed to process ${payload.type} event:`,
286
+ error,
287
+ {
288
+ eventName: payload.name,
289
+ partitionKey: this.getPartitionKey(payload),
290
+ streamName: this.adapterConfig.streamName,
291
+ }
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Graceful shutdown
297
+ */
298
+ async shutdown(): Promise<void> {
299
+ console.log('[KinesisSubscriber] Starting graceful shutdown...');
300
+
301
+ // Flush buffer and drain pending requests
302
+ await super.shutdown();
303
+
304
+ // Kinesis client doesn't need explicit disconnect
305
+ console.log('[KinesisSubscriber] Shutdown complete');
306
+ }
307
+ }