npm package: @flybondi/dynamodb-wrapper
Description: A DynamoDB library that extends aws-sdk with bulk read/write, events, streams, and more
Author: Jeff Bradford
Latest version: 2.0.1

[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![License][license-image]][license-url]

[npm-image]: https://img.shields.io/npm/v/dynamodb-wrapper.svg
[npm-url]: https://www.npmjs.com/package/dynamodb-wrapper

[travis-image]: https://img.shields.io/travis/Shadowblazen/dynamodb-wrapper.svg
[travis-url]: https://travis-ci.org/Shadowblazen/dynamodb-wrapper

[coveralls-image]: https://img.shields.io/coveralls/Shadowblazen/dynamodb-wrapper.svg
[coveralls-url]: https://coveralls.io/github/Shadowblazen/dynamodb-wrapper?branch=master

[license-image]: https://img.shields.io/npm/l/dynamodb-wrapper.svg
[license-url]: https://opensource.org/licenses/MIT

## What is dynamodb-wrapper?

- **Enhanced AWS SDK:** Public interface closely resembles the AWS SDK, making it easier to learn and use.
- **Bulk I/O:** Easily read, write or delete entire collections of items in DynamoDB with a single API call.
- **Events:** Add event hooks to be notified of important events, such as whenever read/write capacity is consumed, or requests are retried due to throttling.
- **Table prefixes:** DynamoDBWrapper can add a table name prefix in requests and remove it in responses. This is helpful if you have multiple environments within the same AWS Account and region.

## Installing

```
npm install dynamodb-wrapper
```

## Usage

### Setup

Construct the DynamoDBWrapper class

```js
var AWS = require('aws-sdk');
var DynamoDBWrapper = require('dynamodb-wrapper');

var dynamoDB = new AWS.DynamoDB({
    // optionally disable AWS retry logic - reasoning explained below
    maxRetries: 0
});

// see the Configuration section of the README for more options
var dynamoDBWrapper = new DynamoDBWrapper(dynamoDB, {
    // optionally enable DynamoDBWrapper retry logic
    maxRetries: 6,
    retryDelayOptions: {
        base: 100
    }
});
```

*(Optional)* If you use DynamoDBWrapper retry logic instead of AWS retry logic, you gain the following benefits:

1. Improved batch processing: DynamoDBWrapper will automatically retry any *UnprocessedItems* in your `batchWriteItem` requests.
2. You can add a `retry` event listener to be notified when requests are throttled. In your application, you can log these events, or even respond by increasing provisioned throughput on the affected table.
3. DynamoDBWrapper's `retryDelayOptions` actually work as documented (this functionality doesn't work in the AWS JavaScript SDK yet, but there's an [open ticket for this feature request](https://github.com/aws/aws-sdk-js/issues/1100)).

```
dynamoDBWrapper.events.on('retry', function (e) {
    console.log(
        'An API call to DynamoDB.' + e.method + '() acting on table ' +
        e.tableName + ' was throttled. Retry attempt #' + e.retryCount +
        ' will occur after a delay of ' + e.retryDelayMs + 'ms.'
    );
});

// An API call to DynamoDB.batchWriteItem() acting on table MyTable
// was throttled. Retry attempt #3 will occur after a delay of 800ms.
```

*(Optional)* If you use the `ReturnConsumedCapacity` property in your AWS requests, the `consumedCapacity` event listener can notify you whenever read/write capacity is consumed. 

```
dynamoDBWrapper.events.on('consumedCapacity', function (e) {
    console.log(
        'An API call to DynamoDB.' + e.method + '() consumed ' +
        e.capacityType, JSON.stringify(e.consumedCapacity, null, 2)
    );
});

// An API call to DynamoDB.batchWriteItem() consumed WriteCapacityUnits
// [
//   {
//     "TableName": "MyTable",
//     "CapacityUnits": 20
//   }
// ]
```

*(Optional)* When using the `DynamoDBWrapper.batchWriteItem()` API method, there is a `batchGroupWritten` event that will notify you of how many items have been processed so far.

```
dynamoDBWrapper.events.on('batchGroupWritten', function (e) {
    console.log(e.tableName, e.processedCount);
});
```

### Example: Bulk Read

Read large collections of data from a DynamoDB table with a single API call. Multiple pages of data are aggregated and returned in a single response.

@see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html

```js
// params for Query - same format as in the AWS SDK
var sampleQueryParams = {
    TableName: 'MyTable',
    KeyConditionExpression: 'MyPartitionKey = :pk',
    ExpressionAttributeValues: {
        ':pk': {
            N: '1'
        }
    }
};

// fetches all pages of data from DynamoDB
// promise resolves with the aggregation of all pages,
// or rejects immediately if an error occurs

dynamoDBWrapper.query(sampleQueryParams)
    .then(function (response) {
        console.log(response.Items);
    })
    .catch(function (err) {
        console.error(err);
    });
```

### Example: Bulk Write/Delete

Insert or delete large collections of items in one or more DynamoDB tables with a single API call. DynamoDBWrapper batches your requests and aggregates the results into a single response. Use configuration values to fine tune throughput consumption for your use case.

@see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html

```js
// params for BatchWriteItem - same format as in the AWS SDK
var sampleParams = {
    RequestItems: {
        MyTable: [
            {
                PutRequest: {
                    Item: {
                        MyPartitionKey: { N: '1' }
                    }
                }
            },
            {
                DeleteRequest: {
                    Key: {
                        MyPartitionKey: { N: '2' }
                    }
                }
            },
            // this array can have thousands of items ...
        ],
        AnotherTable: [
            {
                PutRequest: {
                    Item: {
                        User: { S: 'Batman' }
                    }
                }
            },
            {
                DeleteRequest: {
                    Key: {
                        User: { S: 'Superman' }
                    }
                }
            },
            // this array can have thousands of items ...
        ]
    }
};

// performs all Puts/Deletes in the array
// promise resolves when all items are written successfully,
// or rejects immediately if an error occurs

dynamoDBWrapper.batchWriteItem(sampleParams, {
    // use configuration to control and optimize throughput consumption

    // write 10 items to MyTable every 500 milliseconds
    // this strategy is best if you have known, consistent item sizes
    MyTable: {
        partitionStrategy: 'EqualItemCount',
        targetItemCount: 10,
        groupDelayMs: 500
    }

    // write up to 50 WCU of data to AnotherTable every 1000 milliseconds
    // this strategy is best if you have unknown or variable item sizes,
    // because it evenly distributes the items across requests so as
    // to minimize throughput spikes (which can cause throttling)
    AnotherTable: {
        partitionStrategy: 'EvenlyDistributedGroupWCU',
        targetGroupWCU: 50,
        groupDelayMs: 1000
    }
})
    .then(function (response) {
        console.log(response);
    })
    .catch(function (err) {
        console.error(err);
    });
```

### Example: Table Prefixes

You may wish to work with duplicate copies of the same set of tables. For example: "dev-MyTable" and "stg-MyTable" if you have dev and stage environments under the same AWS account. DynamoDBWrapper supports this use-case via a configuration-driven `tableNamePrefix` option.

```js
// load this "environment" variable from a config file
// so it will have different values per environment
// we'll just use dev for this example

var environment = 'dev-';

// Configure DynamoDBWrapper with the environment-specific prefix
// Note: we only do this here in one place; there's no need to
// sprinkle the prefix throughout your codebase.

var AWS = require('aws-sdk');
var DynamoDBWrapper = require('dynamodb-wrapper');
var dynamoDB = new AWS.DynamoDB();
var dynamoDBWrapper = new DynamoDBWrapper(dynamoDB, {
    tableNamePrefix: environment
});

// Create the table like usual...

dynamoDBWrapper.createTable({
    TableName: 'MyTable',
    // more params...
});

// The new table will be named "dev-MyTable" instead of "MyTable"
// This works with all the other API methods too.

var promise = dynamoDBWrapper.getItem({
    TableName: 'MyTable',
    ReturnConsumedCapacity: 'TOTAL',
    // more params...
});

// Although the real table name in AWS DynamoDB is "dev-MyTable",
// the prefix will be stripped from the response for transparency:

promise.then(function (response) {
    console.log(response);
});

// {
//   ConsumedCapacity: {
//     TableName: 'MyTable', // <-- no prefix in the response
//     CapacityUnits: 1
//   },
//   Item: { ... }
// }

// In summary: the prefix is prepended to all requests
// and stripped from all responses
```

## Configuration

*This section is copied from the `IDynamoDBWrapperOptions` interface in the `index.d.ts` file.*

The `DynamoDBWrapper` constructor accepts an optional configuration object with the following properties:
- `tableNamePrefix` (string) - A prefix to add to all requests and remove from all responses.
- `groupDelayMs` (number) - The delay (in millseconds) between individual requests made by `query()`, `scan()`, and `batchWriteItem()`. Defaults to 100 ms.
- `maxRetries` (number) - The maximum amount of retries to attempt with a request. Note: this property is identical to the one described in [the AWS documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property).
- `retryDelayOptions` (object) - A set of options to configure the retry delay on retryable errors. Note: this property is identical to the one described in [the AWS documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property). Currently supported options are:
    - `base` (number) - The base number of milliseconds to use in the exponential backoff for operation retries. Defaults to 100 ms.
    - `customBackoff` (Function) - A custom function that accepts a retry count and returns the amount of time to delay in milliseconds. The `base` option will be ignored if this option is supplied.

## API

The `DynamoDBWrapper` class supports a Promise-based API with the following methods. These are wrappers around the AWS SDK method of the same name. Please refer to the AWS [API documentation](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations.html) and [JavaScript SDK documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html) for more details:

The following methods are passed straight through to the AWS JavaScript SDK:

- `createTable(params)`
- `updateTable(params)`
- `describeTable(params)`
- `deleteTable(params)`
- `getItem(params)`
- `updateItem(params)`
- `putItem(params)`
- `deleteItem(params)`
- `batchGetItem(params)`

## Enhanced API methods

The following API methods have enhanced behavior to support bulk I/O:

- `query(params, options)` - Fetches all pages of data from a DynamoDB query, making multiple requests and aggregating responses when necessary.
    - `options.groupDelayMs` (number) - the delay between individual requests. Overrides the configuration property of the same name in the constructor. Defaults to 100 ms.
- `scan(params, options)` - Fetches all pages of data from a DynamoDB scan, making multiple requests and aggregating responses when necessary.
    - `options.groupDelayMs` (number) - the delay between individual requests. Overrides the configuration property of the same name in the constructor. Defaults to 100 ms.
- `batchWriteItem(params, options)` - Writes or deletes large collections of items in multiple DynamoDB tables, batching items and making multiple requests when necessary.
    - `options` is a mapping of table names to option hashes. Each option hash may have the following properties:
        - `groupDelayMs` (number) - the delay between individual requests. Overrides the configuration property of the same name in the constructor. Defaults to 100 ms.
        - `partitionStrategy` (string) - strategy to use when partitioning the write requests array. Possible values: *EqualItemCount* or *EvenlyDistributedGroupWCU*.
        - `targetItemCount` (number) - the number of items to put in each group when using the *EqualItemCount* partition strategy.
        - `targetGroupWCU` (number) - the size threshold (in WriteCapacityUnits) of each group when using the *EvenlyDistributedGroupWCU* partition strategy.

## Roadmap

- **Streams:** Add method signatures that return Streams (instead of Promises), allowing for better integration ecosystems such as gulp