Skip to content

Commit 8c40215

Browse files
Copilotsnehithv
andauthored
Add removeUndefinedValues support for update operations (#178)
* Initial plan * Add removeUndefinedValues option and filter undefined in updates Co-authored-by: snehithv <7270678+snehithv@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: snehithv <7270678+snehithv@users.noreply.github.com>
1 parent c5d7a80 commit 8c40215

File tree

5 files changed

+88
-5
lines changed

5 files changed

+88
-5
lines changed

src/DynamoHelper.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { DynamoHelper } from './DynamoHelper';
2+
import { DynamoDBDocumentClient, UpdateCommand } from '@aws-sdk/lib-dynamodb';
3+
4+
describe('DynamoHelper', () => {
5+
const tableConfig = {
6+
name: 'test-table',
7+
indexes: {
8+
default: {
9+
partitionKeyName: 'pk',
10+
sortKeyName: 'sk',
11+
},
12+
},
13+
};
14+
15+
it('should create DynamoDBDocumentClient with removeUndefinedValues option', () => {
16+
const helper = new DynamoHelper(tableConfig, 'us-east-1');
17+
18+
// Verify that the dbClient is an instance of DynamoDBDocumentClient
19+
expect(helper.dbClient).toBeDefined();
20+
expect(helper.dbClient).toBeInstanceOf(DynamoDBDocumentClient);
21+
});
22+
23+
it('should handle updates with undefined values correctly', async () => {
24+
const helper = new DynamoHelper(tableConfig, 'us-east-1');
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
const spy = jest.spyOn(helper.dbClient, 'send').mockResolvedValue({} as any);
27+
28+
const key = { pk: 'test_pk', sk: 'test_sk' };
29+
const item = {
30+
name: 'Test',
31+
age: 30,
32+
undefinedField: undefined,
33+
description: 'A test item',
34+
};
35+
36+
await helper.updateItem(key, item, []);
37+
38+
// Verify that the send method was called
39+
expect(spy).toHaveBeenCalled();
40+
41+
// Verify that the UpdateExpression does not include the undefinedField
42+
const command = spy.mock.calls[0][0] as UpdateCommand;
43+
expect(command.input.UpdateExpression).toBeDefined();
44+
expect(command.input.UpdateExpression).not.toContain('undefinedField');
45+
expect(command.input.UpdateExpression).toContain('name');
46+
expect(command.input.UpdateExpression).toContain('age');
47+
expect(command.input.UpdateExpression).toContain('description');
48+
49+
spy.mockRestore();
50+
});
51+
});

src/DynamoHelper.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ export class DynamoHelper {
4343
region,
4444
endpoint,
4545
});
46-
this.dbClient = DynamoDBDocumentClient.from(client);
46+
this.dbClient = DynamoDBDocumentClient.from(client, {
47+
marshallOptions: {
48+
removeUndefinedValues: true,
49+
},
50+
});
4751
this.table = table;
4852
}
4953

src/mutation/expressionBuilder.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,30 @@ describe('expressionBuilder', () => {
133133
expect(result.attrNames).toEqual({});
134134
expect(result.attrValues).toEqual({});
135135
});
136+
137+
it('should remove undefined values from update expression', () => {
138+
const result = buildUpdateExpressions({
139+
name: 'John',
140+
age: 30,
141+
undefinedField: undefined,
142+
status: 'active',
143+
});
144+
145+
expect(result.expression).toContain('SET ');
146+
expect(result.expression).toContain('#key_name = :val_name');
147+
expect(result.expression).toContain('#key_age = :val_age');
148+
expect(result.expression).toContain('#key_status = :val_status');
149+
expect(result.expression).not.toContain('undefinedField');
150+
expect(result.attrNames).toEqual({
151+
'#key_name': 'name',
152+
'#key_age': 'age',
153+
'#key_status': 'status',
154+
});
155+
expect(result.attrValues).toEqual({
156+
':val_name': 'John',
157+
':val_age': 30,
158+
':val_status': 'active',
159+
});
160+
});
136161
});
137162
});

src/mutation/expressionBuilder.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ export const buildUpdateExpressions = (item: object): ConditionExpressionReturn
6262
const expressionNames = {};
6363

6464
Object.keys(item)?.forEach(key => {
65-
expressions.push(`#key_${key} = :val_${key}`);
66-
expressionNames[`#key_${key}`] = key;
67-
expressionValues[`:val_${key}`] = item[key];
65+
// Skip undefined values to remove them from update expression
66+
if (item[key] !== undefined) {
67+
expressions.push(`#key_${key} = :val_${key}`);
68+
expressionNames[`#key_${key}`] = key;
69+
expressionValues[`:val_${key}`] = item[key];
70+
}
6871
});
6972

7073
return {

tsconfig.build.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
"skipLibCheck": true
1313
},
1414

15-
"exclude": ["node_modules", "dist"]
15+
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
1616
}

0 commit comments

Comments
 (0)