Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shape, dynamodb): Pick/Extend mechanics for Records to support DynamoDB Indexes #108

Merged
merged 9 commits into from
Feb 7, 2020
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,19 @@ class TableRecord extends Record({

// table of TableRecord, with a single hash-key: 'id'
const table = new DynamoDB.Table(stack, 'my-table', {
key: 'id',
attributes: TableRecord
data: TableRecord
key: {
partition: 'id'
}
});
```

Now, when getting an item from DynamoDB, there is no need to use `AttributeValues` such as `{ S: 'my string' }`, like you would when using the low-level `aws-sdk`. You simply use ordinary javascript types:

```ts
const item = await table.get('state');
const item = await table.get({
id: 'state'
});
```

The interface is statically typed and derived from the definition of the `Table` - we specified the `partitionKey` as the `id` field which has type `string`, and so the object passed to the `get` method must correspond.
Expand All @@ -120,10 +124,15 @@ await table.put(new TableRecord({
});

// increment the count property by 1 if it is less than 0
await table.update('state', {
await table.update({
// value of the partition key
id: 'state'
}, {
// use the DSL to construt an array of update actions
actions: _ => [
_.count.increment(1)
],
// optional: use the DSL to construct a conditional expression for the update
if: _ => _.id.lessThan(0)
});
```
Expand All @@ -132,15 +141,24 @@ To also specify `sortKey`, use a tuple of `TableRecord's` keys:

```ts
const table = new DynamoDB.Table(stack, 'my-table',{
attributes: TableRecord,
key: ['id', 'count']
data: TableRecord,
key: {
partition: 'id',
sort: 'count'
}
});
```

Now, you can also build typesafe query expressions:

```ts
await table.query(['id', _ => _.count.greaterThan(1)], {
await table.query({
// id is the partition key, so we must provide a literal value
id: 'id',
// count is the sort key, so use the DSL to construct the sort-key condition
count: _ => _.greaterThan(1)
}, {
// optional: use the DSL to construct a filter expression
filter: _ => _.count.lessThan(0)
})
```
Expand Down
31 changes: 21 additions & 10 deletions docs/5-dynamodb-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ class TableRecord extends Record({
}) {}

const table = new DynamoDB.Table(stack, 'my-table', {
key: 'id',
attributes: TableRecord
partitionKey: 'id',
data: TableRecord
key: {
partition: 'id'
}
}, Build.lazy(() => ({
billingMode: BillingMode.PAY_PER_REQUEST
})));
Expand All @@ -22,15 +23,17 @@ const table = new DynamoDB.Table(stack, 'my-table', {
The Table API is derived from the definition encoded within the `DynamoDB.Table` type, containing the partition key (`'id'`), sort key (`undefined`) and the `Shape` of an item.

```ts
DynamoDB.Table<TableRecord, 'id'>
DynamoDB.Table<TableRecord, { partition: 'id' }>
```
This model enables a dynamic interface to DynamoDB while also maintaining type-safety.

# Get Item
When getting an item from DynamoDB, there is no need to use `AttributeValues` such as `{ S: 'my string' }`. You simply use ordinary javascript types:

```ts
const item = await table.get('state');
const item = await table.get({
id: 'state'
});
item.id; // string
item.count; // number
//item.missing // does not compile
Expand Down Expand Up @@ -77,7 +80,9 @@ Which automatically (and safely) renders the following expression:

Build **Update Expressions** by assembling an array of `actions`:
```ts
await table.update('state', {
await table.update({
id: 'state'
}, {
actions: _ => [
_.count.increment(1)
]
Expand All @@ -104,20 +109,26 @@ Which automaticlaly (and safely) renders the following expression:
If you also specified a `sortKey` for your Table:
```ts
const table = new DynamoDB.Table(stack, 'my-table', {
key: ['id', 'count'] // specify a sortKey with a tuple
attributes: TableRecord
data: TableRecord,
key: {
partition: 'id',
sort: 'count'
}
});
```

*(Where the Table type looks like this)*
```ts
DynamoDB.Table<TableRecord, ['id', 'count']>
DynamoDB.Table<TableRecord, { partition: 'id', sort: 'count' }>
```

Then, you can also build typesafe **Query Expressions**:

```ts
await table.query(['id', _ => _.greaterThan(1)]);
await table.query({
id: 'id',
count: _ => _.greaterThan(1)
});
```

Which automatically (and safely) renders the following low-level expression:
Expand Down
28 changes: 20 additions & 8 deletions examples/lib/dynamodb.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { BillingMode } from '@aws-cdk/aws-dynamodb';
import cdk = require('@aws-cdk/core');
import { Duration } from '@aws-cdk/core';
import { Schedule } from '@aws-cdk/aws-events';

Expand Down Expand Up @@ -29,16 +28,21 @@ class Item extends Record({
// the type can be inferred, but we explicitly define them to illustrate how it works
// 'id' is the partitionKey, undefined is the sortKey (no sort key), and Item is the attributes of data in the table
const table = new DynamoDB.Table(stack, 'hash-table', {
attributes: Item,
key: 'id'
data: Item,
key: {
partition: 'id'
}
}, Build.of({
billingMode: BillingMode.PAY_PER_REQUEST
}));

// 'count' is the sortKey in this case
const sortedTable = new DynamoDB.Table(stack, 'sorted-table', {
attributes: Item,
key: ['id', 'count']
data: Item,
key: {
partition: 'id',
sort: 'count'
}
}, Build.of({
billingMode: BillingMode.PAY_PER_REQUEST
}));
Expand All @@ -48,7 +52,9 @@ Lambda.schedule(stack, 'Caller', {
depends: Core.Dependency.concat(table.readWriteAccess(), sortedTable.readAccess()),
schedule: Schedule.rate(Duration.minutes(1)),
}, async (_, [table, sortedTable]) => {
await table.get('id');
await table.get({
id: 'id'
});

await table.put(new Item({
// the item is type-safe and well structured
Expand Down Expand Up @@ -100,10 +106,13 @@ Lambda.schedule(stack, 'Caller', {
})))
});

await table.update('id', {
await table.update({
id: 'id'
}, {
actions: _ => [
// strings
_.name.set('name'), // item.name = 'name'

// numbers
_.count.set(1), // item.count = 1
_.count.decrement(1), // item.count -- or item.count -= 1
Expand All @@ -128,7 +137,10 @@ Lambda.schedule(stack, 'Caller', {
});

// sorted tables can be queried
await sortedTable.query(['id', count => count.greaterThan(1)], {
await sortedTable.query({
id: 'id',
count: _ => _.greaterThan(1)
}, {
filter: item => item.array.length.equals(item.count) // item.array.lenth === item.count
});
});
Loading