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

rotorise

v0.3.5

Published

Supercharge your DynamoDB with Rotorise!

Readme

Rotorise

Type-safe DynamoDB composite key management for TypeScript.

DynamoDB offers incredible flexibility, but managing advanced patterns and techniques can be a challenge. Rotorise simplifies complex operations by providing abstractions for key definitions, composite key constructors, partial composite keys, and advanced sort key usage in queries. It integrates seamlessly with Brushless for a frictionless and performant DynamoDB experience.

Installation

npm install rotorise

Quick Start

Define your entity type and schema. Rotorise infers the exact key string types.

import { tableEntry } from 'rotorise'

type User = {
  orgId: string
  id: string
  role: 'admin' | 'user' | 'guest'
  email: string
}

const UserTable = tableEntry<User>()({
  PK: ['orgId', 'id'],
  SK: ['role'],
  GSI1PK: ['role'],
  GSI1SK: 'email',
})

API

tableEntry<Entity>()(schema, separator?)

Entry point for defining a DynamoDB table schema. Returns an object with the methods below.

The double-call <Entity>()(schema) is required because TypeScript does not support partial type parameter inference — Entity is explicit while Schema is inferred from arguments.

The optional separator defaults to '#'.

.key(keyName, attributes, config?)

Builds a specific key value from the given attributes.

UserTable.key('PK', { orgId: 'acme', id: '123' })
// => 'ORGID#acme#ID#123'

UserTable.key('GSI1SK', { email: '[email protected]' })
// => '[email protected]'

config options:

  • depth — Limit composite key to the first N components. Useful for begins_with queries.
  • allowPartial — When true, stops building the key when an attribute is missing instead of throwing. Returns a union of all valid partial prefixes at the type level.
  • enforceBoundary — When true, appends a trailing separator if the key is partial. Ensures a begins_with query doesn't match unintended prefixes.
UserTable.key('PK', { orgId: 'acme' }, { allowPartial: true })
// => 'ORGID#acme'
// Type: 'ORGID#acme' | `ORGID#${string}#ID#${string}`

UserTable.key('PK', { orgId: 'acme', id: '1' }, { depth: 1 })
// => 'ORGID#acme'

UserTable.key('PK', { orgId: 'acme', id: '1' }, { depth: 1, enforceBoundary: true })
// => 'ORGID#acme#'

.toEntry(item)

Converts a raw entity into a complete DynamoDB item with all keys computed.

const item = UserTable.toEntry({
  orgId: 'acme',
  id: '123',
  role: 'admin',
  email: '[email protected]',
})
// => { orgId: 'acme', id: '123', role: 'admin', email: '[email protected]',
//      PK: 'ORGID#acme#ID#123', SK: 'ROLE#admin',
//      GSI1PK: 'ROLE#admin', GSI1SK: '[email protected]' }

Rejects excess properties at the type level.

.fromEntry(entry)

Strips computed keys from a table entry, returning the raw entity.

const user = UserTable.fromEntry(item)
// => { orgId: 'acme', id: '123', role: 'admin', email: '[email protected]' }

.infer

Zero-runtime inference helper. Use with typeof to get the full table entry type.

type UserEntry = typeof UserTable.infer

.path()

Creates a proxy that builds DynamoDB expression paths as strings.

UserTable.path().email.toString()       // => 'email'
UserTable.path().PK.toString()          // => 'PK'

Advanced Features

Transforms

Override how a field maps to its key segment using a transform function.

const Table = tableEntry<User>()({
  PK: [
    ['orgId', (id: string) => ({ tag: 'ORG', value: id })],
    ['id', (id: string) => ({ tag: 'USER', value: id })],
  ],
  SK: ['role'],
})

Table.key('PK', { orgId: 'acme', id: '123' })
// => 'ORG#acme#USER#123'

A transform returns either:

  • A joinable value (the field name uppercased becomes the tag)
  • { value } (no tag segment emitted)
  • { tag, value } (custom tag)

Discriminated Schemas

When your table stores a union of entity types, use a discriminator to define per-variant key specs.

type Item =
  | { kind: 'order'; orderId: string; userId: string }
  | { kind: 'refund'; refundId: string; orderId: string }

const ItemTable = tableEntry<Item>()({
  PK: {
    discriminator: 'kind',
    spec: {
      order: ['userId', 'orderId'],
      refund: ['orderId', 'refundId'],
    },
  },
  SK: ['kind'],
})

ItemTable.key('PK', { kind: 'order', userId: 'u1', orderId: 'o1' })
// => 'USERID#u1#ORDERID#o1'

ItemTable.key('PK', { kind: 'refund', orderId: 'o1', refundId: 'r1' })
// => 'ORDERID#o1#REFUNDID#r1'

Set a discriminated spec value to null to produce undefined (useful for GSIs that don't apply to all variants).

Custom Separator

const Table = tableEntry<User>()(schema, '-')
// Keys use '-' instead of '#': 'ORGID-acme-ID-123'

Error Handling

All runtime errors throw RotoriseError (exported), so you can distinguish library errors from your own.

import { RotoriseError } from 'rotorise'

try {
  Table.key('PK', { /* missing required attrs */ })
} catch (e) {
  if (e instanceof RotoriseError) { /* ... */ }
}

Contributing

Open an issue or a PR. We are open to any kind of contribution and feedback.

License

Apache-2.0