@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,573 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
-
import { createUpdateBuilder } from './create-update-builder';
|
|
4
|
-
|
|
5
|
-
describe('UpdateBuilder', () => {
|
|
6
|
-
const client = new DynamoDBClient({});
|
|
7
|
-
const tableName = 'TestTable';
|
|
8
|
-
|
|
9
|
-
interface TestModel {
|
|
10
|
-
pk: string;
|
|
11
|
-
sk: string;
|
|
12
|
-
name?: string;
|
|
13
|
-
age?: number;
|
|
14
|
-
score?: number;
|
|
15
|
-
tags?: string[];
|
|
16
|
-
followerCount?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('SET operations', () => {
|
|
20
|
-
test('should build params with single SET operation', () => {
|
|
21
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
22
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
23
|
-
.set('name', 'John Doe')
|
|
24
|
-
.dbParams();
|
|
25
|
-
|
|
26
|
-
expect(params.TableName).toBe(tableName);
|
|
27
|
-
expect(params.Key).toEqual(key);
|
|
28
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0');
|
|
29
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#name': 'name' });
|
|
30
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
31
|
-
':name_0': 'John Doe',
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('should build params with multiple SET operations', () => {
|
|
36
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
37
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
38
|
-
.set('name', 'John Doe')
|
|
39
|
-
.set('age', 30)
|
|
40
|
-
.set('score', 100)
|
|
41
|
-
.dbParams();
|
|
42
|
-
|
|
43
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0, #age = :age_1, #score = :score_2');
|
|
44
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
45
|
-
'#name': 'name',
|
|
46
|
-
'#age': 'age',
|
|
47
|
-
'#score': 'score',
|
|
48
|
-
});
|
|
49
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
50
|
-
':name_0': 'John Doe',
|
|
51
|
-
':age_1': 30,
|
|
52
|
-
':score_2': 100,
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('should build params with multiple SET operations using object', () => {
|
|
57
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
58
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
59
|
-
.set({
|
|
60
|
-
name: 'John Doe',
|
|
61
|
-
age: 30,
|
|
62
|
-
score: 100,
|
|
63
|
-
})
|
|
64
|
-
.dbParams();
|
|
65
|
-
|
|
66
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0, #age = :age_1, #score = :score_2');
|
|
67
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
68
|
-
'#name': 'name',
|
|
69
|
-
'#age': 'age',
|
|
70
|
-
'#score': 'score',
|
|
71
|
-
});
|
|
72
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
73
|
-
':name_0': 'John Doe',
|
|
74
|
-
':age_1': 30,
|
|
75
|
-
':score_2': 100,
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('should build params with single-property object whose key is "name"', () => {
|
|
80
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
81
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
82
|
-
.set({ name: 'Ministro Pistarini' })
|
|
83
|
-
.dbParams();
|
|
84
|
-
|
|
85
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0');
|
|
86
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#name': 'name' });
|
|
87
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
88
|
-
':name_0': 'Ministro Pistarini',
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('should build params with single-property object whose key is not "name"', () => {
|
|
93
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
94
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
95
|
-
.set({ age: 30 })
|
|
96
|
-
.dbParams();
|
|
97
|
-
|
|
98
|
-
expect(params.UpdateExpression).toBe('SET #age = :age_0');
|
|
99
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#age': 'age' });
|
|
100
|
-
expect(params.ExpressionAttributeValues).toEqual({ ':age_0': 30 });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('should combine single and multiple SET operations', () => {
|
|
104
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
105
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
106
|
-
.set('name', 'John Doe')
|
|
107
|
-
.set({ age: 30, score: 100 })
|
|
108
|
-
.dbParams();
|
|
109
|
-
|
|
110
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0, #age = :age_1, #score = :score_2');
|
|
111
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
112
|
-
'#name': 'name',
|
|
113
|
-
'#age': 'age',
|
|
114
|
-
'#score': 'score',
|
|
115
|
-
});
|
|
116
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
117
|
-
':name_0': 'John Doe',
|
|
118
|
-
':age_1': 30,
|
|
119
|
-
':score_2': 100,
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('REMOVE operations', () => {
|
|
125
|
-
test('should build params with single REMOVE operation', () => {
|
|
126
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
127
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
128
|
-
.remove('age')
|
|
129
|
-
.dbParams();
|
|
130
|
-
|
|
131
|
-
expect(params.UpdateExpression).toBe('REMOVE #age');
|
|
132
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#age': 'age' });
|
|
133
|
-
expect(params.ExpressionAttributeValues).toBeUndefined();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('should build params with multiple REMOVE operations', () => {
|
|
137
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
138
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
139
|
-
.remove('age')
|
|
140
|
-
.remove('score')
|
|
141
|
-
.dbParams();
|
|
142
|
-
|
|
143
|
-
expect(params.UpdateExpression).toBe('REMOVE #age, #score');
|
|
144
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
145
|
-
'#age': 'age',
|
|
146
|
-
'#score': 'score',
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe('ADD operations', () => {
|
|
152
|
-
test('should build params with single ADD operation', () => {
|
|
153
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
154
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
155
|
-
.add('followerCount', 1)
|
|
156
|
-
.dbParams();
|
|
157
|
-
|
|
158
|
-
expect(params.UpdateExpression).toBe('ADD #followerCount :followerCount_0');
|
|
159
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
160
|
-
'#followerCount': 'followerCount',
|
|
161
|
-
});
|
|
162
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
163
|
-
':followerCount_0': 1,
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('should build params with multiple ADD operations', () => {
|
|
168
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
169
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
170
|
-
.add('followerCount', 1)
|
|
171
|
-
.add('score', 10)
|
|
172
|
-
.dbParams();
|
|
173
|
-
|
|
174
|
-
expect(params.UpdateExpression).toBe('ADD #followerCount :followerCount_0, #score :score_1');
|
|
175
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
176
|
-
'#followerCount': 'followerCount',
|
|
177
|
-
'#score': 'score',
|
|
178
|
-
});
|
|
179
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
180
|
-
':followerCount_0': 1,
|
|
181
|
-
':score_1': 10,
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
describe('DELETE operations', () => {
|
|
187
|
-
test('should build params with single DELETE operation', () => {
|
|
188
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
189
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
190
|
-
.delete('tags', ['inactive'])
|
|
191
|
-
.dbParams();
|
|
192
|
-
|
|
193
|
-
expect(params.UpdateExpression).toBe('DELETE #tags :tags_0');
|
|
194
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#tags': 'tags' });
|
|
195
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
196
|
-
':tags_0': ['inactive'],
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test('should build params with multiple DELETE operations', () => {
|
|
201
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
202
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
203
|
-
.delete('tags', ['inactive'])
|
|
204
|
-
.delete('tags', ['old'])
|
|
205
|
-
.dbParams();
|
|
206
|
-
|
|
207
|
-
expect(params.UpdateExpression).toBe('DELETE #tags :tags_0, #tags :tags_1');
|
|
208
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#tags': 'tags' });
|
|
209
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
210
|
-
':tags_0': ['inactive'],
|
|
211
|
-
':tags_1': ['old'],
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe('Mixed operations', () => {
|
|
217
|
-
test('should build params with SET and REMOVE', () => {
|
|
218
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
219
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
220
|
-
.set('name', 'John Doe')
|
|
221
|
-
.remove('age')
|
|
222
|
-
.dbParams();
|
|
223
|
-
|
|
224
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0 REMOVE #age');
|
|
225
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
226
|
-
'#name': 'name',
|
|
227
|
-
'#age': 'age',
|
|
228
|
-
});
|
|
229
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
230
|
-
':name_0': 'John Doe',
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('should build params with all operation types', () => {
|
|
235
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
236
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
237
|
-
.set('name', 'John Doe')
|
|
238
|
-
.remove('age')
|
|
239
|
-
.add('followerCount', 1)
|
|
240
|
-
.delete('tags', ['old'])
|
|
241
|
-
.dbParams();
|
|
242
|
-
|
|
243
|
-
expect(params.UpdateExpression).toBe(
|
|
244
|
-
'SET #name = :name_0 REMOVE #age ADD #followerCount :followerCount_1 DELETE #tags :tags_2'
|
|
245
|
-
);
|
|
246
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
247
|
-
'#name': 'name',
|
|
248
|
-
'#age': 'age',
|
|
249
|
-
'#followerCount': 'followerCount',
|
|
250
|
-
'#tags': 'tags',
|
|
251
|
-
});
|
|
252
|
-
expect(params.ExpressionAttributeValues).toEqual({
|
|
253
|
-
':name_0': 'John Doe',
|
|
254
|
-
':followerCount_1': 1,
|
|
255
|
-
':tags_2': ['old'],
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
describe('Condition expressions', () => {
|
|
261
|
-
test('should build params with where condition', () => {
|
|
262
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
263
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
264
|
-
.set('name', 'John Doe')
|
|
265
|
-
.where((attr, op) => op.eq(attr.age, 25))
|
|
266
|
-
.dbParams();
|
|
267
|
-
|
|
268
|
-
expect(params.UpdateExpression).toBe('SET #name = :name_0');
|
|
269
|
-
expect(params.ConditionExpression).toMatch(/#age = :age_\d+/);
|
|
270
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
271
|
-
'#name': 'name',
|
|
272
|
-
'#age': 'age',
|
|
273
|
-
});
|
|
274
|
-
expect(Object.values(params.ExpressionAttributeValues || {})).toContain('John Doe');
|
|
275
|
-
expect(Object.values(params.ExpressionAttributeValues || {})).toContain(25);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test('should build params with multiple where conditions using AND', () => {
|
|
279
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
280
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
281
|
-
.set('name', 'John Doe')
|
|
282
|
-
.where((attr, op) => op.and(op.gt(attr.age, 18), op.lt(attr.age, 65)))
|
|
283
|
-
.dbParams();
|
|
284
|
-
|
|
285
|
-
expect(params.ConditionExpression).toMatch(/\(#age > :age_\d+\) AND \(#age < :age_\d+\)/);
|
|
286
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
287
|
-
'#name': 'name',
|
|
288
|
-
'#age': 'age',
|
|
289
|
-
});
|
|
290
|
-
expect(Object.values(params.ExpressionAttributeValues || {})).toContain(18);
|
|
291
|
-
expect(Object.values(params.ExpressionAttributeValues || {})).toContain(65);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test('should build params with OR conditions', () => {
|
|
295
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
296
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
297
|
-
.set('name', 'John Doe')
|
|
298
|
-
.where((attr, op) => op.or(op.eq(attr.age, 25), op.eq(attr.age, 30)))
|
|
299
|
-
.dbParams();
|
|
300
|
-
|
|
301
|
-
expect(params.ConditionExpression).toMatch(/\(#age = :age_\d+\) OR \(#age = :age_\d+\)/);
|
|
302
|
-
expect(Object.values(params.ExpressionAttributeValues || {})).toContain(25);
|
|
303
|
-
expect(Object.values(params.ExpressionAttributeValues || {})).toContain(30);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe('ReturnValues', () => {
|
|
308
|
-
test('should default to NONE', () => {
|
|
309
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
310
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
311
|
-
.set('name', 'John Doe')
|
|
312
|
-
.dbParams();
|
|
313
|
-
|
|
314
|
-
expect(params.ReturnValues).toBeUndefined();
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test('should support ALL_OLD', () => {
|
|
318
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
319
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
320
|
-
.set('name', 'John Doe')
|
|
321
|
-
.returning('ALL_OLD')
|
|
322
|
-
.dbParams();
|
|
323
|
-
|
|
324
|
-
expect(params.ReturnValues).toBe('ALL_OLD');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test('should support ALL_NEW', () => {
|
|
328
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
329
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
330
|
-
.set('name', 'John Doe')
|
|
331
|
-
.returning('ALL_NEW')
|
|
332
|
-
.dbParams();
|
|
333
|
-
|
|
334
|
-
expect(params.ReturnValues).toBe('ALL_NEW');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test('should support UPDATED_OLD', () => {
|
|
338
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
339
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
340
|
-
.set('name', 'John Doe')
|
|
341
|
-
.returning('UPDATED_OLD')
|
|
342
|
-
.dbParams();
|
|
343
|
-
|
|
344
|
-
expect(params.ReturnValues).toBe('UPDATED_OLD');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test('should support UPDATED_NEW', () => {
|
|
348
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
349
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
350
|
-
.set('name', 'John Doe')
|
|
351
|
-
.returning('UPDATED_NEW')
|
|
352
|
-
.dbParams();
|
|
353
|
-
|
|
354
|
-
expect(params.ReturnValues).toBe('UPDATED_NEW');
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
describe('Immutability', () => {
|
|
359
|
-
test('should create new builder instance on each method call', () => {
|
|
360
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
361
|
-
const builder1 = createUpdateBuilder<TestModel>(tableName, key, client);
|
|
362
|
-
const builder2 = builder1.set('name', 'John');
|
|
363
|
-
const builder3 = builder2.set('age', 30);
|
|
364
|
-
|
|
365
|
-
expect(builder1.dbParams().UpdateExpression).toBeUndefined();
|
|
366
|
-
expect(builder2.dbParams().UpdateExpression).toBe('SET #name = :name_0');
|
|
367
|
-
expect(builder3.dbParams().UpdateExpression).toBe('SET #name = :name_0, #age = :age_1');
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
describe('Secondary-index recomputation (indexContext)', () => {
|
|
372
|
-
interface PersonnelModel {
|
|
373
|
-
id: string;
|
|
374
|
-
airportId: string;
|
|
375
|
-
firstName: string;
|
|
376
|
-
lastName: string;
|
|
377
|
-
role: string;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const personnelModel = {
|
|
381
|
-
key: {
|
|
382
|
-
PK: { type: String, value: 'PERSON#${id}' },
|
|
383
|
-
SK: { type: String, value: 'PROFILE' },
|
|
384
|
-
},
|
|
385
|
-
index: {
|
|
386
|
-
GSI1PK: { type: String, value: 'AIRPORT#${airportId}' },
|
|
387
|
-
GSI1SK: { type: String, value: 'PERSON#${lastName}#${firstName}' },
|
|
388
|
-
},
|
|
389
|
-
attributes: {
|
|
390
|
-
id: { type: String, required: true },
|
|
391
|
-
airportId: { type: String, required: true },
|
|
392
|
-
firstName: { type: String, required: true },
|
|
393
|
-
lastName: { type: String, required: true },
|
|
394
|
-
role: { type: String },
|
|
395
|
-
},
|
|
396
|
-
} as const;
|
|
397
|
-
|
|
398
|
-
test('does nothing when no .set() field appears in any index template', () => {
|
|
399
|
-
const params = createUpdateBuilder<PersonnelModel>(
|
|
400
|
-
tableName,
|
|
401
|
-
{ id: '1' } as Partial<PersonnelModel>,
|
|
402
|
-
client,
|
|
403
|
-
[],
|
|
404
|
-
{ set: [], remove: [], add: [], delete: [] },
|
|
405
|
-
'NONE',
|
|
406
|
-
0,
|
|
407
|
-
false,
|
|
408
|
-
undefined,
|
|
409
|
-
{ model: personnelModel as any, keyVars: { id: '1' } }
|
|
410
|
-
)
|
|
411
|
-
.set('role', 'pilot')
|
|
412
|
-
.dbParams();
|
|
413
|
-
|
|
414
|
-
expect(params.UpdateExpression).toBe('SET #role = :role_0');
|
|
415
|
-
expect(params.ExpressionAttributeNames).toEqual({ '#role': 'role' });
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
test('recomputes affected index when all template vars are present in updates', () => {
|
|
419
|
-
const params = createUpdateBuilder<PersonnelModel>(
|
|
420
|
-
tableName,
|
|
421
|
-
{ id: '1' } as Partial<PersonnelModel>,
|
|
422
|
-
client,
|
|
423
|
-
[],
|
|
424
|
-
{ set: [], remove: [], add: [], delete: [] },
|
|
425
|
-
'NONE',
|
|
426
|
-
0,
|
|
427
|
-
false,
|
|
428
|
-
undefined,
|
|
429
|
-
{ model: personnelModel as any, keyVars: { id: '1' } }
|
|
430
|
-
)
|
|
431
|
-
.set({ firstName: 'Ada', lastName: 'Lovelace' })
|
|
432
|
-
.dbParams();
|
|
433
|
-
|
|
434
|
-
// GSI1SK depends on lastName + firstName — both supplied → recomputed.
|
|
435
|
-
// GSI1PK depends on airportId — untouched, no recompute.
|
|
436
|
-
expect(params.UpdateExpression).toBe(
|
|
437
|
-
'SET #firstName = :firstName_0, #lastName = :lastName_1, #GSI1SK = :GSI1SK_2'
|
|
438
|
-
);
|
|
439
|
-
expect(params.ExpressionAttributeValues).toMatchObject({
|
|
440
|
-
':firstName_0': 'Ada',
|
|
441
|
-
':lastName_1': 'Lovelace',
|
|
442
|
-
':GSI1SK_2': 'PERSON#Lovelace#Ada',
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
test('throws when an affected index template references a field not in updates or key', () => {
|
|
447
|
-
const builder = createUpdateBuilder<PersonnelModel>(
|
|
448
|
-
tableName,
|
|
449
|
-
{ id: '1' } as Partial<PersonnelModel>,
|
|
450
|
-
client,
|
|
451
|
-
[],
|
|
452
|
-
{ set: [], remove: [], add: [], delete: [] },
|
|
453
|
-
'NONE',
|
|
454
|
-
0,
|
|
455
|
-
false,
|
|
456
|
-
undefined,
|
|
457
|
-
{ model: personnelModel as any, keyVars: { id: '1' } }
|
|
458
|
-
).set({ lastName: 'Lovelace' });
|
|
459
|
-
|
|
460
|
-
// GSI1SK template = "PERSON#${lastName}#${firstName}" — firstName is missing.
|
|
461
|
-
expect(() => builder.dbParams()).toThrow(/firstName/);
|
|
462
|
-
expect(() => builder.dbParams()).toThrow(/GSI1SK/);
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
test('uses primary-key template vars when resolving index templates', () => {
|
|
466
|
-
const userModel = {
|
|
467
|
-
key: {
|
|
468
|
-
PK: { type: String, value: 'USER#${username}' },
|
|
469
|
-
SK: { type: String, value: 'USER#${username}' },
|
|
470
|
-
},
|
|
471
|
-
index: {
|
|
472
|
-
GSI1PK: { type: String, value: 'USER#${username}#STATUS#${status}' },
|
|
473
|
-
},
|
|
474
|
-
attributes: {
|
|
475
|
-
username: { type: String, required: true },
|
|
476
|
-
status: { type: String, required: true },
|
|
477
|
-
},
|
|
478
|
-
} as const;
|
|
479
|
-
|
|
480
|
-
const params = createUpdateBuilder<{ username: string; status: string }>(
|
|
481
|
-
tableName,
|
|
482
|
-
{ username: 'jane' } as any,
|
|
483
|
-
client,
|
|
484
|
-
[],
|
|
485
|
-
{ set: [], remove: [], add: [], delete: [] },
|
|
486
|
-
'NONE',
|
|
487
|
-
0,
|
|
488
|
-
false,
|
|
489
|
-
undefined,
|
|
490
|
-
{ model: userModel as any, keyVars: { username: 'jane' } }
|
|
491
|
-
)
|
|
492
|
-
.set('status', 'active')
|
|
493
|
-
.dbParams();
|
|
494
|
-
|
|
495
|
-
expect(params.UpdateExpression).toBe(
|
|
496
|
-
'SET #status = :status_0, #GSI1PK = :GSI1PK_1'
|
|
497
|
-
);
|
|
498
|
-
expect(params.ExpressionAttributeValues).toMatchObject({
|
|
499
|
-
':status_0': 'active',
|
|
500
|
-
':GSI1PK_1': 'USER#jane#STATUS#active',
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test('accumulates set inputs across chained calls before resolving', () => {
|
|
505
|
-
const params = createUpdateBuilder<PersonnelModel>(
|
|
506
|
-
tableName,
|
|
507
|
-
{ id: '1' } as Partial<PersonnelModel>,
|
|
508
|
-
client,
|
|
509
|
-
[],
|
|
510
|
-
{ set: [], remove: [], add: [], delete: [] },
|
|
511
|
-
'NONE',
|
|
512
|
-
0,
|
|
513
|
-
false,
|
|
514
|
-
undefined,
|
|
515
|
-
{ model: personnelModel as any, keyVars: { id: '1' } }
|
|
516
|
-
)
|
|
517
|
-
.set('lastName', 'Lovelace')
|
|
518
|
-
.set('firstName', 'Ada')
|
|
519
|
-
.dbParams();
|
|
520
|
-
|
|
521
|
-
// Each individual .set() was missing one var, but the accumulator has both
|
|
522
|
-
// by the time dbParams() runs.
|
|
523
|
-
expect(params.UpdateExpression).toBe(
|
|
524
|
-
'SET #lastName = :lastName_0, #firstName = :firstName_1, #GSI1SK = :GSI1SK_2'
|
|
525
|
-
);
|
|
526
|
-
expect(params.ExpressionAttributeValues).toMatchObject({
|
|
527
|
-
':GSI1SK_2': 'PERSON#Lovelace#Ada',
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test('does nothing when indexContext is omitted (backwards-compatible default)', () => {
|
|
532
|
-
const params = createUpdateBuilder<PersonnelModel>(
|
|
533
|
-
tableName,
|
|
534
|
-
{ id: '1' } as Partial<PersonnelModel>,
|
|
535
|
-
client
|
|
536
|
-
)
|
|
537
|
-
.set({ lastName: 'Lovelace' })
|
|
538
|
-
.dbParams();
|
|
539
|
-
|
|
540
|
-
expect(params.UpdateExpression).toBe('SET #lastName = :lastName_0');
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
describe('Complex scenarios', () => {
|
|
545
|
-
test('should handle complex update with all features', () => {
|
|
546
|
-
const key: Partial<TestModel> = { pk: 'USER#1', sk: 'USER#1' };
|
|
547
|
-
const params = createUpdateBuilder<TestModel>(tableName, key, client)
|
|
548
|
-
.set('name', 'John Doe')
|
|
549
|
-
.set('score', 100)
|
|
550
|
-
.add('followerCount', 5)
|
|
551
|
-
.remove('age')
|
|
552
|
-
.where((attr, op) => op.and(op.gt(attr.followerCount, 0), op.lt(attr.followerCount, 1000)))
|
|
553
|
-
.returning('ALL_NEW')
|
|
554
|
-
.dbParams();
|
|
555
|
-
|
|
556
|
-
expect(params.TableName).toBe(tableName);
|
|
557
|
-
expect(params.Key).toEqual(key);
|
|
558
|
-
expect(params.UpdateExpression).toContain('SET');
|
|
559
|
-
expect(params.UpdateExpression).toContain('ADD');
|
|
560
|
-
expect(params.UpdateExpression).toContain('REMOVE');
|
|
561
|
-
expect(params.ConditionExpression).toMatch(
|
|
562
|
-
/\(#followerCount > :followerCount_\d+\) AND \(#followerCount < :followerCount_\d+\)/
|
|
563
|
-
);
|
|
564
|
-
expect(params.ReturnValues).toBe('ALL_NEW');
|
|
565
|
-
expect(params.ExpressionAttributeNames).toEqual({
|
|
566
|
-
'#name': 'name',
|
|
567
|
-
'#score': 'score',
|
|
568
|
-
'#followerCount': 'followerCount',
|
|
569
|
-
'#age': 'age',
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
|
-
});
|