@auriclabs/events 0.1.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 +17 -17
- package/CHANGELOG.md +22 -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/eslint.config.js +3 -0
- package/package.json +7 -7
- package/src/create-event-listener.test.ts +8 -5
- package/src/dispatch-event.test.ts +13 -1
- package/src/dispatch-event.ts +1 -1
- package/src/dispatch-events.test.ts +4 -6
- package/src/event-service.test.ts +76 -33
- package/src/event-service.ts +5 -4
- package/src/init.test.ts +2 -0
- package/src/stream-handler.test.ts +63 -43
- package/src/types.ts +3 -0
- package/tsconfig.test.json +9 -0
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,8 +7,8 @@
|
|
|
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:
|
|
@@ -17,23 +17,23 @@ Detected dependencies in bundle:
|
|
|
17
17
|
See more at [4mhttps://tsdown.dev/options/dependencies#deps-onlybundle[24m
|
|
18
18
|
Detected dependencies in bundle:
|
|
19
19
|
- [34m@types/aws-lambda[39m
|
|
20
|
-
[34mℹ[39m [
|
|
21
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.mjs.map [2m23.73 kB[22m [2m│ gzip: 6.75 kB[22m
|
|
22
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.d.mts.map [2m 7.93 kB[22m [2m│ gzip: 2.65 kB[22m
|
|
23
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m15.74 kB[22m [2m│ gzip: 4.28 kB[22m
|
|
24
|
-
[34mℹ[39m [34m[ESM][39m 4 files, total: 57.71 kB
|
|
20
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mindex.d.cts.map [2m 7.95 kB[22m [2m│ gzip: 2.65 kB[22m
|
|
25
21
|
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
26
|
-
|
|
27
|
-
|
|
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%)
|
|
28
26
|
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
29
27
|
|
|
30
|
-
[32m✔[39m Build complete in [
|
|
31
|
-
[34mℹ[39m [
|
|
32
|
-
[34mℹ[39m [
|
|
33
|
-
[34mℹ[39m [
|
|
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 [32m5275ms[39m
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @auriclabs/events
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9614859: Add tenantId to EventRecord and a tenantIndex GSI on the event store for tenant-scoped
|
|
8
|
+
queries.
|
|
9
|
+
|
|
10
|
+
## 0.2.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- eeb513b: Add @auriclabs/events package with event dispatching, event listeners, stream handler,
|
|
15
|
+
and event service for DynamoDB-backed event sourcing.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [eeb513b]
|
|
20
|
+
- @auriclabs/api-core@0.1.1
|
|
21
|
+
- @auriclabs/logger@0.1.1
|
|
22
|
+
- @auriclabs/pagination@1.0.1
|
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":";;;;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,QAAA;;EAGA,OAAA,EAAS,OAAA;EACT,SAAA;EACA,aAAA;EACA,UAAA;;EAGA,aAAA;EACA,WAAA;EACA,OAAA;EAhCiB;EAmCjB,OAAA,EAAS,QAAA,CAAS,CAAA;AAAA;AAAA,UAIH,aAAA;EApCL;EAsCV,EAAA,EAAI,WAAA;EACJ,EAAA;EACA,QAAA,EAAU,OAAA,CAAQ,QAAA;EAxCG;EA2CrB,WAAA,EAAa,WAAA;EACb,aAAA,EAAe,aAAA;;EAGf,cAAA;EA9CiB;EAiDjB,WAAA,GAAc,OAAA;EACd,WAAA;EACA,SAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,MAAA,WAGxB,KAAA,EAAO,WAAA,UAAqB,OAAA;;;UCpCf,UAAA;EACf,QAAA;EACA,aAAA;EACA,WAAA;EACA,MAAA;EDjCgE;ECmChE,eAAA;EDnCmB;ECqCnB,cAAA;EAGA,OAAA;EACA,SAAA;EACA,UAAA;EACA,OAAA,GAAU,QAAA,CAAS,CAAA;EACnB,aAAA;EAGA,aAAA;EACA,WAAA;EACA,OAAA;AAAA;AAAA,UAGe,iBAAA;EACf,EAAA;EACA,EAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,WAAA,cAAyB,IAAA,EAAM,UAAA,CAAW,CAAA,IAAK,OAAA,CAAQ,iBAAA;EACvD,OAAA,CAAQ,aAAA,UAAuB,WAAA,WAAsB,OAAA,CAAQ,aAAA;EAC7D,QAAA,CACE,aAAA,UACA,WAAA,UACA,OAAA,WACC,OAAA,CAAQ,WAAA;EACX,UAAA,CAAW,MAAA;IACT,aAAA;IACA,WAAA;IACA,oBAAA;IACA,kBAAA;IACA,KAAA;EAAA,IACE,OAAA,CAAQ,kBAAA,CAAmB,WAAA;AAAA;AAAA,iBAGjB,kBAAA,CAAmB,SAAA,WAAoB,YAAA;;;iBCxEvC,UAAA,CAAW,MAAA;EAAU,SAAA;AAAA;AAAA,iBAIrB,eAAA,CAAA,GAAmB,YAAA;;;KCNvB,YAAA,GAAe,OAAA,CAAQ,UAAA;AAAA,cAItB,eAAA,GAAmB,UAAA,EAAY,YAAA;AAAA,cAI/B,eAAA,QAAe,OAAA,CAAA,UAAA;AAAA,cAEf,iBAAA;AAAA,cAIA,kBAAA,GAAsB,KAAA,EAAO,YAAA;;;KCR9B,iBAAA,GAAoB,IAAA,CAC9B,UAAA,uFAGA,OAAA,CAAQ,IAAA,CAAK,UAAA;AAAA,cAEF,aAAA,GAAuB,KAAA,EAAO,iBAAA,KAAoB,OAAA,CAAQ,iBAAA;;;UCZtD,kBAAA;EACf,OAAA;AAAA;AAAA,cAGW,cAAA,GACX,MAAA,EAAQ,iBAAA;EACR;AAAA,IAAqB,kBAAA,KAAuB,OAAA;;;KCFlC,WAAA,SAAoB,IAAA,CAAK,CAAA,QAAS,CAAA,IAAK,OAAA,CAAQ,CAAA;AAAA,KAE/C,cAAA,iBACM,OAAA,CAAQ,iBAAA,IAAqB,OAAA,CAAQ,iBAAA,KACnD,MAAA,aAGG,IAAA,YAED,oBAAA,CAAqB,WAAA,CAAY,iBAAA,EAAmB,OAAA,KACpD,wBAAA,CAAyB,OAAA;AAAA,KAGnB,cAAA,MAAoB,CAAA,KAAM,OAAA,EAAS,YAAA,KAAiB,CAAA;AAAA,KACpD,oBAAA,oBACE,CAAA,GAAI,cAAA,CAAe,CAAA,CAAE,CAAA;AAAA,KAGvB,wBAAA,iBAAyC,OAAA,CAAQ,iBAAA,MAC3D,OAAA,EAAS,YAAA,KACN,WAAA,CAAY,iBAAA,EAAmB,OAAA;AAAA,KAExB,sBAAA,WACA,cAAA,CAAe,CAAA,aACf,OAAA,CAAQ,iBAAA,mBAEN,CAAA,OAAQ,IAAA,EAAM,UAAA,CAAW,CAAA,CAAE,CAAA,OAAQ,OAAA,CAAQ,iBAAA;AAAA,iBAGzC,cAAA,YAA0B,cAAA,CAAe,CAAA,aAAc,OAAA,CAAQ,iBAAA,EAAA,CAC7E,MAAA,EAAQ,EAAA,EACR,gBAAA,GAAmB,oBAAA,CAAqB,CAAA,MAAO,OAAA,EAAS,YAAA,KAAiB,CAAA,IACxE,sBAAA,CAAuB,EAAA,EAAI,CAAA;;;;;;ANM9B;UOmDiBI,OAAAA;EACbS,8BAAAA;EACAC,YAAAA;EACAC,eAAAA;EACAC,kBAAAA;EACAC,eAAAA;EACAC,YAAAA;EACAC,YAAAA;EACAC,aAAAA;EACAC,QAAAA,GAAWX,eAAAA;EACXY,aAAAA,GAAgBX,aAAAA;EAChBY,QAAAA;EAEAC,wBAAAA;EAAAA;EAAAA;EPxDa;EO+DbC,IAAAA,CAAKC,KAAAA,GAAQd,KAAAA,EAAOe,MAAAA;EPzDtB;EO2DEC,IAAAA,CAAKF,KAAAA,EAAOd,KAAAA;EP1Dd;EO4DEiB,OAAAA,CAAQC,eAAAA;EAAAA;EPxDA;EO4DRD,OAAAA,CAAQE,OAAAA,UAAiBC,MAAAA;AAAAA;AAAAA,UAGZtB,eAAAA;EACbuB,iBAAAA;EACAC,qBAAAA;AAAAA;AAAAA,UAGavB,aAAAA;EACb0B,MAAAA,EAAQF,mBAAAA;EACRG,MAAAA;EACAC,GAAAA,EAAKH,gBAAAA;AAAAA;AAAAA,UAGQD,mBAAAA;EACbK,cAAAA;EACAC,QAAAA;EACAC,cAAAA;EACAC,cAAAA;EACAC,cAAAA;AAAAA;AAAAA,UAGaR,gBAAAA;EACbS,eAAAA;EACAC,QAAAA;EACAC,IAAAA;EACAC,KAAAA;EACAC,MAAAA;AAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6DQC,gBAAAA,iCACR3C,KAAAA,EAAOL,MAAAA,EACPmD,cAAAA,EAAgBF,SAAAA,CAAUC,kBAAAA,EAC1B5C,OAAAA,EAASJ,OAAAA,KACRD,OAAAA,GAAUG,OAAAA,CAAQH,OAAAA;AAAAA,QAEfqD,MAAAA;EAAAA,UACML,SAAAA;IAAAA,MACAC,kBAAAA,SAA2BpD,QAAAA;MAAAA,OACtByD,IAAAA,CACHC,QAAAA,EAAU1D,QAAAA,EACV2D,QAAAA,EAAUJ,MAAAA,oBACXH,kBAAAA;MACHQ,cAAAA,GAAiBC,WAAAA;IAAAA;IL5NH;;;AAI1B;;;;;;;;ACNA;;;;;AAIA;;;;;AAIA;;;;;AAEA;;;;;AAIA;;;;;IDZ0B,SKmQTC,iBAAAA,8BAAAA,CACLC,OAAAA,EAASb,gBAAAA,CAAiBhD,MAAAA,EAAQC,OAAAA,IACnC+C,gBAAAA,CAAiBhD,MAAAA,EAAQC,OAAAA;EAAAA;AAAAA;;;;UCnQnBkE,cAAAA;EACbC,CAAAA;EACAC,EAAAA;EACAC,IAAAA;EACAC,CAAAA,GAAIJ,cAAAA;EACJK,CAAAA;IAAAA,CAAOC,EAAAA,WAAaN,cAAAA;EAAAA;EACpBO,CAAAA;EACAC,EAAAA;EACAC,IAAAA;EACAC,CAAAA;EACAC,EAAAA;AAAAA;AAAAA;AAAAA,UAIaC,YAAAA;EACbC,2BAAAA;EACAC,IAAAA;IAAAA,CAAUC,GAAAA,WAAcf,cAAAA;EAAAA;EACxBgB,QAAAA;IAAAA,CAAcD,GAAAA,WAAcf,cAAAA;EAAAA;EAC5BiB,QAAAA;IAAAA,CAAcF,GAAAA,WAAcf,cAAAA;EAAAA;EAC5BkB,cAAAA;EACAC,SAAAA;EACAC,cAAAA;AAAAA;AAAAA;AAAAA,UAIaC,cAAAA;EACbC,SAAAA;EACAC,QAAAA,GAAWX,YAAAA;EACXY,OAAAA;EACAC,SAAAA;EACAC,WAAAA;EACAC,cAAAA;EACAC,YAAAA;EACAC,YAAAA;AAAAA;AAAAA;AAAAA,UAIa/B,mBAAAA;EACbgC,OAAAA,EAAST,cAAAA;AAAAA;;;;;UCrCIiB,SAAAA;EACbG,SAAAA;EACAC,aAAAA;EACAC,IAAAA;EACAC,UAAAA,EAAYL,mBAAAA;EACZM,iBAAAA,EAAmBL,oBAAAA;EACnBM,SAAAA;EACAC,sBAAAA;EACAC,WAAAA;EACAC,cAAAA;EACAC,SAAAA;AAAAA;AAAAA,UAGad,QAAAA;EACbe,OAAAA,EAASb,SAAAA;AAAAA;AAAAA,UAGIC,mBAAAA;EACba,cAAAA;EACAC,uBAAAA;EACAC,aAAAA;EACAC,QAAAA;EACAC,gCAAAA;EACAC,cAAAA;EACAC,cAAAA;EACAC,sBAAAA;EACAC,wBAAAA;AAAAA;AAAAA,KAGQC,2BAAAA;AAAAA,UAEKC,mBAAAA;EACbC,WAAAA;EACAC,WAAAA;EACAC,gBAAAA;EACAC,gBAAAA;EACAC,QAAAA,EAAUN,2BAAAA;AAAAA;AAAAA,UAGGrB,oBAAAA;EAAAA,CACZ4B,IAAAA,WAAeN,mBAAAA;AAAAA;AAAAA;AAAAA,UAIHzB,gBAAAA;EACbiC,iBAAAA,EAAmBD,mBAAAA;AAAAA;AAAAA,UAGNA,mBAAAA;EACbE,cAAAA;AAAAA;;;UClDa,0BAAA;EACf,KAAA;AAAA;AAAA,cAGW,mBAAA,GACV,aAAA,EAAe,aAAA;EAAe;AAAA,IAAmB,0BAAA,MAC3C,QAAA,EAAU,QAAA,KAAQ,OAAA,CAAA,gBAAA;;;UCAV,yBAAA;EACf,OAAA;EACA,SAAA;AAAA;;;;;iBAOc,mBAAA,CAAoB,MAAA,EAAQ,yBAAA,IAqE5B,KAAA,EAAO,mBAAA,KAAsB,OAAA"}
|
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(\n aggregateType: string,\n aggregateId: string,\n ): 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 { AppendArgs, AppendEventResult } from './event-service';\nimport { getEventService } from './init';\nimport { getEventContext } from './context';\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/eslint.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auriclabs/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Event sourcing runtime utilities for DynamoDB-backed event stores",
|
|
5
5
|
"prettier": "@auriclabs/prettier-config",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/aws-lambda": "^8.10.148",
|
|
26
26
|
"@types/lodash-es": "4.17.12",
|
|
27
|
-
"@auriclabs/api-core": "0.1.
|
|
28
|
-
"@auriclabs/logger": "0.1.
|
|
29
|
-
"@auriclabs/pagination": "1.0.
|
|
27
|
+
"@auriclabs/api-core": "0.1.1",
|
|
28
|
+
"@auriclabs/logger": "0.1.1",
|
|
29
|
+
"@auriclabs/pagination": "1.0.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@auriclabs/api-core": "^0.1.
|
|
33
|
-
"@auriclabs/logger": "^0.1.
|
|
34
|
-
"@auriclabs/pagination": "^0.1
|
|
32
|
+
"@auriclabs/api-core": "^0.1.1",
|
|
33
|
+
"@auriclabs/logger": "^0.1.1",
|
|
34
|
+
"@auriclabs/pagination": "^1.0.1",
|
|
35
35
|
"@aws-sdk/client-dynamodb": "^3.0.0",
|
|
36
36
|
"@aws-sdk/lib-dynamodb": "^3.0.0",
|
|
37
37
|
"@aws-sdk/client-eventbridge": "^3.0.0",
|
|
@@ -10,15 +10,17 @@ vi.mock('./context', () => ({
|
|
|
10
10
|
setEventContext: mockSetEventContext,
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
-
import { createEventListener } from './create-event-listener';
|
|
14
13
|
import { logger } from '@auriclabs/logger';
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
import { createEventListener } from './create-event-listener';
|
|
16
|
+
|
|
17
|
+
import type { SQSEvent, SQSRecordAttributes } from 'aws-lambda';
|
|
16
18
|
|
|
17
19
|
const makeRecord = (body: object, messageId = 'msg-1') => ({
|
|
18
20
|
messageId,
|
|
19
21
|
body: JSON.stringify(body),
|
|
20
22
|
receiptHandle: 'handle',
|
|
21
|
-
attributes: {} as
|
|
23
|
+
attributes: {} as SQSRecordAttributes,
|
|
22
24
|
messageAttributes: {},
|
|
23
25
|
md5OfBody: '',
|
|
24
26
|
eventSource: 'aws:sqs',
|
|
@@ -29,6 +31,7 @@ const makeRecord = (body: object, messageId = 'msg-1') => ({
|
|
|
29
31
|
const makeEvent = (overrides = {}) => ({
|
|
30
32
|
eventType: 'OrderCreated',
|
|
31
33
|
eventId: 'evt-1',
|
|
34
|
+
tenantId: 'tenant-1',
|
|
32
35
|
aggregateType: 'order',
|
|
33
36
|
aggregateId: 'o-1',
|
|
34
37
|
correlationId: 'corr-1',
|
|
@@ -184,7 +187,7 @@ describe('createEventListener', () => {
|
|
|
184
187
|
await handler(sqsEvent);
|
|
185
188
|
|
|
186
189
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
187
|
-
{ event: expect.objectContaining({ eventType: 'OrderCreated' }) },
|
|
190
|
+
{ event: expect.objectContaining({ eventType: 'OrderCreated' }) as unknown },
|
|
188
191
|
'Processing event',
|
|
189
192
|
);
|
|
190
193
|
});
|
|
@@ -217,7 +220,7 @@ describe('createEventListener', () => {
|
|
|
217
220
|
await handler(sqsEvent);
|
|
218
221
|
|
|
219
222
|
expect(logger.error).toHaveBeenCalledWith(
|
|
220
|
-
expect.objectContaining({ error, event: expect.any(Object) }),
|
|
223
|
+
expect.objectContaining({ error, event: expect.any(Object) as unknown }),
|
|
221
224
|
'Error processing event',
|
|
222
225
|
);
|
|
223
226
|
});
|
|
@@ -27,9 +27,10 @@ vi.mock('ulid', () => ({
|
|
|
27
27
|
ulid: mockUlid,
|
|
28
28
|
}));
|
|
29
29
|
|
|
30
|
-
import { dispatchEvent } from './dispatch-event';
|
|
31
30
|
import { retry } from '@auriclabs/api-core';
|
|
32
31
|
|
|
32
|
+
import { dispatchEvent } from './dispatch-event';
|
|
33
|
+
|
|
33
34
|
describe('dispatchEvent', () => {
|
|
34
35
|
beforeEach(() => {
|
|
35
36
|
vi.clearAllMocks();
|
|
@@ -49,6 +50,7 @@ describe('dispatchEvent', () => {
|
|
|
49
50
|
aggregateId: 'o-1',
|
|
50
51
|
source: 'test',
|
|
51
52
|
eventType: 'OrderCreated',
|
|
53
|
+
tenantId: 'tenant-1',
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -64,6 +66,7 @@ describe('dispatchEvent', () => {
|
|
|
64
66
|
aggregateId: 'o-1',
|
|
65
67
|
source: 'test',
|
|
66
68
|
eventType: 'OrderCreated',
|
|
69
|
+
tenantId: 'tenant-1',
|
|
67
70
|
eventId: 'custom-event-id',
|
|
68
71
|
});
|
|
69
72
|
|
|
@@ -80,6 +83,7 @@ describe('dispatchEvent', () => {
|
|
|
80
83
|
aggregateId: 'o-1',
|
|
81
84
|
source: 'test',
|
|
82
85
|
eventType: 'OrderCreated',
|
|
86
|
+
tenantId: 'tenant-1',
|
|
83
87
|
idempotencyKey: 'my-idem-key',
|
|
84
88
|
});
|
|
85
89
|
|
|
@@ -96,6 +100,7 @@ describe('dispatchEvent', () => {
|
|
|
96
100
|
aggregateId: 'o-1',
|
|
97
101
|
source: 'test',
|
|
98
102
|
eventType: 'OrderCreated',
|
|
103
|
+
tenantId: 'tenant-1',
|
|
99
104
|
});
|
|
100
105
|
|
|
101
106
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -113,6 +118,7 @@ describe('dispatchEvent', () => {
|
|
|
113
118
|
aggregateId: 'o-1',
|
|
114
119
|
source: 'test',
|
|
115
120
|
eventType: 'OrderUpdated',
|
|
121
|
+
tenantId: 'tenant-1',
|
|
116
122
|
});
|
|
117
123
|
|
|
118
124
|
expect(mockGetHead).toHaveBeenCalledWith('order', 'o-1');
|
|
@@ -131,6 +137,7 @@ describe('dispatchEvent', () => {
|
|
|
131
137
|
aggregateId: 'o-1',
|
|
132
138
|
source: 'test',
|
|
133
139
|
eventType: 'OrderCreated',
|
|
140
|
+
tenantId: 'tenant-1',
|
|
134
141
|
});
|
|
135
142
|
|
|
136
143
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -151,6 +158,7 @@ describe('dispatchEvent', () => {
|
|
|
151
158
|
aggregateId: 'o-1',
|
|
152
159
|
source: 'test',
|
|
153
160
|
eventType: 'OrderCreated',
|
|
161
|
+
tenantId: 'tenant-1',
|
|
154
162
|
});
|
|
155
163
|
|
|
156
164
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -172,6 +180,7 @@ describe('dispatchEvent', () => {
|
|
|
172
180
|
aggregateId: 'o-1',
|
|
173
181
|
source: 'test',
|
|
174
182
|
eventType: 'OrderCreated',
|
|
183
|
+
tenantId: 'tenant-1',
|
|
175
184
|
correlationId: 'event-corr',
|
|
176
185
|
});
|
|
177
186
|
|
|
@@ -189,6 +198,7 @@ describe('dispatchEvent', () => {
|
|
|
189
198
|
aggregateId: 'o-1',
|
|
190
199
|
source: 'test',
|
|
191
200
|
eventType: 'OrderCreated',
|
|
201
|
+
tenantId: 'tenant-1',
|
|
192
202
|
});
|
|
193
203
|
|
|
194
204
|
expect(retry).toHaveBeenCalledTimes(1);
|
|
@@ -201,6 +211,7 @@ describe('dispatchEvent', () => {
|
|
|
201
211
|
aggregateId: 'o-1',
|
|
202
212
|
source: 'test',
|
|
203
213
|
eventType: 'OrderCreated',
|
|
214
|
+
tenantId: 'tenant-1',
|
|
204
215
|
});
|
|
205
216
|
|
|
206
217
|
expect(mockAppendEvent).toHaveBeenCalledWith(
|
|
@@ -219,6 +230,7 @@ describe('dispatchEvent', () => {
|
|
|
219
230
|
aggregateId: 'o-1',
|
|
220
231
|
source: 'test',
|
|
221
232
|
eventType: 'OrderCreated',
|
|
233
|
+
tenantId: 'tenant-1',
|
|
222
234
|
});
|
|
223
235
|
|
|
224
236
|
expect(result).toEqual(expected);
|
package/src/dispatch-event.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { retry } from '@auriclabs/api-core';
|
|
|
2
2
|
import { logger } from '@auriclabs/logger';
|
|
3
3
|
import { ulid } from 'ulid';
|
|
4
4
|
|
|
5
|
+
import { getEventContext } from './context';
|
|
5
6
|
import { AppendArgs, AppendEventResult } from './event-service';
|
|
6
7
|
import { getEventService } from './init';
|
|
7
|
-
import { getEventContext } from './context';
|
|
8
8
|
|
|
9
9
|
export type DispatchEventArgs = Omit<
|
|
10
10
|
AppendArgs,
|
|
@@ -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 = [
|
|
@@ -61,9 +61,7 @@ describe('dispatchEvents', () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('defaults inOrder to false when options not provided', async () => {
|
|
64
|
-
const events = [
|
|
65
|
-
{ aggregateType: 'order', aggregateId: '1', source: 'test', eventType: 'A' },
|
|
66
|
-
];
|
|
64
|
+
const events = [{ aggregateType: 'order', aggregateId: '1', source: 'test', eventType: 'A' }];
|
|
67
65
|
|
|
68
66
|
await dispatchEvents(events);
|
|
69
67
|
|
|
@@ -23,9 +23,45 @@ vi.mock('@auriclabs/pagination', () => ({
|
|
|
23
23
|
normalizePaginationResponse: vi.fn((input: unknown) => input),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
-
import { createEventService } from './event-service';
|
|
27
26
|
import { TransactWriteCommand, GetCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';
|
|
28
27
|
|
|
28
|
+
import { createEventService } from './event-service';
|
|
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
|
+
|
|
29
65
|
describe('event-service', () => {
|
|
30
66
|
const TABLE_NAME = 'test-events';
|
|
31
67
|
|
|
@@ -52,6 +88,7 @@ describe('event-service', () => {
|
|
|
52
88
|
mockSend.mockResolvedValue({});
|
|
53
89
|
|
|
54
90
|
await service.appendEvent({
|
|
91
|
+
tenantId: 'tenant-1',
|
|
55
92
|
aggregateType: 'order',
|
|
56
93
|
aggregateId: 'order-123',
|
|
57
94
|
source: 'order-service',
|
|
@@ -63,24 +100,24 @@ describe('event-service', () => {
|
|
|
63
100
|
});
|
|
64
101
|
|
|
65
102
|
expect(TransactWriteCommand).toHaveBeenCalledTimes(1);
|
|
66
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
103
|
+
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0] as TransactWriteInput;
|
|
67
104
|
|
|
68
105
|
// Check Update item (HEAD)
|
|
69
|
-
const updateItem = cmdInput.TransactItems
|
|
70
|
-
expect(updateItem
|
|
71
|
-
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' });
|
|
72
109
|
|
|
73
110
|
// Check Put item (event)
|
|
74
|
-
const putItem = cmdInput.TransactItems
|
|
75
|
-
expect(putItem
|
|
76
|
-
expect(putItem
|
|
77
|
-
expect(putItem
|
|
78
|
-
expect(putItem
|
|
79
|
-
expect(putItem
|
|
80
|
-
expect(putItem
|
|
81
|
-
expect(putItem
|
|
82
|
-
expect(putItem
|
|
83
|
-
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);
|
|
84
121
|
});
|
|
85
122
|
|
|
86
123
|
it('pads version number to 9 digits', async () => {
|
|
@@ -88,6 +125,7 @@ describe('event-service', () => {
|
|
|
88
125
|
mockSend.mockResolvedValue({});
|
|
89
126
|
|
|
90
127
|
await service.appendEvent({
|
|
128
|
+
tenantId: 'tenant-1',
|
|
91
129
|
aggregateType: 'order',
|
|
92
130
|
aggregateId: '1',
|
|
93
131
|
source: 'test',
|
|
@@ -97,10 +135,10 @@ describe('event-service', () => {
|
|
|
97
135
|
eventType: 'T',
|
|
98
136
|
});
|
|
99
137
|
|
|
100
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
101
|
-
const putItem = cmdInput.TransactItems
|
|
102
|
-
expect(putItem
|
|
103
|
-
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);
|
|
104
142
|
});
|
|
105
143
|
|
|
106
144
|
it('returns pk, sk, and version', async () => {
|
|
@@ -108,6 +146,7 @@ describe('event-service', () => {
|
|
|
108
146
|
mockSend.mockResolvedValue({});
|
|
109
147
|
|
|
110
148
|
const result = await service.appendEvent({
|
|
149
|
+
tenantId: 'tenant-1',
|
|
111
150
|
aggregateType: 'wallet',
|
|
112
151
|
aggregateId: 'w-1',
|
|
113
152
|
source: 'billing',
|
|
@@ -129,6 +168,7 @@ describe('event-service', () => {
|
|
|
129
168
|
mockSend.mockResolvedValue({});
|
|
130
169
|
|
|
131
170
|
await service.appendEvent({
|
|
171
|
+
tenantId: 'tenant-1',
|
|
132
172
|
aggregateType: 'order',
|
|
133
173
|
aggregateId: '1',
|
|
134
174
|
source: 'test',
|
|
@@ -139,9 +179,9 @@ describe('event-service', () => {
|
|
|
139
179
|
occurredAt: '2025-01-01T00:00:00.000Z',
|
|
140
180
|
});
|
|
141
181
|
|
|
142
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
143
|
-
const putItem = cmdInput.TransactItems
|
|
144
|
-
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');
|
|
145
185
|
});
|
|
146
186
|
|
|
147
187
|
it('includes optional metadata fields', async () => {
|
|
@@ -149,6 +189,7 @@ describe('event-service', () => {
|
|
|
149
189
|
mockSend.mockResolvedValue({});
|
|
150
190
|
|
|
151
191
|
await service.appendEvent({
|
|
192
|
+
tenantId: 'tenant-1',
|
|
152
193
|
aggregateType: 'order',
|
|
153
194
|
aggregateId: '1',
|
|
154
195
|
source: 'test',
|
|
@@ -162,12 +203,12 @@ describe('event-service', () => {
|
|
|
162
203
|
schemaVersion: 2,
|
|
163
204
|
});
|
|
164
205
|
|
|
165
|
-
const cmdInput = vi.mocked(TransactWriteCommand).mock.calls[0][0];
|
|
166
|
-
const putItem = cmdInput.TransactItems
|
|
167
|
-
expect(putItem
|
|
168
|
-
expect(putItem
|
|
169
|
-
expect(putItem
|
|
170
|
-
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);
|
|
171
212
|
});
|
|
172
213
|
|
|
173
214
|
it('throws OCC error on ConditionalCheckFailedException', async () => {
|
|
@@ -177,6 +218,7 @@ describe('event-service', () => {
|
|
|
177
218
|
|
|
178
219
|
await expect(
|
|
179
220
|
service.appendEvent({
|
|
221
|
+
tenantId: 'tenant-1',
|
|
180
222
|
aggregateType: 'order',
|
|
181
223
|
aggregateId: 'o-1',
|
|
182
224
|
source: 'test',
|
|
@@ -194,6 +236,7 @@ describe('event-service', () => {
|
|
|
194
236
|
|
|
195
237
|
await expect(
|
|
196
238
|
service.appendEvent({
|
|
239
|
+
tenantId: 'tenant-1',
|
|
197
240
|
aggregateType: 'order',
|
|
198
241
|
aggregateId: 'o-1',
|
|
199
242
|
source: 'test',
|
|
@@ -284,9 +327,9 @@ describe('event-service', () => {
|
|
|
284
327
|
fromVersionExclusive: 5,
|
|
285
328
|
});
|
|
286
329
|
|
|
287
|
-
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0];
|
|
330
|
+
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0] as QueryInput;
|
|
288
331
|
// fromVersionExclusive=5 means start from version 6
|
|
289
|
-
expect(cmdInput.ExpressionAttributeValues
|
|
332
|
+
expect(cmdInput.ExpressionAttributeValues?.[':from']).toBe('EVT#000000006');
|
|
290
333
|
});
|
|
291
334
|
|
|
292
335
|
it('respects toVersionInclusive', async () => {
|
|
@@ -299,8 +342,8 @@ describe('event-service', () => {
|
|
|
299
342
|
toVersionInclusive: 10,
|
|
300
343
|
});
|
|
301
344
|
|
|
302
|
-
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0];
|
|
303
|
-
expect(cmdInput.ExpressionAttributeValues
|
|
345
|
+
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0] as QueryInput;
|
|
346
|
+
expect(cmdInput.ExpressionAttributeValues?.[':to']).toBe('EVT#000000010');
|
|
304
347
|
});
|
|
305
348
|
|
|
306
349
|
it('respects limit parameter', async () => {
|
|
@@ -313,7 +356,7 @@ describe('event-service', () => {
|
|
|
313
356
|
limit: 25,
|
|
314
357
|
});
|
|
315
358
|
|
|
316
|
-
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0];
|
|
359
|
+
const cmdInput = vi.mocked(QueryCommand).mock.calls[0][0] as QueryInput;
|
|
317
360
|
expect(cmdInput.Limit).toBe(25);
|
|
318
361
|
});
|
|
319
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,
|
|
@@ -168,10 +172,7 @@ export function createEventService(tableName: string): EventService {
|
|
|
168
172
|
return { pk, sk, version: nextVersion };
|
|
169
173
|
},
|
|
170
174
|
|
|
171
|
-
async getHead(
|
|
172
|
-
aggregateType: string,
|
|
173
|
-
aggregateId: string,
|
|
174
|
-
): Promise<AggregateHead | undefined> {
|
|
175
|
+
async getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined> {
|
|
175
176
|
const pk = pkFor(aggregateType, aggregateId);
|
|
176
177
|
const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk: 'HEAD' } }));
|
|
177
178
|
return res.Item as AggregateHead | undefined;
|
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
|
|
|
@@ -26,11 +26,13 @@ vi.mock('lodash-es', () => ({
|
|
|
26
26
|
kebabCase: vi.fn((s: string) => s.toLowerCase().replace(/[.\s]+/g, '-')),
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
|
-
import { createStreamHandler } from './stream-handler';
|
|
30
|
-
import { SendMessageBatchCommand } from '@aws-sdk/client-sqs';
|
|
31
|
-
import { PutEventsCommand } from '@aws-sdk/client-eventbridge';
|
|
32
29
|
import { logger } from '@auriclabs/logger';
|
|
33
|
-
import
|
|
30
|
+
import { PutEventsCommand } from '@aws-sdk/client-eventbridge';
|
|
31
|
+
import { SendMessageBatchCommand } from '@aws-sdk/client-sqs';
|
|
32
|
+
|
|
33
|
+
import { createStreamHandler } from './stream-handler';
|
|
34
|
+
|
|
35
|
+
import type { DynamoDBRecord, DynamoDBStreamEvent } from 'aws-lambda';
|
|
34
36
|
|
|
35
37
|
const makeEventRecord = (overrides = {}) => ({
|
|
36
38
|
pk: 'AGG#order#o-1',
|
|
@@ -40,6 +42,7 @@ const makeEventRecord = (overrides = {}) => ({
|
|
|
40
42
|
aggregateId: 'o-1',
|
|
41
43
|
aggregateType: 'order',
|
|
42
44
|
version: 1,
|
|
45
|
+
tenantId: 'tenant-1',
|
|
43
46
|
eventId: 'evt-1',
|
|
44
47
|
eventType: 'OrderCreated',
|
|
45
48
|
schemaVersion: 1,
|
|
@@ -51,11 +54,15 @@ const makeEventRecord = (overrides = {}) => ({
|
|
|
51
54
|
const makeStreamRecord = (
|
|
52
55
|
eventName: string,
|
|
53
56
|
newImage: object | undefined = {},
|
|
54
|
-
) => ({
|
|
57
|
+
): DynamoDBRecord => ({
|
|
55
58
|
eventID: '1',
|
|
56
59
|
eventVersion: '1.1',
|
|
57
60
|
dynamodb: {
|
|
58
|
-
NewImage: newImage
|
|
61
|
+
NewImage: newImage as DynamoDBRecord['dynamodb'] extends infer D
|
|
62
|
+
? D extends { NewImage?: infer N }
|
|
63
|
+
? N
|
|
64
|
+
: never
|
|
65
|
+
: never,
|
|
59
66
|
StreamViewType: 'NEW_IMAGE',
|
|
60
67
|
},
|
|
61
68
|
awsRegion: 'us-east-1',
|
|
@@ -64,6 +71,25 @@ const makeStreamRecord = (
|
|
|
64
71
|
eventSource: 'aws:dynamodb',
|
|
65
72
|
});
|
|
66
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
|
+
|
|
67
93
|
describe('stream-handler', () => {
|
|
68
94
|
const config = {
|
|
69
95
|
busName: 'test-bus',
|
|
@@ -91,7 +117,7 @@ describe('stream-handler', () => {
|
|
|
91
117
|
makeStreamRecord('INSERT', { data: { S: 'x' } }),
|
|
92
118
|
makeStreamRecord('MODIFY', { data: { S: 'y' } }),
|
|
93
119
|
makeStreamRecord('REMOVE', undefined),
|
|
94
|
-
]
|
|
120
|
+
],
|
|
95
121
|
};
|
|
96
122
|
|
|
97
123
|
await handler(event);
|
|
@@ -104,25 +130,23 @@ describe('stream-handler', () => {
|
|
|
104
130
|
const headRecord = { pk: 'AGG#order#o-1', sk: 'HEAD', itemType: 'head' };
|
|
105
131
|
const eventRecord = makeEventRecord();
|
|
106
132
|
|
|
107
|
-
mockUnmarshall
|
|
108
|
-
.mockReturnValueOnce(headRecord)
|
|
109
|
-
.mockReturnValueOnce(eventRecord);
|
|
133
|
+
mockUnmarshall.mockReturnValueOnce(headRecord).mockReturnValueOnce(eventRecord);
|
|
110
134
|
|
|
111
135
|
const handler = createStreamHandler(config);
|
|
112
136
|
const event: DynamoDBStreamEvent = {
|
|
113
137
|
Records: [
|
|
114
138
|
makeStreamRecord('INSERT', { a: { S: '1' } }),
|
|
115
139
|
makeStreamRecord('INSERT', { b: { S: '2' } }),
|
|
116
|
-
]
|
|
140
|
+
],
|
|
117
141
|
};
|
|
118
142
|
|
|
119
143
|
await handler(event);
|
|
120
144
|
|
|
121
145
|
// Only eventRecord (itemType='event') should be sent
|
|
122
146
|
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(1);
|
|
123
|
-
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
147
|
+
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
124
148
|
expect(sqsInput.Entries).toHaveLength(1);
|
|
125
|
-
expect(JSON.parse(sqsInput.Entries
|
|
149
|
+
expect(JSON.parse(sqsInput.Entries?.[0]?.MessageBody ?? '')).toEqual(eventRecord);
|
|
126
150
|
});
|
|
127
151
|
|
|
128
152
|
it('sends to all configured queues', async () => {
|
|
@@ -139,14 +163,14 @@ describe('stream-handler', () => {
|
|
|
139
163
|
|
|
140
164
|
const handler = createStreamHandler(multiQueueConfig);
|
|
141
165
|
const event: DynamoDBStreamEvent = {
|
|
142
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
166
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
143
167
|
};
|
|
144
168
|
|
|
145
169
|
await handler(event);
|
|
146
170
|
|
|
147
171
|
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(2);
|
|
148
|
-
const call1 = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
149
|
-
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;
|
|
150
174
|
expect(call1.QueueUrl).toBe('https://sqs.us-east-1.amazonaws.com/123/queue-1');
|
|
151
175
|
expect(call2.QueueUrl).toBe('https://sqs.us-east-1.amazonaws.com/123/queue-2');
|
|
152
176
|
});
|
|
@@ -157,18 +181,18 @@ describe('stream-handler', () => {
|
|
|
157
181
|
|
|
158
182
|
const handler = createStreamHandler(config);
|
|
159
183
|
const event: DynamoDBStreamEvent = {
|
|
160
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
184
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
161
185
|
};
|
|
162
186
|
|
|
163
187
|
await handler(event);
|
|
164
188
|
|
|
165
189
|
expect(PutEventsCommand).toHaveBeenCalledTimes(1);
|
|
166
|
-
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0];
|
|
190
|
+
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0] as EbPutEventsInput;
|
|
167
191
|
expect(ebInput.Entries).toHaveLength(1);
|
|
168
|
-
expect(ebInput.Entries
|
|
169
|
-
expect(ebInput.Entries
|
|
170
|
-
expect(ebInput.Entries
|
|
171
|
-
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);
|
|
172
196
|
});
|
|
173
197
|
|
|
174
198
|
it('uses kebabCase of aggregateType as source fallback when source is undefined', async () => {
|
|
@@ -177,14 +201,14 @@ describe('stream-handler', () => {
|
|
|
177
201
|
|
|
178
202
|
const handler = createStreamHandler(config);
|
|
179
203
|
const event: DynamoDBStreamEvent = {
|
|
180
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
204
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
181
205
|
};
|
|
182
206
|
|
|
183
207
|
await handler(event);
|
|
184
208
|
|
|
185
|
-
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0];
|
|
209
|
+
const ebInput = vi.mocked(PutEventsCommand).mock.calls[0][0] as EbPutEventsInput;
|
|
186
210
|
// kebabCase splits on '.', takes first part 'Order', which becomes 'order'
|
|
187
|
-
expect(ebInput.Entries
|
|
211
|
+
expect(ebInput.Entries?.[0]?.Source).toBe('order');
|
|
188
212
|
});
|
|
189
213
|
|
|
190
214
|
it('uses aggregateId as MessageGroupId', async () => {
|
|
@@ -193,13 +217,13 @@ describe('stream-handler', () => {
|
|
|
193
217
|
|
|
194
218
|
const handler = createStreamHandler(config);
|
|
195
219
|
const event: DynamoDBStreamEvent = {
|
|
196
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
220
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
197
221
|
};
|
|
198
222
|
|
|
199
223
|
await handler(event);
|
|
200
224
|
|
|
201
|
-
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
202
|
-
expect(sqsInput.Entries
|
|
225
|
+
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0] as SqsBatchInput;
|
|
226
|
+
expect(sqsInput.Entries?.[0]?.MessageGroupId).toBe('agg-123');
|
|
203
227
|
});
|
|
204
228
|
|
|
205
229
|
it('uses eventId as MessageDeduplicationId', async () => {
|
|
@@ -208,39 +232,35 @@ describe('stream-handler', () => {
|
|
|
208
232
|
|
|
209
233
|
const handler = createStreamHandler(config);
|
|
210
234
|
const event: DynamoDBStreamEvent = {
|
|
211
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
235
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
212
236
|
};
|
|
213
237
|
|
|
214
238
|
await handler(event);
|
|
215
239
|
|
|
216
|
-
const sqsInput = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
217
|
-
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');
|
|
218
242
|
});
|
|
219
243
|
|
|
220
244
|
it('batches correctly (respects BATCH_SIZE of 10)', async () => {
|
|
221
245
|
// Create 12 event records to trigger 2 batches
|
|
222
246
|
const records = Array.from({ length: 12 }, (_, i) =>
|
|
223
|
-
makeEventRecord({ eventId: `evt-${i}`, version: i + 1 }),
|
|
247
|
+
makeEventRecord({ eventId: `evt-${String(i)}`, version: i + 1 }),
|
|
224
248
|
);
|
|
225
249
|
|
|
226
|
-
mockUnmarshall.mockImplementation((_, i) => records[i]);
|
|
227
|
-
// Reset to return each record in sequence
|
|
228
250
|
mockUnmarshall.mockReset();
|
|
229
251
|
records.forEach((r) => mockUnmarshall.mockReturnValueOnce(r));
|
|
230
252
|
|
|
231
253
|
const handler = createStreamHandler(config);
|
|
232
254
|
const event: DynamoDBStreamEvent = {
|
|
233
|
-
Records: records.map((
|
|
234
|
-
makeStreamRecord('INSERT', { idx: { N: String(i) } }),
|
|
235
|
-
) as any,
|
|
255
|
+
Records: records.map((_r, i) => makeStreamRecord('INSERT', { idx: { N: String(i) } })),
|
|
236
256
|
};
|
|
237
257
|
|
|
238
258
|
await handler(event);
|
|
239
259
|
|
|
240
260
|
// 2 batches for SQS (10 + 2), 1 queue = 2 calls
|
|
241
261
|
expect(SendMessageBatchCommand).toHaveBeenCalledTimes(2);
|
|
242
|
-
const firstBatch = vi.mocked(SendMessageBatchCommand).mock.calls[0][0];
|
|
243
|
-
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;
|
|
244
264
|
expect(firstBatch.Entries).toHaveLength(10);
|
|
245
265
|
expect(secondBatch.Entries).toHaveLength(2);
|
|
246
266
|
|
|
@@ -255,13 +275,13 @@ describe('stream-handler', () => {
|
|
|
255
275
|
|
|
256
276
|
const handler = createStreamHandler(config);
|
|
257
277
|
const event: DynamoDBStreamEvent = {
|
|
258
|
-
Records: [makeStreamRecord('INSERT', { bad: { S: 'data' } })]
|
|
278
|
+
Records: [makeStreamRecord('INSERT', { bad: { S: 'data' } })],
|
|
259
279
|
};
|
|
260
280
|
|
|
261
281
|
await handler(event);
|
|
262
282
|
|
|
263
283
|
expect(logger.error).toHaveBeenCalledWith(
|
|
264
|
-
expect.objectContaining({ error: expect.any(Error) }),
|
|
284
|
+
expect.objectContaining({ error: expect.any(Error) as unknown }),
|
|
265
285
|
'Error unmarshalling event record',
|
|
266
286
|
);
|
|
267
287
|
// Should not send to queues since no valid records
|
|
@@ -286,7 +306,7 @@ describe('stream-handler', () => {
|
|
|
286
306
|
|
|
287
307
|
const handler = createStreamHandler(config);
|
|
288
308
|
const event: DynamoDBStreamEvent = {
|
|
289
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
309
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
290
310
|
};
|
|
291
311
|
|
|
292
312
|
await expect(handler(event)).rejects.toThrow('SQS error');
|
|
@@ -300,7 +320,7 @@ describe('stream-handler', () => {
|
|
|
300
320
|
|
|
301
321
|
const handler = createStreamHandler(config);
|
|
302
322
|
const event: DynamoDBStreamEvent = {
|
|
303
|
-
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })]
|
|
323
|
+
Records: [makeStreamRecord('INSERT', { a: { S: '1' } })],
|
|
304
324
|
};
|
|
305
325
|
|
|
306
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;
|