Punchcard uses TypeScript's Advanced Types to derive a DynamoDB DSL from Shapes
. This DSL simplifies the code required to marshal and unmarshal items from the DynamoDB format and build Condition, Query and Update Expressions.
To demonstrate, let's create a DynamoDB.Table
"with some Shape":
class TableRecord extends Type({
id: string,
count: integer
}) {}
const table = new DynamoDB.Table(stack, 'my-table', {
data: TableRecord
key: {
partition: 'id'
}
}, Build.lazy(() => ({
billingMode: BillingMode.PAY_PER_REQUEST
})));
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.
DynamoDB.Table<TableRecord, { partition: 'id' }>
This model enables a dynamic interface to DynamoDB while also maintaining type-safety.
When getting an item from DynamoDB, there is no need to use AttributeValues
such as { S: 'my string' }
. You simply use ordinary javascript types:
const item = await table.get({
id: 'state'
});
item.id; // string
item.count; // number
//item.missing // does not compile
Putting an item is as simple as putting to an ordinary Map
.
await table.put(new TableRecord({
id: 'state',
count: 1,
//invalid: 'value', // does not compile
}));
A Condition Expression can optionally be included with if
:
await table.put(new TableRecord({
id: 'state',
count: 1
}), {
if: _ => _.count.equals(0)
});
Which automatically (and safely) renders the following expression:
{
ConditionExpression: '#0 = :0',
ExpressionAttributeNames: {
'#0': 'count'
},
ExpressionAttributeValues: {
':0': {
N: '0'
}
}
}
Build Update Expressions by assembling an array of actions
:
await table.update({
id: 'state'
}, {
actions: _ => [
_.count.increment(1)
]
});
Which automaticlaly (and safely) renders the following expression:
{
UpdateExpression: '#0 = #0 + :0',
ExpressionAttributeNames: {
'#0': 'count'
},
ExpressionAttributeValues: {
':0': {
N: '1'
}
}
}
If you also specified a sortKey
for your Table:
const table = new DynamoDB.Table(stack, 'my-table', {
data: TableRecord,
key: {
partition: 'id',
sort: 'count'
}
});
(Where the Table type looks like this)
DynamoDB.Table<TableRecord, { partition: 'id', sort: 'count' }>
Then, you can also build typesafe Query Expressions:
await table.query({
id: 'id',
count: _ => _.greaterThan(1)
});
Which automatically (and safely) renders the following low-level expression:
{
KeyConditionExpression: '#0 = :0 AND #1 > :1',
ExpressionAttributeNames: {
'#0': 'id',
'#1': 'count'
},
ExpressionAttributeValues: {
':0': {
S: 'id'
},
':1': {
N: '1'
}
}
}
Check out the DynamoDB example app for more magic.
Next: Stream Processing