Skip to content

Commit

Permalink
feat: avoid instantiating DocumentClient
Browse files Browse the repository at this point in the history
This refactor avoids instantiating a DynamoDB.DocumentClient object
(which in turn would instantiate an underlying DynamoDB service client)
by accessing the `createSet` method from the prototype directly.

The interface for `createSet` is:

```typescript
    createSet(list: number[]|string[]|DocumentClient.binaryType[], options?: DocumentClient.CreateSetOptions): DocumentClient.DynamoDbSet;
```

And the implementation is:

```typescript
  createSet: function(list, options) {
    options = options || {};
    return new DynamoDBSet(list, options);
  }
```

As we can see, there is no reliance on the `this` instance, so calling
the method from the prototype with an undefined `this` value should work
fine. It would have perhaps been preferred to utilize the `DynamoDBSet`
class directly, but that is marked with `@private` annotations within
the AWS SDK while the `createSet` method on DocumentClient is part of
the public API.

Aside from the removal of client instantiation, this change also scopes
imports to just the DynamoDB specific resources. This has been measured
to improve performance by several milliseconds in Lambda runtimes.

This somewhat relates to issue #29, but it doesn't really address it.
  • Loading branch information
bilalq authored and ruicsh committed Jul 4, 2022
1 parent 49b74f1 commit 7f89a18
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 13 deletions.
7 changes: 3 additions & 4 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import AWS from 'aws-sdk';

import { DocumentClient } from 'aws-sdk/clients/dynamodb';
import type { DynoexprOutput } from './dynoexpr';
import dynoexpr from '.';

Expand All @@ -23,13 +22,13 @@ describe('high level API', () => {

it('accepts a type to be applied to the output', () => {
expect.assertions(1);
const params = dynoexpr<AWS.DynamoDB.DocumentClient.UpdateItemInput>({
const params = dynoexpr<DocumentClient.UpdateItemInput>({
TableName: 'Table',
Key: 123,
UpdateSet: { color: 'pink' },
});

assertType<AWS.DynamoDB.DocumentClient.ScanInput, typeof params>();
assertType<DocumentClient.ScanInput, typeof params>();
expect(params.Key).toBe(123);
});
});
56 changes: 50 additions & 6 deletions src/operations/single.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import AWS from 'aws-sdk';

import type { DynoexprInput, DynoexprOutput } from '../dynoexpr';
import {
createDynamoDbSet,
getSingleTableExpressions,
convertValuesToDynamoDbSet,
} from './single';

const docClient = new AWS.DynamoDB.DocumentClient();

describe('single table operations', () => {
it('applies consecutive expression getters to a parameters object', () => {
const params: DynoexprInput = {
Expand Down Expand Up @@ -154,6 +151,53 @@ describe('single table operations', () => {
expect(result).toStrictEqual(expected);
});

it('creates DynamoDBSet instances for strings', () => {
const params = ['hello', 'world'];
const result = createDynamoDbSet(params);
expect(result.type).toBe('String');
expect(result.values).toHaveLength(params.length);
expect(result.values).toContain('hello');
expect(result.values).toContain('world');
});

it('creates DynamoDBSet instances for numbers', () => {
const params = [42, 1, 2];
const result = createDynamoDbSet(params);
expect(result.type).toBe('Number');
expect(result.values).toHaveLength(params.length);
expect(result.values).toContain(42);
expect(result.values).toContain(1);
expect(result.values).toContain(2);
});

it('creates DynamoDBSet instances for binary types', () => {
const params = [
Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]),
Buffer.from([0x61, 0x62, 0x63]),
];
const result = createDynamoDbSet(params);
expect(result.type).toBe('Binary');
expect(result.values).toHaveLength(params.length);
expect(result.values).toContainEqual(params[0]);
expect(result.values).toContain(params[1]);
});

it('does not throw an error with mixed set types if validation is not explicitly enabled', () => {
const params = ['hello', 42];
const result = createDynamoDbSet(params);
expect(result.type).toBe('String');
expect(result.values).toHaveLength(params.length);
expect(result.values).toContain('hello');
expect(result.values).toContain(42);
});

it('throws an error with mixed set types if validation is enabled', () => {
const params = ['hello', 42];
const expression = () => createDynamoDbSet(params, { validate: true });
const expectedErrorMessage = 'String Set contains Number value';
expect(expression).toThrow(expectedErrorMessage);
});

it('converts Sets to DynamoDbSet if present in ExpressionsAttributeValues', () => {
const values = {
a: 1,
Expand All @@ -170,8 +214,8 @@ describe('single table operations', () => {
b: 'foo',
c: [1, 2, 3],
d: { foo: 'bar' },
e: docClient.createSet([1, 2]),
f: docClient.createSet(['foo', 'bar']),
e: createDynamoDbSet([1, 2]),
f: createDynamoDbSet(['foo', 'bar']),
};
expect(result).toStrictEqual(expected);
});
Expand Down
7 changes: 4 additions & 3 deletions src/operations/single.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AWS from 'aws-sdk';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

import type { DynoexprInput, DynamoDbValue, DynoexprOutput } from '../dynoexpr';
import { getConditionExpression } from '../expressions/condition';
Expand All @@ -10,7 +10,8 @@ import { getKeyConditionExpression } from '../expressions/key-condition';

import { trimEmptyExpressionAttributes } from './helpers';

const docClient = new AWS.DynamoDB.DocumentClient();
export const createDynamoDbSet =
DocumentClient.prototype.createSet.bind(undefined);

type ConvertValuesToDynamoDbSetFn = (
attributeValues: Record<string, unknown>
Expand All @@ -20,7 +21,7 @@ export const convertValuesToDynamoDbSet: ConvertValuesToDynamoDbSetFn = (
) =>
Object.entries(attributeValues).reduce((acc, [key, value]) => {
if (value instanceof Set) {
acc[key] = docClient.createSet(Array.from(value));
acc[key] = createDynamoDbSet(Array.from(value));
} else {
acc[key] = value as DynamoDbValue;
}
Expand Down

0 comments on commit 7f89a18

Please sign in to comment.