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

@skadhi/dynamo

v0.1.0

Published

Type-safe DynamoDB document mapper with schema-based operations

Readme

@skadhi/dynamo

A type-safe, schema-first DynamoDB ODM (Object Document Mapper) for TypeScript, designed specifically for Single Table Design patterns.

Features

  • 🔒 Full Type Safety - Complete TypeScript support with schema validation using Zod
  • 🏗️ Single Table Design - Native support for DynamoDB Single Table Design patterns
  • 🔍 Schema Validation - Runtime validation with Zod schemas
  • Performance Optimized - Built for AWS DynamoDB best practices
  • 🛠️ Fluent API - Intuitive, chainable API for all operations
  • 🎯 GSI Support - First-class Global Secondary Index support
  • 🔄 ACID Transactions - Type-safe transaction support
  • 📄 Pagination - Built-in cursor-based pagination
  • 🎛️ Condition Expressions - Type-safe condition and filter expressions

Installation

npm (public registry)

pnpm add @skadhi/dynamo

GitHub Packages

Adicione ao seu .npmrc:

@skadi:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

Instale:

pnpm add @skadhi/dynamo

Quick Start

1. Connect to Existing Table

import { Table, Entity, zdynamo } from '@skadhi/dynamo';

// Connect to existing DynamoDB table
const AppTable = Table
  .connect('my-dynamodb-table')
  .region('us-east-1')
  .globalSecondaryIndexes([
    {
      alias: 'byStatus',
      partitionKey: 'gsi_1_pk',
      sortKey: 'gsi_1_sk',
      projectionType: 'ALL'
    }
  ])
  .build();

2. Define Entity Schema

import { z } from 'zod';

const UserEntity = Entity
  .define('User')
  .table(AppTable)
  .schema({
    // Required partition key
    pk: zdynamo.partitionKey('USER#{userId}', {
      userId: zdynamo.uuid()
    }),
    
    // Required sort key (if defined)
    sk: zdynamo.sortKey('PROFILE#{userId}', {
      userId: zdynamo.uuid()
    }),
    
    // Entity fields with validation
    name: z.string().min(1).max(100),
    email: z.string().email(),
    age: z.number().int().min(18).max(120),
    isActive: z.boolean().default(true),
    
    // GSI keys
    gsi_1_pk: zdynamo.gsiPartitionKey('STATUS#{isActive}', {
      isActive: z.boolean()
    }),
    gsi_1_sk: zdynamo.timestamp(),
    
    // Automatic timestamps
    createdAt: zdynamo.timestamp(),
    updatedAt: zdynamo.timestamp()
  });

3. CRUD Operations

// CREATE
const [newUser, createError] = await UserEntity
  .create()
  .item({
    pk: { userId: 'user-123' },
    sk: { userId: 'user-123' },
    name: 'John Doe',
    email: '[email protected]',
    age: 30,
    gsi_1_pk: { isActive: true },
    gsi_1_sk: new Date()
  })
  .ifNotExists()
  .exec();

if (createError) {
  console.error('Failed to create user:', createError);
  return;
}

console.log('User created:', newUser);

// READ
const [user, getError] = await UserEntity
  .get()
  .key({ userId: 'user-123' })
  .exec();

if (getError) {
  console.error('Failed to get user:', getError);
  return;
}

if (user) {
  console.log('User found:', user);
} else {
  console.log('User not found.');
}

// QUERY
const [activeUsers, queryError] = await UserEntity
  .query()
  .index('byStatus')
  .pk({ isActive: true })
  .limit(50)
  .exec();

if (queryError) {
  console.error('Failed to query users:', queryError);
  return;
}

console.log('Active users:', activeUsers);

// UPDATE
const [updatedUser, updateError] = await UserEntity
  .update()
  .key({ userId: 'user-123' })
  .set({ name: 'Jane Doe', age: 31 })
  .condition('attribute_exists(pk)')
  .returnValues('ALL_NEW')
  .exec();

if (updateError) {
  console.error('Failed to update user:', updateError);
  return;
}

console.log('User updated:', updatedUser);

// DELETE
const [result, deleteError] = await UserEntity
  .delete()
  .key({ userId: 'user-123' })
  .condition('attribute_exists(pk)')
  .exec();

if (deleteError) {
  console.error('Failed to delete user:', deleteError);
  return;
}

console.log('User deleted successfully.');

Advanced Usage

Complex Schema Validation

const AccountEntity = Entity
  .define('Account')
  .table(AppTable)
  .schema({
    pk: zdynamo.partitionKey('USER#{userId}', {
      userId: zdynamo.uuid()
    }),
    sk: zdynamo.sortKey('ACCOUNT#{accountId}', {
      accountId: zdynamo.uuid()
    }),
    
    type: z.enum(['CHECKING', 'SAVINGS', 'CREDIT']),
    balance: z.number().default(0),
    
    // Nested object validation
    settings: z.object({
      notifications: z.boolean().default(true),
      overdraftLimit: z.number().min(0).optional(),
      autoPayments: z.array(z.object({
        payeeId: z.string().uuid(),
        amount: z.number().positive(),
        frequency: z.enum(['WEEKLY', 'MONTHLY'])
      })).default([])
    }).optional(),
    
    // Array validation
    tags: z.array(z.string()).default([]),
    
    // Custom validation
    accountNumber: z.string().regex(/^\d{10,12}$/, 'Invalid account number'),
    
    // GSI keys for workspace queries
    gsi_1_pk: zdynamo.gsiPartitionKey('WORKSPACE#{workspaceId}', {
      workspaceId: zdynamo.uuid()
    }),
    gsi_1_sk: zdynamo.gsiSortKey('ACCOUNT#{type}#{createdAt}', {
      type: z.enum(['CHECKING', 'SAVINGS', 'CREDIT']),
      createdAt: z.date()
    })
  });

API Reference

Schema Helpers (zdynamo)

Key Templates

zdynamo.partitionKey(template: string, params: ZodSchema)
zdynamo.sortKey(template: string, params: ZodSchema)
zdynamo.gsiPartitionKey(template: string, params: ZodSchema)
zdynamo.gsiSortKey(template: string, params: ZodSchema)

Common Types

zdynamo.uuid()                    // UUID validation
zdynamo.timestamp()               // Date with default now
zdynamo.currency()                // 3-letter currency code
zdynamo.entityType(type: string)  // Literal with default
zdynamo.nonEmptyString()          // String with min length 1
zdynamo.positiveNumber()          // Number > 0
zdynamo.email()                   // Email validation
zdynamo.url()                     // URL validation

Table Connection

Table.connect(tableName: string)
  .region(region: string)
  .client(client: DynamoDBDocumentClient)
  .transform({ caseStyle: 'snake_case', timestamps: boolean })
  .globalSecondaryIndexes(indexes: GSIDefinition[])
  .build()

Entity Definition

Entity.define(name: string)
  .table(table: ConnectedTable)
  .schema(schema: EntitySchemaDefinition)

Error Handling

All data operations, such as create, get, query, update, and delete, return a tuple [data, error]. If the operation is successful, data will contain the result and error will be null. If an error occurs, data will be null and error will be an instance of a Skadi-specific error.

You can use the isSkadiDynamoError type guard to safely handle different types of errors.

import { 
  isSkadiDynamoError
} from '@skadhi/dynamo';

const [user, error] = await UserEntity.create().item(invalidData).exec();

if (error) {
  if (isSkadiDynamoError(error)) {
    switch (error.code) {
      case 'ENTITY_VALIDATION_ERROR':
        // Handle validation errors
        console.error('Validation failed:', error.details);
        break;
      case 'MISSING_KEY_ERROR':
        // Handle missing key errors  
        console.error('A required key is missing:', error.message);
        break;
      case 'DYNAMO_OPERATION_ERROR':
        // Handle native DynamoDB errors
        console.error('DynamoDB error:', error.cause);
        break;
      default:
        // Handle other Skadi errors
        console.error('An unexpected Skadi error occurred:', error);
    }
  } else {
    // Handle non-Skadi errors
    console.error('An unexpected error occurred:', error);
  }
}

Best Practices

1. Schema Design

  • Use descriptive entity types
  • Include all GSI keys in your schema
  • Validate all business rules with Zod
  • Use templates for consistent key formats

2. Query Patterns

  • Design GSIs for your access patterns
  • Use meaningful aliases for GSIs
  • Implement proper pagination
  • Add appropriate filters to reduce data transfer

3. Type Safety

  • Export and reuse type definitions
  • Use the automatically inferred types
  • Validate input at service boundaries
  • Handle errors appropriately

Examples

See the complete example implementation for a full-featured account management system demonstrating all features of the library.

License

MIT © Skadi Team