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,227 @@
|
|
|
1
|
+
import { EventAttributes, FunnelStatus, OutcomeStatus, EventSubscriber } from 'autotel/event-subscriber';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Middleware System for Events Subscribers
|
|
5
|
+
*
|
|
6
|
+
* Compose subscriber behaviors using middleware (like Redux/Express).
|
|
7
|
+
* Add retry logic, sampling, enrichment, logging, and more without modifying subscriber code.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { applyMiddleware, retryMiddleware, loggingMiddleware } from 'autotel-subscribers/middleware';
|
|
12
|
+
*
|
|
13
|
+
* const subscriber = applyMiddleware(
|
|
14
|
+
* new PostHogSubscriber({ apiKey: '...' }),
|
|
15
|
+
* [
|
|
16
|
+
* retryMiddleware({ maxRetries: 3 }),
|
|
17
|
+
* loggingMiddleware()
|
|
18
|
+
* ]
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Unified event type for middleware
|
|
25
|
+
*/
|
|
26
|
+
type EventsEvent = {
|
|
27
|
+
type: 'event';
|
|
28
|
+
name: string;
|
|
29
|
+
attributes?: EventAttributes;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'funnel';
|
|
32
|
+
funnel: string;
|
|
33
|
+
step: FunnelStatus;
|
|
34
|
+
attributes?: EventAttributes;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'outcome';
|
|
37
|
+
operation: string;
|
|
38
|
+
outcome: OutcomeStatus;
|
|
39
|
+
attributes?: EventAttributes;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'value';
|
|
42
|
+
name: string;
|
|
43
|
+
value: number;
|
|
44
|
+
attributes?: EventAttributes;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Middleware function signature.
|
|
48
|
+
*
|
|
49
|
+
* Like Express middleware: `(event, next) => Promise<void>`
|
|
50
|
+
*/
|
|
51
|
+
type SubscriberMiddleware = (event: EventsEvent, next: (event: EventsEvent) => Promise<void>) => Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Apply middleware to an subscriber.
|
|
54
|
+
*
|
|
55
|
+
* Middleware is executed in order. Each middleware can:
|
|
56
|
+
* - Transform the event before passing to next()
|
|
57
|
+
* - Add side effects (logging, metrics)
|
|
58
|
+
* - Skip calling next() (filtering)
|
|
59
|
+
* - Handle errors
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const subscriber = applyMiddleware(
|
|
64
|
+
* new WebhookSubscriber({ url: '...' }),
|
|
65
|
+
* [
|
|
66
|
+
* loggingMiddleware(),
|
|
67
|
+
* retryMiddleware({ maxRetries: 3 }),
|
|
68
|
+
* samplingMiddleware(0.1) // Only 10% of events
|
|
69
|
+
* ]
|
|
70
|
+
* );
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function applyMiddleware(subscriber: EventSubscriber, middlewares: SubscriberMiddleware[]): EventSubscriber;
|
|
74
|
+
/**
|
|
75
|
+
* Retry failed requests with exponential backoff.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
80
|
+
* retryMiddleware({ maxRetries: 3, delayMs: 1000 })
|
|
81
|
+
* ]);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
declare function retryMiddleware(options: {
|
|
85
|
+
maxRetries?: number;
|
|
86
|
+
delayMs?: number;
|
|
87
|
+
onRetry?: (attempt: number, error: Error) => void;
|
|
88
|
+
}): SubscriberMiddleware;
|
|
89
|
+
/**
|
|
90
|
+
* Sample events (only send a percentage).
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* // Only send 10% of events (reduce costs)
|
|
95
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
96
|
+
* samplingMiddleware(0.1)
|
|
97
|
+
* ]);
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare function samplingMiddleware(rate: number): SubscriberMiddleware;
|
|
101
|
+
/**
|
|
102
|
+
* Enrich events with additional attributes.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
107
|
+
* enrichmentMiddleware((event) => ({
|
|
108
|
+
* ...event,
|
|
109
|
+
* attributes: {
|
|
110
|
+
* ...event.attributes,
|
|
111
|
+
* environment: process.env.NODE_ENV,
|
|
112
|
+
* timestamp: Date.now()
|
|
113
|
+
* }
|
|
114
|
+
* }))
|
|
115
|
+
* ]);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function enrichmentMiddleware(enricher: (event: EventsEvent) => EventsEvent): SubscriberMiddleware;
|
|
119
|
+
/**
|
|
120
|
+
* Log events to console.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
125
|
+
* loggingMiddleware({ prefix: '[Events]', logAttributes: true })
|
|
126
|
+
* ]);
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare function loggingMiddleware(options?: {
|
|
130
|
+
prefix?: string;
|
|
131
|
+
logAttributes?: boolean;
|
|
132
|
+
}): SubscriberMiddleware;
|
|
133
|
+
/**
|
|
134
|
+
* Filter events based on a predicate.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // Only send 'order' events
|
|
139
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
140
|
+
* filterMiddleware((event) =>
|
|
141
|
+
* event.type === 'event' && event.name.startsWith('order.')
|
|
142
|
+
* )
|
|
143
|
+
* ]);
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function filterMiddleware(predicate: (event: EventsEvent) => boolean): SubscriberMiddleware;
|
|
147
|
+
/**
|
|
148
|
+
* Transform events.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* // Lowercase all event names
|
|
153
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
154
|
+
* transformMiddleware((event) => {
|
|
155
|
+
* if (event.type === 'event') {
|
|
156
|
+
* return { ...event, name: event.name.toLowerCase() };
|
|
157
|
+
* }
|
|
158
|
+
* return event;
|
|
159
|
+
* })
|
|
160
|
+
* ]);
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
declare function transformMiddleware(transformer: (event: EventsEvent) => EventsEvent): SubscriberMiddleware;
|
|
164
|
+
/**
|
|
165
|
+
* Batch events and flush periodically.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
170
|
+
* batchingMiddleware({ batchSize: 100, flushInterval: 5000 })
|
|
171
|
+
* ]);
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare function batchingMiddleware(options: {
|
|
175
|
+
batchSize?: number;
|
|
176
|
+
flushInterval?: number;
|
|
177
|
+
}): SubscriberMiddleware;
|
|
178
|
+
/**
|
|
179
|
+
* Rate limit events (throttle).
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* // Max 100 events per second
|
|
184
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
185
|
+
* rateLimitMiddleware({ requestsPerSecond: 100 })
|
|
186
|
+
* ]);
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare function rateLimitMiddleware(options: {
|
|
190
|
+
requestsPerSecond: number;
|
|
191
|
+
}): SubscriberMiddleware;
|
|
192
|
+
/**
|
|
193
|
+
* Circuit breaker pattern.
|
|
194
|
+
*
|
|
195
|
+
* Opens circuit after N failures, prevents further requests for a timeout period.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
200
|
+
* circuitBreakerMiddleware({
|
|
201
|
+
* failureThreshold: 5,
|
|
202
|
+
* timeout: 60000 // 1 minute
|
|
203
|
+
* })
|
|
204
|
+
* ]);
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
declare function circuitBreakerMiddleware(options: {
|
|
208
|
+
failureThreshold?: number;
|
|
209
|
+
timeout?: number;
|
|
210
|
+
onOpen?: () => void;
|
|
211
|
+
onClose?: () => void;
|
|
212
|
+
}): SubscriberMiddleware;
|
|
213
|
+
/**
|
|
214
|
+
* Add timeout to events.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const subscriber = applyMiddleware(adapter, [
|
|
219
|
+
* timeoutMiddleware({ timeoutMs: 5000 })
|
|
220
|
+
* ]);
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function timeoutMiddleware(options: {
|
|
224
|
+
timeoutMs: number;
|
|
225
|
+
}): SubscriberMiddleware;
|
|
226
|
+
|
|
227
|
+
export { type EventsEvent, type SubscriberMiddleware, applyMiddleware, batchingMiddleware, circuitBreakerMiddleware, enrichmentMiddleware, filterMiddleware, loggingMiddleware, rateLimitMiddleware, retryMiddleware, samplingMiddleware, timeoutMiddleware, transformMiddleware };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// src/middleware.ts
|
|
2
|
+
function applyMiddleware(subscriber, middlewares) {
|
|
3
|
+
const trackEvent = async (event) => {
|
|
4
|
+
switch (event.type) {
|
|
5
|
+
case "event": {
|
|
6
|
+
await subscriber.trackEvent(event.name, event.attributes);
|
|
7
|
+
break;
|
|
8
|
+
}
|
|
9
|
+
case "funnel": {
|
|
10
|
+
await subscriber.trackFunnelStep(event.funnel, event.step, event.attributes);
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
case "outcome": {
|
|
14
|
+
await subscriber.trackOutcome(event.operation, event.outcome, event.attributes);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
case "value": {
|
|
18
|
+
await subscriber.trackValue(event.name, event.value, event.attributes);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const reversedMiddlewares = middlewares.toReversed();
|
|
24
|
+
let chain = trackEvent;
|
|
25
|
+
for (const middleware of reversedMiddlewares) {
|
|
26
|
+
const next = chain;
|
|
27
|
+
chain = (event) => middleware(event, next);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: `${subscriber.name}(middleware)`,
|
|
31
|
+
version: subscriber.version,
|
|
32
|
+
async trackEvent(name, attributes) {
|
|
33
|
+
await chain({ type: "event", name, attributes });
|
|
34
|
+
},
|
|
35
|
+
async trackFunnelStep(funnel, step, attributes) {
|
|
36
|
+
await chain({ type: "funnel", funnel, step, attributes });
|
|
37
|
+
},
|
|
38
|
+
async trackOutcome(operation, outcome, attributes) {
|
|
39
|
+
await chain({ type: "outcome", operation, outcome, attributes });
|
|
40
|
+
},
|
|
41
|
+
async trackValue(name, value, attributes) {
|
|
42
|
+
await chain({ type: "value", name, value, attributes });
|
|
43
|
+
},
|
|
44
|
+
async shutdown() {
|
|
45
|
+
await subscriber.shutdown?.();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function retryMiddleware(options) {
|
|
50
|
+
const { maxRetries = 3, delayMs = 1e3, onRetry } = options;
|
|
51
|
+
return async (event, next) => {
|
|
52
|
+
let lastError;
|
|
53
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
await next(event);
|
|
56
|
+
return;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
lastError = error;
|
|
59
|
+
if (attempt < maxRetries) {
|
|
60
|
+
onRetry?.(attempt, lastError);
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs * Math.pow(2, attempt - 1)));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw lastError;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function samplingMiddleware(rate) {
|
|
69
|
+
if (rate < 0 || rate > 1) {
|
|
70
|
+
throw new Error("Sample rate must be between 0 and 1");
|
|
71
|
+
}
|
|
72
|
+
return async (event, next) => {
|
|
73
|
+
if (Math.random() < rate) {
|
|
74
|
+
await next(event);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function enrichmentMiddleware(enricher) {
|
|
79
|
+
return async (event, next) => {
|
|
80
|
+
const enriched = enricher(event);
|
|
81
|
+
await next(enriched);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function loggingMiddleware(options = {}) {
|
|
85
|
+
const { prefix = "[Events]", logAttributes = false } = options;
|
|
86
|
+
return async (event, next) => {
|
|
87
|
+
if (logAttributes) {
|
|
88
|
+
console.log(prefix, event.type, event);
|
|
89
|
+
} else {
|
|
90
|
+
const eventName = "name" in event ? event.name : `${event.funnel || event.operation}`;
|
|
91
|
+
console.log(prefix, event.type, eventName);
|
|
92
|
+
}
|
|
93
|
+
await next(event);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function filterMiddleware(predicate) {
|
|
97
|
+
return async (event, next) => {
|
|
98
|
+
if (predicate(event)) {
|
|
99
|
+
await next(event);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function transformMiddleware(transformer) {
|
|
104
|
+
return async (event, next) => {
|
|
105
|
+
const transformed = transformer(event);
|
|
106
|
+
await next(transformed);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function batchingMiddleware(options) {
|
|
110
|
+
const { batchSize = 100, flushInterval = 5e3 } = options;
|
|
111
|
+
const buffer = [];
|
|
112
|
+
let flushTimer = null;
|
|
113
|
+
const flush = async () => {
|
|
114
|
+
const batch = [...buffer];
|
|
115
|
+
buffer.length = 0;
|
|
116
|
+
await Promise.all(batch.map(({ event, next }) => next(event)));
|
|
117
|
+
};
|
|
118
|
+
const scheduleFlush = () => {
|
|
119
|
+
if (flushTimer) return;
|
|
120
|
+
flushTimer = setTimeout(() => {
|
|
121
|
+
flush().catch(console.error);
|
|
122
|
+
flushTimer = null;
|
|
123
|
+
}, flushInterval);
|
|
124
|
+
};
|
|
125
|
+
return async (event, next) => {
|
|
126
|
+
buffer.push({ event, next });
|
|
127
|
+
if (buffer.length >= batchSize) {
|
|
128
|
+
await flush();
|
|
129
|
+
} else {
|
|
130
|
+
scheduleFlush();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function rateLimitMiddleware(options) {
|
|
135
|
+
const { requestsPerSecond } = options;
|
|
136
|
+
const intervalMs = 1e3 / requestsPerSecond;
|
|
137
|
+
let lastCallTime = 0;
|
|
138
|
+
const queue = [];
|
|
139
|
+
let processing = false;
|
|
140
|
+
const processQueue = async () => {
|
|
141
|
+
if (processing) return;
|
|
142
|
+
processing = true;
|
|
143
|
+
while (queue.length > 0) {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
const timeSinceLastCall = now - lastCallTime;
|
|
146
|
+
if (timeSinceLastCall < intervalMs) {
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs - timeSinceLastCall));
|
|
148
|
+
}
|
|
149
|
+
const fn = queue.shift();
|
|
150
|
+
if (fn) {
|
|
151
|
+
lastCallTime = Date.now();
|
|
152
|
+
fn();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
processing = false;
|
|
156
|
+
};
|
|
157
|
+
return async (event, next) => {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
queue.push(() => {
|
|
160
|
+
next(event).then(resolve).catch(reject);
|
|
161
|
+
});
|
|
162
|
+
processQueue().catch(reject);
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function circuitBreakerMiddleware(options) {
|
|
167
|
+
const { failureThreshold = 5, timeout = 6e4, onOpen, onClose } = options;
|
|
168
|
+
let failureCount = 0;
|
|
169
|
+
let lastFailureTime = 0;
|
|
170
|
+
let circuitOpen = false;
|
|
171
|
+
return async (event, next) => {
|
|
172
|
+
if (circuitOpen) {
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
if (now - lastFailureTime > timeout) {
|
|
175
|
+
circuitOpen = false;
|
|
176
|
+
failureCount = 0;
|
|
177
|
+
onClose?.();
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error("Circuit breaker is open");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
await next(event);
|
|
184
|
+
failureCount = 0;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
failureCount++;
|
|
187
|
+
lastFailureTime = Date.now();
|
|
188
|
+
if (failureCount >= failureThreshold) {
|
|
189
|
+
circuitOpen = true;
|
|
190
|
+
onOpen?.();
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function timeoutMiddleware(options) {
|
|
197
|
+
const { timeoutMs } = options;
|
|
198
|
+
return async (event, next) => {
|
|
199
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
200
|
+
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
201
|
+
});
|
|
202
|
+
await Promise.race([next(event), timeoutPromise]);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { applyMiddleware, batchingMiddleware, circuitBreakerMiddleware, enrichmentMiddleware, filterMiddleware, loggingMiddleware, rateLimitMiddleware, retryMiddleware, samplingMiddleware, timeoutMiddleware, transformMiddleware };
|
|
207
|
+
//# sourceMappingURL=middleware.js.map
|
|
208
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"names":[],"mappings":"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","file":"middleware.js","sourcesContent":["/**\n * Middleware System for Events Subscribers\n *\n * Compose subscriber behaviors using middleware (like Redux/Express).\n * Add retry logic, sampling, enrichment, logging, and more without modifying subscriber code.\n *\n * @example\n * ```typescript\n * import { applyMiddleware, retryMiddleware, loggingMiddleware } from 'autotel-subscribers/middleware';\n *\n * const subscriber = applyMiddleware(\n * new PostHogSubscriber({ apiKey: '...' }),\n * [\n * retryMiddleware({ maxRetries: 3 }),\n * loggingMiddleware()\n * ]\n * );\n * ```\n */\n\nimport type { EventSubscriber, EventAttributes, FunnelStatus, OutcomeStatus } from 'autotel/event-subscriber';\n\n/**\n * Unified event type for middleware\n */\nexport type EventsEvent =\n | {\n type: 'event';\n name: string;\n attributes?: EventAttributes;\n }\n | {\n type: 'funnel';\n funnel: string;\n step: FunnelStatus;\n attributes?: EventAttributes;\n }\n | {\n type: 'outcome';\n operation: string;\n outcome: OutcomeStatus;\n attributes?: EventAttributes;\n }\n | {\n type: 'value';\n name: string;\n value: number;\n attributes?: EventAttributes;\n };\n\n/**\n * Middleware function signature.\n *\n * Like Express middleware: `(event, next) => Promise<void>`\n */\nexport type SubscriberMiddleware = (\n event: EventsEvent,\n next: (event: EventsEvent) => Promise<void>\n) => Promise<void>;\n\n/**\n * Apply middleware to an subscriber.\n *\n * Middleware is executed in order. Each middleware can:\n * - Transform the event before passing to next()\n * - Add side effects (logging, metrics)\n * - Skip calling next() (filtering)\n * - Handle errors\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(\n * new WebhookSubscriber({ url: '...' }),\n * [\n * loggingMiddleware(),\n * retryMiddleware({ maxRetries: 3 }),\n * samplingMiddleware(0.1) // Only 10% of events\n * ]\n * );\n * ```\n */\nexport function applyMiddleware(\n subscriber: EventSubscriber,\n middlewares: SubscriberMiddleware[]\n): EventSubscriber {\n // Convert subscriber methods to event format\n const trackEvent = async (event: EventsEvent): Promise<void> => {\n switch (event.type) {\n case 'event': {\n await subscriber.trackEvent(event.name, event.attributes);\n break;\n }\n case 'funnel': {\n await subscriber.trackFunnelStep(event.funnel, event.step, event.attributes);\n break;\n }\n case 'outcome': {\n await subscriber.trackOutcome(event.operation, event.outcome, event.attributes);\n break;\n }\n case 'value': {\n await subscriber.trackValue(event.name, event.value, event.attributes);\n break;\n }\n }\n };\n\n // Build middleware chain\n type ChainFunction = (event: EventsEvent) => Promise<void>;\n const reversedMiddlewares = middlewares.toReversed();\n let chain: ChainFunction = trackEvent;\n for (const middleware of reversedMiddlewares) {\n const next = chain;\n chain = (event: EventsEvent) => middleware(event, next);\n }\n\n // Wrap subscriber with middleware chain\n return {\n name: `${subscriber.name}(middleware)`,\n version: subscriber.version,\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n await chain({ type: 'event', name, attributes });\n },\n\n async trackFunnelStep(funnel: string, step: FunnelStatus, attributes?: EventAttributes): Promise<void> {\n await chain({ type: 'funnel', funnel, step, attributes });\n },\n\n async trackOutcome(operation: string, outcome: OutcomeStatus, attributes?: EventAttributes): Promise<void> {\n await chain({ type: 'outcome', operation, outcome, attributes });\n },\n\n async trackValue(name: string, value: number, attributes?: EventAttributes): Promise<void> {\n await chain({ type: 'value', name, value, attributes });\n },\n\n async shutdown(): Promise<void> {\n await subscriber.shutdown?.();\n },\n };\n}\n\n// ============================================================================\n// Built-in Middleware\n// ============================================================================\n\n/**\n * Retry failed requests with exponential backoff.\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(adapter, [\n * retryMiddleware({ maxRetries: 3, delayMs: 1000 })\n * ]);\n * ```\n */\nexport function retryMiddleware(options: {\n maxRetries?: number;\n delayMs?: number;\n onRetry?: (attempt: number, error: Error) => void;\n}): SubscriberMiddleware {\n const { maxRetries = 3, delayMs = 1000, onRetry } = options;\n\n return async (event, next) => {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n await next(event);\n return; // Success!\n } catch (error) {\n lastError = error as Error;\n\n if (attempt < maxRetries) {\n onRetry?.(attempt, lastError);\n // Exponential backoff: 1s, 2s, 4s, 8s...\n await new Promise((resolve) => setTimeout(resolve, delayMs * Math.pow(2, attempt - 1)));\n }\n }\n }\n\n throw lastError;\n };\n}\n\n/**\n * Sample events (only send a percentage).\n *\n * @example\n * ```typescript\n * // Only send 10% of events (reduce costs)\n * const subscriber = applyMiddleware(adapter, [\n * samplingMiddleware(0.1)\n * ]);\n * ```\n */\nexport function samplingMiddleware(rate: number): SubscriberMiddleware {\n if (rate < 0 || rate > 1) {\n throw new Error('Sample rate must be between 0 and 1');\n }\n\n return async (event, next) => {\n if (Math.random() < rate) {\n await next(event);\n }\n // Else: skip this event\n };\n}\n\n/**\n * Enrich events with additional attributes.\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(adapter, [\n * enrichmentMiddleware((event) => ({\n * ...event,\n * attributes: {\n * ...event.attributes,\n * environment: process.env.NODE_ENV,\n * timestamp: Date.now()\n * }\n * }))\n * ]);\n * ```\n */\nexport function enrichmentMiddleware(\n enricher: (event: EventsEvent) => EventsEvent\n): SubscriberMiddleware {\n return async (event, next) => {\n const enriched = enricher(event);\n await next(enriched);\n };\n}\n\n/**\n * Log events to console.\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(adapter, [\n * loggingMiddleware({ prefix: '[Events]', logAttributes: true })\n * ]);\n * ```\n */\nexport function loggingMiddleware(options: {\n prefix?: string;\n logAttributes?: boolean;\n} = {}): SubscriberMiddleware {\n const { prefix = '[Events]', logAttributes = false } = options;\n\n return async (event, next) => {\n if (logAttributes) {\n console.log(prefix, event.type, event);\n } else {\n // Just log event type and name\n const eventName = 'name' in event ? event.name : `${(event as any).funnel || (event as any).operation}`;\n console.log(prefix, event.type, eventName);\n }\n\n await next(event);\n };\n}\n\n/**\n * Filter events based on a predicate.\n *\n * @example\n * ```typescript\n * // Only send 'order' events\n * const subscriber = applyMiddleware(adapter, [\n * filterMiddleware((event) =>\n * event.type === 'event' && event.name.startsWith('order.')\n * )\n * ]);\n * ```\n */\nexport function filterMiddleware(\n predicate: (event: EventsEvent) => boolean\n): SubscriberMiddleware {\n return async (event, next) => {\n if (predicate(event)) {\n await next(event);\n }\n };\n}\n\n/**\n * Transform events.\n *\n * @example\n * ```typescript\n * // Lowercase all event names\n * const subscriber = applyMiddleware(adapter, [\n * transformMiddleware((event) => {\n * if (event.type === 'event') {\n * return { ...event, name: event.name.toLowerCase() };\n * }\n * return event;\n * })\n * ]);\n * ```\n */\nexport function transformMiddleware(\n transformer: (event: EventsEvent) => EventsEvent\n): SubscriberMiddleware {\n return async (event, next) => {\n const transformed = transformer(event);\n await next(transformed);\n };\n}\n\n/**\n * Batch events and flush periodically.\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(adapter, [\n * batchingMiddleware({ batchSize: 100, flushInterval: 5000 })\n * ]);\n * ```\n */\nexport function batchingMiddleware(options: {\n batchSize?: number;\n flushInterval?: number;\n}): SubscriberMiddleware {\n const { batchSize = 100, flushInterval = 5000 } = options;\n const buffer: Array<{ event: EventsEvent; next: (event: EventsEvent) => Promise<void> }> = [];\n let flushTimer: NodeJS.Timeout | null = null;\n\n const flush = async () => {\n const batch = [...buffer];\n buffer.length = 0;\n\n await Promise.all(batch.map(({ event, next }) => next(event)));\n };\n\n const scheduleFlush = () => {\n if (flushTimer) return;\n flushTimer = setTimeout(() => {\n flush().catch(console.error);\n flushTimer = null;\n }, flushInterval);\n };\n\n return async (event, next) => {\n buffer.push({ event, next });\n\n if (buffer.length >= batchSize) {\n await flush();\n } else {\n scheduleFlush();\n }\n };\n}\n\n/**\n * Rate limit events (throttle).\n *\n * @example\n * ```typescript\n * // Max 100 events per second\n * const subscriber = applyMiddleware(adapter, [\n * rateLimitMiddleware({ requestsPerSecond: 100 })\n * ]);\n * ```\n */\nexport function rateLimitMiddleware(options: {\n requestsPerSecond: number;\n}): SubscriberMiddleware {\n const { requestsPerSecond } = options;\n const intervalMs = 1000 / requestsPerSecond;\n let lastCallTime = 0;\n const queue: Array<() => void> = [];\n let processing = false;\n\n const processQueue = async () => {\n if (processing) return;\n processing = true;\n\n while (queue.length > 0) {\n const now = Date.now();\n const timeSinceLastCall = now - lastCallTime;\n\n if (timeSinceLastCall < intervalMs) {\n await new Promise((resolve) => setTimeout(resolve, intervalMs - timeSinceLastCall));\n }\n\n const fn = queue.shift();\n if (fn) {\n lastCallTime = Date.now();\n fn();\n }\n }\n\n processing = false;\n };\n\n return async (event, next) => {\n return new Promise<void>((resolve, reject) => {\n queue.push(() => {\n next(event).then(resolve).catch(reject);\n });\n processQueue().catch(reject);\n });\n };\n}\n\n/**\n * Circuit breaker pattern.\n *\n * Opens circuit after N failures, prevents further requests for a timeout period.\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(adapter, [\n * circuitBreakerMiddleware({\n * failureThreshold: 5,\n * timeout: 60000 // 1 minute\n * })\n * ]);\n * ```\n */\nexport function circuitBreakerMiddleware(options: {\n failureThreshold?: number;\n timeout?: number;\n onOpen?: () => void;\n onClose?: () => void;\n}): SubscriberMiddleware {\n const { failureThreshold = 5, timeout = 60_000, onOpen, onClose } = options;\n let failureCount = 0;\n let lastFailureTime = 0;\n let circuitOpen = false;\n\n return async (event, next) => {\n // Check if circuit should close\n if (circuitOpen) {\n const now = Date.now();\n if (now - lastFailureTime > timeout) {\n circuitOpen = false;\n failureCount = 0;\n onClose?.();\n } else {\n throw new Error('Circuit breaker is open');\n }\n }\n\n try {\n await next(event);\n // Success resets failure count\n failureCount = 0;\n } catch (error) {\n failureCount++;\n lastFailureTime = Date.now();\n\n if (failureCount >= failureThreshold) {\n circuitOpen = true;\n onOpen?.();\n }\n\n throw error;\n }\n };\n}\n\n/**\n * Add timeout to events.\n *\n * @example\n * ```typescript\n * const subscriber = applyMiddleware(adapter, [\n * timeoutMiddleware({ timeoutMs: 5000 })\n * ]);\n * ```\n */\nexport function timeoutMiddleware(options: {\n timeoutMs: number;\n}): SubscriberMiddleware {\n const { timeoutMs } = options;\n\n return async (event, next) => {\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);\n });\n\n await Promise.race([next(event), timeoutPromise]);\n };\n}\n"]}
|