6.0.191 • Published 5 months ago

@hexlabs/dynamo-ts v6.0.191

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@hexlabs/dynamo-ts

Version

Version 6.x now supports both CommonJS and ESM

DynamoDB + TypeScript made simple

Typescript ESLint Prettier

Table of contents

Installation

Using npm:

$ npm i -S @hexlabs/dynamo-ts

Get Started

Create a definition for your table

This can be stored and used for type information and generation in CloudFormation for example.

type MyTableType = { identifier: string; sort: string; abc: { xyz: number } };

export const myTableDefinition = TableDefinition.ofType<MyTableType>()
  .withPartitionKey('identifier') // <- type checked to be a key in your type
  .withSortKey('sort') // <- optional, aso type checked
  .withGlobalSecondaryIndex('my-index', 'sort')
  .withNoSortKey(); // Global or Local index

Build a client from the definition above

import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { DynamoDB } from '@aws-sdk/client-dynamodb';
import { myTableDefinition } from './define-table';

const dynamoConfig: DynamoConfig = {
  client: DynamoDBDocument.from(new DynamoDB({})),
  tableName: 'my-table',
  logStatements: true, // Logs all interactions with Dynamo
};

const myTableClient = TableClient.build(myTableDefinition, dynamoConfig);

This client can now be used to interact with DynamoDb

// PUT ITEM
await myTable.put({ identifier: 'id', text: 'some text' }); // This object must match the definition above
// GET ITEM
const result = await myTable.get({ identifier: 'id'}); 
// typeof result.item is {identifier: string; text: string}

Examples

All examples can be found in the examples directory in this repository.

An example table for using these examples can be found in examples/example-table.ts

Scan Table

// Scan Table
const {member, next} = await tableClient.scan();
// typeof member = {identifier: string; make: string; model: string; year: number; colour: string}[]
// use next to paginate by passing in as argument to scan

// Filter results
// Get all cars in the year 2000
await tableClient.scan({filter: compare => compare().year.eq(2000)});

Get Item

// Get Item (Partition Key and Sort Key)
await tableClient.get({make: 'Tesla', identifier: '<identifier>'});

// Get Projected Item
const result = await tableClient.get({identifier: '1234', make: 'Tesla'}, {projection: projector => projector.project('model')});
// typeof result = {model: string} | undefined;

Put Item

// Put Item
await tableClient.put({identifier: '1234', make: 'Tesla', model: 'Model S', year: 2022, colour: 'white'});

//Put Item Return overwritten item
const result = await tableClient.put(
    {identifier: '1234', make: 'Tesla', model: 'Model S', year: 2022, colour: 'white'},
    {returnOldValues: true}
);
// typeof result.item = {identifier: string; make: string; model: string; year: number; colour: string}

// Conditionally Put Item if doesn't already exist (throws ConditionError)
await tableClient.put(
    {identifier: '1234', make: 'Tesla', model: 'Model S', year: 2022, colour: 'white'},
    {condition: compare => compare().notExists('identifier')}
)

Delete Item

//Delete (requires Partition Key and Sort Key)
await tableClient.delete({identifier: '1234', make: 'Tesla'})

Query Items

// Simple Query against Partition
// Get all Cars with make 'Tesla'
await tableClient.query({make: 'Tesla'});

// Query and Filter
// Get all Nissan Cars in the year 2006
await tableClient.query({make: 'Nissan', filter: compare => compare().year.eq(2006)});

// Query an Index using KeyConditionExpression
// Get all Nissan Cars with a model beginning with '3' and order backwards
await tableClient.index('model-index').query({
    make: 'Nissan',
    model: sortKey => sortKey.beginsWith('3'),
    dynamo: { ScanIndexForward: false }
});

// Filter with between
// Get all Nissan Cars between 2006 and 2022
await tableClient.query({make: 'Nissan', filter: compare => compare().year.between(2006, 2022)});

// Combining Filter Comparisons (and / or)
// Get all Nissan Cars between 2006 & 2007 AND with colour 'Metallic Black'
await tableClient.query({
    make: 'Nissan',
    filter: compare => compare().year.between(2006, 2007).and(compare().colour.eq('Metallic Black'))
});

// Projection
// Get only model and year
const result = await tableClient.query({make: 'Tesla', projection: projector => projector.project('model').project('year')});
// typeof result.member = {model: string; year: string}

Update Items

//Update Model S Tesla by setting the year to 2022 and deleting the colour (undefined means delete)
await tableClient.update({key: {identifier: '1234', make: 'Tesla'}, updates: {year: 2022, colour: undefined}});

//Atomic Addition
//Update by incrementing the year by 1 atomically, if it doesn't exist set it to 2020, also set model to 'Another Model'
await tableClient.update({key: {identifier: '1234', make: 'Tesla'}, updates: {year: 1, model: 'Another Model'}, increments: [{key: 'year', start: 2020}]});

//Return Old Values
const result = await tableClient.update({key: {identifier: '1234', make: 'Tesla'}, updates: {year: 2022, colour: undefined}, return: 'ALL_OLD'});
// typeof result.item = {identifier: string; make: string; model: string; year: number; colour: string}

Multi-Table Batch Gets (With Projections)

const result = await testTable
        .batchGet([
          { identifier: '0' },
          { identifier: '3' },
          { identifier: '4' },
        ])
        //Use and() to combine other operations against other tables
        .and(
          testTable2.batchGet(
            [
              { identifier: '10000', sort: '0' },
              { identifier: '10008', sort: '8' },
            ],
            { projection: (projector) => projector.project('sort') },
          ),
        )
        .execute();

Multi-Table Batch Writes

const result = await testTable
        //Choose batchPut or Delete to begin the operation agains an initial table
        .batchDelete({ identifier: 'id1' })
        //Then, use and() to combine other operations against other tables
        .and(testTable.batchPut([{ identifier: 'id2', text: 'text' }]))
        .and(testTable2.batchPut([{ identifier: 'id3', text: 'text' }]))
        .execute();

Transactional Writes

const result = await transactionTable
    .transaction
    .put({
      item: { identifier: '777', count: 1, description: 'some description' },
      condition: compare => compare().description.notExists
    })
    .then(
      transactionTable.transaction.update({
        key: { identifier: '777-000' },
        increments: [{key: 'count', start: 0}],
        updates: { count: 5 }
      })
    )
  .execute();

Transactional Gets

const result = await transactionTable
  .transaction.get([{identifier: '0'}])
    .and(
      testTable2.transaction.get([{identifier: '10000', sort: '0'}])
    ).execute()

Single Table Design

See the Single Table Design section on Medium for a detailed explanation

Testing

Testing is no different than how you would have tested dynamo before. We use @shelf/jest-dynamodb to run a local version of dynamodb when we test. If you would like us to generate table definitions that can be used in this testing library, do the following:

  1. Create a file called jest-setup.ts
import {table1, table2}  from './test/tables';
import {writeJestDynamoConfig} from "./src/dynamo-jest-setup";

(async () => writeJestDynamoConfig({testTable: table1, 'ThisIsTheTableNameForTable2': table2}, 'jest-dynamodb-config.js',{port: 5001}))();
  1. Then, in package.json, Update your scripts to include a pretest command which executes the setup file. Note that you may need to install ts-node as a dev dependency.

This will create a file named jest-dynamodb-config.js at the root of the project which is the config file searched for by the testing library to build tables.

"scripts": {
  "pretest": "ts-node ./jest-setup.ts",
  ...
}
  1. At the top of the test file you want to use dynamo in add the following to get a document client:
import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";

const dynamo = new DynamoDB({
  endpoint: { hostname: 'localhost', port: 5001, protocol: 'http:', path: '/'  },
  region: 'local-env',
  credentials: { accessKeyId: 'x', secretAccessKey: 'x' }
});
const dynamoClient = DynamoDBDocument.from(dynamo);
  1. Inject the client wherever you use dynamo, and you will have tables that match your dynamo definitions.

Contributors

Thanks to everyone who has contributed so far!

6.0.189

8 months ago

6.0.190

8 months ago

6.0.191

5 months ago

6.0.188

9 months ago

6.0.187

10 months ago

6.0.186

10 months ago

6.0.185

10 months ago

6.0.183

11 months ago

6.0.184

11 months ago

6.0.182

11 months ago

6.0.181

1 year ago

6.0.173

1 year ago

6.0.172

1 year ago

6.0.169

1 year ago

6.0.167

1 year ago

6.0.168

1 year ago

6.0.165

1 year ago

6.0.166

1 year ago

6.0.163

1 year ago

6.0.164

1 year ago

6.0.170

1 year ago

4.0.149

2 years ago

4.0.152

1 year ago

4.0.153

1 year ago

4.0.150

2 years ago

4.0.151

1 year ago

4.0.154

1 year ago

4.0.155

1 year ago

5.0.158

1 year ago

5.0.161

1 year ago

4.0.148

2 years ago

4.0.147

2 years ago

3.0.141

2 years ago

3.0.140

2 years ago

3.0.142

2 years ago

3.0.138

2 years ago

3.0.139

2 years ago

3.0.134

2 years ago

3.0.133

2 years ago

3.0.136

2 years ago

3.0.137

2 years ago

3.0.130

2 years ago

3.0.132

2 years ago

3.0.131

2 years ago

3.0.123

2 years ago

3.0.122

2 years ago

3.0.124

2 years ago

3.0.127

2 years ago

3.0.126

2 years ago

3.0.129

2 years ago

3.0.128

2 years ago

3.0.119

2 years ago

3.0.120

2 years ago

3.0.118

2 years ago

3.0.117

2 years ago

2.1.114

3 years ago

2.1.116

3 years ago

2.1.115

3 years ago

2.1.110

3 years ago

2.1.112

3 years ago

2.1.107

3 years ago

2.1.106

3 years ago

2.1.108

3 years ago

2.0.79

3 years ago

2.1.89

3 years ago

2.0.77

3 years ago

2.0.78

3 years ago

2.1.88

3 years ago

2.0.76

3 years ago

2.0.73

3 years ago

2.0.74

3 years ago

2.0.71

3 years ago

2.0.72

3 years ago

2.0.70

3 years ago

2.1.98

3 years ago

2.1.99

3 years ago

2.0.84

3 years ago

2.1.100

3 years ago

2.1.97

3 years ago

2.1.94

3 years ago

2.0.82

3 years ago

2.1.102

3 years ago

2.1.95

3 years ago

2.0.83

3 years ago

2.0.80

3 years ago

2.1.104

3 years ago

2.1.93

3 years ago

2.0.81

3 years ago

2.1.90

3 years ago

2.1.91

3 years ago

2.0.68

3 years ago

2.0.69

3 years ago

2.0.67

3 years ago

2.0.66

3 years ago

2.0.65

3 years ago

2.0.64

3 years ago

2.0.59

3 years ago

2.0.58

3 years ago

2.0.62

3 years ago

2.0.63

3 years ago

2.0.60

3 years ago

2.0.61

3 years ago

2.0.57

3 years ago

2.0.56

3 years ago

2.0.53

3 years ago

2.0.54

3 years ago

2.0.52

3 years ago

2.0.51

3 years ago

2.0.50

3 years ago

2.0.48

3 years ago

2.0.49

3 years ago

2.0.46

3 years ago

2.0.47

3 years ago

2.0.45

3 years ago

2.0.44

3 years ago

2.0.42

3 years ago

2.0.43

3 years ago

2.0.40

3 years ago

2.0.41

3 years ago

2.0.38

3 years ago

2.0.39

3 years ago

2.0.36

3 years ago

2.0.35

3 years ago

2.0.34

3 years ago

2.0.32

3 years ago

2.0.31

3 years ago

2.0.30

3 years ago

2.0.29

3 years ago

2.0.28

3 years ago