@hexlabs/dynamo-ts v6.0.191
@hexlabs/dynamo-ts
Version 6.x now supports both CommonJS and ESM
DynamoDB + TypeScript made simple
Table of contents
- Scan Table
- Get Item
- Put Item
- Delete Item
- Query Items
- Update Items
- Multi-Table Batch Gets (With Projections)
- Multi-Table Batch Writes
- Transactional Writes
- Transactional Gets
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:
- 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}))();
- 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",
...
}
- 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);
- 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 months ago
5 months ago
3 months ago
7 months ago
7 months ago
7 months ago
8 months ago
8 months ago
8 months ago
9 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
11 months ago
12 months ago
12 months ago
12 months ago
12 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago