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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nikovirtala/typesafe-dynamodb

v0.0.64

Published

[![npm version](https://badge.fury.io/js/@nikovirtala%2Ftypesafe-dynamodb.svg)](https://badge.fury.io/js/@nikovirtala%2Ftypesafe-dynamodb)

Readme

@nikovirtala/typesafe-dynamodb

npm version

@nikovirtala/typesafe-dynamodb is a fork of typesafe-dynamodb (a type-only library which replaces the type signatures of the AWS SDK v3's DynamoDB client) with schema validation based on zod.

It substitutes getItem, putItem, deleteItem and query API methods with type-safe and schema validated alternatives that are aware of the data in your tables and also adaptive to the semantics of the API request, e.g. by validating ExpressionAttributeNames and ExpressionAttributeValues contain all the values used in a ConditionExpression string, or by understanding the effect of a ProjectionExpression on the returned data type.

The end goal is to provide types and validation that have total understanding of the AWS DynamoDB API and enable full utilization of the TypeScript type system for modeling complex DynamoDB Tables, such as the application of union types and template string literals for single-table designs without forgetting runtime safety.

typesafe putItem ConditionExpression

Installation

npm install --save-dev @nikovirtala/typesafe-dynamodb

Usage

This library contains type definitions for AWS SDK v3 (@aws-sdk/client-dynamodb).

AWS SDK v3

Option 1 - DynamoDB

DynamoDB is a convenient way of using the DynamoDB API, except it is not optimized for tree-shaking (for that, see Option 2).

Start by declaring a standard TypeScript interface which describes the structure of data in your DynamoDB Table:

interface Record {
  key: string;
  sort: number;
  attribute: string;
  // all types are allowed, such as recursive nested types
  records?: Record[];
}

Then, cast the DynamoDB client instance to TypeSafeDynamoDBv3:

import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { TypeSafeDynamoDBv3 } from "typesafe-dynamodb/lib/client-v3";

const client = new DynamoDB({..});
const typesafeClient: TypeSafeDynamoDBv3<Record, "key", "sort"> = client;

"key" is the name of the Hash Key attribute, and "sort" is the name of the Range Key attribute.

Finally, use the client as you normally would, except now with intelligent type hints and validations.

Option 2 - DynamoDBClient (a Command-Response interface optimized for tree-shaking)

DynamoDBClient is a generic interface with a single method, send. To invoke an API, call send with an instance of the API's corresponding Command.

This option is designed for optimal tree-shaking when bundling code, ensuring that the bundle only includes code for the APIs your application uses.

For this option, type-safety is achieved by declaring your own commands and then calling the standard DynamoDBClient:

interface MyType {
  key: string;
  sort: number;
  list: string[];
}

const client = new DynamoDBClient({});

const GetMyTypeCommand = TypeSafeGetItemCommand<MyType, "key", "sort">();

await client.send(
  new GetMyTypeCommand({
    ..
  })
);

Document Client

The AWS SDK v3 provides a javascript-friendly interface called the DocumentClient. Instead of using the AttributeValue format, such as { S: "hello" } or { N: "123" }, the DocumentClient enables you to use native javascript types, e.g. "hello" or 123.

When defining your Command types, use the corresponding TypeSafe*DocumentCommand type, for example TypeSafeGetDocumentCommand instead of TypeSafeGetItemCommand:

  • GetItem - TypeSafeGetDocumentCommand
  • PutItem - TypeSafePutDocumentCommand
  • DeleteItem - TypeSafeDeleteDocumentCommand
  • UpdateItem - TypeSafeUpdateDocumentCommand
  • Query - TypeSafeQueryDocumentCommand
  • Scan - TypeSafeScanDocumentCommand

See: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_dynamodb.html

import { JsonFormat } from "typesafe-dynamodb";
import { TypeSafeGetDocumentCommand } from "typesafe-dynamodb/lib/get-document-command";

const MyGetItemCommand = TypeSafeGetDocumentCommand<MyType, "key", "sort">();

For the SDK V3 client, cast it to TypeSafeDocumentClientV3:

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
import { TypeSafeDocumentClientV3 } from "typesafe-dynamodb/lib/document-client-v3";

const client = new DynamoDBClient({});

const docClient = DynamoDBDocumentClient.from(
  client,
) as TypeSafeDocumentClientV3<MyType, "key", "sort">;

And then call .send with an instance of your TypeSafe Command:

docClient.send(new MyGetItemCommand({
  ..
}));

Features

Type-aware Input and Output

The type of the Key is derived from the Record type.

typesafe GetItem Key

Same for the Item in the response:

typesafe GetItemOutput Item

Single Table Design

Below are two interface declarations, representing two types of data stored in a single DynamoDB Table - User and Order. Single table design in DynamoDB is achieved by creating "composite keys", e.g. USER#${UserID}. In TypeScript, we use template literal types to encode this in the Type System.

interface User<UserID extends string = string> {
  PK: `USER#${UserID}`;
  SK: `#PROFILE#${UserID}`;
  Username: string;
  FullName: string;
  Email: string;
  CreatedAt: Date;
  Address: string;
}

interface Order<
  UserID extends string = string,
  OrderID extends string = string,
> {
  PK: `USER#${UserID}`;
  SK: `ORDER#${OrderID}`;
  Username: string;
  OrderID: string;
  Status: "PLACED" | "SHIPPED";
  CreatedAt: Date;
  Address: string;
}

With these two types defined, you can now use a union type to declare a TypeSafeDynamoDB instance aware of the two types of data in your tables:

const client: TypeSafeDynamoDB<User | Order, "PK", "SK">;

When making calls such as getItem, TypeScript will narrow the returned data type to the corresponding type based on the structure of the Key in your request:

narrowed getItem

Type-Safe DynamoDBStreamEvent

Leverage your data types in Lambda Functions attached to the DynamoDB Table Stream:

import { DynamoDBStreamEvent } from "typesafe-dynamodb/lib/stream-event";

export async function handle(
  event: DynamoDBStreamEvent<User | Order, "PK", "SK", "KEYS_ONLY">
) {
  ..
}

The event's type is derived from the data type and the the StreamViewType, e.g. "NEW_IMAGE" | "OLD_IMAGE" | "KEYS_ONLY" | "NEW_AND_OLD_IMAGES".

Schema-Validated DynamoDBStreamEvent

Validate DynamoDB stream events at runtime using Zod schemas:

import { z } from "zod";
import { validateStreamEvent } from "typesafe-dynamodb";
import type { DynamoDBStreamEvent } from "typesafe-dynamodb/lib/stream-event";

const UserSchema = z.object({
  PK: z.string(),
  SK: z.string(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

export async function handle(
  event: DynamoDBStreamEvent<User, "PK", "SK", "NEW_AND_OLD_IMAGES">,
) {
  try {
    const validatedEvent = validateStreamEvent(event, UserSchema);
    // Process validated stream records
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error("Schema validation failed:", error.issues);
    }
    throw error;
  }
}

The validateStreamEvent function validates both NewImage and OldImage data against your Zod schema, ensuring runtime type safety for your stream processing logic.

Filter result with ProjectionExpression

The ProjectionExpression field is parsed and applied to filter the returned type of getItem and query.

typesafe ProjectionExpression

Filter with AttributesToGet

If you specify AttributesToGet, then the returned type only contains those properties.

typesafe AttributesToGet

Validate ExpressionAttributeNames and ExpressionAttributeValues

If you add a ConditionExpression in putItem, you will be prompted for any #name or :valu placeholders:

typesafe putItem ConditionExpression

Same is true for a query:

typesafe query KeyConditionExpression and Filter

Marshall a JS Object to an AttributeMap

A better type definition @aws-sdk/util-dynamodb's marshall function is provided which maintains the typing information of the value passed to it and also adapts the output type based on the input marshallOptions

Given an object literal:

const myObject = {
  key: "key",
  sort: 123,
  binary: new Uint16Array([1]),
  buffer: Buffer.from("buffer", "utf8"),
  optional: 456,
  list: ["hello", "world"],
  record: {
    key: "nested key",
    sort: 789,
  },
} as const;

Call the marshall function to convert it to an AttributeMap which maintains the exact structure in the type system:

import { marshall } from "typesafe-dynamodb/lib/marshall";
// marshall the above JS object to its corresponding AttributeMap
const marshalled = marshall(myObject)

// typing information is carried across exactly, including literal types
const marshalled: {
  readonly key: S<"key">;
  readonly sort: N<123>;
  readonly binary: B;
  readonly buffer: B;
  readonly optional: N<456>;
  readonly list: L<readonly [S<"hello">, S<"world">]>;
  readonly record: M<...>;
}

Unmarshall an AttributeMap back to a JS Object

A better type definition @aws-sdk/util-dynamodb's unmarshall function is provided which maintains the typing information of the value passed to it and also adapts the output type based on the input unmarshallOptions.

import { unmarshall } from "typesafe-dynamodb/lib/marshall";

// unmarshall the AttributeMap back into the original object
const unmarshalled = unmarshall(marshalled);

// it maintains the literal typing information (as much as possible)
const unmarshalled: {
  readonly key: "key";
  readonly sort: 123;
  readonly binary: NativeBinaryAttribute;
  readonly buffer: NativeBinaryAttribute;
  readonly optional: 456;
  readonly list: readonly [...];
  readonly record: Unmarshall<...>;
}

If you specify {wrapNumbers: true}, then all number types will be wrapped as { value: string }:

const unmarshalled = unmarshall(marshalled, {
  wrapNumbers: true,
});

// numbers are wrapped in { value: string } because of `wrappedNumbers: true`
unmarshalled.sort.value; // string

// it maintains the literal typing information (as much as possible)
const unmarshalled: {
  readonly key: "key";
  // notice how the number is wrapped in the `NumberValue` type?
  // this is because `wrapNumbers: true`
  readonly sort: NumberValue<123>;
  readonly binary: NativeBinaryAttribute;
  readonly buffer: NativeBinaryAttribute;
  readonly optional: NumberValue<...>;
  readonly list: readonly [...];
  readonly record: Unmarshall<...>;
};