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.
npm install brushlessEvery 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.
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"
}
}
}
Open an issue or a PR. We are open to any kind of contribution and feedback.