/tsynamo

Type-friendly DynamoDB query builder!

Primary LanguageTypeScriptMIT LicenseMIT

Tsynamo logo

Tsynamo

✨ Type-friendly DynamoDB query builder! ✨
Inspired by Kysely


Tsynamo CI status License

Tsynamo simplifies the DynamoDB API so that you don't have to write commands with raw expressions and hassle with the attribute names and values. Moreover, Tsynamo makes sure you use correct types in your DynamoDB expressions, and the queries are nicer to write with autocompletion!

Tsynamo is still an early stage project, please post issues if you notice something missing from the API! Currently the update-item command is still a work in progress, and support for batch commands is missing.

Table of contents

Requirements

Installation

Available in NPM.

npm i tsynamo
pnpm install tsynamo
yarn add tsynamo

Usage

Creating a Tsynamo client

  1. Define the types for your DynamoDB (DDB) tables:
import { PartitionKey, SortKey } from "tsynamo";

export interface DDB {
  UserEvents: {
    userId: PartitionKey<string>;
    eventId: SortKey<number>;
    eventType: string;
    userAuthenticated: boolean;
  };
}

Notice that you can have multiple tables in the DDB schema. Nested attributes are supported too.

  1. Create a DynamoDB document client:
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";

const ddbClient = DynamoDBDocumentClient.from(
  new DynamoDBClient({
    /* Configure client... */
  })
);

The document client must come from @aws-sdk/lib-dynamodb!

  1. Create a Tsynamo client with the defined DynamoDB types and client:
const tsynamoClient = new Tsynamo<DDB>({
  ddbClient: dynamoDbDocumentClient,
});

Get item

await tsynamoClient
  .getItem("UserEvents")
  .keys({
    userId: "123",
    eventId: 222,
  })
  .attributes(["userId"])
  .execute();

Query item

Partition key condition

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .execute();

Partition and sort key conditions

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .keyCondition("eventId", "<", 1000)
  .execute();

Simple filter expression

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .filterExpression("eventType", "=", "LOG_IN_EVENT")
  .execute();

Filter expression with a function

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .filterExpression("eventType", "begins_with", "LOG")
  .execute();

Multiple filter expressions

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .filterExpression("eventType", "begins_with", "LOG_IN")
  .orFilterExpression("eventType", "begins_with", "SIGN_IN")
  .execute();

Nested filter expressions

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .filterExpression("eventType", "=", "LOG_IN")
  .orFilterExpression((qb) =>
    qb
      .filterExpression("eventType", "=", "UNAUTHORIZED_ACCESS")
      .filterExpression("userAuthenticated", "=", true)
  )
  .orFilterExpression("eventType", "begins_with", "SIGN_IN")
  .execute();

This would compile as the following FilterExpression: eventType = "LOG_IN" OR (eventType = "UNAUTHORIZED_ACCESS" AND userAuthenticated = true)

NOT filter expression

await tsynamoClient
  .query("UserEvents")
  .keyCondition("userId", "=", "123")
  .filterExpression("NOT", (qb) =>
    qb.filterExpression("eventType", "=", "LOG_IN")
  )
  .execute();

This would compile as the following FilterExpression: NOT eventType = "LOG_IN", i.e. return all events whose types is not "LOG_IN"

Put item

Simple put item

await tsynamoClient
  .putItem("myTable")
  .item({
    userId: "123",
    eventId: 313,
  })
  .execute();

Put item with ConditionExpression

await tsynamoClient
  .putItem("myTable")
  .item({
    userId: "123",
    eventId: 313,
  })
  .conditionExpression("userId", "attribute_not_exists")
  .execute();

Put item with multiple ConditionExpressions

await tsynamoClient
  .putItem("myTable")
  .item({
    userId: "123",
    eventId: 313,
  })
  .conditionExpression("userId", "attribute_not_exists")
  .orConditionExpression("eventType", "begins_with", "LOG_")
  .execute();

Delete item

Simple delete item

await tsynamoClient
  .deleteItem("myTable")
  .keys({
    userId: "123",
    eventId: 313,
  })
  .execute();

Simple delete item with ConditionExpression

await tsynamoClient
  .deleteItem("myTable")
  .keys({
    userId: "123",
    eventId: 313,
  })
  .conditionExpression("eventType", "attribute_not_exists")
  .execute();

Update item

WIP

Contributors