@auriclabs/events 0.2.0 → 0.3.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 +20 -20
- package/CHANGELOG.md +7 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- 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.test.ts +60 -35
- package/src/types.ts +3 -0
- package/tsconfig.test.json +2 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @auriclabs/events@0.
|
|
2
|
+
> @auriclabs/events@0.3.0 build /home/runner/work/packages/packages/packages/events
|
|
3
3
|
> tsdown src/index.ts --format cjs,esm --dts --no-hash
|
|
4
4
|
|
|
5
5
|
[33m[tsdown] Node.js v20.20.1 is deprecated. Support will be removed in the next minor release. Please upgrade to Node.js 22.18.0 or later.[39m
|
|
@@ -7,33 +7,33 @@
|
|
|
7
7
|
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
8
8
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
9
|
[34mℹ[39m Build start
|
|
10
|
-
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mindex.cjs[22m [2m11.
|
|
11
|
-
[34mℹ[39m [33m[CJS][39m 1 files, total: 11.
|
|
10
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mindex.cjs[22m [2m11.09 kB[22m [2m│ gzip: 3.32 kB[22m
|
|
11
|
+
[34mℹ[39m [33m[CJS][39m 1 files, total: 11.09 kB
|
|
12
12
|
[34mℹ[39m Hint: consider adding [34mdeps.onlyBundle[39m option to avoid unintended bundling of dependencies, or set [34mdeps.onlyBundle: false[39m to disable this hint.
|
|
13
13
|
See more at [4mhttps://tsdown.dev/options/dependencies#deps-onlybundle[24m
|
|
14
14
|
Detected dependencies in bundle:
|
|
15
15
|
- [34m@types/aws-lambda[39m
|
|
16
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mindex.mjs[22m [2m10.31 kB[22m [2m│ gzip: 3.19 kB[22m
|
|
17
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.mjs.map [2m23.71 kB[22m [2m│ gzip: 6.74 kB[22m
|
|
18
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.d.mts.map [2m 7.93 kB[22m [2m│ gzip: 2.65 kB[22m
|
|
19
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m15.74 kB[22m [2m│ gzip: 4.28 kB[22m
|
|
20
|
-
[34mℹ[39m [34m[ESM][39m 4 files, total: 57.69 kB
|
|
21
|
-
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
22
|
-
- rolldown-plugin-dts:generate (67%)
|
|
23
|
-
- rolldown-plugin-dts:resolver (24%)
|
|
24
|
-
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
25
|
-
|
|
26
|
-
[32m✔[39m Build complete in [32m4816ms[39m
|
|
27
16
|
[34mℹ[39m Hint: consider adding [34mdeps.onlyBundle[39m option to avoid unintended bundling of dependencies, or set [34mdeps.onlyBundle: false[39m to disable this hint.
|
|
28
17
|
See more at [4mhttps://tsdown.dev/options/dependencies#deps-onlybundle[24m
|
|
29
18
|
Detected dependencies in bundle:
|
|
30
19
|
- [34m@types/aws-lambda[39m
|
|
31
|
-
[34mℹ[39m [33m[CJS][39m [2mdist/[22mindex.d.cts.map [2m 7.
|
|
32
|
-
[
|
|
33
|
-
[34mℹ[39m [33m[CJS][39m
|
|
20
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mindex.d.cts.map [2m 7.95 kB[22m [2m│ gzip: 2.65 kB[22m
|
|
21
|
+
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
22
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mindex.d.cts[22m[39m [2m15.81 kB[22m [2m│ gzip: 4.29 kB[22m
|
|
23
|
+
[34mℹ[39m [33m[CJS][39m 2 files, total: 23.76 kB
|
|
24
|
+
- rolldown-plugin-dts:resolver (63%)
|
|
25
|
+
- rolldown-plugin-dts:generate (24%)
|
|
26
|
+
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
27
|
+
|
|
28
|
+
[32m✔[39m Build complete in [32m5301ms[39m
|
|
29
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mindex.mjs[22m [2m10.34 kB[22m [2m│ gzip: 3.21 kB[22m
|
|
30
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.mjs.map [2m23.79 kB[22m [2m│ gzip: 6.76 kB[22m
|
|
31
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.d.mts.map [2m 7.95 kB[22m [2m│ gzip: 2.65 kB[22m
|
|
32
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m15.81 kB[22m [2m│ gzip: 4.29 kB[22m
|
|
33
|
+
[34mℹ[39m [34m[ESM][39m 4 files, total: 57.89 kB
|
|
34
34
|
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
35
|
-
- rolldown-plugin-dts:
|
|
36
|
-
|
|
35
|
+
- rolldown-plugin-dts:generate (57%)
|
|
36
|
+
[32m✔[39m Build complete in [32m5304ms[39m
|
|
37
|
+
- rolldown-plugin-dts:resolver (34%)
|
|
37
38
|
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
38
39
|
|
|
39
|
-
[32m✔[39m Build complete in [32m4824ms[39m
|
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -17,7 +17,7 @@ function createEventService(tableName) {
|
|
|
17
17
|
const TABLE = tableName;
|
|
18
18
|
return {
|
|
19
19
|
async appendEvent(args) {
|
|
20
|
-
const { aggregateType, aggregateId, expectedVersion, idempotencyKey, eventId, eventType, occurredAt, source, payload, schemaVersion, correlationId, causationId, actorId } = args;
|
|
20
|
+
const { tenantId, aggregateType, aggregateId, expectedVersion, idempotencyKey, eventId, eventType, occurredAt, source, payload, schemaVersion, correlationId, causationId, actorId } = args;
|
|
21
21
|
const pk = pkFor(aggregateType, aggregateId);
|
|
22
22
|
const nextVersion = expectedVersion + 1;
|
|
23
23
|
const sk = `EVT#${pad(nextVersion)}`;
|
|
@@ -52,6 +52,7 @@ function createEventService(tableName) {
|
|
|
52
52
|
aggregateId,
|
|
53
53
|
aggregateType,
|
|
54
54
|
version: nextVersion,
|
|
55
|
+
tenantId,
|
|
55
56
|
eventId,
|
|
56
57
|
eventType,
|
|
57
58
|
schemaVersion: schemaVersion ?? 1,
|
package/dist/index.d.cts
CHANGED
|
@@ -22,6 +22,8 @@ interface EventRecord<P = unknown> {
|
|
|
22
22
|
aggregateId: AggregateId;
|
|
23
23
|
aggregateType: AggregateType;
|
|
24
24
|
version: number;
|
|
25
|
+
/** Tenant isolation */
|
|
26
|
+
tenantId: string;
|
|
25
27
|
/** Event identity & semantics */
|
|
26
28
|
eventId: EventId;
|
|
27
29
|
eventType: string;
|
|
@@ -53,6 +55,7 @@ type EventHandlers = Record<string, ((event: EventRecord<any>) => Promise<void>
|
|
|
53
55
|
//#endregion
|
|
54
56
|
//#region src/event-service.d.ts
|
|
55
57
|
interface AppendArgs<P = unknown> {
|
|
58
|
+
tenantId: string;
|
|
56
59
|
aggregateType: string;
|
|
57
60
|
aggregateId: string;
|
|
58
61
|
source: string;
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":["Writable","Handler","TEvent","TResult","Context","Callback","Promise","event","context","callback","CognitoIdentity","ClientContext","Error","callbackWaitsForEmptyEventLoop","functionName","functionVersion","invokedFunctionArn","memoryLimitInMB","awsRequestId","logGroupName","logStreamName","identity","clientContext","tenantId","getRemainingTimeInMillis","done","error","result","fail","succeed","messageOrObject","message","object","cognitoIdentityId","cognitoIdentityPoolId","ClientContextClient","ClientContextEnv","client","Custom","env","installationId","appTitle","appVersionName","appVersionCode","appPackageName","platformVersion","platform","make","model","locale","StreamifyHandler","awslambda","HttpResponseStream","responseStream","_0","Record","global","from","writable","metadata","setContentType","contentType","streamifyResponse","handler","sideEffect","Handler","DynamoDBStreamHandler","DynamoDBStreamEvent","DynamoDBBatchResponse","AttributeValue","B","BS","BOOL","L","M","id","N","NS","NULL","S","SS","StreamRecord","ApproximateCreationDateTime","Keys","key","NewImage","OldImage","SequenceNumber","SizeBytes","StreamViewType","DynamoDBRecord","awsRegion","dynamodb","eventID","eventName","eventSource","eventSourceARN","eventVersion","userIdentity","Records","DynamoDBBatchItemFailure","batchItemFailures","itemIdentifier","Handler","SQSHandler","SQSEvent","SQSBatchResponse","SQSRecord","SQSRecordAttributes","SQSMessageAttributes","messageId","receiptHandle","body","attributes","messageAttributes","md5OfBody","md5OfMessageAttributes","eventSource","eventSourceARN","awsRegion","Records","AWSTraceHeader","ApproximateReceiveCount","SentTimestamp","SenderId","ApproximateFirstReceiveTimestamp","SequenceNumber","MessageGroupId","MessageDeduplicationId","DeadLetterQueueSourceArn","SQSMessageAttributeDataType","SQSMessageAttribute","stringValue","binaryValue","stringListValues","binaryListValues","dataType","name","SQSBatchItemFailure","batchItemFailures","itemIdentifier"],"sources":["../src/types.ts","../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/handler.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/dynamodb-stream.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/sqs.d.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"x_google_ignoreList":[7,8,9],"mappings":";;;;KACY,KAAA,wBAA6B,CAAA;EAAA,SAAe,OAAA,EAAS,CAAA;AAAA;AAAA,KACrD,MAAA,GAAS,KAAA;AAAA,KACT,WAAA,GAAc,KAAA;AAAA,KACd,aAAA,GAAgB,KAAA;AAAA,KAChB,OAAA,GAAU,KAAA;AAAA,KAGV,WAAA;AAAA,KACA,OAAA;AAAA,KACA,QAAA;AAAA,UAGK,WAAA;EAZuC;EActD,EAAA,EAAI,WAAA;EACJ,EAAA,EAAI,OAAA;EACJ,QAAA,EAAU,OAAA,CAAQ,QAAA;EAfR;EAkBV,MAAA,EAAQ,MAAA;EACR,WAAA,EAAa,WAAA;EACb,aAAA,EAAe,aAAA;EACf,OAAA;EApBU;EAuBV,OAAA,EAAS,OAAA;EACT,SAAA;EACA,aAAA;EACA,UAAA
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":["Writable","Handler","TEvent","TResult","Context","Callback","Promise","event","context","callback","CognitoIdentity","ClientContext","Error","callbackWaitsForEmptyEventLoop","functionName","functionVersion","invokedFunctionArn","memoryLimitInMB","awsRequestId","logGroupName","logStreamName","identity","clientContext","tenantId","getRemainingTimeInMillis","done","error","result","fail","succeed","messageOrObject","message","object","cognitoIdentityId","cognitoIdentityPoolId","ClientContextClient","ClientContextEnv","client","Custom","env","installationId","appTitle","appVersionName","appVersionCode","appPackageName","platformVersion","platform","make","model","locale","StreamifyHandler","awslambda","HttpResponseStream","responseStream","_0","Record","global","from","writable","metadata","setContentType","contentType","streamifyResponse","handler","sideEffect","Handler","DynamoDBStreamHandler","DynamoDBStreamEvent","DynamoDBBatchResponse","AttributeValue","B","BS","BOOL","L","M","id","N","NS","NULL","S","SS","StreamRecord","ApproximateCreationDateTime","Keys","key","NewImage","OldImage","SequenceNumber","SizeBytes","StreamViewType","DynamoDBRecord","awsRegion","dynamodb","eventID","eventName","eventSource","eventSourceARN","eventVersion","userIdentity","Records","DynamoDBBatchItemFailure","batchItemFailures","itemIdentifier","Handler","SQSHandler","SQSEvent","SQSBatchResponse","SQSRecord","SQSRecordAttributes","SQSMessageAttributes","messageId","receiptHandle","body","attributes","messageAttributes","md5OfBody","md5OfMessageAttributes","eventSource","eventSourceARN","awsRegion","Records","AWSTraceHeader","ApproximateReceiveCount","SentTimestamp","SenderId","ApproximateFirstReceiveTimestamp","SequenceNumber","MessageGroupId","MessageDeduplicationId","DeadLetterQueueSourceArn","SQSMessageAttributeDataType","SQSMessageAttribute","stringValue","binaryValue","stringListValues","binaryListValues","dataType","name","SQSBatchItemFailure","batchItemFailures","itemIdentifier"],"sources":["../src/types.ts","../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/handler.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/dynamodb-stream.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/sqs.d.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"x_google_ignoreList":[7,8,9],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -22,6 +22,8 @@ interface EventRecord<P = unknown> {
|
|
|
22
22
|
aggregateId: AggregateId;
|
|
23
23
|
aggregateType: AggregateType;
|
|
24
24
|
version: number;
|
|
25
|
+
/** Tenant isolation */
|
|
26
|
+
tenantId: string;
|
|
25
27
|
/** Event identity & semantics */
|
|
26
28
|
eventId: EventId;
|
|
27
29
|
eventType: string;
|
|
@@ -53,6 +55,7 @@ type EventHandlers = Record<string, ((event: EventRecord<any>) => Promise<void>
|
|
|
53
55
|
//#endregion
|
|
54
56
|
//#region src/event-service.d.ts
|
|
55
57
|
interface AppendArgs<P = unknown> {
|
|
58
|
+
tenantId: string;
|
|
56
59
|
aggregateType: string;
|
|
57
60
|
aggregateId: string;
|
|
58
61
|
source: string;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":["Writable","Handler","TEvent","TResult","Context","Callback","Promise","event","context","callback","CognitoIdentity","ClientContext","Error","callbackWaitsForEmptyEventLoop","functionName","functionVersion","invokedFunctionArn","memoryLimitInMB","awsRequestId","logGroupName","logStreamName","identity","clientContext","tenantId","getRemainingTimeInMillis","done","error","result","fail","succeed","messageOrObject","message","object","cognitoIdentityId","cognitoIdentityPoolId","ClientContextClient","ClientContextEnv","client","Custom","env","installationId","appTitle","appVersionName","appVersionCode","appPackageName","platformVersion","platform","make","model","locale","StreamifyHandler","awslambda","HttpResponseStream","responseStream","_0","Record","global","from","writable","metadata","setContentType","contentType","streamifyResponse","handler","sideEffect","Handler","DynamoDBStreamHandler","DynamoDBStreamEvent","DynamoDBBatchResponse","AttributeValue","B","BS","BOOL","L","M","id","N","NS","NULL","S","SS","StreamRecord","ApproximateCreationDateTime","Keys","key","NewImage","OldImage","SequenceNumber","SizeBytes","StreamViewType","DynamoDBRecord","awsRegion","dynamodb","eventID","eventName","eventSource","eventSourceARN","eventVersion","userIdentity","Records","DynamoDBBatchItemFailure","batchItemFailures","itemIdentifier","Handler","SQSHandler","SQSEvent","SQSBatchResponse","SQSRecord","SQSRecordAttributes","SQSMessageAttributes","messageId","receiptHandle","body","attributes","messageAttributes","md5OfBody","md5OfMessageAttributes","eventSource","eventSourceARN","awsRegion","Records","AWSTraceHeader","ApproximateReceiveCount","SentTimestamp","SenderId","ApproximateFirstReceiveTimestamp","SequenceNumber","MessageGroupId","MessageDeduplicationId","DeadLetterQueueSourceArn","SQSMessageAttributeDataType","SQSMessageAttribute","stringValue","binaryValue","stringListValues","binaryListValues","dataType","name","SQSBatchItemFailure","batchItemFailures","itemIdentifier"],"sources":["../src/types.ts","../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/handler.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/dynamodb-stream.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/sqs.d.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"x_google_ignoreList":[7,8,9],"mappings":";;;;KACY,KAAA,wBAA6B,CAAA;EAAA,SAAe,OAAA,EAAS,CAAA;AAAA;AAAA,KACrD,MAAA,GAAS,KAAA;AAAA,KACT,WAAA,GAAc,KAAA;AAAA,KACd,aAAA,GAAgB,KAAA;AAAA,KAChB,OAAA,GAAU,KAAA;AAAA,KAGV,WAAA;AAAA,KACA,OAAA;AAAA,KACA,QAAA;AAAA,UAGK,WAAA;EAZuC;EActD,EAAA,EAAI,WAAA;EACJ,EAAA,EAAI,OAAA;EACJ,QAAA,EAAU,OAAA,CAAQ,QAAA;EAfR;EAkBV,MAAA,EAAQ,MAAA;EACR,WAAA,EAAa,WAAA;EACb,aAAA,EAAe,aAAA;EACf,OAAA;EApBU;EAuBV,OAAA,EAAS,OAAA;EACT,SAAA;EACA,aAAA;EACA,UAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["Writable","Handler","TEvent","TResult","Context","Callback","Promise","event","context","callback","CognitoIdentity","ClientContext","Error","callbackWaitsForEmptyEventLoop","functionName","functionVersion","invokedFunctionArn","memoryLimitInMB","awsRequestId","logGroupName","logStreamName","identity","clientContext","tenantId","getRemainingTimeInMillis","done","error","result","fail","succeed","messageOrObject","message","object","cognitoIdentityId","cognitoIdentityPoolId","ClientContextClient","ClientContextEnv","client","Custom","env","installationId","appTitle","appVersionName","appVersionCode","appPackageName","platformVersion","platform","make","model","locale","StreamifyHandler","awslambda","HttpResponseStream","responseStream","_0","Record","global","from","writable","metadata","setContentType","contentType","streamifyResponse","handler","sideEffect","Handler","DynamoDBStreamHandler","DynamoDBStreamEvent","DynamoDBBatchResponse","AttributeValue","B","BS","BOOL","L","M","id","N","NS","NULL","S","SS","StreamRecord","ApproximateCreationDateTime","Keys","key","NewImage","OldImage","SequenceNumber","SizeBytes","StreamViewType","DynamoDBRecord","awsRegion","dynamodb","eventID","eventName","eventSource","eventSourceARN","eventVersion","userIdentity","Records","DynamoDBBatchItemFailure","batchItemFailures","itemIdentifier","Handler","SQSHandler","SQSEvent","SQSBatchResponse","SQSRecord","SQSRecordAttributes","SQSMessageAttributes","messageId","receiptHandle","body","attributes","messageAttributes","md5OfBody","md5OfMessageAttributes","eventSource","eventSourceARN","awsRegion","Records","AWSTraceHeader","ApproximateReceiveCount","SentTimestamp","SenderId","ApproximateFirstReceiveTimestamp","SequenceNumber","MessageGroupId","MessageDeduplicationId","DeadLetterQueueSourceArn","SQSMessageAttributeDataType","SQSMessageAttribute","stringValue","binaryValue","stringListValues","binaryListValues","dataType","name","SQSBatchItemFailure","batchItemFailures","itemIdentifier"],"sources":["../src/types.ts","../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/handler.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/dynamodb-stream.d.ts","../../../node_modules/.pnpm/@types+aws-lambda@8.10.152/node_modules/@types/aws-lambda/trigger/sqs.d.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"x_google_ignoreList":[7,8,9],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ function createEventService(tableName) {
|
|
|
16
16
|
const TABLE = tableName;
|
|
17
17
|
return {
|
|
18
18
|
async appendEvent(args) {
|
|
19
|
-
const { aggregateType, aggregateId, expectedVersion, idempotencyKey, eventId, eventType, occurredAt, source, payload, schemaVersion, correlationId, causationId, actorId } = args;
|
|
19
|
+
const { tenantId, aggregateType, aggregateId, expectedVersion, idempotencyKey, eventId, eventType, occurredAt, source, payload, schemaVersion, correlationId, causationId, actorId } = args;
|
|
20
20
|
const pk = pkFor(aggregateType, aggregateId);
|
|
21
21
|
const nextVersion = expectedVersion + 1;
|
|
22
22
|
const sk = `EVT#${pad(nextVersion)}`;
|
|
@@ -51,6 +51,7 @@ function createEventService(tableName) {
|
|
|
51
51
|
aggregateId,
|
|
52
52
|
aggregateType,
|
|
53
53
|
version: nextVersion,
|
|
54
|
+
tenantId,
|
|
54
55
|
eventId,
|
|
55
56
|
eventType,
|
|
56
57
|
schemaVersion: schemaVersion ?? 1,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"sourcesContent":["import { normalizePaginationResponse, PaginationResponse } from '@auriclabs/pagination';\nimport { DynamoDBClient, ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';\nimport {\n DynamoDBDocumentClient,\n TransactWriteCommand,\n GetCommand,\n QueryCommand,\n} from '@aws-sdk/lib-dynamodb';\n\nimport type {\n EventRecord,\n AggregateHead,\n AggregatePK,\n EventId,\n AggregateId,\n AggregateType,\n EventSK,\n Source,\n} from './types';\n\nconst ddb = DynamoDBDocumentClient.from(new DynamoDBClient(), {\n marshallOptions: {\n removeUndefinedValues: true,\n },\n});\n\nconst pad = (n: number, w = 9): EventId => String(n).padStart(w, '0') as EventId;\nconst pkFor = (aggregateType: string, aggregateId: string): AggregatePK =>\n `AGG#${aggregateType}#${aggregateId}`;\n\nexport interface AppendArgs<P = unknown> {\n aggregateType: string;\n aggregateId: string;\n source: string;\n /** Version you observed before appending (0 for brand new) */\n expectedVersion: number;\n /** Required for idempotent retries (e.g., the command id) */\n idempotencyKey: string;\n\n // Event properties (flattened)\n eventId: string; // ULID/UUID – must be stable across retries\n eventType: string;\n occurredAt?: string; // default: now ISO\n payload?: Readonly<P>;\n schemaVersion?: number; // optional but recommended\n\n // Optional metadata\n correlationId?: string;\n causationId?: string;\n actorId?: string;\n}\n\nexport interface AppendEventResult {\n pk: string;\n sk: string;\n version: number;\n}\n\nexport interface EventService {\n appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult>;\n getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined>;\n getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined>;\n listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>>;\n}\n\nexport function createEventService(tableName: string): EventService {\n const TABLE = tableName;\n\n return {\n async appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult> {\n const {\n aggregateType,\n aggregateId,\n expectedVersion,\n idempotencyKey,\n eventId,\n eventType,\n occurredAt,\n source,\n payload,\n schemaVersion,\n correlationId,\n causationId,\n actorId,\n } = args;\n\n const pk = pkFor(aggregateType, aggregateId);\n const nextVersion = expectedVersion + 1;\n const sk = `EVT#${pad(nextVersion)}` as EventSK;\n const nowIso = new Date().toISOString();\n const eventOccurredAt = occurredAt ?? nowIso;\n\n try {\n await ddb.send(\n new TransactWriteCommand({\n TransactItems: [\n {\n Update: {\n TableName: TABLE,\n Key: { pk, sk: 'HEAD' },\n UpdateExpression:\n 'SET currentVersion = :next, lastEventId = :eid, lastIdemKey = :idem, updatedAt = :now, aggregateId = if_not_exists(aggregateId, :aid), aggregateType = if_not_exists(aggregateType, :atype)',\n ConditionExpression:\n '(attribute_not_exists(currentVersion) AND :expected = :zero) ' +\n 'OR currentVersion = :expected ' +\n 'OR lastIdemKey = :idem',\n ExpressionAttributeValues: {\n ':zero': 0,\n ':expected': expectedVersion,\n ':next': nextVersion,\n ':eid': eventId,\n ':idem': idempotencyKey,\n ':now': nowIso,\n ':aid': aggregateId,\n ':atype': aggregateType,\n },\n },\n },\n {\n Put: {\n TableName: TABLE,\n Item: {\n pk,\n sk,\n itemType: 'event',\n source: source as Source,\n aggregateId: aggregateId as AggregateId,\n aggregateType: aggregateType as AggregateType,\n version: nextVersion,\n\n eventId: eventId as EventId,\n eventType: eventType,\n schemaVersion: schemaVersion ?? 1,\n occurredAt: eventOccurredAt,\n\n correlationId,\n causationId,\n actorId,\n\n payload: payload as Readonly<unknown>,\n } satisfies EventRecord,\n ConditionExpression: 'attribute_not_exists(pk) OR eventId = :eid',\n ExpressionAttributeValues: { ':eid': eventId },\n },\n },\n ],\n }),\n );\n } catch (err) {\n if (err instanceof ConditionalCheckFailedException) {\n throw new Error(\n `OCC failed for aggregate ${aggregateType}/${aggregateId}: expectedVersion=${expectedVersion}`,\n );\n }\n throw err;\n }\n\n return { pk, sk, version: nextVersion };\n },\n\n async getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk: 'HEAD' } }));\n return res.Item as AggregateHead | undefined;\n },\n\n async getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const sk = `EVT#${pad(version)}`;\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk } }));\n return res.Item as EventRecord | undefined;\n },\n\n async listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>> {\n const pk = pkFor(params.aggregateType, params.aggregateId);\n const fromSk =\n params.fromVersionExclusive != null\n ? `EVT#${pad(params.fromVersionExclusive + 1)}`\n : 'EVT#000000000';\n const toSk =\n params.toVersionInclusive != null\n ? `EVT#${pad(params.toVersionInclusive)}`\n : 'EVT#999999999';\n\n const res = await ddb.send(\n new QueryCommand({\n TableName: TABLE,\n KeyConditionExpression: 'pk = :pk AND sk BETWEEN :from AND :to',\n ExpressionAttributeValues: {\n ':pk': pk,\n ':from': fromSk,\n ':to': toSk,\n },\n ScanIndexForward: true,\n Limit: params.limit,\n }),\n );\n\n return normalizePaginationResponse({\n data: (res.Items ?? []) as EventRecord[],\n cursor: res.LastEvaluatedKey && (res.LastEvaluatedKey as { pk: string; sk: string }).sk,\n });\n },\n };\n}\n","import { createEventService, EventService } from './event-service';\n\nlet _eventService: EventService | undefined;\n\nexport function initEvents(config: { tableName: string }): void {\n _eventService = createEventService(config.tableName);\n}\n\nexport function getEventService(): EventService {\n if (!_eventService) {\n throw new Error('Call initEvents() before using events');\n }\n return _eventService;\n}\n","import { AppendArgs } from './event-service';\n\nexport type EventContext = Partial<AppendArgs>;\n\nlet context: EventContext = {};\n\nexport const setEventContext = (newContext: EventContext) => {\n context = { ...newContext };\n};\n\nexport const getEventContext = () => context;\n\nexport const resetEventContext = () => {\n context = {};\n};\n\nexport const appendEventContext = (event: EventContext) => {\n context = { ...context, ...event };\n};\n","import { retry } from '@auriclabs/api-core';\nimport { logger } from '@auriclabs/logger';\nimport { ulid } from 'ulid';\n\nimport { getEventContext } from './context';\nimport { AppendArgs, AppendEventResult } from './event-service';\nimport { getEventService } from './init';\n\nexport type DispatchEventArgs = Omit<\n AppendArgs,\n 'eventId' | 'expectedVersion' | 'schemaVersion' | 'occurredAt' | 'idempotencyKey'\n> &\n Partial<Pick<AppendArgs, 'idempotencyKey' | 'eventId'>>;\n\nexport const dispatchEvent = async (event: DispatchEventArgs): Promise<AppendEventResult> => {\n const eventService = getEventService();\n const eventId = event.eventId ?? `evt-${ulid()}`;\n const occurredAt = new Date().toISOString();\n const idempotencyKey = event.idempotencyKey ?? eventId;\n\n return retry(async () => {\n const head = await eventService.getHead(event.aggregateType, event.aggregateId);\n logger.debug({ event }, 'Dispatching event');\n return eventService.appendEvent({\n ...getEventContext(),\n ...event,\n eventId,\n expectedVersion: head?.currentVersion ?? 0,\n schemaVersion: 1,\n occurredAt,\n idempotencyKey,\n });\n });\n};\n","import { dispatchEvent, DispatchEventArgs } from './dispatch-event';\n\nexport interface DispatchEventsArgs {\n inOrder?: boolean;\n}\n\nexport const dispatchEvents = async (\n events: DispatchEventArgs[],\n { inOrder = false }: DispatchEventsArgs = {},\n) => {\n if (inOrder) {\n for (const event of events) {\n await dispatchEvent(event);\n }\n } else {\n await Promise.all(events.map((event) => dispatchEvent(event)));\n }\n};\n","import { ulid } from 'ulid';\n\nimport { EventContext, getEventContext } from './context';\nimport { dispatchEvent, DispatchEventArgs } from './dispatch-event';\nimport { AppendEventResult } from './event-service';\n\nexport type MakePartial<T, O> = Omit<T, keyof O> & Partial<O>;\n\nexport type DispatchRecord<\n Options extends Partial<DispatchEventArgs> = Partial<DispatchEventArgs>,\n> = Record<\n string,\n (\n ...args: any[]\n ) =>\n | ValueOrFactoryRecord<MakePartial<DispatchEventArgs, Options>>\n | DispatchEventArgsFactory<Options>\n>;\n\nexport type ValueOrFactory<T> = T | ((context: EventContext) => T);\nexport type ValueOrFactoryRecord<T> = {\n [K in keyof T]: ValueOrFactory<T[K]>;\n};\n\nexport type DispatchEventArgsFactory<Options extends Partial<DispatchEventArgs>> = (\n context: EventContext,\n) => MakePartial<DispatchEventArgs, Options>;\n\nexport type DispatchRecordResponse<\n R extends DispatchRecord<O>,\n O extends Partial<DispatchEventArgs>,\n> = {\n [K in keyof R]: (...args: Parameters<R[K]>) => Promise<AppendEventResult>;\n};\n\nexport function createDispatch<DR extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>>(\n record: DR,\n optionsOrFactory?: ValueOrFactoryRecord<O> | ((context: EventContext) => O),\n): DispatchRecordResponse<DR, O> {\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [\n key,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (...args: any[]) => {\n const eventId = `evt-${ulid()}`;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = value(...args);\n const context: EventContext = { eventId, ...getEventContext() };\n const executeValueFn = (value: any) =>\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call\n typeof value === 'function' ? value(context) : value;\n const parseResponse = (result: any) =>\n Object.fromEntries(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n Object.entries(result).map(([key, value]) => [key, executeValueFn(value)]),\n );\n Object.assign(\n context,\n typeof result === 'function' ? result(context) : parseResponse(result),\n );\n Object.assign(\n context,\n typeof optionsOrFactory === 'function'\n ? optionsOrFactory(context)\n : parseResponse(optionsOrFactory),\n );\n return dispatchEvent(context as DispatchEventArgs);\n },\n ]),\n ) as DispatchRecordResponse<DR, O>;\n}\n","import { logger } from '@auriclabs/logger';\nimport { SQSBatchResponse, SQSEvent } from 'aws-lambda';\n\nimport { setEventContext } from './context';\nimport { EventHandlers, EventRecord } from './types';\n\nexport interface CreateEventListenerOptions {\n debug?: boolean;\n}\n\nexport const createEventListener =\n (eventHandlers: EventHandlers, { debug = false }: CreateEventListenerOptions = {}) =>\n async (sqsEvent: SQSEvent) => {\n const response: SQSBatchResponse = {\n batchItemFailures: [],\n };\n let hasFailed = false;\n for (const record of sqsEvent.Records) {\n // skip the job if it has failed\n if (hasFailed) {\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n continue;\n }\n\n let event: EventRecord | undefined;\n try {\n event = JSON.parse(record.body) as EventRecord;\n if (debug) {\n logger.debug({ event }, 'Processing event');\n }\n let handler = eventHandlers[event.eventType];\n while (typeof handler === 'string') {\n handler = eventHandlers[handler];\n }\n if (typeof handler === 'function') {\n setEventContext({\n causationId: event.eventId,\n correlationId: event.correlationId,\n actorId: event.actorId,\n });\n await handler(event);\n }\n } catch (error) {\n hasFailed = true;\n logger.error({ error, event, body: record.body }, 'Error processing event');\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n }\n }\n return response;\n };\n","import { logger } from '@auriclabs/logger';\nimport { AttributeValue } from '@aws-sdk/client-dynamodb';\nimport { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';\nimport { SendMessageBatchCommand, SQSClient } from '@aws-sdk/client-sqs';\nimport { unmarshall } from '@aws-sdk/util-dynamodb';\nimport { DynamoDBStreamEvent } from 'aws-lambda';\nimport { kebabCase } from 'lodash-es';\n\nimport { AggregateHead, EventRecord } from './types';\n\nconst BATCH_SIZE = 10;\n\nexport interface CreateStreamHandlerConfig {\n busName: string;\n queueUrls: string[];\n}\n\n/**\n * Creates a Lambda handler for DynamoDB stream events.\n * Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.\n */\nexport function createStreamHandler(config: CreateStreamHandlerConfig) {\n const sqsClient = new SQSClient();\n const eventBridge = new EventBridgeClient({});\n\n function chunkArray<T>(array: T[], chunkSize: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < array.length; i += chunkSize) {\n chunks.push(array.slice(i, i + chunkSize));\n }\n return chunks;\n }\n\n async function sendToQueuesBatch(eventRecords: EventRecord[]) {\n await Promise.all(config.queueUrls.map((queue) => sendToQueueBatch(eventRecords, queue)));\n }\n\n async function sendToQueueBatch(eventRecords: EventRecord[], queue: string) {\n const batches = chunkArray(eventRecords, BATCH_SIZE);\n\n for (const batch of batches) {\n try {\n const entries = batch.map((eventRecord, index) => ({\n Id: `${eventRecord.eventId}-${index}`,\n MessageBody: JSON.stringify(eventRecord),\n MessageGroupId: eventRecord.aggregateId,\n MessageDeduplicationId: eventRecord.eventId,\n }));\n\n await sqsClient.send(\n new SendMessageBatchCommand({\n QueueUrl: queue,\n Entries: entries,\n }),\n );\n } catch (error) {\n logger.error({ error, batch, queue }, 'Error sending batch to queue');\n throw error;\n }\n }\n }\n\n async function sendToBusBatch(eventRecords: EventRecord[]) {\n const batches = chunkArray(eventRecords, BATCH_SIZE);\n\n for (const batch of batches) {\n try {\n const entries = batch.map((eventRecord) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const source = eventRecord.source ?? kebabCase(eventRecord.aggregateType.split('.')[0]);\n return {\n Source: source,\n DetailType: eventRecord.eventType,\n Detail: JSON.stringify(eventRecord),\n EventBusName: config.busName,\n };\n });\n\n await eventBridge.send(\n new PutEventsCommand({\n Entries: entries,\n }),\n );\n } catch (error) {\n logger.error({ error, batch }, 'Error sending batch to bus');\n throw error;\n }\n }\n }\n\n return async (event: DynamoDBStreamEvent): Promise<void> => {\n const eventRecords = event.Records.filter((record) => record.eventName === 'INSERT')\n .map((record) => {\n try {\n const data = record.dynamodb?.NewImage;\n return unmarshall(data as Record<string, AttributeValue>) as EventRecord | AggregateHead;\n } catch (error) {\n logger.error({ error, record }, 'Error unmarshalling event record');\n return undefined;\n }\n })\n .filter((eventRecord): eventRecord is EventRecord => eventRecord?.itemType === 'event');\n\n if (eventRecords.length > 0) {\n await Promise.all([sendToBusBatch(eventRecords), sendToQueuesBatch(eventRecords)]);\n }\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"sourcesContent":["import { normalizePaginationResponse, PaginationResponse } from '@auriclabs/pagination';\nimport { DynamoDBClient, ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';\nimport {\n DynamoDBDocumentClient,\n TransactWriteCommand,\n GetCommand,\n QueryCommand,\n} from '@aws-sdk/lib-dynamodb';\n\nimport type {\n EventRecord,\n AggregateHead,\n AggregatePK,\n EventId,\n AggregateId,\n AggregateType,\n EventSK,\n Source,\n} from './types';\n\nconst ddb = DynamoDBDocumentClient.from(new DynamoDBClient(), {\n marshallOptions: {\n removeUndefinedValues: true,\n },\n});\n\nconst pad = (n: number, w = 9): EventId => String(n).padStart(w, '0') as EventId;\nconst pkFor = (aggregateType: string, aggregateId: string): AggregatePK =>\n `AGG#${aggregateType}#${aggregateId}`;\n\nexport interface AppendArgs<P = unknown> {\n tenantId: string;\n aggregateType: string;\n aggregateId: string;\n source: string;\n /** Version you observed before appending (0 for brand new) */\n expectedVersion: number;\n /** Required for idempotent retries (e.g., the command id) */\n idempotencyKey: string;\n\n // Event properties (flattened)\n eventId: string; // ULID/UUID – must be stable across retries\n eventType: string;\n occurredAt?: string; // default: now ISO\n payload?: Readonly<P>;\n schemaVersion?: number; // optional but recommended\n\n // Optional metadata\n correlationId?: string;\n causationId?: string;\n actorId?: string;\n}\n\nexport interface AppendEventResult {\n pk: string;\n sk: string;\n version: number;\n}\n\nexport interface EventService {\n appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult>;\n getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined>;\n getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined>;\n listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>>;\n}\n\nexport function createEventService(tableName: string): EventService {\n const TABLE = tableName;\n\n return {\n async appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult> {\n const {\n tenantId,\n aggregateType,\n aggregateId,\n expectedVersion,\n idempotencyKey,\n eventId,\n eventType,\n occurredAt,\n source,\n payload,\n schemaVersion,\n correlationId,\n causationId,\n actorId,\n } = args;\n\n const pk = pkFor(aggregateType, aggregateId);\n const nextVersion = expectedVersion + 1;\n const sk = `EVT#${pad(nextVersion)}` as EventSK;\n const nowIso = new Date().toISOString();\n const eventOccurredAt = occurredAt ?? nowIso;\n\n try {\n await ddb.send(\n new TransactWriteCommand({\n TransactItems: [\n {\n Update: {\n TableName: TABLE,\n Key: { pk, sk: 'HEAD' },\n UpdateExpression:\n 'SET currentVersion = :next, lastEventId = :eid, lastIdemKey = :idem, updatedAt = :now, aggregateId = if_not_exists(aggregateId, :aid), aggregateType = if_not_exists(aggregateType, :atype)',\n ConditionExpression:\n '(attribute_not_exists(currentVersion) AND :expected = :zero) ' +\n 'OR currentVersion = :expected ' +\n 'OR lastIdemKey = :idem',\n ExpressionAttributeValues: {\n ':zero': 0,\n ':expected': expectedVersion,\n ':next': nextVersion,\n ':eid': eventId,\n ':idem': idempotencyKey,\n ':now': nowIso,\n ':aid': aggregateId,\n ':atype': aggregateType,\n },\n },\n },\n {\n Put: {\n TableName: TABLE,\n Item: {\n pk,\n sk,\n itemType: 'event',\n source: source as Source,\n aggregateId: aggregateId as AggregateId,\n aggregateType: aggregateType as AggregateType,\n version: nextVersion,\n\n tenantId,\n\n eventId: eventId as EventId,\n eventType: eventType,\n schemaVersion: schemaVersion ?? 1,\n occurredAt: eventOccurredAt,\n\n correlationId,\n causationId,\n actorId,\n\n payload: payload as Readonly<unknown>,\n } satisfies EventRecord,\n ConditionExpression: 'attribute_not_exists(pk) OR eventId = :eid',\n ExpressionAttributeValues: { ':eid': eventId },\n },\n },\n ],\n }),\n );\n } catch (err) {\n if (err instanceof ConditionalCheckFailedException) {\n throw new Error(\n `OCC failed for aggregate ${aggregateType}/${aggregateId}: expectedVersion=${expectedVersion}`,\n );\n }\n throw err;\n }\n\n return { pk, sk, version: nextVersion };\n },\n\n async getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk: 'HEAD' } }));\n return res.Item as AggregateHead | undefined;\n },\n\n async getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const sk = `EVT#${pad(version)}`;\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk } }));\n return res.Item as EventRecord | undefined;\n },\n\n async listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>> {\n const pk = pkFor(params.aggregateType, params.aggregateId);\n const fromSk =\n params.fromVersionExclusive != null\n ? `EVT#${pad(params.fromVersionExclusive + 1)}`\n : 'EVT#000000000';\n const toSk =\n params.toVersionInclusive != null\n ? `EVT#${pad(params.toVersionInclusive)}`\n : 'EVT#999999999';\n\n const res = await ddb.send(\n new QueryCommand({\n TableName: TABLE,\n KeyConditionExpression: 'pk = :pk AND sk BETWEEN :from AND :to',\n ExpressionAttributeValues: {\n ':pk': pk,\n ':from': fromSk,\n ':to': toSk,\n },\n ScanIndexForward: true,\n Limit: params.limit,\n }),\n );\n\n return normalizePaginationResponse({\n data: (res.Items ?? []) as EventRecord[],\n cursor: res.LastEvaluatedKey && (res.LastEvaluatedKey as { pk: string; sk: string }).sk,\n });\n },\n };\n}\n","import { createEventService, EventService } from './event-service';\n\nlet _eventService: EventService | undefined;\n\nexport function initEvents(config: { tableName: string }): void {\n _eventService = createEventService(config.tableName);\n}\n\nexport function getEventService(): EventService {\n if (!_eventService) {\n throw new Error('Call initEvents() before using events');\n }\n return _eventService;\n}\n","import { AppendArgs } from './event-service';\n\nexport type EventContext = Partial<AppendArgs>;\n\nlet context: EventContext = {};\n\nexport const setEventContext = (newContext: EventContext) => {\n context = { ...newContext };\n};\n\nexport const getEventContext = () => context;\n\nexport const resetEventContext = () => {\n context = {};\n};\n\nexport const appendEventContext = (event: EventContext) => {\n context = { ...context, ...event };\n};\n","import { retry } from '@auriclabs/api-core';\nimport { logger } from '@auriclabs/logger';\nimport { ulid } from 'ulid';\n\nimport { getEventContext } from './context';\nimport { AppendArgs, AppendEventResult } from './event-service';\nimport { getEventService } from './init';\n\nexport type DispatchEventArgs = Omit<\n AppendArgs,\n 'eventId' | 'expectedVersion' | 'schemaVersion' | 'occurredAt' | 'idempotencyKey'\n> &\n Partial<Pick<AppendArgs, 'idempotencyKey' | 'eventId'>>;\n\nexport const dispatchEvent = async (event: DispatchEventArgs): Promise<AppendEventResult> => {\n const eventService = getEventService();\n const eventId = event.eventId ?? `evt-${ulid()}`;\n const occurredAt = new Date().toISOString();\n const idempotencyKey = event.idempotencyKey ?? eventId;\n\n return retry(async () => {\n const head = await eventService.getHead(event.aggregateType, event.aggregateId);\n logger.debug({ event }, 'Dispatching event');\n return eventService.appendEvent({\n ...getEventContext(),\n ...event,\n eventId,\n expectedVersion: head?.currentVersion ?? 0,\n schemaVersion: 1,\n occurredAt,\n idempotencyKey,\n });\n });\n};\n","import { dispatchEvent, DispatchEventArgs } from './dispatch-event';\n\nexport interface DispatchEventsArgs {\n inOrder?: boolean;\n}\n\nexport const dispatchEvents = async (\n events: DispatchEventArgs[],\n { inOrder = false }: DispatchEventsArgs = {},\n) => {\n if (inOrder) {\n for (const event of events) {\n await dispatchEvent(event);\n }\n } else {\n await Promise.all(events.map((event) => dispatchEvent(event)));\n }\n};\n","import { ulid } from 'ulid';\n\nimport { EventContext, getEventContext } from './context';\nimport { dispatchEvent, DispatchEventArgs } from './dispatch-event';\nimport { AppendEventResult } from './event-service';\n\nexport type MakePartial<T, O> = Omit<T, keyof O> & Partial<O>;\n\nexport type DispatchRecord<\n Options extends Partial<DispatchEventArgs> = Partial<DispatchEventArgs>,\n> = Record<\n string,\n (\n ...args: any[]\n ) =>\n | ValueOrFactoryRecord<MakePartial<DispatchEventArgs, Options>>\n | DispatchEventArgsFactory<Options>\n>;\n\nexport type ValueOrFactory<T> = T | ((context: EventContext) => T);\nexport type ValueOrFactoryRecord<T> = {\n [K in keyof T]: ValueOrFactory<T[K]>;\n};\n\nexport type DispatchEventArgsFactory<Options extends Partial<DispatchEventArgs>> = (\n context: EventContext,\n) => MakePartial<DispatchEventArgs, Options>;\n\nexport type DispatchRecordResponse<\n R extends DispatchRecord<O>,\n O extends Partial<DispatchEventArgs>,\n> = {\n [K in keyof R]: (...args: Parameters<R[K]>) => Promise<AppendEventResult>;\n};\n\nexport function createDispatch<DR extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>>(\n record: DR,\n optionsOrFactory?: ValueOrFactoryRecord<O> | ((context: EventContext) => O),\n): DispatchRecordResponse<DR, O> {\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [\n key,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (...args: any[]) => {\n const eventId = `evt-${ulid()}`;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = value(...args);\n const context: EventContext = { eventId, ...getEventContext() };\n const executeValueFn = (value: any) =>\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call\n typeof value === 'function' ? value(context) : value;\n const parseResponse = (result: any) =>\n Object.fromEntries(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n Object.entries(result).map(([key, value]) => [key, executeValueFn(value)]),\n );\n Object.assign(\n context,\n typeof result === 'function' ? result(context) : parseResponse(result),\n );\n Object.assign(\n context,\n typeof optionsOrFactory === 'function'\n ? optionsOrFactory(context)\n : parseResponse(optionsOrFactory),\n );\n return dispatchEvent(context as DispatchEventArgs);\n },\n ]),\n ) as DispatchRecordResponse<DR, O>;\n}\n","import { logger } from '@auriclabs/logger';\nimport { SQSBatchResponse, SQSEvent } from 'aws-lambda';\n\nimport { setEventContext } from './context';\nimport { EventHandlers, EventRecord } from './types';\n\nexport interface CreateEventListenerOptions {\n debug?: boolean;\n}\n\nexport const createEventListener =\n (eventHandlers: EventHandlers, { debug = false }: CreateEventListenerOptions = {}) =>\n async (sqsEvent: SQSEvent) => {\n const response: SQSBatchResponse = {\n batchItemFailures: [],\n };\n let hasFailed = false;\n for (const record of sqsEvent.Records) {\n // skip the job if it has failed\n if (hasFailed) {\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n continue;\n }\n\n let event: EventRecord | undefined;\n try {\n event = JSON.parse(record.body) as EventRecord;\n if (debug) {\n logger.debug({ event }, 'Processing event');\n }\n let handler = eventHandlers[event.eventType];\n while (typeof handler === 'string') {\n handler = eventHandlers[handler];\n }\n if (typeof handler === 'function') {\n setEventContext({\n causationId: event.eventId,\n correlationId: event.correlationId,\n actorId: event.actorId,\n });\n await handler(event);\n }\n } catch (error) {\n hasFailed = true;\n logger.error({ error, event, body: record.body }, 'Error processing event');\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n }\n }\n return response;\n };\n","import { logger } from '@auriclabs/logger';\nimport { AttributeValue } from '@aws-sdk/client-dynamodb';\nimport { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';\nimport { SendMessageBatchCommand, SQSClient } from '@aws-sdk/client-sqs';\nimport { unmarshall } from '@aws-sdk/util-dynamodb';\nimport { DynamoDBStreamEvent } from 'aws-lambda';\nimport { kebabCase } from 'lodash-es';\n\nimport { AggregateHead, EventRecord } from './types';\n\nconst BATCH_SIZE = 10;\n\nexport interface CreateStreamHandlerConfig {\n busName: string;\n queueUrls: string[];\n}\n\n/**\n * Creates a Lambda handler for DynamoDB stream events.\n * Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.\n */\nexport function createStreamHandler(config: CreateStreamHandlerConfig) {\n const sqsClient = new SQSClient();\n const eventBridge = new EventBridgeClient({});\n\n function chunkArray<T>(array: T[], chunkSize: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < array.length; i += chunkSize) {\n chunks.push(array.slice(i, i + chunkSize));\n }\n return chunks;\n }\n\n async function sendToQueuesBatch(eventRecords: EventRecord[]) {\n await Promise.all(config.queueUrls.map((queue) => sendToQueueBatch(eventRecords, queue)));\n }\n\n async function sendToQueueBatch(eventRecords: EventRecord[], queue: string) {\n const batches = chunkArray(eventRecords, BATCH_SIZE);\n\n for (const batch of batches) {\n try {\n const entries = batch.map((eventRecord, index) => ({\n Id: `${eventRecord.eventId}-${index}`,\n MessageBody: JSON.stringify(eventRecord),\n MessageGroupId: eventRecord.aggregateId,\n MessageDeduplicationId: eventRecord.eventId,\n }));\n\n await sqsClient.send(\n new SendMessageBatchCommand({\n QueueUrl: queue,\n Entries: entries,\n }),\n );\n } catch (error) {\n logger.error({ error, batch, queue }, 'Error sending batch to queue');\n throw error;\n }\n }\n }\n\n async function sendToBusBatch(eventRecords: EventRecord[]) {\n const batches = chunkArray(eventRecords, BATCH_SIZE);\n\n for (const batch of batches) {\n try {\n const entries = batch.map((eventRecord) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const source = eventRecord.source ?? kebabCase(eventRecord.aggregateType.split('.')[0]);\n return {\n Source: source,\n DetailType: eventRecord.eventType,\n Detail: JSON.stringify(eventRecord),\n EventBusName: config.busName,\n };\n });\n\n await eventBridge.send(\n new PutEventsCommand({\n Entries: entries,\n }),\n );\n } catch (error) {\n logger.error({ error, batch }, 'Error sending batch to bus');\n throw error;\n }\n }\n }\n\n return async (event: DynamoDBStreamEvent): Promise<void> => {\n const eventRecords = event.Records.filter((record) => record.eventName === 'INSERT')\n .map((record) => {\n try {\n const data = record.dynamodb?.NewImage;\n return unmarshall(data as Record<string, AttributeValue>) as EventRecord | AggregateHead;\n } catch (error) {\n logger.error({ error, record }, 'Error unmarshalling event record');\n return undefined;\n }\n })\n .filter((eventRecord): eventRecord is EventRecord => eventRecord?.itemType === 'event');\n\n if (eventRecords.length > 0) {\n await Promise.all([sendToBusBatch(eventRecords), sendToQueuesBatch(eventRecords)]);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;AAoBA,MAAM,MAAM,uBAAuB,KAAK,IAAI,gBAAgB,EAAE,EAC5D,iBAAiB,EACf,uBAAuB,MACxB,EACF,CAAC;AAEF,MAAM,OAAO,GAAW,IAAI,MAAe,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACrE,MAAM,SAAS,eAAuB,gBACpC,OAAO,cAAc,GAAG;AAgD1B,SAAgB,mBAAmB,WAAiC;CAClE,MAAM,QAAQ;AAEd,QAAO;EACL,MAAM,YAAyB,MAAiD;GAC9E,MAAM,EACJ,UACA,eACA,aACA,iBACA,gBACA,SACA,WACA,YACA,QACA,SACA,eACA,eACA,aACA,YACE;GAEJ,MAAM,KAAK,MAAM,eAAe,YAAY;GAC5C,MAAM,cAAc,kBAAkB;GACtC,MAAM,KAAK,OAAO,IAAI,YAAY;GAClC,MAAM,0BAAS,IAAI,MAAM,EAAC,aAAa;GACvC,MAAM,kBAAkB,cAAc;AAEtC,OAAI;AACF,UAAM,IAAI,KACR,IAAI,qBAAqB,EACvB,eAAe,CACb,EACE,QAAQ;KACN,WAAW;KACX,KAAK;MAAE;MAAI,IAAI;MAAQ;KACvB,kBACE;KACF,qBACE;KAGF,2BAA2B;MACzB,SAAS;MACT,aAAa;MACb,SAAS;MACT,QAAQ;MACR,SAAS;MACT,QAAQ;MACR,QAAQ;MACR,UAAU;MACX;KACF,EACF,EACD,EACE,KAAK;KACH,WAAW;KACX,MAAM;MACJ;MACA;MACA,UAAU;MACF;MACK;MACE;MACf,SAAS;MAET;MAES;MACE;MACX,eAAe,iBAAiB;MAChC,YAAY;MAEZ;MACA;MACA;MAES;MACV;KACD,qBAAqB;KACrB,2BAA2B,EAAE,QAAQ,SAAS;KAC/C,EACF,CACF,EACF,CAAC,CACH;YACM,KAAK;AACZ,QAAI,eAAe,gCACjB,OAAM,IAAI,MACR,4BAA4B,cAAc,GAAG,YAAY,oBAAoB,kBAC9E;AAEH,UAAM;;AAGR,UAAO;IAAE;IAAI;IAAI,SAAS;IAAa;;EAGzC,MAAM,QAAQ,eAAuB,aAAyD;GAC5F,MAAM,KAAK,MAAM,eAAe,YAAY;AAE5C,WADY,MAAM,IAAI,KAAK,IAAI,WAAW;IAAE,WAAW;IAAO,KAAK;KAAE;KAAI,IAAI;KAAQ;IAAE,CAAC,CAAC,EAC9E;;EAGb,MAAM,SACJ,eACA,aACA,SACkC;GAClC,MAAM,KAAK,MAAM,eAAe,YAAY;GAC5C,MAAM,KAAK,OAAO,IAAI,QAAQ;AAE9B,WADY,MAAM,IAAI,KAAK,IAAI,WAAW;IAAE,WAAW;IAAO,KAAK;KAAE;KAAI;KAAI;IAAE,CAAC,CAAC,EACtE;;EAGb,MAAM,WAAW,QAM4B;GAC3C,MAAM,KAAK,MAAM,OAAO,eAAe,OAAO,YAAY;GAC1D,MAAM,SACJ,OAAO,wBAAwB,OAC3B,OAAO,IAAI,OAAO,uBAAuB,EAAE,KAC3C;GACN,MAAM,OACJ,OAAO,sBAAsB,OACzB,OAAO,IAAI,OAAO,mBAAmB,KACrC;GAEN,MAAM,MAAM,MAAM,IAAI,KACpB,IAAI,aAAa;IACf,WAAW;IACX,wBAAwB;IACxB,2BAA2B;KACzB,OAAO;KACP,SAAS;KACT,OAAO;KACR;IACD,kBAAkB;IAClB,OAAO,OAAO;IACf,CAAC,CACH;AAED,UAAO,4BAA4B;IACjC,MAAO,IAAI,SAAS,EAAE;IACtB,QAAQ,IAAI,oBAAqB,IAAI,iBAAgD;IACtF,CAAC;;EAEL;;;;ACjOH,IAAI;AAEJ,SAAgB,WAAW,QAAqC;AAC9D,iBAAgB,mBAAmB,OAAO,UAAU;;AAGtD,SAAgB,kBAAgC;AAC9C,KAAI,CAAC,cACH,OAAM,IAAI,MAAM,wCAAwC;AAE1D,QAAO;;;;ACRT,IAAI,UAAwB,EAAE;AAE9B,MAAa,mBAAmB,eAA6B;AAC3D,WAAU,EAAE,GAAG,YAAY;;AAG7B,MAAa,wBAAwB;AAErC,MAAa,0BAA0B;AACrC,WAAU,EAAE;;AAGd,MAAa,sBAAsB,UAAwB;AACzD,WAAU;EAAE,GAAG;EAAS,GAAG;EAAO;;;;ACHpC,MAAa,gBAAgB,OAAO,UAAyD;CAC3F,MAAM,eAAe,iBAAiB;CACtC,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM;CAC9C,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;CAC3C,MAAM,iBAAiB,MAAM,kBAAkB;AAE/C,QAAO,MAAM,YAAY;EACvB,MAAM,OAAO,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM,YAAY;AAC/E,SAAO,MAAM,EAAE,OAAO,EAAE,oBAAoB;AAC5C,SAAO,aAAa,YAAY;GAC9B,GAAG,iBAAiB;GACpB,GAAG;GACH;GACA,iBAAiB,MAAM,kBAAkB;GACzC,eAAe;GACf;GACA;GACD,CAAC;GACF;;;;AC1BJ,MAAa,iBAAiB,OAC5B,QACA,EAAE,UAAU,UAA8B,EAAE,KACzC;AACH,KAAI,QACF,MAAK,MAAM,SAAS,OAClB,OAAM,cAAc,MAAM;KAG5B,OAAM,QAAQ,IAAI,OAAO,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;;;;ACoBlE,SAAgB,eACd,QACA,kBAC+B;AAC/B,QAAO,OAAO,YACZ,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,KAAK,WAAW,CAC3C,MAEC,GAAG,SAAgB;EAClB,MAAM,UAAU,OAAO,MAAM;EAE7B,MAAM,SAAS,MAAM,GAAG,KAAK;EAC7B,MAAM,UAAwB;GAAE;GAAS,GAAG,iBAAiB;GAAE;EAC/D,MAAM,kBAAkB,UAEtB,OAAO,UAAU,aAAa,MAAM,QAAQ,GAAG;EACjD,MAAM,iBAAiB,WACrB,OAAO,YAEL,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,eAAe,MAAM,CAAC,CAAC,CAC3E;AACH,SAAO,OACL,SACA,OAAO,WAAW,aAAa,OAAO,QAAQ,GAAG,cAAc,OAAO,CACvE;AACD,SAAO,OACL,SACA,OAAO,qBAAqB,aACxB,iBAAiB,QAAQ,GACzB,cAAc,iBAAiB,CACpC;AACD,SAAO,cAAc,QAA6B;GAErD,CAAC,CACH;;;;AC3DH,MAAa,uBACV,eAA8B,EAAE,QAAQ,UAAsC,EAAE,KACjF,OAAO,aAAuB;CAC5B,MAAM,WAA6B,EACjC,mBAAmB,EAAE,EACtB;CACD,IAAI,YAAY;AAChB,MAAK,MAAM,UAAU,SAAS,SAAS;AAErC,MAAI,WAAW;AACb,YAAS,kBAAkB,KAAK,EAC9B,gBAAgB,OAAO,WACxB,CAAC;AACF;;EAGF,IAAI;AACJ,MAAI;AACF,WAAQ,KAAK,MAAM,OAAO,KAAK;AAC/B,OAAI,MACF,QAAO,MAAM,EAAE,OAAO,EAAE,mBAAmB;GAE7C,IAAI,UAAU,cAAc,MAAM;AAClC,UAAO,OAAO,YAAY,SACxB,WAAU,cAAc;AAE1B,OAAI,OAAO,YAAY,YAAY;AACjC,oBAAgB;KACd,aAAa,MAAM;KACnB,eAAe,MAAM;KACrB,SAAS,MAAM;KAChB,CAAC;AACF,UAAM,QAAQ,MAAM;;WAEf,OAAO;AACd,eAAY;AACZ,UAAO,MAAM;IAAE;IAAO;IAAO,MAAM,OAAO;IAAM,EAAE,yBAAyB;AAC3E,YAAS,kBAAkB,KAAK,EAC9B,gBAAgB,OAAO,WACxB,CAAC;;;AAGN,QAAO;;;;AC1CX,MAAM,aAAa;;;;;AAWnB,SAAgB,oBAAoB,QAAmC;CACrE,MAAM,YAAY,IAAI,WAAW;CACjC,MAAM,cAAc,IAAI,kBAAkB,EAAE,CAAC;CAE7C,SAAS,WAAc,OAAY,WAA0B;EAC3D,MAAM,SAAgB,EAAE;AACxB,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,UACrC,QAAO,KAAK,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC;AAE5C,SAAO;;CAGT,eAAe,kBAAkB,cAA6B;AAC5D,QAAM,QAAQ,IAAI,OAAO,UAAU,KAAK,UAAU,iBAAiB,cAAc,MAAM,CAAC,CAAC;;CAG3F,eAAe,iBAAiB,cAA6B,OAAe;EAC1E,MAAM,UAAU,WAAW,cAAc,WAAW;AAEpD,OAAK,MAAM,SAAS,QAClB,KAAI;GACF,MAAM,UAAU,MAAM,KAAK,aAAa,WAAW;IACjD,IAAI,GAAG,YAAY,QAAQ,GAAG;IAC9B,aAAa,KAAK,UAAU,YAAY;IACxC,gBAAgB,YAAY;IAC5B,wBAAwB,YAAY;IACrC,EAAE;AAEH,SAAM,UAAU,KACd,IAAI,wBAAwB;IAC1B,UAAU;IACV,SAAS;IACV,CAAC,CACH;WACM,OAAO;AACd,UAAO,MAAM;IAAE;IAAO;IAAO;IAAO,EAAE,+BAA+B;AACrE,SAAM;;;CAKZ,eAAe,eAAe,cAA6B;EACzD,MAAM,UAAU,WAAW,cAAc,WAAW;AAEpD,OAAK,MAAM,SAAS,QAClB,KAAI;GACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;AAGzC,WAAO;KACL,QAFa,YAAY,UAAU,UAAU,YAAY,cAAc,MAAM,IAAI,CAAC,GAAG;KAGrF,YAAY,YAAY;KACxB,QAAQ,KAAK,UAAU,YAAY;KACnC,cAAc,OAAO;KACtB;KACD;AAEF,SAAM,YAAY,KAChB,IAAI,iBAAiB,EACnB,SAAS,SACV,CAAC,CACH;WACM,OAAO;AACd,UAAO,MAAM;IAAE;IAAO;IAAO,EAAE,6BAA6B;AAC5D,SAAM;;;AAKZ,QAAO,OAAO,UAA8C;EAC1D,MAAM,eAAe,MAAM,QAAQ,QAAQ,WAAW,OAAO,cAAc,SAAS,CACjF,KAAK,WAAW;AACf,OAAI;IACF,MAAM,OAAO,OAAO,UAAU;AAC9B,WAAO,WAAW,KAAuC;YAClD,OAAO;AACd,WAAO,MAAM;KAAE;KAAO;KAAQ,EAAE,mCAAmC;AACnE;;IAEF,CACD,QAAQ,gBAA4C,aAAa,aAAa,QAAQ;AAEzF,MAAI,aAAa,SAAS,EACxB,OAAM,QAAQ,IAAI,CAAC,eAAe,aAAa,EAAE,kBAAkB,aAAa,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -14,13 +14,13 @@ import { logger } from '@auriclabs/logger';
|
|
|
14
14
|
|
|
15
15
|
import { createEventListener } from './create-event-listener';
|
|
16
16
|
|
|
17
|
-
import type { SQSEvent } from 'aws-lambda';
|
|
17
|
+
import type { SQSEvent, SQSRecordAttributes } from 'aws-lambda';
|
|
18
18
|
|
|
19
19
|
const makeRecord = (body: object, messageId = 'msg-1') => ({
|
|
20
20
|
messageId,
|
|
21
21
|
body: JSON.stringify(body),
|
|
22
22
|
receiptHandle: 'handle',
|
|
23
|
-
attributes: {} as
|
|
23
|
+
attributes: {} as SQSRecordAttributes,
|
|
24
24
|
messageAttributes: {},
|
|
25
25
|
md5OfBody: '',
|
|
26
26
|
eventSource: 'aws:sqs',
|
|
@@ -31,6 +31,7 @@ const makeRecord = (body: object, messageId = 'msg-1') => ({
|
|
|
31
31
|
const makeEvent = (overrides = {}) => ({
|
|
32
32
|
eventType: 'OrderCreated',
|
|
33
33
|
eventId: 'evt-1',
|
|
34
|
+
tenantId: 'tenant-1',
|
|
34
35
|
aggregateType: 'order',
|
|
35
36
|
aggregateId: 'o-1',
|
|
36
37
|
correlationId: 'corr-1',
|
|
@@ -186,7 +187,7 @@ describe('createEventListener', () => {
|
|
|
186
187
|
await handler(sqsEvent);
|
|
187
188
|
|
|
188
189
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
189
|
-
{ event: expect.objectContaining({ eventType: 'OrderCreated' }) },
|
|
190
|
+
{ event: expect.objectContaining({ eventType: 'OrderCreated' }) as unknown },
|
|
190
191
|
'Processing event',
|
|
191
192
|
);
|
|
192
193
|
});
|
|
@@ -219,7 +220,7 @@ describe('createEventListener', () => {
|
|
|
219
220
|
await handler(sqsEvent);
|
|
220
221
|
|
|
221
222
|
expect(logger.error).toHaveBeenCalledWith(
|
|
222
|
-
expect.objectContaining({ error, event: expect.any(Object) }),
|
|
223
|
+
expect.objectContaining({ error, event: expect.any(Object) as unknown }),
|
|
223
224
|
'Error processing event',
|
|
224
225
|
);
|
|
225
226
|
});
|
|
@@ -50,6 +50,7 @@ describe('dispatchEvent', () => {
|
|
|
50
50
|
aggregateId: 'o-1',
|
|
51
51
|
source: 'test',
|
|
52
52
|
eventType: 'OrderCreated',
|
|
53
|
+
tenantId: 'tenant-1',
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -65,6 +66,7 @@ describe('dispatchEvent', () => {
|
|
|
65
66
|
aggregateId: 'o-1',
|
|
66
67
|
source: 'test',
|
|
67
68
|
eventType: 'OrderCreated',
|
|
69
|
+
tenantId: 'tenant-1',
|
|
68
70
|
eventId: 'custom-event-id',
|
|
69
71
|
});
|
|
70
72
|
|
|
@@ -81,6 +83,7 @@ describe('dispatchEvent', () => {
|
|
|
81
83
|
aggregateId: 'o-1',
|
|
82
84
|
source: 'test',
|
|
83
85
|
eventType: 'OrderCreated',
|
|
86
|
+
tenantId: 'tenant-1',
|
|
84
87
|
idempotencyKey: 'my-idem-key',
|
|
85
88
|
});
|
|
86
89
|
|
|
@@ -97,6 +100,7 @@ describe('dispatchEvent', () => {
|
|
|
97
100
|
aggregateId: 'o-1',
|
|
98
101
|
source: 'test',
|
|
99
102
|
eventType: 'OrderCreated',
|
|
103
|
+
tenantId: 'tenant-1',
|
|
100
104
|
});
|
|
101
105
|
|
|
102
106
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -114,6 +118,7 @@ describe('dispatchEvent', () => {
|
|
|
114
118
|
aggregateId: 'o-1',
|
|
115
119
|
source: 'test',
|
|
116
120
|
eventType: 'OrderUpdated',
|
|
121
|
+
tenantId: 'tenant-1',
|
|
117
122
|
});
|
|
118
123
|
|
|
119
124
|
expect(mockGetHead).toHaveBeenCalledWith('order', 'o-1');
|
|
@@ -132,6 +137,7 @@ describe('dispatchEvent', () => {
|
|
|
132
137
|
aggregateId: 'o-1',
|
|
133
138
|
source: 'test',
|
|
134
139
|
eventType: 'OrderCreated',
|
|
140
|
+
tenantId: 'tenant-1',
|
|
135
141
|
});
|
|
136
142
|
|
|
137
143
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -152,6 +158,7 @@ describe('dispatchEvent', () => {
|
|
|
152
158
|
aggregateId: 'o-1',
|
|
153
159
|
source: 'test',
|
|
154
160
|
eventType: 'OrderCreated',
|
|
161
|
+
tenantId: 'tenant-1',
|
|
155
162
|
});
|
|
156
163
|
|
|
157
164
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -173,6 +180,7 @@ describe('dispatchEvent', () => {
|
|
|
173
180
|
aggregateId: 'o-1',
|
|
174
181
|
source: 'test',
|
|
175
182
|
eventType: 'OrderCreated',
|
|
183
|
+
tenantId: 'tenant-1',
|
|
176
184
|
correlationId: 'event-corr',
|
|
177
185
|
});
|
|
178
186
|
|
|
@@ -190,6 +198,7 @@ describe('dispatchEvent', () => {
|
|
|
190
198
|
aggregateId: 'o-1',
|
|
191
199
|
source: 'test',
|
|
192
200
|
eventType: 'OrderCreated',
|
|
201
|
+
tenantId: 'tenant-1',
|
|
193
202
|
});
|
|
194
203
|
|
|
195
204
|
expect(retry).toHaveBeenCalledTimes(1);
|
|
@@ -202,6 +211,7 @@ describe('dispatchEvent', () => {
|
|
|
202
211
|
aggregateId: 'o-1',
|
|
203
212
|
source: 'test',
|
|
204
213
|
eventType: 'OrderCreated',
|
|
214
|
+
tenantId: 'tenant-1',
|
|
205
215
|
});
|
|
206
216
|
|
|
207
217
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -220,6 +230,7 @@ describe('dispatchEvent', () => {
|
|
|
220
230
|
aggregateId: 'o-1',
|
|
221
231
|
source: 'test',
|
|
222
232
|
eventType: 'OrderCreated',
|
|
233
|
+
tenantId: 'tenant-1',
|
|
223
234
|
});
|
|
224
235
|
|
|
225
236
|
expect(result).toEqual(expected);
|
|
@@ -30,10 +30,10 @@ describe('dispatchEvents', () => {
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it('dispatches events sequentially with inOrder: true', async () => {
|
|
33
|
-
const callOrder:
|
|
34
|
-
mockDispatchEvent.mockImplementation(
|
|
33
|
+
const callOrder: string[] = [];
|
|
34
|
+
mockDispatchEvent.mockImplementation((event: { aggregateId: string }) => {
|
|
35
35
|
callOrder.push(event.aggregateId);
|
|
36
|
-
return { pk: 'pk', sk: 'sk', version: 1 };
|
|
36
|
+
return Promise.resolve({ pk: 'pk', sk: 'sk', version: 1 });
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
const events = [
|
|
@@ -27,6 +27,41 @@ import { TransactWriteCommand, GetCommand, QueryCommand } from '@aws-sdk/lib-dyn
|
|
|
27
27
|
|
|
28
28
|
import { createEventService } from './event-service';
|
|
29
29
|
|
|
30
|
+
interface TransactItem {
|
|
31
|
+
TableName?: string;
|
|
32
|
+
Key?: Record<string, string>;
|
|
33
|
+
Item?: {
|
|
34
|
+
pk?: string;
|
|
35
|
+
sk?: string;
|
|
36
|
+
itemType?: string;
|
|
37
|
+
eventType?: string;
|
|
38
|
+
payload?: unknown;
|
|
39
|
+
version?: number;
|
|
40
|
+
source?: string;
|
|
41
|
+
schemaVersion?: number;
|
|
42
|
+
occurredAt?: string;
|
|
43
|
+
correlationId?: string;
|
|
44
|
+
causationId?: string;
|
|
45
|
+
actorId?: string;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
};
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface TransactWriteInput {
|
|
52
|
+
TransactItems?: {
|
|
53
|
+
Update?: TransactItem;
|
|
54
|
+
Put?: TransactItem;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface QueryInput {
|
|
60
|
+
ExpressionAttributeValues?: Record<string, unknown>;
|
|
61
|
+
Limit?: number;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
|
|
30
65
|
describe('event-service', () => {
|
|
31
66
|
const TABLE_NAME = 'test-events';
|
|
32
67
|
|
|
@@ -53,6 +88,7 @@ describe('event-service', () => {
|
|
|
53
88
|
mockSend.mockResolvedValue({});
|
|
54
89
|
|
|
55
90
|
await service.appendEvent({
|
|
91
|
+
tenantId: 'tenant-1',
|
|
56
92
|
aggregateType: 'order',
|
|
57
93
|
aggregateId: 'order-123',
|
|
58
94
|
source: 'order-service',
|
|
@@ -64,24 +100,24 @@ describe('event-service', () => {
|
|
|
64
100
|
});
|
|
65
101
|
|
|
66
102
|
expect(TransactWriteCommand).toHaveBeenCalledTimes(1);
|
|
67
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
103
|
+
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0] as TransactWriteInput;
|
|
68
104
|
|
|
69
105
|
// Check Update item (HEAD)
|
|
70
|
-
const updateItem = cmdInput.TransactItems
|
|
71
|
-
expect(updateItem
|
|
72
|
-
expect(updateItem
|
|
106
|
+
const updateItem = cmdInput.TransactItems?.[0]?.Update;
|
|
107
|
+
expect(updateItem?.TableName).toBe(TABLE_NAME);
|
|
108
|
+
expect(updateItem?.Key).toEqual({ pk: 'AGG#order#order-123', sk: 'HEAD' });
|
|
73
109
|
|
|
74
110
|
// Check Put item (event)
|
|
75
|
-
const putItem = cmdInput.TransactItems
|
|
76
|
-
expect(putItem
|
|
77
|
-
expect(putItem
|
|
78
|
-
expect(putItem
|
|
79
|
-
expect(putItem
|
|
80
|
-
expect(putItem
|
|
81
|
-
expect(putItem
|
|
82
|
-
expect(putItem
|
|
83
|
-
expect(putItem
|
|
84
|
-
expect(putItem
|
|
111
|
+
const putItem = cmdInput.TransactItems?.[1]?.Put;
|
|
112
|
+
expect(putItem?.TableName).toBe(TABLE_NAME);
|
|
113
|
+
expect(putItem?.Item?.pk).toBe('AGG#order#order-123');
|
|
114
|
+
expect(putItem?.Item?.sk).toBe('EVT#000000001');
|
|
115
|
+
expect(putItem?.Item?.itemType).toBe('event');
|
|
116
|
+
expect(putItem?.Item?.eventType).toBe('OrderCreated');
|
|
117
|
+
expect(putItem?.Item?.payload).toEqual({ total: 100 });
|
|
118
|
+
expect(putItem?.Item?.version).toBe(1);
|
|
119
|
+
expect(putItem?.Item?.source).toBe('order-service');
|
|
120
|
+
expect(putItem?.Item?.schemaVersion).toBe(1);
|
|
85
121
|
});
|
|
86
122
|
|
|
87
123
|
it('pads version number to 9 digits', async () => {
|
|
@@ -89,6 +125,7 @@ describe('event-service', () => {
|
|
|
89
125
|
mockSend.mockResolvedValue({});
|
|
90
126
|
|
|
91
127
|
await service.appendEvent({
|
|
128
|
+
tenantId: 'tenant-1',
|
|
92
129
|
aggregateType: 'order',
|
|
93
130
|
aggregateId: '1',
|
|
94
131
|
source: 'test',
|
|
@@ -98,10 +135,10 @@ describe('event-service', () => {
|
|
|
98
135
|
eventType: 'T',
|
|
99
136
|
});
|
|
100
137
|
|
|
101
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
102
|
-
const putItem = cmdInput.TransactItems
|
|
103
|
-
expect(putItem
|
|
104
|
-
expect(putItem
|
|
138
|
+
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0] as TransactWriteInput;
|
|
139
|
+
const putItem = cmdInput.TransactItems?.[1]?.Put;
|
|
140
|
+
expect(putItem?.Item?.sk).toBe('EVT#000000042');
|
|
141
|
+
expect(putItem?.Item?.version).toBe(42);
|
|
105
142
|
});
|
|
106
143
|
|
|
107
144
|
it('returns pk, sk, and version', async () => {
|
|
@@ -109,6 +146,7 @@ describe('event-service', () => {
|
|
|
109
146
|
mockSend.mockResolvedValue({});
|
|
110
147
|
|
|
111
148
|
const result = await service.appendEvent({
|
|
149
|
+
tenantId: 'tenant-1',
|
|
112
150
|
aggregateType: 'wallet',
|
|
113
151
|
aggregateId: 'w-1',
|
|
114
152
|
source: 'billing',
|
|
@@ -130,6 +168,7 @@ describe('event-service', () => {
|
|
|
130
168
|
mockSend.mockResolvedValue({});
|
|
131
169
|
|
|
132
170
|
await service.appendEvent({
|
|
171
|
+
tenantId: 'tenant-1',
|
|
133
172
|
aggregateType: 'order',
|
|
134
173
|
aggregateId: '1',
|
|
135
174
|
source: 'test',
|
|
@@ -140,9 +179,9 @@ describe('event-service', () => {
|
|
|
140
179
|
occurredAt: '2025-01-01T00:00:00.000Z',
|
|
141
180
|
});
|
|
142
181
|
|
|
143
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
144
|
-
const putItem = cmdInput.TransactItems
|
|
145
|
-
expect(putItem
|
|
182
|
+
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0] as TransactWriteInput;
|
|
183
|
+
const putItem = cmdInput.TransactItems?.[1]?.Put;
|
|
184
|
+
expect(putItem?.Item?.occurredAt).toBe('2025-01-01T00:00:00.000Z');
|
|
146
185
|
});
|
|
147
186
|
|
|
148
187
|
it('includes optional metadata fields', async () => {
|
|
@@ -150,6 +189,7 @@ describe('event-service', () => {
|
|
|
150
189
|
mockSend.mockResolvedValue({});
|
|
151
190
|
|
|
152
191
|
await service.appendEvent({
|
|
192
|
+
tenantId: 'tenant-1',
|
|
153
193
|
aggregateType: 'order',
|
|
154
194
|
aggregateId: '1',
|
|
155
195
|
source: 'test',
|
|
@@ -163,12 +203,12 @@ describe('event-service', () => {
|
|
|
163
203
|
schemaVersion: 2,
|
|
164
204
|
});
|
|
165
205
|
|
|
166
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
167
|
-
const putItem = cmdInput.TransactItems
|
|
168
|
-
expect(putItem
|
|
169
|
-
expect(putItem
|
|
170
|
-
expect(putItem
|
|
171
|
-
expect(putItem
|
|
206
|
+
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0] as TransactWriteInput;
|
|
207
|
+
const putItem = cmdInput.TransactItems?.[1]?.Put;
|
|
208
|
+
expect(putItem?.Item?.correlationId).toBe('corr-1');
|
|
209
|
+
expect(putItem?.Item?.causationId).toBe('cause-1');
|
|
210
|
+
expect(putItem?.Item?.actorId).toBe('user-1');
|
|
211
|
+
expect(putItem?.Item?.schemaVersion).toBe(2);
|
|
172
212
|
});
|
|
173
213
|
|
|
174
214
|
it('throws OCC error on ConditionalCheckFailedException', async () => {
|
|
@@ -178,6 +218,7 @@ describe('event-service', () => {
|
|
|
178
218
|
|
|
179
219
|
await expect(
|
|
180
220
|
service.appendEvent({
|
|
221
|
+
tenantId: 'tenant-1',
|
|
181
222
|
aggregateType: 'order',
|
|
182
223
|
aggregateId: 'o-1',
|
|
183
224
|
source: 'test',
|
|
@@ -195,6 +236,7 @@ describe('event-service', () => {
|
|
|
195
236
|
|
|
196
237
|
await expect(
|
|
197
238
|
service.appendEvent({
|
|
239
|
+
tenantId: 'tenant-1',
|
|
198
240
|
aggregateType: 'order',
|
|
199
241
|
aggregateId: 'o-1',
|
|
200
242
|
source: 'test',
|
|
@@ -285,9 +327,9 @@ describe('event-service', () => {
|
|
|
285
327
|
fromVersionExclusive: 5,
|
|
286
328
|
});
|
|
287
329
|
|
|
288
|
-
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0];
|
|
330
|
+
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0] as QueryInput;
|
|
289
331
|
// fromVersionExclusive=5 means start from version 6
|
|
290
|
-
expect(cmdInput.ExpressionAttributeValues
|
|
332
|
+
expect(cmdInput.ExpressionAttributeValues?.[':from']).toBe('EVT#000000006');
|
|
291
333
|
});
|
|
292
334
|
|
|
293
335
|
it('respects toVersionInclusive', async () => {
|
|
@@ -300,8 +342,8 @@ describe('event-service', () => {
|
|
|
300
342
|
toVersionInclusive: 10,
|
|
301
343
|
});
|
|
302
344
|
|
|
303
|
-
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0];
|
|
304
|
-
expect(cmdInput.ExpressionAttributeValues
|
|
345
|
+
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0] as QueryInput;
|
|
346
|
+
expect(cmdInput.ExpressionAttributeValues?.[':to']).toBe('EVT#000000010');
|
|
305
347
|
});
|
|
306
348
|
|
|
307
349
|
it('respects limit parameter', async () => {
|
|
@@ -314,7 +356,7 @@ describe('event-service', () => {
|
|
|
314
356
|
limit: 25,
|
|
315
357
|
});
|
|
316
358
|
|
|
317
|
-
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0];
|
|
359
|
+
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0] as QueryInput;
|
|
318
360
|
expect(cmdInput.Limit).toBe(25);
|
|
319
361
|
});
|
|
320
362
|
|
package/src/event-service.ts
CHANGED
|
@@ -29,6 +29,7 @@ const pkFor = (aggregateType: string, aggregateId: string): AggregatePK =>
|
|
|
29
29
|
`AGG#${aggregateType}#${aggregateId}`;
|
|
30
30
|
|
|
31
31
|
export interface AppendArgs<P = unknown> {
|
|
32
|
+
tenantId: string;
|
|
32
33
|
aggregateType: string;
|
|
33
34
|
aggregateId: string;
|
|
34
35
|
source: string;
|
|
@@ -79,6 +80,7 @@ export function createEventService(tableName: string): EventService {
|
|
|
79
80
|
return {
|
|
80
81
|
async appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult> {
|
|
81
82
|
const {
|
|
83
|
+
tenantId,
|
|
82
84
|
aggregateType,
|
|
83
85
|
aggregateId,
|
|
84
86
|
expectedVersion,
|
|
@@ -138,6 +140,8 @@ export function createEventService(tableName: string): EventService {
|
|
|
138
140
|
aggregateType: aggregateType as AggregateType,
|
|
139
141
|
version: nextVersion,
|
|
140
142
|
|
|
143
|
+
tenantId,
|
|
144
|
+
|
|
141
145
|
eventId: eventId as EventId,
|
|
142
146
|
eventType: eventType,
|
|
143
147
|
schemaVersion: schemaVersion ?? 1,
|
package/src/init.test.ts
CHANGED
|
@@ -19,6 +19,8 @@ describe('init', () => {
|
|
|
19
19
|
// Re-import to get fresh module state
|
|
20
20
|
// Since the module state persists, we need to test the throw case first
|
|
21
21
|
// Actually with vi.mock the module is already imported. We need to use dynamic import.
|
|
22
|
+
// See 'init (fresh module)' describe block below for the actual test
|
|
23
|
+
expect(true).toBe(true);
|
|
22
24
|
});
|
|
23
25
|
});
|
|
24
26
|
|
|
@@ -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,7 +306,7 @@ 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');
|
|
@@ -295,7 +320,7 @@ describe('stream-handler', () => {
|
|
|
295
320
|
|
|
296
321
|
const handler = createStreamHandler(config);
|
|
297
322
|
const event: DynamoDBStreamEvent = {
|
|
298
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
323
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
299
324
|
};
|
|
300
325
|
|
|
301
326
|
await expect(handler(event)).rejects.toThrow();
|
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;
|