@ftschopp/dynatable-core 1.4.2 → 1.4.3
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 +7 -0
- package/dist/builders/query/create-query-builder.d.ts.map +1 -1
- package/dist/builders/query/create-query-builder.js +19 -2
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/eslint.config.mjs +0 -4
- package/jest.config.js +0 -11
- package/src/builders/README.md +0 -339
- package/src/builders/batch-get/README.md +0 -43
- package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
- package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
- package/src/builders/batch-get/index.ts +0 -2
- package/src/builders/batch-get/types.ts +0 -28
- package/src/builders/batch-write/README.md +0 -204
- package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
- package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
- package/src/builders/batch-write/index.ts +0 -2
- package/src/builders/batch-write/types.ts +0 -33
- package/src/builders/delete/create-delete-builder.test.ts +0 -294
- package/src/builders/delete/create-delete-builder.ts +0 -100
- package/src/builders/delete/index.ts +0 -2
- package/src/builders/delete/types.ts +0 -17
- package/src/builders/get/create-get-builder.test.ts +0 -272
- package/src/builders/get/create-get-builder.ts +0 -117
- package/src/builders/get/index.ts +0 -2
- package/src/builders/get/types.ts +0 -39
- package/src/builders/index.ts +0 -14
- package/src/builders/put/create-put-builder.test.ts +0 -213
- package/src/builders/put/create-put-builder.ts +0 -160
- package/src/builders/put/index.ts +0 -2
- package/src/builders/put/types.ts +0 -24
- package/src/builders/query/create-query-builder.test.ts +0 -770
- package/src/builders/query/create-query-builder.ts +0 -455
- package/src/builders/query/index.ts +0 -2
- package/src/builders/query/types.ts +0 -109
- package/src/builders/scan/create-scan-builder.test.ts +0 -416
- package/src/builders/scan/create-scan-builder.ts +0 -265
- package/src/builders/scan/index.ts +0 -2
- package/src/builders/scan/types.ts +0 -88
- package/src/builders/shared/conditions.ts +0 -58
- package/src/builders/shared/index.ts +0 -4
- package/src/builders/shared/operators.test.ts +0 -270
- package/src/builders/shared/operators.ts +0 -208
- package/src/builders/shared/projection.ts +0 -28
- package/src/builders/shared/types.ts +0 -101
- package/src/builders/transact-get/README.md +0 -167
- package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
- package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
- package/src/builders/transact-get/index.ts +0 -2
- package/src/builders/transact-get/types.ts +0 -31
- package/src/builders/transact-write/README.md +0 -202
- package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
- package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
- package/src/builders/transact-write/index.ts +0 -2
- package/src/builders/transact-write/types.ts +0 -71
- package/src/builders/update/create-update-builder.test.ts +0 -573
- package/src/builders/update/create-update-builder.ts +0 -375
- package/src/builders/update/index.ts +0 -2
- package/src/builders/update/types.ts +0 -63
- package/src/core/types.test.ts +0 -641
- package/src/core/types.ts +0 -366
- package/src/entity/create-entity-api.ts +0 -224
- package/src/entity/index.ts +0 -19
- package/src/entity/middleware/factories.ts +0 -15
- package/src/entity/middleware/types.ts +0 -7
- package/src/entity/middleware/with-middleware.ts +0 -37
- package/src/entity/types.ts +0 -79
- package/src/entity/validation/key-validation.ts +0 -34
- package/src/index.ts +0 -23
- package/src/table.ts +0 -116
- package/src/utils/dynamodb-logger.test.ts +0 -246
- package/src/utils/dynamodb-logger.ts +0 -175
- package/src/utils/model-utils.test.ts +0 -452
- package/src/utils/model-utils.ts +0 -188
- package/src/utils/zod-utils.test.ts +0 -363
- package/src/utils/zod-utils.ts +0 -56
- package/tests/integration/instagram-clone.integration.test.ts +0 -1019
- package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
- package/tests/integration/transactions.integration.test.ts +0 -528
- package/tsconfig.json +0 -12
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
-
import { GetCommand } from '@aws-sdk/lib-dynamodb';
|
|
4
|
-
import { createGetBuilder } from './create-get-builder';
|
|
5
|
-
import { mockClient } from 'aws-sdk-client-mock';
|
|
6
|
-
|
|
7
|
-
const ddbMock = mockClient(DynamoDBClient);
|
|
8
|
-
|
|
9
|
-
type TestUser = {
|
|
10
|
-
username: string;
|
|
11
|
-
email: string;
|
|
12
|
-
name: string;
|
|
13
|
-
status: string;
|
|
14
|
-
age: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
describe('GetBuilder', () => {
|
|
18
|
-
let client: DynamoDBClient;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
ddbMock.reset();
|
|
22
|
-
client = new DynamoDBClient({});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('ProjectionExpression with ExpressionAttributeNames', () => {
|
|
26
|
-
it('should use ExpressionAttributeNames for simple attributes', () => {
|
|
27
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
28
|
-
'TestTable',
|
|
29
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
30
|
-
client
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const params = builder.select(['username', 'email']).dbParams();
|
|
34
|
-
|
|
35
|
-
expect(params.ProjectionExpression).toBe('#username, #email');
|
|
36
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
37
|
-
'#username': 'username',
|
|
38
|
-
'#email': 'email',
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should handle reserved words correctly', () => {
|
|
43
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
44
|
-
'TestTable',
|
|
45
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
46
|
-
client
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// 'name' and 'status' are DynamoDB reserved words
|
|
50
|
-
const params = builder.select(['name', 'status']).dbParams();
|
|
51
|
-
|
|
52
|
-
expect(params.ProjectionExpression).toBe('#name, #status');
|
|
53
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
54
|
-
'#name': 'name',
|
|
55
|
-
'#status': 'status',
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should work with mixed reserved and non-reserved words', () => {
|
|
60
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
61
|
-
'TestTable',
|
|
62
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
63
|
-
client
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const params = builder.select(['username', 'name', 'status', 'email']).dbParams();
|
|
67
|
-
|
|
68
|
-
expect(params.ProjectionExpression).toBe('#username, #name, #status, #email');
|
|
69
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
70
|
-
'#username': 'username',
|
|
71
|
-
'#name': 'name',
|
|
72
|
-
'#status': 'status',
|
|
73
|
-
'#email': 'email',
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should not add ExpressionAttributeNames when no projection', () => {
|
|
78
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
79
|
-
'TestTable',
|
|
80
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
81
|
-
client
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const params = builder.dbParams();
|
|
85
|
-
|
|
86
|
-
expect(params.ProjectionExpression).toBeUndefined();
|
|
87
|
-
expect(params.ExpressionAttributeNames).toBeUndefined();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('returnConsumedCapacity()', () => {
|
|
92
|
-
it('should add ReturnConsumedCapacity with TOTAL', () => {
|
|
93
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
94
|
-
'TestTable',
|
|
95
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
96
|
-
client
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
const params = builder.returnConsumedCapacity('TOTAL').dbParams();
|
|
100
|
-
|
|
101
|
-
expect(params.ReturnConsumedCapacity).toBe('TOTAL');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should add ReturnConsumedCapacity with INDEXES', () => {
|
|
105
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
106
|
-
'TestTable',
|
|
107
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
108
|
-
client
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const params = builder.returnConsumedCapacity('INDEXES').dbParams();
|
|
112
|
-
|
|
113
|
-
expect(params.ReturnConsumedCapacity).toBe('INDEXES');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should add ReturnConsumedCapacity with NONE', () => {
|
|
117
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
118
|
-
'TestTable',
|
|
119
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
120
|
-
client
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const params = builder.returnConsumedCapacity('NONE').dbParams();
|
|
124
|
-
|
|
125
|
-
expect(params.ReturnConsumedCapacity).toBe('NONE');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should not add ReturnConsumedCapacity when not specified', () => {
|
|
129
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
130
|
-
'TestTable',
|
|
131
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
132
|
-
client
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const params = builder.dbParams();
|
|
136
|
-
|
|
137
|
-
expect(params.ReturnConsumedCapacity).toBeUndefined();
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('Method chaining with immutability', () => {
|
|
142
|
-
it('should chain select() and returnConsumedCapacity()', () => {
|
|
143
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
144
|
-
'TestTable',
|
|
145
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
146
|
-
client
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
const params = builder
|
|
150
|
-
.select(['username', 'email'])
|
|
151
|
-
.returnConsumedCapacity('TOTAL')
|
|
152
|
-
.dbParams();
|
|
153
|
-
|
|
154
|
-
expect(params.ProjectionExpression).toBe('#username, #email');
|
|
155
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
156
|
-
'#username': 'username',
|
|
157
|
-
'#email': 'email',
|
|
158
|
-
});
|
|
159
|
-
expect(params.ReturnConsumedCapacity).toBe('TOTAL');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should chain consistentRead() and returnConsumedCapacity()', () => {
|
|
163
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
164
|
-
'TestTable',
|
|
165
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
166
|
-
client
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const params = builder.consistentRead().returnConsumedCapacity('INDEXES').dbParams();
|
|
170
|
-
|
|
171
|
-
expect(params.ConsistentRead).toBe(true);
|
|
172
|
-
expect(params.ReturnConsumedCapacity).toBe('INDEXES');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('should chain all methods together', () => {
|
|
176
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
177
|
-
'TestTable',
|
|
178
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
179
|
-
client
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
const params = builder
|
|
183
|
-
.select(['name', 'status'])
|
|
184
|
-
.consistentRead()
|
|
185
|
-
.returnConsumedCapacity('TOTAL')
|
|
186
|
-
.dbParams();
|
|
187
|
-
|
|
188
|
-
expect(params.ProjectionExpression).toBe('#name, #status');
|
|
189
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
190
|
-
'#name': 'name',
|
|
191
|
-
'#status': 'status',
|
|
192
|
-
});
|
|
193
|
-
expect(params.ConsistentRead).toBe(true);
|
|
194
|
-
expect(params.ReturnConsumedCapacity).toBe('TOTAL');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should maintain immutability - original builder unchanged', () => {
|
|
198
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
199
|
-
'TestTable',
|
|
200
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
201
|
-
client
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const builder2 = builder.select(['username']);
|
|
205
|
-
const builder3 = builder2.returnConsumedCapacity('TOTAL');
|
|
206
|
-
|
|
207
|
-
// Original builder should not have projection
|
|
208
|
-
const params1 = builder.dbParams();
|
|
209
|
-
expect(params1.ProjectionExpression).toBeUndefined();
|
|
210
|
-
expect(params1.ReturnConsumedCapacity).toBeUndefined();
|
|
211
|
-
|
|
212
|
-
// Builder2 should have projection but not returnConsumedCapacity
|
|
213
|
-
const params2 = builder2.dbParams();
|
|
214
|
-
expect(params2.ProjectionExpression).toBe('#username');
|
|
215
|
-
expect(params2.ReturnConsumedCapacity).toBeUndefined();
|
|
216
|
-
|
|
217
|
-
// Builder3 should have both
|
|
218
|
-
const params3 = builder3.dbParams();
|
|
219
|
-
expect(params3.ProjectionExpression).toBe('#username');
|
|
220
|
-
expect(params3.ReturnConsumedCapacity).toBe('TOTAL');
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe('execute()', () => {
|
|
225
|
-
it('should send GetCommand with correct parameters', async () => {
|
|
226
|
-
ddbMock.on(GetCommand).resolves({
|
|
227
|
-
Item: { username: 'alice', email: 'alice@example.com' },
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
231
|
-
'TestTable',
|
|
232
|
-
{ PK: 'USER#alice', SK: 'USER#alice' },
|
|
233
|
-
client
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
const result = await builder
|
|
237
|
-
.select(['username', 'email'])
|
|
238
|
-
.returnConsumedCapacity('TOTAL')
|
|
239
|
-
.execute();
|
|
240
|
-
|
|
241
|
-
expect(result).toEqual({ username: 'alice', email: 'alice@example.com' });
|
|
242
|
-
|
|
243
|
-
// Verify the command was called with correct params
|
|
244
|
-
const calls = ddbMock.commandCalls(GetCommand);
|
|
245
|
-
expect(calls).toHaveLength(1);
|
|
246
|
-
expect(calls[0]?.args[0]?.input).toMatchObject({
|
|
247
|
-
TableName: 'TestTable',
|
|
248
|
-
Key: { PK: 'USER#alice', SK: 'USER#alice' },
|
|
249
|
-
ProjectionExpression: '#username, #email',
|
|
250
|
-
ExpressionAttributeNames: {
|
|
251
|
-
'#username': 'username',
|
|
252
|
-
'#email': 'email',
|
|
253
|
-
},
|
|
254
|
-
ReturnConsumedCapacity: 'TOTAL',
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('should return undefined when item not found', async () => {
|
|
259
|
-
ddbMock.on(GetCommand).resolves({});
|
|
260
|
-
|
|
261
|
-
const builder = createGetBuilder<any, TestUser>(
|
|
262
|
-
'TestTable',
|
|
263
|
-
{ PK: 'USER#notfound', SK: 'USER#notfound' },
|
|
264
|
-
client
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
const result = await builder.execute();
|
|
268
|
-
|
|
269
|
-
expect(result).toBeUndefined();
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
-
import { GetCommand, GetCommandInput, GetCommandOutput } from '@aws-sdk/lib-dynamodb';
|
|
4
|
-
import { buildProjectionExpression } from '../shared';
|
|
5
|
-
import { GetBuilder } from './types';
|
|
6
|
-
import { DynamoDBLogger } from '../../utils/dynamodb-logger';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Creates a GetBuilder to retrieve an item by its key.
|
|
10
|
-
*/
|
|
11
|
-
export function createGetBuilder<KeyInput, Model>(
|
|
12
|
-
tableName: string,
|
|
13
|
-
key: Record<string, any>,
|
|
14
|
-
client: DynamoDBClient,
|
|
15
|
-
options?: {
|
|
16
|
-
projection?: (keyof Model)[];
|
|
17
|
-
consistentRead?: boolean;
|
|
18
|
-
returnConsumedCapacity?: 'INDEXES' | 'TOTAL' | 'NONE';
|
|
19
|
-
},
|
|
20
|
-
logger?: DynamoDBLogger
|
|
21
|
-
): GetBuilder<KeyInput, Model> {
|
|
22
|
-
const projection = options?.projection ?? [];
|
|
23
|
-
const isConsistent = options?.consistentRead ?? false;
|
|
24
|
-
const consumedCapacity = options?.returnConsumedCapacity;
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
/**
|
|
28
|
-
* Select only specific attributes to retrieve.
|
|
29
|
-
*/
|
|
30
|
-
select(attrs) {
|
|
31
|
-
return createGetBuilder(
|
|
32
|
-
tableName,
|
|
33
|
-
key,
|
|
34
|
-
client,
|
|
35
|
-
{
|
|
36
|
-
projection: attrs,
|
|
37
|
-
consistentRead: isConsistent,
|
|
38
|
-
returnConsumedCapacity: consumedCapacity,
|
|
39
|
-
},
|
|
40
|
-
logger
|
|
41
|
-
);
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Enable strongly consistent read.
|
|
46
|
-
*/
|
|
47
|
-
consistentRead() {
|
|
48
|
-
return createGetBuilder(
|
|
49
|
-
tableName,
|
|
50
|
-
key,
|
|
51
|
-
client,
|
|
52
|
-
{
|
|
53
|
-
projection,
|
|
54
|
-
consistentRead: true,
|
|
55
|
-
returnConsumedCapacity: consumedCapacity,
|
|
56
|
-
},
|
|
57
|
-
logger
|
|
58
|
-
);
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Configure ReturnConsumedCapacity parameter.
|
|
63
|
-
*/
|
|
64
|
-
returnConsumedCapacity(mode) {
|
|
65
|
-
return createGetBuilder(
|
|
66
|
-
tableName,
|
|
67
|
-
key,
|
|
68
|
-
client,
|
|
69
|
-
{
|
|
70
|
-
projection,
|
|
71
|
-
consistentRead: isConsistent,
|
|
72
|
-
returnConsumedCapacity: mode,
|
|
73
|
-
},
|
|
74
|
-
logger
|
|
75
|
-
);
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Build the underlying DynamoDB input parameters.
|
|
80
|
-
*/
|
|
81
|
-
dbParams(): GetCommandInput {
|
|
82
|
-
const params: GetCommandInput = {
|
|
83
|
-
TableName: tableName,
|
|
84
|
-
Key: key,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Add projection with proper ExpressionAttributeNames
|
|
88
|
-
if (projection.length > 0) {
|
|
89
|
-
const projectionExpr = buildProjectionExpression(projection as string[]);
|
|
90
|
-
params.ProjectionExpression = projectionExpr.ProjectionExpression;
|
|
91
|
-
params.ExpressionAttributeNames = projectionExpr.ExpressionAttributeNames;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Add consistent read if enabled
|
|
95
|
-
if (isConsistent) {
|
|
96
|
-
params.ConsistentRead = true;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Add ReturnConsumedCapacity if specified
|
|
100
|
-
if (consumedCapacity) {
|
|
101
|
-
params.ReturnConsumedCapacity = consumedCapacity;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return params;
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Execute the GetItem command and return the item if found.
|
|
109
|
-
*/
|
|
110
|
-
async execute(): Promise<Model | undefined> {
|
|
111
|
-
const params = this.dbParams();
|
|
112
|
-
const result: GetCommandOutput = await client.send(new GetCommand(params));
|
|
113
|
-
logger?.log('GetCommand', params, result);
|
|
114
|
-
return result.Item as unknown as Model | undefined;
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { GetCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
2
|
-
import { ExecutableBuilder } from '../shared';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Builder for DynamoDB GetItem operations.
|
|
6
|
-
* Supports projection, consistent read, and inspection of the final parameters.
|
|
7
|
-
*/
|
|
8
|
-
export interface GetBuilder<KeyInput, Model> extends Omit<
|
|
9
|
-
ExecutableBuilder<Model | undefined>,
|
|
10
|
-
'dbParams'
|
|
11
|
-
> {
|
|
12
|
-
/**
|
|
13
|
-
* Adds a projection expression to only return specific attributes.
|
|
14
|
-
* Returns a new immutable builder.
|
|
15
|
-
*/
|
|
16
|
-
select(attrs: (keyof Model)[]): GetBuilder<KeyInput, Model>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Enables strongly consistent read (eventual consistency is default).
|
|
20
|
-
* Returns a new immutable builder.
|
|
21
|
-
*/
|
|
22
|
-
consistentRead(): GetBuilder<KeyInput, Model>;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Configures the ReturnConsumedCapacity parameter.
|
|
26
|
-
* Returns a new immutable builder.
|
|
27
|
-
*
|
|
28
|
-
* @param mode - 'INDEXES' | 'TOTAL' | 'NONE'
|
|
29
|
-
* - INDEXES: Returns consumed capacity for table and indexes
|
|
30
|
-
* - TOTAL: Returns total consumed capacity
|
|
31
|
-
* - NONE: No consumed capacity data returned (default)
|
|
32
|
-
*/
|
|
33
|
-
returnConsumedCapacity(mode: 'INDEXES' | 'TOTAL' | 'NONE'): GetBuilder<KeyInput, Model>;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Converts the builder state to DynamoDB GetItem parameters
|
|
37
|
-
*/
|
|
38
|
-
dbParams(): GetCommandInput;
|
|
39
|
-
}
|
package/src/builders/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Shared utilities and types
|
|
2
|
-
export * from './shared';
|
|
3
|
-
|
|
4
|
-
// Operation builders
|
|
5
|
-
export * from './get';
|
|
6
|
-
export * from './put';
|
|
7
|
-
export * from './query';
|
|
8
|
-
export * from './scan';
|
|
9
|
-
export * from './update';
|
|
10
|
-
export * from './delete';
|
|
11
|
-
export * from './batch-get';
|
|
12
|
-
export * from './batch-write';
|
|
13
|
-
export * from './transact-write';
|
|
14
|
-
export * from './transact-get';
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
-
import { mockClient } from 'aws-sdk-client-mock';
|
|
3
|
-
import { PutCommand } from '@aws-sdk/lib-dynamodb';
|
|
4
|
-
import { createPutBuilder } from './create-put-builder';
|
|
5
|
-
|
|
6
|
-
const ddbMock = mockClient(DynamoDBClient);
|
|
7
|
-
|
|
8
|
-
describe('createPutBuilder', () => {
|
|
9
|
-
const tableName = 'test-table';
|
|
10
|
-
const client = new DynamoDBClient({});
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
ddbMock.reset();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('Basic functionality', () => {
|
|
17
|
-
it('should create a put builder with minimal config', () => {
|
|
18
|
-
const item = { id: '123', name: 'Test' };
|
|
19
|
-
const builder = createPutBuilder(tableName, item, client);
|
|
20
|
-
|
|
21
|
-
const params = builder.dbParams();
|
|
22
|
-
|
|
23
|
-
expect(params).toEqual({
|
|
24
|
-
TableName: tableName,
|
|
25
|
-
Item: item,
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should build params with conditions', () => {
|
|
30
|
-
const item = { id: '123', name: 'Test' };
|
|
31
|
-
const builder = createPutBuilder(tableName, item, client).where((attrs, ops) =>
|
|
32
|
-
ops.eq(attrs.id, '123')
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const params = builder.dbParams();
|
|
36
|
-
|
|
37
|
-
expect(params.ConditionExpression).toBe('#id = :id_0');
|
|
38
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#id': 'id' });
|
|
39
|
-
expect(params.ExpressionAttributeValues).toEqual({ ':id_0': '123' });
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('Timestamps support', () => {
|
|
44
|
-
it('should add createdAt and updatedAt when enableTimestamps is true', () => {
|
|
45
|
-
const item = { id: '123', name: 'Test' };
|
|
46
|
-
const builder = createPutBuilder(tableName, item, client, [], false, 'NONE', true);
|
|
47
|
-
|
|
48
|
-
const params = builder.dbParams();
|
|
49
|
-
|
|
50
|
-
expect(params.Item).toHaveProperty('createdAt');
|
|
51
|
-
expect(params.Item).toHaveProperty('updatedAt');
|
|
52
|
-
expect(typeof params.Item.createdAt).toBe('string');
|
|
53
|
-
expect(typeof params.Item.updatedAt).toBe('string');
|
|
54
|
-
expect(params.Item.createdAt).toBe(params.Item.updatedAt);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should not add timestamps when enableTimestamps is false', () => {
|
|
58
|
-
const item = { id: '123', name: 'Test' };
|
|
59
|
-
const builder = createPutBuilder(tableName, item, client, [], false, 'NONE', false);
|
|
60
|
-
|
|
61
|
-
const params = builder.dbParams();
|
|
62
|
-
|
|
63
|
-
expect(params.Item).not.toHaveProperty('createdAt');
|
|
64
|
-
expect(params.Item).not.toHaveProperty('updatedAt');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should preserve original item data with timestamps', () => {
|
|
68
|
-
const item = { id: '123', name: 'Test', value: 42 };
|
|
69
|
-
const builder = createPutBuilder(tableName, item, client, [], false, 'NONE', true);
|
|
70
|
-
|
|
71
|
-
const params = builder.dbParams();
|
|
72
|
-
|
|
73
|
-
expect(params.Item.id).toBe('123');
|
|
74
|
-
expect(params.Item.name).toBe('Test');
|
|
75
|
-
expect(params.Item.value).toBe(42);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('ifNotExists functionality', () => {
|
|
80
|
-
it('should add attribute_not_exists conditions for PK and SK', () => {
|
|
81
|
-
const item = { PK: 'user#123', SK: 'profile', name: 'Test' };
|
|
82
|
-
const builder = createPutBuilder(tableName, item, client).ifNotExists();
|
|
83
|
-
|
|
84
|
-
const params = builder.dbParams();
|
|
85
|
-
|
|
86
|
-
expect(params.ConditionExpression).toContain('attribute_not_exists(#PK)');
|
|
87
|
-
expect(params.ConditionExpression).toContain('attribute_not_exists(#SK)');
|
|
88
|
-
expect(params.ExpressionAttributeNames).toHaveProperty('#PK');
|
|
89
|
-
expect(params.ExpressionAttributeNames).toHaveProperty('#SK');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle lowercase pk and sk', () => {
|
|
93
|
-
const item = { pk: 'user#123', sk: 'profile', name: 'Test' };
|
|
94
|
-
const builder = createPutBuilder(tableName, item, client).ifNotExists();
|
|
95
|
-
|
|
96
|
-
const params = builder.dbParams();
|
|
97
|
-
|
|
98
|
-
expect(params.ConditionExpression).toContain('attribute_not_exists(#pk)');
|
|
99
|
-
expect(params.ConditionExpression).toContain('attribute_not_exists(#sk)');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should combine ifNotExists with other conditions', () => {
|
|
103
|
-
const item = { PK: 'user#123', SK: 'profile', name: 'Test' };
|
|
104
|
-
const builder = createPutBuilder(tableName, item, client)
|
|
105
|
-
.where((attrs, ops) => ops.eq(attrs.name, 'Test'))
|
|
106
|
-
.ifNotExists();
|
|
107
|
-
|
|
108
|
-
const params = builder.dbParams();
|
|
109
|
-
|
|
110
|
-
expect(params.ConditionExpression).toContain('attribute_not_exists(#PK)');
|
|
111
|
-
expect(params.ConditionExpression).toContain('#name = :name_0');
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('returning functionality', () => {
|
|
116
|
-
it('should set ReturnValues to NONE by default', () => {
|
|
117
|
-
const item = { id: '123', name: 'Test' };
|
|
118
|
-
const builder = createPutBuilder(tableName, item, client);
|
|
119
|
-
|
|
120
|
-
const params = builder.dbParams();
|
|
121
|
-
|
|
122
|
-
expect(params.ReturnValues).toBeUndefined();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should set ReturnValues to ALL_OLD when specified', () => {
|
|
126
|
-
const item = { id: '123', name: 'Test' };
|
|
127
|
-
const builder = createPutBuilder(tableName, item, client).returning('ALL_OLD');
|
|
128
|
-
|
|
129
|
-
const params = builder.dbParams();
|
|
130
|
-
|
|
131
|
-
expect(params.ReturnValues).toBe('ALL_OLD');
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe('execute functionality', () => {
|
|
136
|
-
it('should return the new item when returnMode is NONE', async () => {
|
|
137
|
-
const item = { id: '123', name: 'Test' };
|
|
138
|
-
|
|
139
|
-
ddbMock.on(PutCommand).resolves({});
|
|
140
|
-
|
|
141
|
-
const builder = createPutBuilder(tableName, item, client);
|
|
142
|
-
const result = await builder.execute();
|
|
143
|
-
|
|
144
|
-
expect(result).toEqual(item);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should return the old item when returnMode is ALL_OLD and item was replaced', async () => {
|
|
148
|
-
const item = { id: '123', name: 'New Name' };
|
|
149
|
-
const oldItem = { id: '123', name: 'Old Name' };
|
|
150
|
-
|
|
151
|
-
ddbMock.on(PutCommand).resolves({
|
|
152
|
-
Attributes: oldItem,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const builder = createPutBuilder(tableName, item, client).returning('ALL_OLD');
|
|
156
|
-
const result = await builder.execute();
|
|
157
|
-
|
|
158
|
-
expect(result).toEqual(oldItem);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should return the new item when returnMode is ALL_OLD but no old item existed', async () => {
|
|
162
|
-
const item = { id: '123', name: 'Test' };
|
|
163
|
-
|
|
164
|
-
ddbMock.on(PutCommand).resolves({});
|
|
165
|
-
|
|
166
|
-
const builder = createPutBuilder(tableName, item, client).returning('ALL_OLD');
|
|
167
|
-
const result = await builder.execute();
|
|
168
|
-
|
|
169
|
-
expect(result).toEqual(item);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should return item with timestamps when timestamps are enabled', async () => {
|
|
173
|
-
const item = { id: '123', name: 'Test' };
|
|
174
|
-
|
|
175
|
-
ddbMock.on(PutCommand).resolves({});
|
|
176
|
-
|
|
177
|
-
const builder = createPutBuilder(tableName, item, client, [], false, 'NONE', true);
|
|
178
|
-
const result = await builder.execute();
|
|
179
|
-
|
|
180
|
-
expect(result).toHaveProperty('createdAt');
|
|
181
|
-
expect(result).toHaveProperty('updatedAt');
|
|
182
|
-
expect(result.id).toBe('123');
|
|
183
|
-
expect(result.name).toBe('Test');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe('Method chaining', () => {
|
|
188
|
-
it('should allow chaining multiple methods', () => {
|
|
189
|
-
const item = { PK: 'user#123', SK: 'profile', name: 'Test' };
|
|
190
|
-
const builder = createPutBuilder(tableName, item, client)
|
|
191
|
-
.where((attrs, ops) => ops.eq(attrs.name, 'Test'))
|
|
192
|
-
.ifNotExists()
|
|
193
|
-
.returning('ALL_OLD');
|
|
194
|
-
|
|
195
|
-
const params = builder.dbParams();
|
|
196
|
-
|
|
197
|
-
expect(params.ConditionExpression).toBeTruthy();
|
|
198
|
-
expect(params.ReturnValues).toBe('ALL_OLD');
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('should preserve timestamps through chaining', () => {
|
|
202
|
-
const item = { id: '123', name: 'Test' };
|
|
203
|
-
const builder = createPutBuilder(tableName, item, client, [], false, 'NONE', true)
|
|
204
|
-
.where((attrs, ops) => ops.eq(attrs.id, '123'))
|
|
205
|
-
.returning('ALL_OLD');
|
|
206
|
-
|
|
207
|
-
const params = builder.dbParams();
|
|
208
|
-
|
|
209
|
-
expect(params.Item).toHaveProperty('createdAt');
|
|
210
|
-
expect(params.Item).toHaveProperty('updatedAt');
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
});
|