@quarry-systems/drift-event-listener 0.1.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 (2) hide show
  1. package/README.md +430 -0
  2. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,430 @@
1
+ # MCG Event Listener Plugin
2
+
3
+ A powerful event listening plugin for Managed Cyclic Graph (MCG) that enables nodes to pause and wait for external events from webhooks, polling, pub/sub systems, or custom sources.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Webhook Listening**: Wait for HTTP webhook calls
8
+ - ✅ **Polling**: Poll endpoints until conditions are met
9
+ - ✅ **Pub/Sub**: Subscribe to topics and wait for messages
10
+ - ✅ **Custom Listeners**: Implement your own event sources
11
+ - ✅ **Event Filtering**: Validate events before accepting
12
+ - ✅ **Event Transformation**: Process event payloads
13
+ - ✅ **Timeouts**: Configure maximum wait times
14
+ - ✅ **Callbacks**: React to events and timeouts
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @quarry-systems/mcg-event-listener
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Plugin-Based Approach
25
+
26
+ ```typescript
27
+ import { ManagedCyclicGraph } from '@quarry-systems/managed-cyclic-graph';
28
+ import { mcgEventListenerPlugin, webhook, pubsub } from '@quarry-systems/mcg-event-listener';
29
+
30
+ const graph = new ManagedCyclicGraph()
31
+ .use(mcgEventListenerPlugin)
32
+
33
+ .node('waitForPayment', {
34
+ type: 'eventnode',
35
+ meta: {
36
+ event: webhook('/payment-complete', {
37
+ secret: 'my-webhook-secret',
38
+ secretHeader: 'X-Webhook-Secret'
39
+ })
40
+ }
41
+ })
42
+
43
+ .node('waitForInventory', {
44
+ type: 'eventnode',
45
+ meta: {
46
+ event: pubsub('inventory-updated')
47
+ }
48
+ })
49
+
50
+ .build();
51
+ ```
52
+
53
+ ### Action-Based Approach
54
+
55
+ ```typescript
56
+ import { ManagedCyclicGraph } from '@quarry-systems/managed-cyclic-graph';
57
+ import { createEventAction, poll } from '@quarry-systems/mcg-event-listener';
58
+
59
+ const graph = new ManagedCyclicGraph()
60
+ .node('submitJob', {
61
+ execute: [
62
+ // ... submit job logic
63
+ ]
64
+ })
65
+
66
+ .node('waitForCompletion', {
67
+ execute: [
68
+ createEventAction('waitForCompletion',
69
+ poll('https://api.example.com/job/status',
70
+ (response) => response.status === 'completed',
71
+ 5000 // Poll every 5 seconds
72
+ )
73
+ )
74
+ ]
75
+ })
76
+
77
+ .node('processResults', {
78
+ execute: [
79
+ // ... process results
80
+ ]
81
+ })
82
+
83
+ .build();
84
+ ```
85
+
86
+ ## API Reference
87
+
88
+ ### Helper Functions
89
+
90
+ #### `webhook(path: string, options?: WebhookConfig)`
91
+ Create a webhook listener that waits for HTTP requests.
92
+
93
+ ```typescript
94
+ webhook('/payment-complete')
95
+
96
+ webhook('/order-update', {
97
+ method: 'POST',
98
+ secret: 'my-secret',
99
+ secretHeader: 'X-Webhook-Secret',
100
+ port: 3000
101
+ })
102
+ ```
103
+
104
+ #### `poll(url: string, condition: (response) => boolean, intervalMs?: number)`
105
+ Create a polling listener that checks an endpoint repeatedly.
106
+
107
+ ```typescript
108
+ poll(
109
+ 'https://api.example.com/job/123',
110
+ (response) => response.status === 'done',
111
+ 2000 // Check every 2 seconds
112
+ )
113
+ ```
114
+
115
+ #### `pubsub(topic: string, provider?: 'memory' | 'redis' | 'custom')`
116
+ Create a pub/sub listener that waits for messages on a topic.
117
+
118
+ ```typescript
119
+ pubsub('order-completed')
120
+ pubsub('inventory-updated', 'memory')
121
+ ```
122
+
123
+ #### `custom(listen: (ctx) => Promise<any>)`
124
+ Create a custom event listener with your own logic.
125
+
126
+ ```typescript
127
+ custom(async (ctx) => {
128
+ // Your custom event listening logic
129
+ return await myEventSource.waitForEvent();
130
+ })
131
+ ```
132
+
133
+ ### Configuration Options
134
+
135
+ ```typescript
136
+ interface EventListenerConfig {
137
+ source: 'webhook' | 'poll' | 'pubsub' | 'custom';
138
+ sourceConfig: WebhookConfig | PollConfig | PubSubConfig | CustomConfig;
139
+ timeoutMs?: number; // Timeout in milliseconds
140
+ filter?: (event: any) => boolean; // Event filter function
141
+ transform?: (event: any, ctx) => any; // Event transformation
142
+ storePath?: string; // Custom storage path
143
+ onEvent?: (event: any, ctx) => void; // Event callback
144
+ onTimeout?: (ctx) => void; // Timeout callback
145
+ }
146
+ ```
147
+
148
+ ### Webhook Configuration
149
+
150
+ ```typescript
151
+ interface WebhookConfig {
152
+ path: string; // Webhook endpoint path
153
+ method?: 'GET' | 'POST' | ...; // HTTP method
154
+ port?: number; // Port to listen on
155
+ secret?: string; // Validation secret
156
+ secretHeader?: string; // Header name for secret
157
+ }
158
+ ```
159
+
160
+ ### Poll Configuration
161
+
162
+ ```typescript
163
+ interface PollConfig {
164
+ url: string; // URL to poll
165
+ intervalMs: number; // Polling interval
166
+ maxAttempts?: number; // Max poll attempts
167
+ method?: 'GET' | 'POST'; // HTTP method
168
+ headers?: Record<string, string>; // Request headers
169
+ condition: (response: any) => boolean; // Success condition
170
+ }
171
+ ```
172
+
173
+ ### Pub/Sub Configuration
174
+
175
+ ```typescript
176
+ interface PubSubConfig {
177
+ topic: string; // Topic/channel name
178
+ provider?: 'memory' | 'redis' | 'custom';
179
+ subscribe?: (topic, callback) => unsubscribe; // Custom subscriber
180
+ }
181
+ ```
182
+
183
+ ## Examples
184
+
185
+ ### Payment Processing Workflow
186
+
187
+ ```typescript
188
+ const graph = new ManagedCyclicGraph()
189
+ .use(mcgEventListenerPlugin)
190
+
191
+ .node('createOrder', {
192
+ execute: [/* create order */]
193
+ })
194
+
195
+ .node('waitForPayment', {
196
+ type: 'eventnode',
197
+ meta: {
198
+ event: {
199
+ ...webhook('/stripe-webhook'),
200
+ timeoutMs: 300000, // 5 minute timeout
201
+ filter: (event) => event.type === 'payment_intent.succeeded',
202
+ onTimeout: (ctx) => {
203
+ console.log('Payment timeout, canceling order');
204
+ }
205
+ }
206
+ }
207
+ })
208
+
209
+ .node('fulfillOrder', {
210
+ execute: [/* fulfill order */]
211
+ })
212
+
213
+ .build();
214
+ ```
215
+
216
+ ### Job Status Polling
217
+
218
+ ```typescript
219
+ const graph = new ManagedCyclicGraph()
220
+ .use(mcgEventListenerPlugin)
221
+
222
+ .node('submitJob', {
223
+ execute: [
224
+ {
225
+ id: 'submit',
226
+ run: async (ctx) => {
227
+ const jobId = await submitJob();
228
+ ctx.data.jobId = jobId;
229
+ return ctx;
230
+ }
231
+ }
232
+ ]
233
+ })
234
+
235
+ .node('pollStatus', {
236
+ type: 'eventnode',
237
+ meta: {
238
+ event: poll(
239
+ 'https://api.example.com/jobs/${data.jobId}',
240
+ (response) => response.status === 'completed',
241
+ 5000
242
+ )
243
+ }
244
+ })
245
+
246
+ .node('processResults', {
247
+ execute: [/* process results */]
248
+ })
249
+
250
+ .build();
251
+ ```
252
+
253
+ ### Multi-Event Coordination
254
+
255
+ ```typescript
256
+ const graph = new ManagedCyclicGraph()
257
+ .use(mcgEventListenerPlugin)
258
+
259
+ .node('waitForInventory', {
260
+ type: 'eventnode',
261
+ meta: {
262
+ event: pubsub('inventory-available')
263
+ }
264
+ })
265
+
266
+ .node('waitForApproval', {
267
+ type: 'eventnode',
268
+ meta: {
269
+ event: pubsub('manager-approved')
270
+ }
271
+ })
272
+
273
+ .node('processOrder', {
274
+ execute: [/* both events received, process order */]
275
+ })
276
+
277
+ .build();
278
+ ```
279
+
280
+ ### Event Transformation
281
+
282
+ ```typescript
283
+ const graph = new ManagedCyclicGraph()
284
+ .use(mcgEventListenerPlugin)
285
+
286
+ .node('waitForWebhook', {
287
+ type: 'eventnode',
288
+ meta: {
289
+ event: {
290
+ ...webhook('/data-update'),
291
+ transform: (event, ctx) => {
292
+ // Extract only what you need
293
+ return {
294
+ userId: event.body.user.id,
295
+ timestamp: event.body.timestamp,
296
+ changes: event.body.changes
297
+ };
298
+ },
299
+ onEvent: (event, ctx) => {
300
+ console.log('Received event:', event);
301
+ }
302
+ }
303
+ }
304
+ })
305
+
306
+ .build();
307
+ ```
308
+
309
+ ### Custom Event Source
310
+
311
+ ```typescript
312
+ import { custom } from '@quarry-systems/mcg-event-listener';
313
+
314
+ const graph = new ManagedCyclicGraph()
315
+ .use(mcgEventListenerPlugin)
316
+
317
+ .node('waitForCustomEvent', {
318
+ type: 'eventnode',
319
+ meta: {
320
+ event: custom(async (ctx) => {
321
+ // Connect to your custom event source
322
+ const connection = await connectToEventSource();
323
+
324
+ return new Promise((resolve) => {
325
+ connection.on('myEvent', (data) => {
326
+ resolve(data);
327
+ });
328
+ });
329
+ })
330
+ }
331
+ })
332
+
333
+ .build();
334
+ ```
335
+
336
+ ## Metadata Storage
337
+
338
+ Event metadata is stored in the context at `data.events.{nodeId}` by default:
339
+
340
+ ```typescript
341
+ {
342
+ data: {
343
+ events: {
344
+ waitForPayment: {
345
+ source: 'webhook',
346
+ receivedAt: 1701234567890,
347
+ event: { /* event payload */ },
348
+ timedOut: false
349
+ }
350
+ }
351
+ }
352
+ }
353
+ ```
354
+
355
+ ## Publishing Events
356
+
357
+ For testing or integration, you can publish events to the global event bus:
358
+
359
+ ```typescript
360
+ import { globalEventBus } from '@quarry-systems/mcg-event-listener';
361
+
362
+ // Publish to webhook
363
+ globalEventBus.publish('webhook:/payment-complete', {
364
+ method: 'POST',
365
+ headers: { 'X-Webhook-Secret': 'my-secret' },
366
+ body: { orderId: '123', status: 'paid' }
367
+ });
368
+
369
+ // Publish to pub/sub
370
+ globalEventBus.publish('order-completed', {
371
+ orderId: '123',
372
+ total: 99.99
373
+ });
374
+ ```
375
+
376
+ ## Use Cases
377
+
378
+ - **Payment Processing**: Wait for payment confirmations
379
+ - **Job Completion**: Poll job status until done
380
+ - **Inventory Management**: React to stock updates
381
+ - **Approval Workflows**: Wait for human approval
382
+ - **External Integrations**: Respond to third-party events
383
+ - **Real-time Updates**: React to live data changes
384
+ - **Async Operations**: Coordinate distributed systems
385
+ - **Event-Driven Architecture**: Build reactive workflows
386
+
387
+ ## Best Practices
388
+
389
+ 1. **Always Set Timeouts** to prevent infinite waits
390
+ 2. **Use Filters** to validate events before processing
391
+ 3. **Transform Events** to extract only needed data
392
+ 4. **Add Callbacks** for logging and monitoring
393
+ 5. **Secure Webhooks** with secrets and validation
394
+ 6. **Poll Responsibly** with appropriate intervals
395
+ 7. **Handle Timeouts** gracefully with fallback logic
396
+
397
+ ## Integration with Other Plugins
398
+
399
+ Combine with Timer plugin for retry logic:
400
+
401
+ ```typescript
402
+ import { mcgEventListenerPlugin, poll } from '@quarry-systems/mcg-event-listener';
403
+ import { mcgTimerPlugin, sleep } from '@quarry-systems/mcg-timer';
404
+
405
+ const graph = new ManagedCyclicGraph()
406
+ .use(mcgEventListenerPlugin)
407
+ .use(mcgTimerPlugin)
408
+
409
+ .node('pollJob', {
410
+ type: 'eventnode',
411
+ meta: {
412
+ event: poll(url, condition, 5000),
413
+ timeoutMs: 30000
414
+ }
415
+ })
416
+
417
+ .node('waitBeforeRetry', {
418
+ type: 'timernode',
419
+ meta: { timer: sleep(10000) }
420
+ })
421
+
422
+ .edge('pollJob', 'waitBeforeRetry', ['timedOut'])
423
+ .edge('waitBeforeRetry', 'pollJob', 'any')
424
+
425
+ .build();
426
+ ```
427
+
428
+ ## License
429
+
430
+ ISC
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@quarry-systems/drift-event-listener",
3
+ "version": "0.1.0",
4
+ "description": "Event listener and webhook plugin for Drift",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc -p .",
9
+ "clean": "rimraf dist || rm -rf dist",
10
+ "dev": "tsc -p . --watch",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "example": "npx ts-node examples/basic-usage.ts",
14
+ "example:basic": "npx ts-node examples/basic-usage.ts",
15
+ "example:plugin": "npx ts-node examples/plugin-based-usage.ts"
16
+ },
17
+ "keywords": [
18
+ "drift",
19
+ "plugin",
20
+ "event",
21
+ "webhook",
22
+ "pubsub",
23
+ "listener"
24
+ ],
25
+ "author": "Brett Nye",
26
+ "license": "ISC",
27
+ "devDependencies": {
28
+ "@types/node": "^20.10.0",
29
+ "ts-node": "^10.9.1",
30
+ "typescript": "^5.3.0",
31
+ "vitest": "^2.1.0"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "LICENSE.md",
37
+ "CHANGELOG.md"
38
+ ],
39
+ "type": "commonjs"
40
+ }