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.
- package/LICENSE +21 -0
- package/README.md +669 -0
- package/dist/amplitude.cjs +2486 -0
- package/dist/amplitude.cjs.map +1 -0
- package/dist/amplitude.d.cts +49 -0
- package/dist/amplitude.d.ts +49 -0
- package/dist/amplitude.js +2463 -0
- package/dist/amplitude.js.map +1 -0
- package/dist/event-subscriber-base-CnF3V56W.d.cts +182 -0
- package/dist/event-subscriber-base-CnF3V56W.d.ts +182 -0
- package/dist/factories.cjs +16660 -0
- package/dist/factories.cjs.map +1 -0
- package/dist/factories.d.cts +304 -0
- package/dist/factories.d.ts +304 -0
- package/dist/factories.js +16624 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.cjs +16575 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +179 -0
- package/dist/index.d.ts +179 -0
- package/dist/index.js +16539 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.cjs +220 -0
- package/dist/middleware.cjs.map +1 -0
- package/dist/middleware.d.cts +227 -0
- package/dist/middleware.d.ts +227 -0
- package/dist/middleware.js +208 -0
- package/dist/middleware.js.map +1 -0
- package/dist/mixpanel.cjs +2940 -0
- package/dist/mixpanel.cjs.map +1 -0
- package/dist/mixpanel.d.cts +47 -0
- package/dist/mixpanel.d.ts +47 -0
- package/dist/mixpanel.js +2932 -0
- package/dist/mixpanel.js.map +1 -0
- package/dist/posthog.cjs +4115 -0
- package/dist/posthog.cjs.map +1 -0
- package/dist/posthog.d.cts +299 -0
- package/dist/posthog.d.ts +299 -0
- package/dist/posthog.js +4113 -0
- package/dist/posthog.js.map +1 -0
- package/dist/segment.cjs +6822 -0
- package/dist/segment.cjs.map +1 -0
- package/dist/segment.d.cts +49 -0
- package/dist/segment.d.ts +49 -0
- package/dist/segment.js +6794 -0
- package/dist/segment.js.map +1 -0
- package/dist/slack.cjs +368 -0
- package/dist/slack.cjs.map +1 -0
- package/dist/slack.d.cts +126 -0
- package/dist/slack.d.ts +126 -0
- package/dist/slack.js +366 -0
- package/dist/slack.js.map +1 -0
- package/dist/webhook.cjs +100 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.cts +53 -0
- package/dist/webhook.d.ts +53 -0
- package/dist/webhook.js +98 -0
- package/dist/webhook.js.map +1 -0
- package/examples/quickstart-custom-subscriber.ts +144 -0
- package/examples/subscriber-bigquery.ts +219 -0
- package/examples/subscriber-databricks.ts +280 -0
- package/examples/subscriber-kafka.ts +326 -0
- package/examples/subscriber-kinesis.ts +307 -0
- package/examples/subscriber-posthog.ts +421 -0
- package/examples/subscriber-pubsub.ts +336 -0
- package/examples/subscriber-snowflake.ts +232 -0
- package/package.json +141 -0
- package/src/amplitude.test.ts +231 -0
- package/src/amplitude.ts +148 -0
- package/src/event-subscriber-base.ts +325 -0
- package/src/factories.ts +197 -0
- package/src/index.ts +50 -0
- package/src/middleware.ts +489 -0
- package/src/mixpanel.test.ts +194 -0
- package/src/mixpanel.ts +134 -0
- package/src/mock-event-subscriber.ts +333 -0
- package/src/posthog.test.ts +629 -0
- package/src/posthog.ts +530 -0
- package/src/segment.test.ts +228 -0
- package/src/segment.ts +148 -0
- package/src/slack.ts +383 -0
- package/src/streaming-event-subscriber.ts +323 -0
- package/src/testing/index.ts +37 -0
- package/src/testing/mock-webhook-server.ts +242 -0
- package/src/testing/subscriber-test-harness.ts +365 -0
- package/src/webhook.test.ts +264 -0
- 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
|
+
}
|