@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/builders/query/create-query-builder.d.ts.map +1 -1
  3. package/dist/builders/query/create-query-builder.js +19 -2
  4. package/dist/core/types.d.ts +5 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/package.json +11 -2
  7. package/eslint.config.mjs +0 -4
  8. package/jest.config.js +0 -11
  9. package/src/builders/README.md +0 -339
  10. package/src/builders/batch-get/README.md +0 -43
  11. package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
  12. package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
  13. package/src/builders/batch-get/index.ts +0 -2
  14. package/src/builders/batch-get/types.ts +0 -28
  15. package/src/builders/batch-write/README.md +0 -204
  16. package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
  17. package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
  18. package/src/builders/batch-write/index.ts +0 -2
  19. package/src/builders/batch-write/types.ts +0 -33
  20. package/src/builders/delete/create-delete-builder.test.ts +0 -294
  21. package/src/builders/delete/create-delete-builder.ts +0 -100
  22. package/src/builders/delete/index.ts +0 -2
  23. package/src/builders/delete/types.ts +0 -17
  24. package/src/builders/get/create-get-builder.test.ts +0 -272
  25. package/src/builders/get/create-get-builder.ts +0 -117
  26. package/src/builders/get/index.ts +0 -2
  27. package/src/builders/get/types.ts +0 -39
  28. package/src/builders/index.ts +0 -14
  29. package/src/builders/put/create-put-builder.test.ts +0 -213
  30. package/src/builders/put/create-put-builder.ts +0 -160
  31. package/src/builders/put/index.ts +0 -2
  32. package/src/builders/put/types.ts +0 -24
  33. package/src/builders/query/create-query-builder.test.ts +0 -770
  34. package/src/builders/query/create-query-builder.ts +0 -455
  35. package/src/builders/query/index.ts +0 -2
  36. package/src/builders/query/types.ts +0 -109
  37. package/src/builders/scan/create-scan-builder.test.ts +0 -416
  38. package/src/builders/scan/create-scan-builder.ts +0 -265
  39. package/src/builders/scan/index.ts +0 -2
  40. package/src/builders/scan/types.ts +0 -88
  41. package/src/builders/shared/conditions.ts +0 -58
  42. package/src/builders/shared/index.ts +0 -4
  43. package/src/builders/shared/operators.test.ts +0 -270
  44. package/src/builders/shared/operators.ts +0 -208
  45. package/src/builders/shared/projection.ts +0 -28
  46. package/src/builders/shared/types.ts +0 -101
  47. package/src/builders/transact-get/README.md +0 -167
  48. package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
  49. package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
  50. package/src/builders/transact-get/index.ts +0 -2
  51. package/src/builders/transact-get/types.ts +0 -31
  52. package/src/builders/transact-write/README.md +0 -202
  53. package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
  54. package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
  55. package/src/builders/transact-write/index.ts +0 -2
  56. package/src/builders/transact-write/types.ts +0 -71
  57. package/src/builders/update/create-update-builder.test.ts +0 -573
  58. package/src/builders/update/create-update-builder.ts +0 -375
  59. package/src/builders/update/index.ts +0 -2
  60. package/src/builders/update/types.ts +0 -63
  61. package/src/core/types.test.ts +0 -641
  62. package/src/core/types.ts +0 -366
  63. package/src/entity/create-entity-api.ts +0 -224
  64. package/src/entity/index.ts +0 -19
  65. package/src/entity/middleware/factories.ts +0 -15
  66. package/src/entity/middleware/types.ts +0 -7
  67. package/src/entity/middleware/with-middleware.ts +0 -37
  68. package/src/entity/types.ts +0 -79
  69. package/src/entity/validation/key-validation.ts +0 -34
  70. package/src/index.ts +0 -23
  71. package/src/table.ts +0 -116
  72. package/src/utils/dynamodb-logger.test.ts +0 -246
  73. package/src/utils/dynamodb-logger.ts +0 -175
  74. package/src/utils/model-utils.test.ts +0 -452
  75. package/src/utils/model-utils.ts +0 -188
  76. package/src/utils/zod-utils.test.ts +0 -363
  77. package/src/utils/zod-utils.ts +0 -56
  78. package/tests/integration/instagram-clone.integration.test.ts +0 -1019
  79. package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
  80. package/tests/integration/transactions.integration.test.ts +0 -528
  81. 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,2 +0,0 @@
1
- export * from './types';
2
- export * from './create-get-builder';
@@ -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
- }
@@ -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
- });