Skip to content

Repository files navigation

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 brushless

Imports 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.

About

Write DynamoDB expressions without the friction

Topics

Resources

Stars

14 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages