@auriclabs/events 0.2.0 → 0.4.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/.turbo/turbo-build.log +36 -22
- package/CHANGELOG.md +14 -0
- package/dist/index.cjs +4 -82
- package/dist/index.d.cts +1 -142
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -142
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -81
- package/dist/index.mjs.map +1 -1
- package/dist/index2.d.cts +148 -0
- package/dist/index2.d.cts.map +1 -0
- package/dist/index2.d.mts +148 -0
- package/dist/index2.d.mts.map +1 -0
- package/dist/stream-handler.cjs +91 -0
- package/dist/stream-handler.entry.cjs +8 -0
- package/dist/stream-handler.entry.d.cts +7 -0
- package/dist/stream-handler.entry.d.cts.map +1 -0
- package/dist/stream-handler.entry.d.mts +7 -0
- package/dist/stream-handler.entry.d.mts.map +1 -0
- package/dist/stream-handler.entry.mjs +10 -0
- package/dist/stream-handler.entry.mjs.map +1 -0
- package/dist/stream-handler.mjs +88 -0
- package/dist/stream-handler.mjs.map +1 -0
- package/package.json +7 -2
- package/src/create-event-listener.test.ts +5 -4
- package/src/dispatch-event.test.ts +11 -0
- package/src/dispatch-events.test.ts +3 -3
- package/src/event-service.test.ts +74 -32
- package/src/event-service.ts +4 -0
- package/src/init.test.ts +2 -0
- package/src/stream-handler.entry.ts +6 -0
- package/src/stream-handler.test.ts +95 -35
- package/src/stream-handler.ts +6 -2
- package/src/types.ts +3 -0
- package/tsconfig.test.json +2 -1
|
@@ -32,7 +32,7 @@ import { SendMessageBatchCommand } from '@aws-sdk/client-sqs';
|
|
|
32
32
|
|
|
33
33
|
import { createStreamHandler } from './stream-handler';
|
|
34
34
|
|
|
35
|
-
import type { DynamoDBStreamEvent } from 'aws-lambda';
|
|
35
|
+
import type { DynamoDBRecord, DynamoDBStreamEvent } from 'aws-lambda';
|
|
36
36
|
|
|
37
37
|
const makeEventRecord = (overrides = {}) => ({
|
|
38
38
|
pk: 'AGG#order#o-1',
|
|
@@ -42,6 +42,7 @@ const makeEventRecord = (overrides = {}) => ({
|
|
|
42
42
|
aggregateId: 'o-1',
|
|
43
43
|
aggregateType: 'order',
|
|
44
44
|
version: 1,
|
|
45
|
+
tenantId: 'tenant-1',
|
|
45
46
|
eventId: 'evt-1',
|
|
46
47
|
eventType: 'OrderCreated',
|
|
47
48
|
schemaVersion: 1,
|
|
@@ -50,11 +51,18 @@ const makeEventRecord = (overrides = {}) => ({
|
|
|
50
51
|
...overrides,
|
|
51
52
|
});
|
|
52
53
|
|
|
53
|
-
const makeStreamRecord = (
|
|
54
|
+
const makeStreamRecord = (
|
|
55
|
+
eventName: string,
|
|
56
|
+
newImage: object | undefined = {},
|
|
57
|
+
): DynamoDBRecord => ({
|
|
54
58
|
eventID: '1',
|
|
55
59
|
eventVersion: '1.1',
|
|
56
60
|
dynamodb: {
|
|
57
|
-
NewImage: newImage
|
|
61
|
+
NewImage: newImage as DynamoDBRecord['dynamodb'] extends infer D
|
|
62
|
+
? D extends { NewImage?: infer N }
|
|
63
|
+
? N
|
|
64
|
+
: never
|
|
65
|
+
: never,
|
|
58
66
|
StreamViewType: 'NEW_IMAGE',
|
|
59
67
|
},
|
|
60
68
|
awsRegion: 'us-east-1',
|
|
@@ -63,6 +71,25 @@ const makeStreamRecord = (eventName: string, newImage: object | undefined = {})
|
|
|
63
71
|
eventSource: 'aws:dynamodb',
|
|
64
72
|
});
|
|
65
73
|
|
|
74
|
+
interface SqsBatchInput {
|
|
75
|
+
QueueUrl?: string;
|
|
76
|
+
Entries?: {
|
|
77
|
+
Id?: string;
|
|
78
|
+
MessageBody: string;
|
|
79
|
+
MessageGroupId?: string;
|
|
80
|
+
MessageDeduplicationId?: string;
|
|
81
|
+
}[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface EbPutEventsInput {
|
|
85
|
+
Entries?: {
|
|
86
|
+
EventBusName?: string;
|
|
87
|
+
DetailType?: string;
|
|
88
|
+
Source?: string;
|
|
89
|
+
Detail: string;
|
|
90
|
+
}[];
|
|
91
|
+
}
|
|
92
|
+
|
|
66
93
|
describe('stream-handler', () => {
|
|
67
94
|
const config = {
|
|
68
95
|
busName: 'test-bus',
|
|
@@ -90,7 +117,7 @@ describe('stream-handler', () => {
|
|
|
90
117
|
makeStreamRecord('INSERT', { data: { S: 'x' } }),
|
|
91
118
|
makeStreamRecord('MODIFY', { data: { S: 'y' } }),
|
|
92
119
|
makeStreamRecord('REMOVE', undefined),
|
|
93
|
-
]
|
|
120
|
+
],
|
|
94
121
|
};
|
|
95
122
|
|
|
96
123
|
await handler(event);
|
|
@@ -110,16 +137,16 @@ describe('stream-handler', () => {
|
|
|
110
137
|
Records: [
|
|
111
138
|
makeStreamRecord('INSERT', { a: { S: '1' } }),
|
|
112
139
|
makeStreamRecord('INSERT', { b: { S: '2' } }),
|
|
113
|
-
]
|
|
140
|
+
],
|
|
114
141
|
};
|
|
115
142
|
|
|
116
143
|
await handler(event);
|
|
117
144
|
|
|
118
145
|
// Only eventRecord (itemType='event') should be sent
|
|
119
146
|
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(1);
|
|
120
|
-
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
147
|
+
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
121
148
|
expect(sqsInput.Entries).toHaveLength(1);
|
|
122
|
-
expect(JSON.parse(sqsInput.Entries
|
|
149
|
+
expect(JSON.parse(sqsInput.Entries?.[0]?.MessageBody ?? '')).toEqual(eventRecord);
|
|
123
150
|
});
|
|
124
151
|
|
|
125
152
|
it('sends to all configured queues', async () => {
|
|
@@ -136,14 +163,14 @@ describe('stream-handler', () => {
|
|
|
136
163
|
|
|
137
164
|
const handler = createStreamHandler(multiQueueConfig);
|
|
138
165
|
const event: DynamoDBStreamEvent = {
|
|
139
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
166
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
140
167
|
};
|
|
141
168
|
|
|
142
169
|
await handler(event);
|
|
143
170
|
|
|
144
171
|
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(2);
|
|
145
|
-
const call1 = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
146
|
-
const call2 = vi.mocked(SendMessageBatchCommand).mock.calls[1][0];
|
|
172
|
+
const call1 = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
173
|
+
const call2 = vi.mocked(SendMessageBatchCommand).mock.calls[1][0] as SqsBatchInput;
|
|
147
174
|
expect(call1.QueueUrl).toBe('https://sqs.us-east-1.amazonaws.com/123/queue-1');
|
|
148
175
|
expect(call2.QueueUrl).toBe('https://sqs.us-east-1.amazonaws.com/123/queue-2');
|
|
149
176
|
});
|
|
@@ -154,18 +181,18 @@ describe('stream-handler', () => {
|
|
|
154
181
|
|
|
155
182
|
const handler = createStreamHandler(config);
|
|
156
183
|
const event: DynamoDBStreamEvent = {
|
|
157
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
184
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
158
185
|
};
|
|
159
186
|
|
|
160
187
|
await handler(event);
|
|
161
188
|
|
|
162
189
|
expect(PutEventsCommand).toHaveBeenCalledTimes(1);
|
|
163
|
-
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0];
|
|
190
|
+
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0] as EbPutEventsInput;
|
|
164
191
|
expect(ebInput.Entries).toHaveLength(1);
|
|
165
|
-
expect(ebInput.Entries
|
|
166
|
-
expect(ebInput.Entries
|
|
167
|
-
expect(ebInput.Entries
|
|
168
|
-
expect(JSON.parse(ebInput.Entries
|
|
192
|
+
expect(ebInput.Entries?.[0]?.EventBusName).toBe('test-bus');
|
|
193
|
+
expect(ebInput.Entries?.[0]?.DetailType).toBe('CreditAdded');
|
|
194
|
+
expect(ebInput.Entries?.[0]?.Source).toBe('billing');
|
|
195
|
+
expect(JSON.parse(ebInput.Entries?.[0]?.Detail ?? '')).toEqual(eventRecord);
|
|
169
196
|
});
|
|
170
197
|
|
|
171
198
|
it('uses kebabCase of aggregateType as source fallback when source is undefined', async () => {
|
|
@@ -174,14 +201,14 @@ describe('stream-handler', () => {
|
|
|
174
201
|
|
|
175
202
|
const handler = createStreamHandler(config);
|
|
176
203
|
const event: DynamoDBStreamEvent = {
|
|
177
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
204
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
178
205
|
};
|
|
179
206
|
|
|
180
207
|
await handler(event);
|
|
181
208
|
|
|
182
|
-
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0];
|
|
209
|
+
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0] as EbPutEventsInput;
|
|
183
210
|
// kebabCase splits on '.', takes first part 'Order', which becomes 'order'
|
|
184
|
-
expect(ebInput.Entries
|
|
211
|
+
expect(ebInput.Entries?.[0]?.Source).toBe('order');
|
|
185
212
|
});
|
|
186
213
|
|
|
187
214
|
it('uses aggregateId as MessageGroupId', async () => {
|
|
@@ -190,13 +217,13 @@ describe('stream-handler', () => {
|
|
|
190
217
|
|
|
191
218
|
const handler = createStreamHandler(config);
|
|
192
219
|
const event: DynamoDBStreamEvent = {
|
|
193
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
220
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
194
221
|
};
|
|
195
222
|
|
|
196
223
|
await handler(event);
|
|
197
224
|
|
|
198
|
-
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
199
|
-
expect(sqsInput.Entries
|
|
225
|
+
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
226
|
+
expect(sqsInput.Entries?.[0]?.MessageGroupId).toBe('agg-123');
|
|
200
227
|
});
|
|
201
228
|
|
|
202
229
|
it('uses eventId as MessageDeduplicationId', async () => {
|
|
@@ -205,37 +232,35 @@ describe('stream-handler', () => {
|
|
|
205
232
|
|
|
206
233
|
const handler = createStreamHandler(config);
|
|
207
234
|
const event: DynamoDBStreamEvent = {
|
|
208
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
235
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
209
236
|
};
|
|
210
237
|
|
|
211
238
|
await handler(event);
|
|
212
239
|
|
|
213
|
-
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
214
|
-
expect(sqsInput.Entries
|
|
240
|
+
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
241
|
+
expect(sqsInput.Entries?.[0]?.MessageDeduplicationId).toBe('evt-dedup-1');
|
|
215
242
|
});
|
|
216
243
|
|
|
217
244
|
it('batches correctly (respects BATCH_SIZE of 10)', async () => {
|
|
218
245
|
// Create 12 event records to trigger 2 batches
|
|
219
246
|
const records = Array.from({ length: 12 }, (_, i) =>
|
|
220
|
-
makeEventRecord({ eventId: `evt-${i}`, version: i + 1 }),
|
|
247
|
+
makeEventRecord({ eventId: `evt-${String(i)}`, version: i + 1 }),
|
|
221
248
|
);
|
|
222
249
|
|
|
223
|
-
mockUnmarshall.mockImplementation((_, i) => records[i]);
|
|
224
|
-
// Reset to return each record in sequence
|
|
225
250
|
mockUnmarshall.mockReset();
|
|
226
251
|
records.forEach((r) => mockUnmarshall.mockReturnValueOnce(r));
|
|
227
252
|
|
|
228
253
|
const handler = createStreamHandler(config);
|
|
229
254
|
const event: DynamoDBStreamEvent = {
|
|
230
|
-
Records: records.map((
|
|
255
|
+
Records: records.map((_r, i) => makeStreamRecord('INSERT', { idx: { N: String(i) } })),
|
|
231
256
|
};
|
|
232
257
|
|
|
233
258
|
await handler(event);
|
|
234
259
|
|
|
235
260
|
// 2 batches for SQS (10 + 2), 1 queue = 2 calls
|
|
236
261
|
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(2);
|
|
237
|
-
const firstBatch = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
238
|
-
const secondBatch = vi.mocked(SendMessageBatchCommand).mock.calls[1][0];
|
|
262
|
+
const firstBatch = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
263
|
+
const secondBatch = vi.mocked(SendMessageBatchCommand).mock.calls[1][0] as SqsBatchInput;
|
|
239
264
|
expect(firstBatch.Entries).toHaveLength(10);
|
|
240
265
|
expect(secondBatch.Entries).toHaveLength(2);
|
|
241
266
|
|
|
@@ -250,13 +275,13 @@ describe('stream-handler', () => {
|
|
|
250
275
|
|
|
251
276
|
const handler = createStreamHandler(config);
|
|
252
277
|
const event: DynamoDBStreamEvent = {
|
|
253
|
-
Records: [makeStreamRecord('INSERT', { bad: { S: 'data' } })]
|
|
278
|
+
Records: [makeStreamRecord('INSERT', { bad: { S: 'data' } })],
|
|
254
279
|
};
|
|
255
280
|
|
|
256
281
|
await handler(event);
|
|
257
282
|
|
|
258
283
|
expect(logger.error).toHaveBeenCalledWith(
|
|
259
|
-
expect.objectContaining({ error: expect.any(Error) }),
|
|
284
|
+
expect.objectContaining({ error: expect.any(Error) as unknown }),
|
|
260
285
|
'Error unmarshalling event record',
|
|
261
286
|
);
|
|
262
287
|
// Should not send to queues since no valid records
|
|
@@ -281,13 +306,48 @@ describe('stream-handler', () => {
|
|
|
281
306
|
|
|
282
307
|
const handler = createStreamHandler(config);
|
|
283
308
|
const event: DynamoDBStreamEvent = {
|
|
284
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
309
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
285
310
|
};
|
|
286
311
|
|
|
287
312
|
await expect(handler(event)).rejects.toThrow('SQS error');
|
|
288
313
|
expect(logger.error).toHaveBeenCalled();
|
|
289
314
|
});
|
|
290
315
|
|
|
316
|
+
it('skips EventBridge when busName is omitted', async () => {
|
|
317
|
+
const eventRecord = makeEventRecord();
|
|
318
|
+
mockUnmarshall.mockReturnValue(eventRecord);
|
|
319
|
+
|
|
320
|
+
const handler = createStreamHandler({
|
|
321
|
+
queueUrls: ['https://sqs.us-east-1.amazonaws.com/123/queue-1'],
|
|
322
|
+
});
|
|
323
|
+
const event: DynamoDBStreamEvent = {
|
|
324
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
await handler(event);
|
|
328
|
+
|
|
329
|
+
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(1);
|
|
330
|
+
expect(PutEventsCommand).not.toHaveBeenCalled();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('skips EventBridge when busName is empty string', async () => {
|
|
334
|
+
const eventRecord = makeEventRecord();
|
|
335
|
+
mockUnmarshall.mockReturnValue(eventRecord);
|
|
336
|
+
|
|
337
|
+
const handler = createStreamHandler({
|
|
338
|
+
busName: '',
|
|
339
|
+
queueUrls: ['https://sqs.us-east-1.amazonaws.com/123/queue-1'],
|
|
340
|
+
});
|
|
341
|
+
const event: DynamoDBStreamEvent = {
|
|
342
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
await handler(event);
|
|
346
|
+
|
|
347
|
+
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(1);
|
|
348
|
+
expect(PutEventsCommand).not.toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
|
|
291
351
|
it('re-throws EventBridge send errors', async () => {
|
|
292
352
|
const eventRecord = makeEventRecord();
|
|
293
353
|
mockUnmarshall.mockReturnValue(eventRecord);
|
|
@@ -295,7 +355,7 @@ describe('stream-handler', () => {
|
|
|
295
355
|
|
|
296
356
|
const handler = createStreamHandler(config);
|
|
297
357
|
const event: DynamoDBStreamEvent = {
|
|
298
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
358
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
299
359
|
};
|
|
300
360
|
|
|
301
361
|
await expect(handler(event)).rejects.toThrow();
|
package/src/stream-handler.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { AggregateHead, EventRecord } from './types';
|
|
|
11
11
|
const BATCH_SIZE = 10;
|
|
12
12
|
|
|
13
13
|
export interface CreateStreamHandlerConfig {
|
|
14
|
-
busName
|
|
14
|
+
busName?: string;
|
|
15
15
|
queueUrls: string[];
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -102,7 +102,11 @@ export function createStreamHandler(config: CreateStreamHandlerConfig) {
|
|
|
102
102
|
.filter((eventRecord): eventRecord is EventRecord => eventRecord?.itemType === 'event');
|
|
103
103
|
|
|
104
104
|
if (eventRecords.length > 0) {
|
|
105
|
-
|
|
105
|
+
const tasks: Promise<void>[] = [sendToQueuesBatch(eventRecords)];
|
|
106
|
+
if (config.busName) {
|
|
107
|
+
tasks.push(sendToBusBatch(eventRecords));
|
|
108
|
+
}
|
|
109
|
+
await Promise.all(tasks);
|
|
106
110
|
}
|
|
107
111
|
};
|
|
108
112
|
}
|
package/src/types.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface EventRecord<P = unknown> {
|
|
|
23
23
|
aggregateType: AggregateType;
|
|
24
24
|
version: number; // post-apply version
|
|
25
25
|
|
|
26
|
+
/** Tenant isolation */
|
|
27
|
+
tenantId: string;
|
|
28
|
+
|
|
26
29
|
/** Event identity & semantics */
|
|
27
30
|
eventId: EventId; // string (ULID/UUID), not number
|
|
28
31
|
eventType: string;
|