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

@bm8/aws-dynamodb

v0.0.1

Published

A small, type-safe wrapper around `@aws-sdk/lib-dynamodb` for single-table DynamoDB designs. You declare your entities once with `defineEntity`, then `createDynamoClient` returns a per-entity client where `put`/`get`/`update`/`upsert`/`pagedQuery` are typ

Readme

@bm8/aws-dynamodb

A small, type-safe wrapper around @aws-sdk/lib-dynamodb for single-table DynamoDB designs. You declare your entities once with defineEntity, then createDynamoClient returns a per-entity client where put/get/update/upsert/pagedQuery are typed against the entity's attributes and indexes.

Install

pnpm add @bm8/aws-dynamodb

Concepts

  • Single table. All entities live in one table keyed by PK / SK.
  • Type discriminator. Every item carries a type attribute set from the entity definition. Queries automatically filter by type, so a pagedQuery on the user entity will not return order items that happen to share a partition.
  • GSIs. Supported index names are GSI1 and GSI2. Declaring an index on an entity surfaces GSI1PK / GSI1SK (etc.) as typed attributes you can write and query against.

Defining entities

import { defineEntity, type Entity } from '@bm8/aws-dynamodb';

type UserAttrs = {
  PK: `USER#${string}`;
  SK: `USER#${string}`;
  GSI1PK?: 'USERS';
  GSI1SK?: string;
  name: string;
  email: string;
};

export const userEntity = defineEntity<'USER', UserAttrs, ['GSI1']>(
  'USER',
  { indexes: ['GSI1'] },
);

type OrderAttrs = {
  PK: `USER#${string}`;
  SK: `ORDER#${string}`;
  total: number;
};

export const orderEntity = defineEntity<'ORDER', OrderAttrs>('ORDER');

export const entities = { user: userEntity, order: orderEntity };

The first generic must be uppercase — the type tag is enforced as Uppercase<TType>.

Creating the client

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { createDynamoClient } from '@bm8/aws-dynamodb';
import { entities } from './entities';

const db = createDynamoClient({
  tableName: 'app-table',
  entities,
  // optional: pass your own DynamoDBClient (region, endpoint, credentials, etc.)
  client: new DynamoDBClient({}),
});

db is shaped as { user: EntityOperations, order: EntityOperations }, one entry per key in entities.

Operations

put(entity)

Writes the full item. The type attribute is set automatically from the entity definition; any value you pass for type is overwritten.

await db.user.put({
  PK: 'USER#1',
  SK: 'USER#1',
  GSI1PK: 'USERS',
  GSI1SK: 'alice',
  name: 'Alice',
  email: '[email protected]',
});

get(key)

Reads by primary key. Returns undefined if the item does not exist.

const user = await db.user.get({ PK: 'USER#1', SK: 'USER#1' });

update(key, patch, remove?)

Sets the attributes in patch and removes the attributes listed in remove. Fails if the item does not exist (attribute_exists(PK)). The type attribute cannot be updated.

await db.user.update(
  { PK: 'USER#1', SK: 'USER#1' },
  { name: 'Alice B.', GSI1SK: 'alice b.' },
);

// remove optional attributes
await db.user.update(
  { PK: 'USER#1', SK: 'USER#1' },
  {},
  ['GSI1PK', 'GSI1SK'],
);

Only attributes typed as optional on the entity are accepted in the remove list.

upsert(key, patch)

Like update, but creates the item if it does not exist. Sets type on insert.

await db.user.upsert(
  { PK: 'USER#2', SK: 'USER#2' },
  { name: 'Bob', email: '[email protected]' },
);

pagedQuery(params)

Queries the base table or a GSI, paginating through LastEvaluatedKey and returning the accumulated results. A type filter is always applied so cross-entity items in the same partition are excluded.

// base table — all orders for a user
const orders = await db.order.pagedQuery({
  pk: 'USER#1',
  sk: { beginsWith: 'ORDER#' },
});

// GSI1 — first 50 users alphabetically
const users = await db.user.pagedQuery({
  index: 'GSI1',
  pk: 'USERS',
  sk: { between: ['a', 'm'] },
  limit: 50,
});

sk accepts one of:

{ equals: string }
{ beginsWith: string }
{ between: [string, string] }
{ lt: string } | { lte: string } | { gt: string } | { gte: string }

limit caps the in-memory result count; pagination stops as soon as that many items have been collected.

Testing

The package's tests run against amazon/dynamodb-local. With the container up:

pnpm --filter @bm8/aws-dynamodb test