npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tsynamo

v0.0.11

Published

Typed query builder for DynamoDB

Downloads

119

Readme

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!

[!WARNING]
Tsynamo is still an early stage project, please post issues if you notice something missing from the API!

Table of contents

Requirements

Installation

Available in NPM.

npm i tsynamo
pnpm install tsynamo
yarn add tsynamo

[!NOTE] You can also try it out at Tsynamo Playground

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;
  };
}

[!TIP] 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... */
  })
);

[!IMPORTANT] 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();

[!NOTE] 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();

[!NOTE] 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

await tsynamoClient
  .updateItem("myTable")
  .keys({ userId: "1", dataTimestamp: 2 })
  .set("nested.nestedBoolean", "=", true)
  .remove("nested.nestedString")
  .add("somethingElse", 10)
  .add("someSet", new Set(["4", "5"]))
  .delete("nested.nestedSet", new Set(["4", "5"]))
  .conditionExpression("somethingElse", ">", 0)
  .execute();

Transactions

One can also utilise DynamoDB Transaction features using Tsynamo. You can perform operations to multiple tables in a single transaction command.

Write transaction

DynamoDB enables you to do multiple Put, Update and Delete in a single WriteTransaction command. One can also provide an optional ClientRequestToken to the transaction to ensure idempotency.

const trx = tsynamoClient.createWriteTransaction();

trx.addItem({
  Put: tsynamoClient
    .putItem("myTable")
    .item({ userId: "313", dataTimestamp: 1 }),
});

trx.addItem({
  Update: tsynamoClient
    .updateItem("myTable")
    .keys({ userId: "313", dataTimestamp: 2 })
    .set("tags", "=", ["a", "b", "c"]),
});

trx.addItem({
  Delete: tsynamoClient.deleteItem("myTable").keys({
    userId: "313",
    dataTimestamp: 3,
  }),
});

await trx.execute();

[!IMPORTANT] When passing the items into the transaction using the tsynamoClient, do not execute the individual calls! Instead just pass in the query builder as the item.

[!WARNING]
DynamoDB also supports doing ConditionCheck operations in the transaction, but Tsynamo does not yet support those.

Read transaction

Since the read transaction output can affect multiple tables, the resulting output is an array of tuples where the first item is the name of the table and the second item is the item itself (or undefined if the item was not found). This can be used as a discriminated union to determine the resulting item's type.

const trx = tsynamoClient.createReadTransaction();

trx.addItem({
  Get: tsynamoClient.getItem("myTable").keys({
    userId: "123",
    dataTimestamp: 222,
  }),
});

trx.addItem({
  Get: tsynamoClient.getItem("myOtherTable").keys({
    userId: "321",
    stringTimestamp: "222",
  }),
});

const result = await trx.execute();

Then, one can loop through the result items as so:

// note that the items can be undefined if they were not found from DynamoDB
result.forEach(([table, item]) => {
  if (table === "myTable") {
    // item's type is DDB["myTable"]
    // ...
  } else if (table === "myOtherTable") {
    // item's type is DDB["myOtherTable"]
    // ...
  }
});

Contributors