@eventualize/dynamodb-storage-adapter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,210 @@
1
+ import IEvDbEventPayload, { IEvDbPayloadData } from "@eventualize/types/IEvDbEventPayload"
2
+ import { AttributeValue, GetItemCommandInput, PutItemCommand, PutItemCommandInput, QueryCommand, QueryCommandInput, TransactWriteItem } from "@aws-sdk/client-dynamodb";
3
+ import EvDbStreamCursor from "@eventualize/types/EvDbStreamCursor";
4
+ import EvDbStreamAddress from "@eventualize/types/EvDbStreamAddress";
5
+ import EvDbEvent from "@eventualize/types/EvDbEvent";
6
+ import EvDbViewAddress from "@eventualize/types/EvDbViewAddress";
7
+ import { EvDbStoredSnapshotData } from "@eventualize/types/EvDbStoredSnapshotData";
8
+ import { marshall } from "@aws-sdk/util-dynamodb";
9
+
10
+
11
+ export class EventRecord {
12
+ constructor(
13
+ public readonly id: string,
14
+ public readonly stream_cursor: EvDbStreamCursor,
15
+ public readonly event_type: string,
16
+ public readonly captured_by: string,
17
+ public readonly captured_at: Date,
18
+ public readonly payload: IEvDbEventPayload,
19
+ public readonly stored_at?: Date,
20
+ ) { }
21
+
22
+ public static createFromEvent(e: EvDbEvent): EventRecord {
23
+ return new EventRecord(
24
+ crypto.randomUUID(),
25
+ e.streamCursor,
26
+ e.eventType,
27
+ e.capturedBy,
28
+ e.capturedAt,
29
+ e.payload,
30
+ e.storedAt)
31
+ }
32
+
33
+ public toEvDbEvent(): EvDbEvent {
34
+ return new EvDbEvent(
35
+ this.event_type,
36
+ this.stream_cursor,
37
+ this.payload,
38
+ this.captured_at,
39
+ this.captured_by,
40
+ new Date(Number(this.stored_at))
41
+ )
42
+ }
43
+ }
44
+
45
+ export type MessageRecord = {
46
+ id: string
47
+ stream_cursor: EvDbStreamCursor
48
+ channel: string
49
+ message_type: string
50
+ event_type: string
51
+ captured_by: string
52
+ captured_at: Date
53
+ payload: IEvDbPayloadData
54
+ stored_at?: Date
55
+ }
56
+
57
+ const serializeStreamAddress = (streamAddress: EvDbStreamAddress) => {
58
+ return `${streamAddress.streamType}::${streamAddress.streamId}`;
59
+ }
60
+
61
+ export const deserializeStreamAddress = (streamAddressStr: string): EvDbStreamAddress => {
62
+ const [streamType, streamId] = streamAddressStr.split('::');
63
+ return new EvDbStreamAddress(streamType, streamId);
64
+ }
65
+
66
+ const serializeMessageAddress = (m: MessageRecord) => {
67
+ return `${m.channel}::${m.message_type}`;
68
+ }
69
+
70
+ const serializeViewAddress = (viewAddress: EvDbViewAddress) => {
71
+ return `${serializeStreamAddress(viewAddress)}::${viewAddress.viewName}`;
72
+ }
73
+
74
+ export default class EvDbDynamoDbStorageAdapterQueries {
75
+
76
+ public static saveEvents(events: EventRecord[]): TransactWriteItem[] {
77
+ const TransactItems = events.map(e => ({
78
+ Put: {
79
+ TableName: "events",
80
+ Item: {
81
+ stream_address: { S: serializeStreamAddress(e.stream_cursor) } as AttributeValue,
82
+ offset: { N: e.stream_cursor.offset.toString() },
83
+ event_type: { S: e.event_type },
84
+ captured_by: { S: e.captured_by },
85
+ captured_at: { S: e.captured_at.getTime().toString() },
86
+ payload: {
87
+ M: marshall(e.payload, {
88
+ convertClassInstanceToMap: true,
89
+ removeUndefinedValues: true
90
+ })
91
+ },
92
+ stored_at: { S: Date.now().toString() }
93
+ },
94
+ ConditionExpression: "(attribute_not_exists(#sa)) Or (attribute_exists(#sa) And attribute_not_exists(#offset))",
95
+ ExpressionAttributeNames: {
96
+ "#sa": "stream_address",
97
+ "#offset": "offset"
98
+ }
99
+ }
100
+ }));
101
+
102
+ return TransactItems;
103
+
104
+ }
105
+
106
+ public static saveMessages(messages: MessageRecord[]): TransactWriteItem[] {
107
+ const TransactItems = messages.map(m => ({
108
+ Put: {
109
+ TableName: "messages",
110
+ Item: {
111
+ message_address: { S: serializeMessageAddress(m) },
112
+ stream_address: { S: serializeStreamAddress(m.stream_cursor) },
113
+ offset: { N: m.stream_cursor.offset.toString() },
114
+ event_type: { S: m.event_type },
115
+ captured_by: { S: m.captured_by },
116
+ captured_at: { S: `${m.captured_at.getTime().toString()}::${crypto.randomUUID()}` },
117
+ payload: {
118
+ M: marshall(m.payload, {
119
+ convertClassInstanceToMap: true,
120
+ removeUndefinedValues: true
121
+ })
122
+ },
123
+ stored_at: { S: Date.now().toString() }
124
+ }
125
+ }
126
+ }));
127
+
128
+ return TransactItems;
129
+ }
130
+
131
+ public static getLastOffset(streamAddress: EvDbStreamAddress): QueryCommand {
132
+ const queryParams = {
133
+ TableName: "events",
134
+ KeyConditionExpression: "stream_address = :pk",
135
+ ExpressionAttributeValues: {
136
+ ":pk": { S: serializeStreamAddress(streamAddress) }
137
+ },
138
+ ScanIndexForward: false,
139
+ Limit: 1
140
+ }
141
+
142
+ return new QueryCommand(queryParams);
143
+ }
144
+
145
+ public static getEvents(streamCursor: EvDbStreamCursor, queryCursor: Record<string, any> | undefined = undefined, pageSize: number = 100) {
146
+ const queryParams = {
147
+ TableName: "events",
148
+ KeyConditionExpression: "#sa = :sa AND #o >= :offsetValue",
149
+ ExpressionAttributeNames: {
150
+ "#o": "offset",
151
+ "#sa": "stream_address",
152
+ "#cb": "captured_by"
153
+ },
154
+ ExpressionAttributeValues: {
155
+ ":sa": { S: serializeStreamAddress(streamCursor) },
156
+ ":offsetValue": { N: streamCursor.offset.toString() }
157
+ },
158
+ ProjectionExpression: '#sa, #o, id, event_type, captured_at, #cb, stored_at, payload',
159
+ Limit: pageSize,
160
+ ExclusiveStartKey: queryCursor
161
+ }
162
+
163
+ return new QueryCommand(queryParams);
164
+ }
165
+
166
+ public static getSnapshot(viewAddress: EvDbViewAddress) {
167
+ const queryParams = {
168
+ TableName: "snapshots",
169
+ KeyConditionExpression: "view_address = :sa",
170
+
171
+ ExpressionAttributeValues: {
172
+ ":sa": { S: serializeViewAddress(viewAddress) },
173
+ },
174
+ ProjectionExpression: '#o, #s, stored_at',
175
+ ExpressionAttributeNames: {
176
+ "#o": "offset",
177
+ "#s": "state"
178
+ },
179
+ ScanIndexForward: false, // false = descending order
180
+ Limit: 1
181
+ }
182
+
183
+ return new QueryCommand(queryParams);
184
+ }
185
+
186
+ public static saveSnapshot(snapshot: EvDbStoredSnapshotData): PutItemCommand {
187
+ const viewAddress = new EvDbViewAddress(snapshot.streamType, snapshot.streamId, snapshot.viewName);
188
+ const queryParams: PutItemCommandInput = {
189
+ TableName: "snapshots",
190
+ Item: {
191
+ view_address: { S: serializeViewAddress(viewAddress) },
192
+ offset: { N: snapshot.offset.toString() },
193
+ state: {
194
+ M: marshall(snapshot.state, {
195
+ convertClassInstanceToMap: true,
196
+ removeUndefinedValues: true
197
+ })
198
+ },
199
+ stored_at: { S: Date.now().toString() }
200
+ },
201
+ ConditionExpression: "(attribute_not_exists(#va)) Or (attribute_exists(#va) And attribute_not_exists(#offset))",
202
+ ExpressionAttributeNames: {
203
+ "#va": "view_address",
204
+ "#offset": "offset"
205
+ }
206
+ };
207
+
208
+ return new PutItemCommand(queryParams);
209
+ }
210
+ }
@@ -0,0 +1,215 @@
1
+ {
2
+ "ModelName": "Eventualize Events Table",
3
+ "ModelMetadata": {
4
+ "Author": "",
5
+ "DateCreated": "Dec 12, 2025, 02:20 AM",
6
+ "DateLastModified": "Dec 12, 2025, 02:33 AM",
7
+ "Description": "",
8
+ "AWSService": "Amazon DynamoDB",
9
+ "Version": "3.0"
10
+ },
11
+ "DataModel": [
12
+ {
13
+ "TableName": "events",
14
+ "KeyAttributes": {
15
+ "PartitionKey": {
16
+ "AttributeName": "stream_address",
17
+ "AttributeType": "S"
18
+ },
19
+ "SortKey": {
20
+ "AttributeName": "offset",
21
+ "AttributeType": "N"
22
+ }
23
+ },
24
+ "NonKeyAttributes": [
25
+ {
26
+ "AttributeName": "id",
27
+ "AttributeType": "S"
28
+ },
29
+ {
30
+ "AttributeName": "event_type",
31
+ "AttributeType": "S"
32
+ },
33
+ {
34
+ "AttributeName": "captured_at",
35
+ "AttributeType": "S"
36
+ },
37
+ {
38
+ "AttributeName": "captured_by",
39
+ "AttributeType": "S"
40
+ },
41
+ {
42
+ "AttributeName": "stored_at",
43
+ "AttributeType": "S"
44
+ },
45
+ {
46
+ "AttributeName": "payload",
47
+ "AttributeType": "M"
48
+ }
49
+ ],
50
+ "TableFacets": [],
51
+ "GlobalSecondaryIndexes": [
52
+ {
53
+ "IndexName": "event_type__captured_at",
54
+ "KeyAttributes": {
55
+ "PartitionKey": {
56
+ "AttributeName": "event_type",
57
+ "AttributeType": "S"
58
+ },
59
+ "SortKey": {
60
+ "AttributeName": "captured_at",
61
+ "AttributeType": "S"
62
+ }
63
+ },
64
+ "Projection": {
65
+ "ProjectionType": "ALL"
66
+ }
67
+ }
68
+ ],
69
+ "TableData": [],
70
+ "DataAccess": {
71
+ "MySql": {}
72
+ },
73
+ "SampleDataFormats": {
74
+ "offset": [
75
+ "Int"
76
+ ],
77
+ "id": [
78
+ "identifiers",
79
+ "UUID"
80
+ ],
81
+ "event_type": [
82
+ "dataTypes",
83
+ "String"
84
+ ],
85
+ "captured_at": [
86
+ "date",
87
+ "Epoc/Unix date format"
88
+ ],
89
+ "captured_by": [
90
+ "identifiers",
91
+ "Domain"
92
+ ],
93
+ "stored_at": [
94
+ "date",
95
+ "Epoc/Unix date format"
96
+ ]
97
+ },
98
+ "BillingMode": "PAY_PER_REQUEST"
99
+ },
100
+ {
101
+ "TableName": "snapshots",
102
+ "KeyAttributes": {
103
+ "PartitionKey": {
104
+ "AttributeName": "view_address",
105
+ "AttributeType": "S"
106
+ },
107
+ "SortKey": {
108
+ "AttributeName": "offset",
109
+ "AttributeType": "N"
110
+ }
111
+ },
112
+ "NonKeyAttributes": [
113
+ {
114
+ "AttributeName": "stored_at",
115
+ "AttributeType": "S"
116
+ },
117
+ {
118
+ "AttributeName": "state",
119
+ "AttributeType": "M"
120
+ }
121
+ ],
122
+ "TableFacets": [],
123
+ "GlobalSecondaryIndexes": [],
124
+ "TableData": [],
125
+ "DataAccess": {
126
+ "MySql": {}
127
+ },
128
+ "SampleDataFormats": {
129
+ "offset": [
130
+ "Int"
131
+ ],
132
+ "view_address": [
133
+ "dataTypes",
134
+ "String"
135
+ ],
136
+ "stored_at": [
137
+ "date",
138
+ "Epoc/Unix date format"
139
+ ]
140
+ },
141
+ "BillingMode": "PAY_PER_REQUEST"
142
+ },
143
+ {
144
+ "TableName": "messages",
145
+ "KeyAttributes": {
146
+ "PartitionKey": {
147
+ "AttributeName": "message_addres",
148
+ "AttributeType": "S"
149
+ },
150
+ "SortKey": {
151
+ "AttributeName": "captured_at",
152
+ "AttributeType": "S"
153
+ }
154
+ },
155
+ "NonKeyAttributes": [
156
+ {
157
+ "AttributeName": "event_type",
158
+ "AttributeType": "S"
159
+ },
160
+ {
161
+ "AttributeName": "offset",
162
+ "AttributeType": "N"
163
+ },
164
+ {
165
+ "AttributeName": "stored_at",
166
+ "AttributeType": "S"
167
+ },
168
+ {
169
+ "AttributeName": "captured_by",
170
+ "AttributeType": "S"
171
+ },
172
+ {
173
+ "AttributeName": "payload",
174
+ "AttributeType": "M"
175
+ }
176
+ ],
177
+ "TableFacets": [],
178
+ "GlobalSecondaryIndexes": [],
179
+ "TableData": [],
180
+ "DataAccess": {
181
+ "MySql": {}
182
+ },
183
+ "SampleDataFormats": {
184
+ "message_addres": [
185
+ "dataTypes",
186
+ "String"
187
+ ],
188
+ "captured_at": [
189
+ "date",
190
+ "Epoc/Unix date format"
191
+ ],
192
+ "event_type": [
193
+ "dataTypes",
194
+ "String"
195
+ ],
196
+ "offset": [
197
+ "Int"
198
+ ],
199
+ "channel": [
200
+ "dataTypes",
201
+ "String"
202
+ ],
203
+ "stored_at": [
204
+ "date",
205
+ "Epoc/Unix date format"
206
+ ],
207
+ "captured_by": [
208
+ "identifiers",
209
+ "Domain"
210
+ ]
211
+ },
212
+ "BillingMode": "PAY_PER_REQUEST"
213
+ }
214
+ ]
215
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "moduleResolution": "node",
7
+ "module": "es2020",
8
+ "target": "es2020",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ },
12
+ "references": [
13
+ {
14
+ "path": "../types"
15
+ }
16
+ ],
17
+ "include": [
18
+ "src/**/*.ts",
19
+ "../prisma.config.ts",
20
+ ]
21
+ }