@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.
- package/dist/DynamoDbClient.d.ts +3 -0
- package/dist/DynamoDbClient.js +30 -0
- package/dist/DynamoDbClient.js.map +1 -0
- package/dist/EvDBDynamoDBAdmin.d.ts +17 -0
- package/dist/EvDBDynamoDBAdmin.js +101 -0
- package/dist/EvDBDynamoDBAdmin.js.map +1 -0
- package/dist/EvDbDynamoDbStorageAdapter.d.ts +86 -0
- package/dist/EvDbDynamoDbStorageAdapter.js +160 -0
- package/dist/EvDbDynamoDbStorageAdapter.js.map +1 -0
- package/dist/EvDbDynamoDbStorageAdapterQueries.d.ts +39 -0
- package/dist/EvDbDynamoDbStorageAdapterQueries.js +159 -0
- package/dist/EvDbDynamoDbStorageAdapterQueries.js.map +1 -0
- package/models/events-table-schema.json +50 -0
- package/models/messages-table-schema.json +24 -0
- package/models/snapshots-table-schema.json +24 -0
- package/package.json +29 -0
- package/src/DynamoDbClient.ts +32 -0
- package/src/EvDBDynamoDBAdmin.ts +115 -0
- package/src/EvDbDynamoDbStorageAdapter.ts +250 -0
- package/src/EvDbDynamoDbStorageAdapterQueries.ts +210 -0
- package/src/dbModel.json +215 -0
- package/tsconfig.json +21 -0
|
@@ -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
|
+
}
|
package/src/dbModel.json
ADDED
|
@@ -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
|
+
}
|