@ftschopp/dynatable-core 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/CHANGELOG.md +62 -0
- package/README.md +17 -0
- package/dist/builders/batch-get/create-batch-get-builder.d.ts +18 -0
- package/dist/builders/batch-get/create-batch-get-builder.d.ts.map +1 -0
- package/dist/builders/batch-get/create-batch-get-builder.js +71 -0
- package/dist/builders/batch-get/index.d.ts +3 -0
- package/dist/builders/batch-get/index.d.ts.map +1 -0
- package/dist/builders/batch-get/index.js +18 -0
- package/dist/builders/batch-get/types.d.ts +25 -0
- package/dist/builders/batch-get/types.d.ts.map +1 -0
- package/dist/builders/batch-get/types.js +2 -0
- package/dist/builders/batch-write/create-batch-write-builder.d.ts +12 -0
- package/dist/builders/batch-write/create-batch-write-builder.d.ts.map +1 -0
- package/dist/builders/batch-write/create-batch-write-builder.js +35 -0
- package/dist/builders/batch-write/index.d.ts +3 -0
- package/dist/builders/batch-write/index.d.ts.map +1 -0
- package/dist/builders/batch-write/index.js +18 -0
- package/dist/builders/batch-write/types.d.ts +30 -0
- package/dist/builders/batch-write/types.d.ts.map +1 -0
- package/dist/builders/batch-write/types.js +2 -0
- package/dist/builders/delete/create-delete-builder.d.ts +9 -0
- package/dist/builders/delete/create-delete-builder.d.ts.map +1 -0
- package/dist/builders/delete/create-delete-builder.js +72 -0
- package/dist/builders/delete/index.d.ts +3 -0
- package/dist/builders/delete/index.d.ts.map +1 -0
- package/dist/builders/delete/index.js +18 -0
- package/dist/builders/delete/types.d.ts +11 -0
- package/dist/builders/delete/types.d.ts.map +1 -0
- package/dist/builders/delete/types.js +2 -0
- package/dist/builders/get/create-get-builder.d.ts +12 -0
- package/dist/builders/get/create-get-builder.d.ts.map +1 -0
- package/dist/builders/get/create-get-builder.js +95 -0
- package/dist/builders/get/index.d.ts +3 -0
- package/dist/builders/get/index.d.ts.map +1 -0
- package/dist/builders/get/index.js +18 -0
- package/dist/builders/get/types.d.ts +28 -0
- package/dist/builders/get/types.d.ts.map +1 -0
- package/dist/builders/get/types.js +2 -0
- package/dist/builders/index.d.ts +12 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +29 -0
- package/dist/builders/put/create-put-builder.d.ts +9 -0
- package/dist/builders/put/create-put-builder.d.ts.map +1 -0
- package/dist/builders/put/create-put-builder.js +104 -0
- package/dist/builders/put/index.d.ts +3 -0
- package/dist/builders/put/index.d.ts.map +1 -0
- package/dist/builders/put/index.js +18 -0
- package/dist/builders/put/types.d.ts +17 -0
- package/dist/builders/put/types.d.ts.map +1 -0
- package/dist/builders/put/types.js +2 -0
- package/dist/builders/query/create-query-builder.d.ts +9 -0
- package/dist/builders/query/create-query-builder.d.ts.map +1 -0
- package/dist/builders/query/create-query-builder.js +283 -0
- package/dist/builders/query/index.d.ts +3 -0
- package/dist/builders/query/index.d.ts.map +1 -0
- package/dist/builders/query/index.js +18 -0
- package/dist/builders/query/types.d.ts +79 -0
- package/dist/builders/query/types.d.ts.map +1 -0
- package/dist/builders/query/types.js +2 -0
- package/dist/builders/scan/create-scan-builder.d.ts +12 -0
- package/dist/builders/scan/create-scan-builder.d.ts.map +1 -0
- package/dist/builders/scan/create-scan-builder.js +103 -0
- package/dist/builders/scan/index.d.ts +3 -0
- package/dist/builders/scan/index.d.ts.map +1 -0
- package/dist/builders/scan/index.js +18 -0
- package/dist/builders/scan/types.d.ts +43 -0
- package/dist/builders/scan/types.d.ts.map +1 -0
- package/dist/builders/scan/types.js +2 -0
- package/dist/builders/shared/conditions.d.ts +18 -0
- package/dist/builders/shared/conditions.d.ts.map +1 -0
- package/dist/builders/shared/conditions.js +48 -0
- package/dist/builders/shared/index.d.ts +4 -0
- package/dist/builders/shared/index.d.ts.map +1 -0
- package/dist/builders/shared/index.js +19 -0
- package/dist/builders/shared/operators.d.ts +12 -0
- package/dist/builders/shared/operators.d.ts.map +1 -0
- package/dist/builders/shared/operators.js +197 -0
- package/dist/builders/shared/types.d.ts +77 -0
- package/dist/builders/shared/types.d.ts.map +1 -0
- package/dist/builders/shared/types.js +6 -0
- package/dist/builders/transact-get/create-transact-get-builder.d.ts +18 -0
- package/dist/builders/transact-get/create-transact-get-builder.d.ts.map +1 -0
- package/dist/builders/transact-get/create-transact-get-builder.js +60 -0
- package/dist/builders/transact-get/index.d.ts +3 -0
- package/dist/builders/transact-get/index.d.ts.map +1 -0
- package/dist/builders/transact-get/index.js +18 -0
- package/dist/builders/transact-get/types.d.ts +28 -0
- package/dist/builders/transact-get/types.d.ts.map +1 -0
- package/dist/builders/transact-get/types.js +2 -0
- package/dist/builders/transact-write/create-transact-write-builder.d.ts +18 -0
- package/dist/builders/transact-write/create-transact-write-builder.d.ts.map +1 -0
- package/dist/builders/transact-write/create-transact-write-builder.js +97 -0
- package/dist/builders/transact-write/index.d.ts +3 -0
- package/dist/builders/transact-write/index.d.ts.map +1 -0
- package/dist/builders/transact-write/index.js +18 -0
- package/dist/builders/transact-write/types.d.ts +34 -0
- package/dist/builders/transact-write/types.d.ts.map +1 -0
- package/dist/builders/transact-write/types.js +2 -0
- package/dist/builders/update/create-update-builder.d.ts +14 -0
- package/dist/builders/update/create-update-builder.d.ts.map +1 -0
- package/dist/builders/update/create-update-builder.js +180 -0
- package/dist/builders/update/index.d.ts +3 -0
- package/dist/builders/update/index.d.ts.map +1 -0
- package/dist/builders/update/index.js +18 -0
- package/dist/builders/update/types.d.ts +35 -0
- package/dist/builders/update/types.d.ts.map +1 -0
- package/dist/builders/update/types.js +2 -0
- package/dist/core/types.d.ts +224 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/entity.d.ts +73 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +161 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/table.d.ts +70 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +69 -0
- package/dist/utils/dynamodb-logger.d.ts +118 -0
- package/dist/utils/dynamodb-logger.d.ts.map +1 -0
- package/dist/utils/dynamodb-logger.js +125 -0
- package/dist/utils/model-utils.d.ts +24 -0
- package/dist/utils/model-utils.d.ts.map +1 -0
- package/dist/utils/model-utils.js +88 -0
- package/dist/utils/zod-utils.d.ts +13 -0
- package/dist/utils/zod-utils.d.ts.map +1 -0
- package/dist/utils/zod-utils.js +35 -0
- package/eslint.config.mjs +4 -0
- package/jest.config.js +11 -0
- package/package.json +36 -0
- package/src/builders/README.md +272 -0
- package/src/builders/batch-get/README.md +98 -0
- package/src/builders/batch-get/create-batch-get-builder.test.ts +165 -0
- package/src/builders/batch-get/create-batch-get-builder.ts +106 -0
- package/src/builders/batch-get/index.ts +2 -0
- package/src/builders/batch-get/types.ts +29 -0
- package/src/builders/batch-write/README.md +204 -0
- package/src/builders/batch-write/create-batch-write-builder.test.ts +173 -0
- package/src/builders/batch-write/create-batch-write-builder.ts +49 -0
- package/src/builders/batch-write/index.ts +2 -0
- package/src/builders/batch-write/types.ts +33 -0
- package/src/builders/delete/create-delete-builder.test.ts +294 -0
- package/src/builders/delete/create-delete-builder.ts +100 -0
- package/src/builders/delete/index.ts +2 -0
- package/src/builders/delete/types.ts +11 -0
- package/src/builders/get/create-get-builder.test.ts +272 -0
- package/src/builders/get/create-get-builder.ts +140 -0
- package/src/builders/get/index.ts +2 -0
- package/src/builders/get/types.ts +30 -0
- package/src/builders/index.ts +14 -0
- package/src/builders/put/create-put-builder.test.ts +213 -0
- package/src/builders/put/create-put-builder.ts +151 -0
- package/src/builders/put/index.ts +2 -0
- package/src/builders/put/types.ts +18 -0
- package/src/builders/query/create-query-builder.test.ts +230 -0
- package/src/builders/query/create-query-builder.ts +353 -0
- package/src/builders/query/index.ts +2 -0
- package/src/builders/query/types.ts +95 -0
- package/src/builders/scan/create-scan-builder.test.ts +260 -0
- package/src/builders/scan/create-scan-builder.ts +217 -0
- package/src/builders/scan/index.ts +2 -0
- package/src/builders/scan/types.ts +49 -0
- package/src/builders/shared/conditions.ts +58 -0
- package/src/builders/shared/index.ts +3 -0
- package/src/builders/shared/operators.test.ts +270 -0
- package/src/builders/shared/operators.ts +200 -0
- package/src/builders/shared/types.ts +100 -0
- package/src/builders/transact-get/README.md +167 -0
- package/src/builders/transact-get/create-transact-get-builder.test.ts +239 -0
- package/src/builders/transact-get/create-transact-get-builder.ts +67 -0
- package/src/builders/transact-get/index.ts +2 -0
- package/src/builders/transact-get/types.ts +31 -0
- package/src/builders/transact-write/README.md +166 -0
- package/src/builders/transact-write/create-transact-write-builder.test.ts +288 -0
- package/src/builders/transact-write/create-transact-write-builder.ts +118 -0
- package/src/builders/transact-write/index.ts +2 -0
- package/src/builders/transact-write/types.ts +33 -0
- package/src/builders/update/create-update-builder.test.ts +333 -0
- package/src/builders/update/create-update-builder.ts +284 -0
- package/src/builders/update/index.ts +2 -0
- package/src/builders/update/types.ts +42 -0
- package/src/core/types.test.ts +506 -0
- package/src/core/types.ts +290 -0
- package/src/entity.ts +337 -0
- package/src/index.ts +22 -0
- package/src/table.ts +109 -0
- package/src/utils/dynamodb-logger.test.ts +246 -0
- package/src/utils/dynamodb-logger.ts +175 -0
- package/src/utils/model-utils.test.ts +232 -0
- package/src/utils/model-utils.ts +101 -0
- package/src/utils/zod-utils.test.ts +272 -0
- package/src/utils/zod-utils.ts +36 -0
- package/tests/integration/instagram-clone.integration.test.ts +966 -0
- package/tests/integration/pagination-timestamps.integration.test.ts +375 -0
- package/tests/integration/transactions.integration.test.ts +529 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { createTransactWriteBuilder } from './create-transact-write-builder';
|
|
3
|
+
|
|
4
|
+
const client = new DynamoDBClient({});
|
|
5
|
+
|
|
6
|
+
describe('TransactWrite Builder - Functional API', () => {
|
|
7
|
+
test('should create empty transaction', () => {
|
|
8
|
+
const params = createTransactWriteBuilder(client).dbParams();
|
|
9
|
+
|
|
10
|
+
expect(params).toEqual({
|
|
11
|
+
TransactItems: [],
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should add single Put operation', () => {
|
|
16
|
+
const putParams = {
|
|
17
|
+
TableName: 'InstagramClone',
|
|
18
|
+
Item: {
|
|
19
|
+
pk: 'USER#alice',
|
|
20
|
+
sk: 'USER#alice',
|
|
21
|
+
username: 'alice',
|
|
22
|
+
name: 'Alice Smith',
|
|
23
|
+
followerCount: 0,
|
|
24
|
+
followingCount: 0,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const params = createTransactWriteBuilder(client).addPut(putParams).dbParams();
|
|
29
|
+
|
|
30
|
+
expect(params).toEqual({
|
|
31
|
+
TransactItems: [{ Put: putParams }],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('should add single Update operation', () => {
|
|
36
|
+
const updateParams = {
|
|
37
|
+
TableName: 'InstagramClone',
|
|
38
|
+
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
39
|
+
UpdateExpression: 'ADD #followerCount :followerCount_0',
|
|
40
|
+
ExpressionAttributeNames: { '#followerCount': 'followerCount' },
|
|
41
|
+
ExpressionAttributeValues: { ':followerCount_0': 1 },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const params = createTransactWriteBuilder(client).addUpdate(updateParams).dbParams();
|
|
45
|
+
|
|
46
|
+
expect(params).toEqual({
|
|
47
|
+
TransactItems: [{ Update: updateParams }],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should add single Delete operation', () => {
|
|
52
|
+
const deleteParams = {
|
|
53
|
+
TableName: 'InstagramClone',
|
|
54
|
+
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const params = createTransactWriteBuilder(client).addDelete(deleteParams).dbParams();
|
|
58
|
+
|
|
59
|
+
expect(params).toEqual({
|
|
60
|
+
TransactItems: [{ Delete: deleteParams }],
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should add ConditionCheck operation', () => {
|
|
65
|
+
const conditionCheckParams = {
|
|
66
|
+
TableName: 'InstagramClone',
|
|
67
|
+
Key: { pk: 'USER#admin', sk: 'USER#admin' },
|
|
68
|
+
ConditionExpression: 'attribute_exists(#username)',
|
|
69
|
+
ExpressionAttributeNames: { '#username': 'username' },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const params = createTransactWriteBuilder(client)
|
|
73
|
+
.addConditionCheck(conditionCheckParams)
|
|
74
|
+
.dbParams();
|
|
75
|
+
|
|
76
|
+
expect(params).toEqual({
|
|
77
|
+
TransactItems: [{ ConditionCheck: conditionCheckParams }],
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should chain multiple operations - Like a Photo', () => {
|
|
82
|
+
// Like a Photo: Create Like + Increment Photo.likesCount
|
|
83
|
+
const putLikeParams = {
|
|
84
|
+
TableName: 'InstagramClone',
|
|
85
|
+
Item: {
|
|
86
|
+
pk: 'PL#photo123',
|
|
87
|
+
sk: 'LIKE#juanca',
|
|
88
|
+
photoId: 'photo123',
|
|
89
|
+
likingUsername: 'juanca',
|
|
90
|
+
likeId: '01K16ZP43BRX67DG50SHGZ11DS',
|
|
91
|
+
},
|
|
92
|
+
ConditionExpression: '(attribute_not_exists(#PK)) AND (attribute_not_exists(#SK))',
|
|
93
|
+
ExpressionAttributeNames: { '#PK': 'pk', '#SK': 'sk' },
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const updatePhotoParams = {
|
|
97
|
+
TableName: 'InstagramClone',
|
|
98
|
+
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
99
|
+
UpdateExpression: 'ADD #likesCount :likesCount_0',
|
|
100
|
+
ExpressionAttributeNames: { '#likesCount': 'likesCount' },
|
|
101
|
+
ExpressionAttributeValues: { ':likesCount_0': 1 },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const params = createTransactWriteBuilder(client)
|
|
105
|
+
.addPut(putLikeParams)
|
|
106
|
+
.addUpdate(updatePhotoParams)
|
|
107
|
+
.dbParams();
|
|
108
|
+
|
|
109
|
+
expect(params).toEqual({
|
|
110
|
+
TransactItems: [{ Put: putLikeParams }, { Update: updatePhotoParams }],
|
|
111
|
+
});
|
|
112
|
+
expect(params.TransactItems).toHaveLength(2);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('should chain multiple operations - Follow a User', () => {
|
|
116
|
+
// Follow: Create Follow + Update both user counts
|
|
117
|
+
const putFollowParams = {
|
|
118
|
+
TableName: 'InstagramClone',
|
|
119
|
+
Item: {
|
|
120
|
+
pk: 'FOLLOW#alice',
|
|
121
|
+
sk: 'FOLLOW#bob',
|
|
122
|
+
followedUsername: 'alice',
|
|
123
|
+
followingUsername: 'bob',
|
|
124
|
+
},
|
|
125
|
+
ConditionExpression: '(attribute_not_exists(#PK)) AND (attribute_not_exists(#SK))',
|
|
126
|
+
ExpressionAttributeNames: { '#PK': 'pk', '#SK': 'sk' },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const updateFollowedParams = {
|
|
130
|
+
TableName: 'InstagramClone',
|
|
131
|
+
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
132
|
+
UpdateExpression: 'ADD #followerCount :followerCount_0',
|
|
133
|
+
ExpressionAttributeNames: { '#followerCount': 'followerCount' },
|
|
134
|
+
ExpressionAttributeValues: { ':followerCount_0': 1 },
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const updateFollowingParams = {
|
|
138
|
+
TableName: 'InstagramClone',
|
|
139
|
+
Key: { pk: 'USER#bob', sk: 'USER#bob' },
|
|
140
|
+
UpdateExpression: 'ADD #followingCount :followingCount_0',
|
|
141
|
+
ExpressionAttributeNames: { '#followingCount': 'followingCount' },
|
|
142
|
+
ExpressionAttributeValues: { ':followingCount_0': 1 },
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const params = createTransactWriteBuilder(client)
|
|
146
|
+
.addPut(putFollowParams)
|
|
147
|
+
.addUpdate(updateFollowedParams)
|
|
148
|
+
.addUpdate(updateFollowingParams)
|
|
149
|
+
.dbParams();
|
|
150
|
+
|
|
151
|
+
expect(params.TransactItems).toHaveLength(3);
|
|
152
|
+
expect(params.TransactItems[0]).toEqual({ Put: putFollowParams });
|
|
153
|
+
expect(params.TransactItems[1]).toEqual({ Update: updateFollowedParams });
|
|
154
|
+
expect(params.TransactItems[2]).toEqual({ Update: updateFollowingParams });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should chain multiple operations - Comment on Photo', () => {
|
|
158
|
+
const putCommentParams = {
|
|
159
|
+
TableName: 'InstagramClone',
|
|
160
|
+
Item: {
|
|
161
|
+
pk: 'PC#photo123',
|
|
162
|
+
sk: 'COMMENT#comment456',
|
|
163
|
+
photoId: 'photo123',
|
|
164
|
+
commentId: 'comment456',
|
|
165
|
+
commentingUsername: 'bob',
|
|
166
|
+
content: 'Great photo!',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const updatePhotoParams = {
|
|
171
|
+
TableName: 'InstagramClone',
|
|
172
|
+
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
173
|
+
UpdateExpression: 'ADD #commentCount :commentCount_0',
|
|
174
|
+
ExpressionAttributeNames: { '#commentCount': 'commentCount' },
|
|
175
|
+
ExpressionAttributeValues: { ':commentCount_0': 1 },
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const params = createTransactWriteBuilder(client)
|
|
179
|
+
.addPut(putCommentParams)
|
|
180
|
+
.addUpdate(updatePhotoParams)
|
|
181
|
+
.dbParams();
|
|
182
|
+
|
|
183
|
+
expect(params.TransactItems).toHaveLength(2);
|
|
184
|
+
expect(params.TransactItems[0]).toEqual({ Put: putCommentParams });
|
|
185
|
+
expect(params.TransactItems[1]).toEqual({ Update: updatePhotoParams });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should support client request token for idempotency', () => {
|
|
189
|
+
const putParams = {
|
|
190
|
+
TableName: 'InstagramClone',
|
|
191
|
+
Item: { pk: 'USER#alice', sk: 'USER#alice', username: 'alice' },
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const params = createTransactWriteBuilder(client)
|
|
195
|
+
.addPut(putParams)
|
|
196
|
+
.withClientRequestToken('my-unique-token-123')
|
|
197
|
+
.dbParams();
|
|
198
|
+
|
|
199
|
+
expect(params).toEqual({
|
|
200
|
+
TransactItems: [{ Put: putParams }],
|
|
201
|
+
ClientRequestToken: 'my-unique-token-123',
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('should preserve immutability - original builder unchanged', () => {
|
|
206
|
+
const builder1 = createTransactWriteBuilder(client);
|
|
207
|
+
const putParams = {
|
|
208
|
+
TableName: 'InstagramClone',
|
|
209
|
+
Item: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const builder2 = builder1.addPut(putParams);
|
|
213
|
+
const builder3 = builder2.withClientRequestToken('token');
|
|
214
|
+
|
|
215
|
+
// Each builder should be independent
|
|
216
|
+
expect(builder1.dbParams().TransactItems).toHaveLength(0);
|
|
217
|
+
expect(builder2.dbParams().TransactItems).toHaveLength(1);
|
|
218
|
+
expect(builder3.dbParams().ClientRequestToken).toBe('token');
|
|
219
|
+
expect(builder2.dbParams().ClientRequestToken).toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('should handle complex transaction - Delete Photo only if no engagement', () => {
|
|
223
|
+
const deletePhotoParams = {
|
|
224
|
+
TableName: 'InstagramClone',
|
|
225
|
+
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
226
|
+
ConditionExpression: '(#likesCount = :likesCount_0) AND (#commentCount = :commentCount_1)',
|
|
227
|
+
ExpressionAttributeNames: {
|
|
228
|
+
'#likesCount': 'likesCount',
|
|
229
|
+
'#commentCount': 'commentCount',
|
|
230
|
+
},
|
|
231
|
+
ExpressionAttributeValues: {
|
|
232
|
+
':likesCount_0': 0,
|
|
233
|
+
':commentCount_1': 0,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const params = createTransactWriteBuilder(client).addDelete(deletePhotoParams).dbParams();
|
|
238
|
+
|
|
239
|
+
expect(params.TransactItems).toHaveLength(1);
|
|
240
|
+
expect(params.TransactItems[0]).toEqual({ Delete: deletePhotoParams });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('should handle max operations (100 items limit)', () => {
|
|
244
|
+
let builder = createTransactWriteBuilder(client);
|
|
245
|
+
|
|
246
|
+
// Add 100 Put operations (DynamoDB limit)
|
|
247
|
+
for (let i = 0; i < 100; i++) {
|
|
248
|
+
builder = builder.addPut({
|
|
249
|
+
TableName: 'InstagramClone',
|
|
250
|
+
Item: { pk: `USER#user${i}`, sk: `USER#user${i}` },
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const params = builder.dbParams();
|
|
255
|
+
expect(params.TransactItems).toHaveLength(100);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('should mix all operation types', () => {
|
|
259
|
+
const params = createTransactWriteBuilder(client)
|
|
260
|
+
.addPut({
|
|
261
|
+
TableName: 'InstagramClone',
|
|
262
|
+
Item: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
263
|
+
})
|
|
264
|
+
.addUpdate({
|
|
265
|
+
TableName: 'InstagramClone',
|
|
266
|
+
Key: { pk: 'USER#bob', sk: 'USER#bob' },
|
|
267
|
+
UpdateExpression: 'ADD #count :val',
|
|
268
|
+
ExpressionAttributeNames: { '#count': 'followerCount' },
|
|
269
|
+
ExpressionAttributeValues: { ':val': 1 },
|
|
270
|
+
})
|
|
271
|
+
.addDelete({
|
|
272
|
+
TableName: 'InstagramClone',
|
|
273
|
+
Key: { pk: 'PHOTO#old', sk: 'PHOTO#old' },
|
|
274
|
+
})
|
|
275
|
+
.addConditionCheck({
|
|
276
|
+
TableName: 'InstagramClone',
|
|
277
|
+
Key: { pk: 'USER#admin', sk: 'USER#admin' },
|
|
278
|
+
ConditionExpression: 'attribute_exists(pk)',
|
|
279
|
+
})
|
|
280
|
+
.dbParams();
|
|
281
|
+
|
|
282
|
+
expect(params.TransactItems).toHaveLength(4);
|
|
283
|
+
expect(params.TransactItems[0]).toHaveProperty('Put');
|
|
284
|
+
expect(params.TransactItems[1]).toHaveProperty('Update');
|
|
285
|
+
expect(params.TransactItems[2]).toHaveProperty('Delete');
|
|
286
|
+
expect(params.TransactItems[3]).toHaveProperty('ConditionCheck');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
+
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
|
|
4
|
+
import { TransactWriteBuilder, TransactWriteState } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates the initial state for a TransactWrite builder
|
|
8
|
+
*/
|
|
9
|
+
const createInitialState = (client: DynamoDBClient): TransactWriteState => ({
|
|
10
|
+
client,
|
|
11
|
+
items: [],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Adds a Put operation to the transaction
|
|
16
|
+
*/
|
|
17
|
+
const addPutItem =
|
|
18
|
+
(state: TransactWriteState) =>
|
|
19
|
+
(params: any): TransactWriteState => ({
|
|
20
|
+
...state,
|
|
21
|
+
items: [...state.items, { Put: params }],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Adds an Update operation to the transaction
|
|
26
|
+
*/
|
|
27
|
+
const addUpdateItem =
|
|
28
|
+
(state: TransactWriteState) =>
|
|
29
|
+
(params: any): TransactWriteState => ({
|
|
30
|
+
...state,
|
|
31
|
+
items: [...state.items, { Update: params }],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Adds a Delete operation to the transaction
|
|
36
|
+
*/
|
|
37
|
+
const addDeleteItem =
|
|
38
|
+
(state: TransactWriteState) =>
|
|
39
|
+
(params: any): TransactWriteState => ({
|
|
40
|
+
...state,
|
|
41
|
+
items: [...state.items, { Delete: params }],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds a ConditionCheck operation to the transaction
|
|
46
|
+
*/
|
|
47
|
+
const addConditionCheckItem =
|
|
48
|
+
(state: TransactWriteState) =>
|
|
49
|
+
(params: any): TransactWriteState => ({
|
|
50
|
+
...state,
|
|
51
|
+
items: [...state.items, { ConditionCheck: params }],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sets the client request token for idempotency
|
|
56
|
+
*/
|
|
57
|
+
const setClientRequestToken =
|
|
58
|
+
(state: TransactWriteState) =>
|
|
59
|
+
(token: string): TransactWriteState => ({
|
|
60
|
+
...state,
|
|
61
|
+
clientRequestToken: token,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Converts the builder state to DynamoDB parameters
|
|
66
|
+
*/
|
|
67
|
+
const toDbParams = (state: TransactWriteState) => {
|
|
68
|
+
const params: any = {
|
|
69
|
+
TransactItems: state.items,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (state.clientRequestToken) {
|
|
73
|
+
params.ClientRequestToken = state.clientRequestToken;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return params;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Executes the transaction
|
|
81
|
+
*/
|
|
82
|
+
const execute = async (state: TransactWriteState) => {
|
|
83
|
+
const params = toDbParams(state);
|
|
84
|
+
const command = new TransactWriteCommand(params);
|
|
85
|
+
return await state.client.send(command);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Creates a builder from the current state
|
|
90
|
+
*/
|
|
91
|
+
const createBuilder = (state: TransactWriteState): TransactWriteBuilder => ({
|
|
92
|
+
addPut: (params: any) => createBuilder(addPutItem(state)(params)),
|
|
93
|
+
addUpdate: (params: any) => createBuilder(addUpdateItem(state)(params)),
|
|
94
|
+
addDelete: (params: any) => createBuilder(addDeleteItem(state)(params)),
|
|
95
|
+
addConditionCheck: (params: any) => createBuilder(addConditionCheckItem(state)(params)),
|
|
96
|
+
withClientRequestToken: (token: string) => createBuilder(setClientRequestToken(state)(token)),
|
|
97
|
+
dbParams: () => toDbParams(state),
|
|
98
|
+
execute: () => execute(state),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates a new TransactWrite builder
|
|
103
|
+
*
|
|
104
|
+
* @param client - DynamoDB client instance
|
|
105
|
+
* @returns A TransactWriteBuilder
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const result = await createTransactWriteBuilder(client)
|
|
110
|
+
* .addPut(putParams)
|
|
111
|
+
* .addUpdate(updateParams)
|
|
112
|
+
* .execute();
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export const createTransactWriteBuilder = (client: DynamoDBClient): TransactWriteBuilder => {
|
|
116
|
+
const initialState = createInitialState(client);
|
|
117
|
+
return createBuilder(initialState);
|
|
118
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents a single item in a TransactWrite operation
|
|
6
|
+
*/
|
|
7
|
+
export type TransactWriteItem =
|
|
8
|
+
| { Put: any }
|
|
9
|
+
| { Update: any }
|
|
10
|
+
| { Delete: any }
|
|
11
|
+
| { ConditionCheck: any };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* State for the TransactWrite builder
|
|
15
|
+
*/
|
|
16
|
+
export type TransactWriteState = {
|
|
17
|
+
readonly client: DynamoDBClient;
|
|
18
|
+
readonly items: readonly TransactWriteItem[];
|
|
19
|
+
readonly clientRequestToken?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TransactWrite builder interface
|
|
24
|
+
*/
|
|
25
|
+
export type TransactWriteBuilder = {
|
|
26
|
+
readonly addPut: (params: any) => TransactWriteBuilder;
|
|
27
|
+
readonly addUpdate: (params: any) => TransactWriteBuilder;
|
|
28
|
+
readonly addDelete: (params: any) => TransactWriteBuilder;
|
|
29
|
+
readonly addConditionCheck: (params: any) => TransactWriteBuilder;
|
|
30
|
+
readonly withClientRequestToken: (token: string) => TransactWriteBuilder;
|
|
31
|
+
readonly dbParams: () => any;
|
|
32
|
+
readonly execute: () => Promise<any>;
|
|
33
|
+
};
|