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

typesafe-dynamo

v0.7.9

Published

Provide type-safe query operations for AWS DynamoDB.

Downloads

5

Readme

typesafe-dynamo

Provide type-safe query operations for AWS DynamoDB.

Quick start

npm i --save typesafe-dynamo

Example

import { DynamoDB } from "aws-sdk";
import typesafe from "typesafe-dynamo";

const client = new DynamoDB.DocumentClient({
    region: "ap-northeast-2",
});

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "id">(client, "user");

await table.put(({ values }) => [
    values({
        id: "0000",
        name: "jinsu",
        age: 25,
    }),
]);

const result = await table.get(({ key }) => [
    key({
        id: "0000",
    }),
]);

console.log(result);

Don't worry about features that are not supported yet. You can still use the types from aws-sdk.

const result = await user.scan(() => [{
    FilterExpression: "age >= 25",
}]);

Or you can even use some of the features of this library.

const result = await user.scan(({ select }) => [
    {
        FilterExpression: "age >= 25",
    },
    select("name"),
]);

How to define your own table

function typesafe<Schema, PK, SK, GSI>(...props: OperationProps)

This function defines the type of your table. Schema and PK are required generic type parameters. SK and GSI are optional. Last, you should put your DynamoDB.DocumentClient and your table name to the function parameter.

Schema

type UserSchema = {
    id: string;
    name: string;
    age: int;
    updatedAt: Date;
    createdAt: Date;
}
const table = typesafe<UserSchema, ...>(...)

You can put anything to the Schema type parameter. This type defines the columns and their data types in your table.

This library automatically adds updatedAt to your object putting. Even if your Schema does not have the properties.

PK

type PK = "id"
const table = typesafe<UserSchema, PK, ...>(...)

PK should be one of the keys of the Schema. It defines the partition key of your DynamoDB table.

SK

type SK = "age"
const table = typesafe<UserSchema, PK, SK>(...)

SK should be one of the keys of the Schema. It defines the sort key of your DynamoDB table.

GSI

type GSI = {
    "idNameIndex": ["id", "name"]
}
const table = typesafe<UserSchema, PK, SK, GSI>(...)

GSI should extend GSIList<Schema>. The keys of the GSI type parameter should be each name of your GSI table. And its value should be an array that has a PK element and an SK element of your GSI table.

OperationProps

type OperationProps = [client: DynamoDB.DocumentClient, name: string, option?: OperationOption];

It's the type of the parameters in typesafe function. you should put your DynamoDB.DocumentClient and your table name like below:

const client = new DynamoDB.DocumentClient({
    region: "ap-northeast-2",,
})

const name = "user-table"

const table = typesafe<UserSchema, PK, SK, GSI>(client, name)

You can configure some options too. Check out the OperationOption type.

export type OperationOption = {
  soft?: boolean;
  deleteDateColumn?: string;
  dateFormat?: {
    toDate: (value: Date) => string;
    validateDate: (value: unknown) => boolean;
    fromDate: (value: string) => Date;
  }
}

If soft is true, the delete operation won't delete an item in your DynamoDB table. Of course you can not find the item by other operations though. Note that you should set deleteDateColumn by a colum name so that the operations can judge if it has been deleted by the name.

Also, you can configure how your Date type field is saved in your DynamoDB table by toDate, fromDate and validateDate parameters. By default, it saves a date as a ISO8610 date string.

Check out an example using options:

const table = typesafe<UserSchema, PK, SK, GSI>(client, name, { soft: true, deleteDateColumn: "deletedAt" })

Operations

After a table is defined, you can use operation methods from it.

type Operations<Schema, PK extends keyof Schema, SK extends keyof Schema, GSI extends GSIList<Schema>> = {
  get: GetOperation<Schema, PK, SK>;
  query: QueryOperation<Schema, PK, SK, GSI>;
  scan: ScanOperation<Schema, PK, SK>;
  put: PutOperation<Schema>;
  update: UpdateOperation<Schema, PK, SK>;
  remove: RemoveOperation<Schema, PK, SK>;
}

GetOperation

This is a type-safe version get operation of DynamoDB. You have 2 functions by which you can manipulate parameters: key and select.

key

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "id">(client, name)

const result = await table.get(({ key }) => [
    key({ id: "01" })
])

Like above, every function is given through the parameter of the function parameter of each operation.

select

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "id">(client, name)

const result = await table.get(({ key, select }) => [
    key({ id: "01" }),
    select("age", "id")
])

Sometimes you may want to select only some of the properties from the Schema.

QueryOperation

This is a type-safe version query operation of DynamoDB. You have 7 functions by which you can manipulate parameters: condition, filter, nextOf, indexName, select, limit, and direction. But I'll omit the select function here because it's just the same as the example in GetOperation.

condition

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "age", "id">(client, name)

const result = await table.query(({ condition }) => [
    condition({ age: 30 }),
])

It's going to find every user who is 30 years old.

filter

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "age", "id">(client, name)

const result = await table.query(({ condition, filter }) => [
    condition({ age: 30 }),
    filter({ name: "John" })
])

It's going to find every John who is 30 years old.

nextOf

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "age", "id">(client, name)

const result = await table.query(({ condition, nextOf }) => [
    condition({ age: 30 }),
    nextOf({ name: "John", age: 30 })
])

It's going to find every john after the 30 yo john.

indexName

type User = {
    id: string;
    name: string;
    age: number;
}

type GSI = {
    "nameAgeIndex": ["name", "age"]
}

const table = typesafe<User, "age", "id", GSI>(client, name)

const result = await table.query(({ indexName }) => [
    indexName("nameAgeIndex").condition({
        name: "john",
    })
])

It enables you to use the Global Secondary Index of your DynamoDB table.

limit

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "age", "id">(client, name)

const result = await table.query(({ condition, limit }) => [
    condition({ age: 30 }),
    limit(10)
])

It's going to find at most 10 users who are 30 years old.

direction

type User = {
    id: string;
    name: string;
    age: number;
}

type GSI = {
    "nameAgeIndex": ["name", "Age"]
}

const table = typesafe<User, "age", "id", GSI>(client, name)

const result = await table.query(({ indexName }) => [
    indexName("nameAgeIndex").condition({
        name: "john",
    }),
    indexName("nameAgeIndex").direction("BACKWORD")
])

It's going to find every John but from the oldest.

ScanOperation

This is a type-safe version scan operation of DynamoDB. You have 4 functions by which you can manipulate parameters: filter, nextOf, select, and limit. But everything is just the same as with QueryOperation.

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "age", "id">(client, name)

const result = await table.scan(({ filter }) => [
    filter({ name: "John" })
])

PutOperation

This is a type-safe version scan operation of DynamoDB. You have only one function by which you can manipulate parameters: values.

values

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "age", "id">(client, name)

const result = await table.put(({ values }) => [
    values({
        "id": "01",
        "name": "John",
        "age": 30,
    })
])

UpdateOperation

This is a type-safe version update operation of DynamoDB. You have 2 functions by which you can manipulate parameters: key and replace. key is just the same as the one in the GetOperation. It picks an item with the PK and SK if it exists.

replace

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "id", "name">(client, name)

const result = await table.update(({ key, replace }) => [
    key({
        "id": "01",
        "name": "John",
    })
    replace({
        "age": 29,
    })
])

RemoveOperation

This is a type-safe version of the delete operation of DynamoDB. You have only one function by which you can manipulate parameters: key. key is just the same as the one in the GetOperation.

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "id", "name">(client, name)

const result = await table.remove(({ key }) => [
    key({
        "id": "01",
        "name": "John",
    })
])

Errors

Operations throw AWSError if an internal error occurs. Use try-catch

type User = {
    id: string;
    name: string;
    age: number;
}

const table = typesafe<User, "id">(client, name)

try {
    const result = await table.get(({ key }) => [
        key({ id: "01" })
    ])
} catch (e: any) {
    if ("statusCode" in e) {
        //TODO: Error handling
    }
}