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

@verisure-italy/dynamo-kit

v1.8.6

Published

DynamoDB utility library

Readme

DynamoDB Kit

A TypeScript toolkit for working with AWS DynamoDB, providing a simplified client interface with automatic timestamp management, type-safe operations, and repository pattern implementation.

Installation

npm install @verisure-italy/dynamo-kit
# or
pnpm add @verisure-italy/dynamo-kit

Features

  • 🔧 Easy Client Configuration: Simplified DynamoDB client setup with smart defaults
  • Automatic Timestamps: Auto-managed createdAt and updatedAt fields
  • 🎯 Type Safety: Full TypeScript support with generic types
  • 🏗️ Repository Pattern: High-level repository abstraction for common operations
  • 🚀 Performance: Built-in client caching and optimized marshalling
  • 🌍 Environment Aware: Automatic local/offline detection
  • 📊 Query Builder: Fluent query and expression builders

Quick Start

import { getDynamoClient, createRepo } from '@verisure-italy/dynamo-kit'

// Define your entity type
interface User {
  id: string
  email: string
  name: string
  age?: number
}

// Create a repository
const userRepo = createRepo<User>({
  baseTableName: 'users',
  id: { idField: 'id' }
})

// Use the repository
const user = await userRepo.put({
  id: 'user-123',
  email: '[email protected]',
  name: 'John Doe',
  age: 30
})

console.log(user) // Includes createdAt and updatedAt timestamps

Client Configuration

Creating a DynamoDB Client

import { getDynamoClient } from '@verisure-italy/dynamo-kit'

// Use default configuration (reads from environment variables)
const client = getDynamoClient()

// Override specific settings
const client = getDynamoClient({
  region: 'us-east-1',
  endpoint: 'http://localhost:8000', // for local DynamoDB
  tablePrefix: 'dev',
  credentials: {
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-key'
  }
})

Configuration Options

The client accepts the following configuration options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | region | string | 'eu-west-1' | AWS region | | endpoint | string | undefined | Custom DynamoDB endpoint (auto-detected for local) | | tablePrefix | string | '' | Prefix for all table names | | credentials | AwsCredentialIdentity | undefined | AWS credentials |

Environment Variables

The kit automatically reads these environment variables:

  • AWS_REGION: Sets the default region
  • DYNAMO_ENDPOINT: Custom DynamoDB endpoint
  • DYNAMO_TABLE_PREFIX: Default table prefix
  • IS_OFFLINE / AWS_SAM_LOCAL: Auto-enables local development mode

Local Development Setup

// Automatic local detection
const client = getDynamoClient() // Detects IS_OFFLINE=true

// Manual local configuration
const localClient = getDynamoClient({
  region: 'local',
  endpoint: 'http://localhost:8000'
})

Repository Pattern

Creating a Repository

import { createRepo } from '@verisure-italy/dynamo-kit'

interface Product {
  productId: string
  name: string
  price: number
  category: string
}

const productRepo = createRepo<Product>({
  baseTableName: 'products',
  id: { idField: 'productId' }, // Simple ID field
  // Optional: provide custom client
  // client: getDynamoClient({ region: 'us-west-2' })
})

Complex Key Configuration

For composite keys or custom key generation:

interface Order {
  customerId: string
  orderId: string
  items: OrderItem[]
  total: number
}

const orderRepo = createRepo<Order>({
  baseTableName: 'orders',
  id: {
    keyOf: (orderOrId) => {
      if (typeof orderOrId === 'string') {
        // If just orderId is provided
        const [customerId, orderId] = orderOrId.split('#')
        return { customerId, orderId }
      }
      // If full order object is provided
      return { 
        customerId: orderOrId.customerId, 
        orderId: orderOrId.orderId 
      }
    }
  }
})

Repository Operations

Create/Update Items

// Create new item (auto-adds createdAt and updatedAt)
const newUser = await userRepo.put({
  id: 'user-123',
  email: '[email protected]',
  name: 'John Doe'
})

// Update existing item (preserves createdAt, updates updatedAt)
const existingUser = await userRepo.put({
  id: 'user-123',
  email: '[email protected]',
  name: 'John Doe Updated',
  createdAt: 1697234567 // Existing timestamp preserved
})

Retrieve Items

// Get item by ID
const user = await userRepo.get('user-123')

// Get specific fields only
const userEmail = await userRepo.get('user-123', ['email', 'name'])

Update Items

// Partial update (auto-updates updatedAt)
const updatedUser = await userRepo.update('user-123', {
  name: 'John Smith',
  age: 31
})

// Remove fields by setting to undefined
const cleanUser = await userRepo.update('user-123', {
  age: undefined // This field will be removed
})

Delete Items

await userRepo.remove('user-123')

Scan Operations

// Scan all items
const allUsers = await userRepo.scan()

// Scan with pagination
const page1 = await userRepo.scan({ Limit: 10 })
const page2 = await userRepo.scan({ 
  Limit: 10, 
  ExclusiveStartKey: page1.nextToken 
})

// Scan specific fields only
const userNames = await userRepo.scan({
  fields: ['id', 'name'],
  Limit: 100
})

Query Global Secondary Indexes

// Query by email index
const usersByEmail = await userRepo.queryIndex({
  index: 'email-index',
  hash: { field: 'email', value: '[email protected]' }
})

// Query with range condition
const recentOrders = await userRepo.queryIndex({
  index: 'customer-date-index',
  hash: { field: 'customerId', value: 'customer-123' },
  range: {
    field: 'createdAt',
    op: '>=',
    value: Date.now() / 1000 - 86400 // Last 24 hours
  }
})

// Query with BETWEEN range
const ordersInRange = await userRepo.queryIndex({
  index: 'customer-date-index',
  hash: { field: 'customerId', value: 'customer-123' },
  range: {
    field: 'createdAt',
    op: 'between',
    value: [startTimestamp, endTimestamp]
  },
  scanIndexForward: false, // Descending order
  limit: 50
})

// Query with begins_with
const usersByPrefix = await userRepo.queryIndex({
  index: 'name-index',
  hash: { field: 'status', value: 'active' },
  range: {
    field: 'name',
    op: 'begins_with',
    value: 'John'
  },
  fields: ['id', 'name', 'email']
})

Range Operators

Available operators for range conditions:

  • '=': Exact match
  • '<': Less than
  • '<=': Less than or equal
  • '>': Greater than
  • '>=': Greater than or equal
  • 'begins_with': String prefix match
  • 'between': Value between two bounds (inclusive)

Advanced Filtering

Both scan and queryIndex support advanced filtering with FilterExpression:

import { FilterCondition } from '@verisure-italy/dynamo-kit'

// Filter with scan
const activeAdults = await userRepo.scan({
  filters: [
    { field: 'status', op: '=', value: 'active' },
    { field: 'age', op: '>=', value: 18 }
  ],
  Limit: 50
})

// Filter with query
const results = await userRepo.queryIndex({
  index: 'status-index',
  hash: { field: 'status', value: 'active' },
  filters: [
    { field: 'age', op: 'between', value: [18, 65] },
    { field: 'country', op: 'in', value: ['US', 'UK', 'CA'] }
  ],
  limit: 100
})

Filter Operators

Available filter operators:

  • Comparison: '=', '<>', '<', '<=', '>', '>='
  • String: 'begins_with', 'contains'
  • Range/Array: 'between', 'in'
  • Existence: 'attribute_exists', 'attribute_not_exists'

Filter Examples

// Equality
{ field: 'status', op: '=', value: 'active' }

// Greater than
{ field: 'age', op: '>', value: 18 }

// Between (inclusive)
{ field: 'age', op: 'between', value: [18, 65] }

// In array
{ field: 'country', op: 'in', value: ['US', 'UK', 'CA'] }

// String prefix
{ field: 'email', op: 'begins_with', value: 'admin' }

// String contains
{ field: 'description', op: 'contains', value: 'urgent' }

// Attribute exists
{ field: 'phoneNumber', op: 'attribute_exists' }

// Attribute does not exist
{ field: 'deletedAt', op: 'attribute_not_exists' }

Multiple Filters (AND Logic)

All filters are combined with AND logic:

const results = await userRepo.scan({
  filters: [
    { field: 'status', op: '=', value: 'active' },
    { field: 'age', op: '>=', value: 18 },
    { field: 'country', op: 'in', value: ['US', 'UK'] },
    { field: 'emailVerified', op: '=', value: true }
  ]
})
// Returns items where status='active' AND age>=18 AND country IN ('US','UK') AND emailVerified=true

Pagination

All scan and query operations return a Page<T> object:

interface Page<T> {
  items: T[]           // The retrieved items
  count: number        // Number of items in this page
  nextToken: Record<string, any> | null // Token for next page
}

// Example pagination loop
let nextToken = null
do {
  const page = await userRepo.scan({ Limit: 100, ExclusiveStartKey: nextToken })
  console.log(`Found ${page.count} users`)
  page.items.forEach(user => console.log(user.name))
  nextToken = page.nextToken
} while (nextToken)

Automatic Timestamps

All write operations automatically manage timestamp fields:

// PUT operation
const user = await userRepo.put({ id: '123', name: 'John' })
// Result: { id: '123', name: 'John', createdAt: 1697234567, updatedAt: 1697234567 }

// UPDATE operation  
const updated = await userRepo.update('123', { name: 'Jane' })
// Result: { id: '123', name: 'Jane', createdAt: 1697234567, updatedAt: 1697234890 }

To override timestamps for specific operations:

// Preserve existing createdAt when using put()
const user = await userRepo.put({
  id: '123',
  name: 'John',
  createdAt: existingTimestamp // This will be preserved
})

Advanced Examples

Multi-Environment Setup

import { getDynamoClient, createRepo } from '@verisure-italy/dynamo-kit'

// Development
const devClient = getDynamoClient({
  endpoint: 'http://localhost:8000',
  tablePrefix: 'dev'
})

// Staging
const stagingClient = getDynamoClient({
  region: 'eu-west-1',
  tablePrefix: 'staging'
})

// Production
const prodClient = getDynamoClient({
  region: 'eu-west-1',
  tablePrefix: 'prod'
})

// Create environment-specific repositories
const devUserRepo = createRepo<User>({
  baseTableName: 'users',
  id: { idField: 'id' },
  client: devClient
})

Complex Queries with Extra Parameters

// Query with additional DynamoDB parameters
const results = await userRepo.queryIndex({
  index: 'status-created-index',
  hash: { field: 'status', value: 'active' },
  range: { field: 'createdAt', op: '>=', value: lastWeek },
  limit: 100,
  scanIndexForward: false,
  extra: {
    FilterExpression: '#age > :minAge AND attribute_exists(#email)',
    ExpressionAttributeNames: {
      '#age': 'age',
      '#email': 'email'
    },
    ExpressionAttributeValues: {
      ':minAge': 18
    }
  }
})

Custom Table Names

// Use resolved table name directly
const customRepo = createRepo<User>({
  baseTableName: 'users', // This gets prefixed
  // OR use tableName to bypass prefixing
  tableName: 'my-exact-table-name', // This is used as-is
  id: { idField: 'id' }
})

Type Safety

The kit provides full TypeScript support:

interface User {
  id: string
  email: string
  name: string
  age?: number
}

const repo = createRepo<User>({
  baseTableName: 'users',
  id: { idField: 'id' }
})

// TypeScript ensures type safety
const user = await repo.get('123') // Returns User | null
const updated = await repo.update('123', { 
  name: 'New Name' // ✅ Valid
  // invalidField: 'value' // ❌ TypeScript error
})

Error Handling

try {
  const user = await userRepo.get('nonexistent')
  // user will be null if not found
} catch (error) {
  // Handle DynamoDB errors
  console.error('DynamoDB error:', error)
}

Performance Tips

  1. Client Caching: Clients are automatically cached based on configuration
  2. Field Selection: Use fields parameter to retrieve only needed attributes
  3. Pagination: Always use pagination for large datasets
  4. Batch Operations: For bulk operations, consider using the raw DynamoDB client
  5. Indexes: Design GSIs for your query patterns

Migration from Raw DynamoDB

// Before (raw DynamoDB)
const params = {
  TableName: 'users',
  Key: { id: 'user-123' },
  UpdateExpression: 'SET #name = :name, #updatedAt = :now',
  ExpressionAttributeNames: { '#name': 'name', '#updatedAt': 'updatedAt' },
  ExpressionAttributeValues: { ':name': 'New Name', ':now': Date.now() / 1000 },
  ReturnValues: 'ALL_NEW'
}
const result = await client.send(new UpdateCommand(params))

// After (with dynamo-kit)
const user = await userRepo.update('user-123', { name: 'New Name' })

License

MIT