brushless
v0.4.0
Published
DynamoDB utils lib write in Rescript and Typescript
Maintainers
Readme
Brushless
Brushless is a DynamoDB expression library that allows you to write frictionless, maintainable, and type-safe expressions. It has zero dependencies and is built with ReScript, providing full TypeScript support.
Installation
npm install brushlessImports and treeshaking
Every module has its own entry point. Import those and your bundler drops the functions you don't call:
import * as C from 'brushless/condition';
import * as R from 'brushless/register';
import * as AP from 'brushless/attribute-path';Available entry points: brushless/condition, brushless/key-condition,
brushless/update, brushless/projection, brushless/register,
brushless/identifier, brushless/attribute-name, brushless/attribute-value,
brushless/attribute-path, and brushless/types (types only).
The root import still works and exposes the same names as before:
import { Condition, C, Register } from 'brushless';Prefer the per-module entry points in bundled code. Reaching for a module object
from the root (Condition.equals) pins that entire module, because an object
property is not something a bundler can drop — measured with esbuild, one
condition helper costs 92 B via brushless/condition and about 5.4 KB via
C.equals from the root import.
Usage
TypeScript example usage:
import { AttributeName, AttributePath, AttributeValue, Register, Condition, KeyCondition, C, K, U, P } from 'brushless';
import * as DynamoDB from '@aws-sdk/client-dynamodb';
const register = Register.make()
const pk = AttributeName.make("PK")
const sk = AttributeName.make("SK")
const pkVal = AttributeValue.make({
value: {
S: 'X'
},
alias: "PK"
})
const skVal = AttributeValue.make({
value: {
S: 'Y'
},
alias: "SK"
})
const foo = AttributeName.make("foo")
const bar = AttributeName.make("bar")
const baz = AttributeName.make("baz")
const fooVal = AttributeValue.make({
value: {
S: 'foo'
},
alias: "foo"
})
const barVal = AttributeValue.make({
value: {
S: 'bar'
},
alias: "bar"
})
const bazVal = AttributeValue.make({
value: {
S: 'baz'
},
alias: "baz"
})
const path = AttributePath.fromString('foo.bar.baz[0]')
const queryCommand: DynamoDB.QueryCommandInput = {
TableName: 'YourTable',
KeyConditionExpression: KeyCondition.build({
pk: {
name: pk,
value: pkVal
},
sk: K.beginsWith(sk, skVal)
}, register),
FilterExpression: Condition.build(
C.and(C.equals(path, skVal), C.or(C.equals(foo, fooVal), C.and(C.equals(bar, barVal), C.contains(path, bazVal))))
, register),
ProjectionExpression: P.build([foo, bar, baz], register),
ExpressionAttributeNames: register.names,
ExpressionAttributeValues: register.values
}
const updateCommand: DynamoDB.UpdateCommandInput = {
TableName: 'YourTable',
Key: {
PK: pkVal.value,
SK: skVal.value
},
UpdateExpression: U.build({
set: [
[foo, U.ifNotExists(foo, fooVal)],
[bar, barVal],
[baz, U.listAppend(baz, bazVal)],
[path, U.sub(path, AttributeValue.make({
value: {
N: '1'
},
alias: "one"
}))]
]
}, register),
ConditionExpression: Condition.build(
C.and(C.equals(path, skVal), C.or(C.equals(foo, fooVal), C.and(C.equals(bar, barVal), C.contains(path, bazVal)))), register),
ExpressionAttributeNames: register.names,
ExpressionAttributeValues: register.values,
}The above code will generate the following DynamoDB commands:
// queryCommand
{
"TableName": "YourTable",
"KeyConditionExpression": "#PK = :PK AND begins_with(#SK, :SK)",
"FilterExpression": "(#foo.#bar.#baz[0] = :SK) AND ((#foo = :foo) OR ((#bar = :bar) AND (contains(#foo.#bar.#baz[0], :baz))))",
"ProjectionExpression": "#foo, #bar, #baz",
"ExpressionAttributeNames": {
"#PK": "PK",
"#SK": "SK",
"#foo": "foo",
"#bar": "bar",
"#baz": "baz"
},
"ExpressionAttributeValues": {
":PK": {
"S": "X"
},
":SK": {
"S": "Y"
},
":foo": {
"S": "foo"
},
":bar": {
"S": "bar"
},
":baz": {
"S": "baz"
}
}
}
// updateCommand
{
"TableName": "YourTable",
"Key": {
"PK": {
"S": "X"
},
"SK": {
"S": "Y"
}
},
"UpdateExpression": "SET #foo = if_not_exists(#foo, :foo), #bar = :bar, #baz = list_append(#baz, :baz), #foo.#bar.#baz[0] = #foo.#bar.#baz[0] - :one",
"ConditionExpression": "(#foo.#bar.#baz[0] = :SK) AND ((#foo = :foo) OR ((#bar = :bar) AND (contains(#foo.#bar.#baz[0], :baz))))",
"ExpressionAttributeNames": {
"#PK": "PK",
"#SK": "SK",
"#foo": "foo",
"#bar": "bar",
"#baz": "baz"
},
"ExpressionAttributeValues": {
":PK": {
"S": "X"
},
":SK": {
"S": "Y"
},
":foo": {
"S": "foo"
},
":bar": {
"S": "bar"
},
":baz": {
"S": "baz"
},
":one": {
"N": "1"
}
}
}
Contributing
Open an issue or a PR. We are open to any kind of contribution and feedback.
