JSPM

@vitkuz/dynamo-db-adapter

0.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 9
  • Score
    100M100P100Q69570F
  • License MIT

Functional TypeScript adapter for AWS DynamoDB following factory pattern with dual module support

Package Exports

  • @vitkuz/dynamo-db-adapter

Readme

DynamoDB Adapter

Functional TypeScript adapter for AWS DynamoDB following factory pattern with dual module support. Provides a clean, type-safe interface for DynamoDB operations with feature parity to @vitkuz/json-db-adapter.

Features

  • Factory Pattern - Pure functional approach with no classes
  • Single-Table Design - Efficient DynamoDB table structure
  • Full CRUD Operations - 19 operations covering all database needs
  • Type-Safe - Explicit TypeScript types throughout
  • Reference Population - Automatic resolution of related entities
  • Batch Operations - Optimized bulk operations with automatic chunking
  • Dual Module Support - Works with both ESM and CommonJS
  • AWS SDK v3 - Latest AWS SDK with modern features

Installation

npm install @vitkuz/dynamo-db-adapter

Prerequisites

  • AWS credentials configured (environment variables, IAM role, or AWS config)
  • DynamoDB table created with:
    • Partition Key: PK (String)
    • Sort Key: SK (String)

Table Management

Create DynamoDB Table

Use the included scripts to create a DynamoDB table with the correct schema:

TypeScript Script (requires npm install):

# Install dependencies first
npm install

# Create table with default settings
npm run table:create

# Create table with custom name and region
DYNAMODB_TABLE_NAME=my-custom-table AWS_REGION=us-west-2 npm run table:create

# Use PROVISIONED billing mode (default is PAY_PER_REQUEST)
DYNAMODB_BILLING_MODE=PROVISIONED DYNAMODB_READ_CAPACITY=10 DYNAMODB_WRITE_CAPACITY=5 npm run table:create

Shell Script (requires AWS CLI only):

# Create table with default settings
npm run table:create:sh
# or directly:
./scripts/create-table.sh

# Create table with custom name and region
DYNAMODB_TABLE_NAME=my-custom-table AWS_REGION=us-west-2 ./scripts/create-table.sh

# Use PROVISIONED billing mode
DYNAMODB_BILLING_MODE=PROVISIONED DYNAMODB_READ_CAPACITY=10 DYNAMODB_WRITE_CAPACITY=5 ./scripts/create-table.sh

Environment Variables:

Variable Default Description
DYNAMODB_TABLE_NAME dynamo-db-adapter-table Name of the DynamoDB table
AWS_REGION us-east-1 AWS region for the table
DYNAMODB_BILLING_MODE PAY_PER_REQUEST Billing mode (PAY_PER_REQUEST or PROVISIONED)
DYNAMODB_READ_CAPACITY 5 Read capacity units (PROVISIONED mode only)
DYNAMODB_WRITE_CAPACITY 5 Write capacity units (PROVISIONED mode only)

Delete DynamoDB Table

Delete a DynamoDB table (requires confirmation):

TypeScript Script:

# Delete table with default name
npm run table:delete

# Delete specific table
DYNAMODB_TABLE_NAME=my-custom-table npm run table:delete

Shell Script:

# Delete table with default name
npm run table:delete:sh
# or directly:
./scripts/delete-table.sh

# Delete specific table
DYNAMODB_TABLE_NAME=my-custom-table ./scripts/delete-table.sh

Safety Features:

  • Requires explicit confirmation (yes)
  • Shows item count before deletion
  • Waits for complete deletion

Quick Start

import { createDynamoDbAdapter } from '@vitkuz/dynamo-db-adapter';

// Create adapter instance
const adapter = createDynamoDbAdapter({
  tableName: 'my-app-table',
  region: 'us-east-1',
  logger: console, // Optional
});

// Create an entity
const result = await adapter.createOne({
  collection: 'users',
  data: {
    name: 'John Doe',
    email: 'john@example.com',
  },
});

console.log(result.entity);
// { id: 'uuid-generated', name: 'John Doe', email: 'john@example.com' }

// List entities
const users = await adapter.list({
  collection: 'users',
});

console.log(users.entities); // Array of user entities

Single-Table Design

This adapter uses DynamoDB's single-table design pattern where all collections share one table:

PK (Partition Key)          SK (Sort Key)              Data
-------------------------------------------------------------------
COLLECTION#users            ITEM#user-123              { id, name, email, ... }
COLLECTION#users            ITEM#user-456              { id, name, email, ... }
COLLECTION#posts            ITEM#post-789              { id, title, authorId, ... }
COLLECTION#posts            ITEM#post-012              { id, title, authorId, ... }

API Reference

Configuration

interface DynamoDbAdapterSettings {
  tableName: string;           // DynamoDB table name
  region?: string;             // AWS region (optional, uses default credentials)
  logger?: Logger;             // Optional logger interface
  autoCreateTable?: boolean;   // Auto-create table (default: false)
}

Operations

Single Entity Operations

createOne - Create a single entity

const result = await adapter.createOne({
  collection: 'users',
  data: { name: 'John', email: 'john@example.com' },
});

getOneById - Get entity by ID with optional populate

const result = await adapter.getOneById({
  collection: 'users',
  id: 'user-123',
  populate: ['companyId'], // Optional: resolve references
});

replaceOneById - Completely replace an entity

const result = await adapter.replaceOneById({
  collection: 'users',
  id: 'user-123',
  data: { name: 'Jane', email: 'jane@example.com' },
});

patchOneById - Partially update an entity (deep merge)

const result = await adapter.patchOneById({
  collection: 'users',
  id: 'user-123',
  data: { email: 'newemail@example.com' },
});

deleteOneById - Delete an entity

const result = await adapter.deleteOneById({
  collection: 'users',
  id: 'user-123',
});

Batch Operations

createMany - Create multiple entities

const result = await adapter.createMany({
  collection: 'users',
  data: [
    { name: 'User 1', email: 'user1@example.com' },
    { name: 'User 2', email: 'user2@example.com' },
  ],
});

getMany - Get multiple entities by IDs

const result = await adapter.getMany({
  collection: 'users',
  ids: ['user-123', 'user-456'],
  populate: ['companyId'], // Optional
});

deleteMany - Delete multiple entities

const result = await adapter.deleteMany({
  collection: 'users',
  ids: ['user-123', 'user-456'],
});

patchMany - Partially update multiple entities

const result = await adapter.patchMany({
  collection: 'users',
  ids: ['user-123', 'user-456'],
  data: { status: 'active' },
});

replaceMany - Replace multiple entities

const result = await adapter.replaceMany({
  collection: 'users',
  updates: [
    { id: 'user-123', data: { name: 'New Name 1' } },
    { id: 'user-456', data: { name: 'New Name 2' } },
  ],
});

Query Operations

list - List all entities with optional filter and populate

const result = await adapter.list({
  collection: 'users',
  filter: (user) => user.status === 'active', // Optional
  populate: ['companyId'], // Optional
});

findOne - Find first matching entity

const result = await adapter.findOne({
  collection: 'users',
  filter: (user) => user.email === 'john@example.com',
  populate: ['companyId'], // Optional
});

Utility Operations

exists - Check if entity exists

const result = await adapter.exists({
  collection: 'users',
  id: 'user-123',
});
console.log(result.exists); // true or false

count - Count entities with optional filter

const result = await adapter.count({
  collection: 'users',
  filter: (user) => user.status === 'active', // Optional
});
console.log(result.count); // Number of entities

clear - Remove all entities from a collection

const result = await adapter.clear({
  collection: 'users',
});
console.log(result.deletedCount); // Number of deleted entities

save - No-op for DynamoDB (auto-persists)

await adapter.save(); // Returns { success: true }

reload - No-op for DynamoDB (always fresh)

await adapter.reload(); // Returns { success: true }

Reference Population

Automatically resolve related entities by populating reference fields:

// Simple population (auto-detects collection)
const result = await adapter.getOneById({
  collection: 'posts',
  id: 'post-123',
  populate: ['authorId', 'categoryId'],
});
// post.authorId is now the full author object
// post.categoryId is now the full category object

// Advanced population with options
const result = await adapter.getOneById({
  collection: 'posts',
  id: 'post-123',
  populate: [
    {
      field: 'authorId',
      from: 'users', // Explicit collection
      select: ['id', 'name'], // Only populate specific fields
      onNotFound: 'null', // Behavior when reference not found ('null' | 'skip' | 'throw')
    },
  ],
});

Migration from json-db-adapter

The DynamoDB adapter provides API compatibility with @vitkuz/json-db-adapter:

Changes Required

  1. Installation
# Before
npm install @vitkuz/json-db-adapter

# After
npm install @vitkuz/dynamo-db-adapter
  1. Import
// Before
import { createJsonDbAdapter } from '@vitkuz/json-db-adapter';

// After
import { createDynamoDbAdapter } from '@vitkuz/dynamo-db-adapter';
  1. Configuration
// Before
const adapter = createJsonDbAdapter({
  filename: 'data/mydb',
  saveOnPush: true,
  humanReadable: true,
  separator: '/',
});

// After
const adapter = createDynamoDbAdapter({
  tableName: 'my-app-table',
  region: 'us-east-1',
});
  1. DynamoDB Table Setup

Create a DynamoDB table with:

  • Table name: (your choice)
  • Partition key: PK (String)
  • Sort key: SK (String)

API Compatibility

All 19 operations work identically:

// Exact same API for both adapters
await adapter.createOne({ collection: 'users', data: { name: 'John' } });
await adapter.getOneById({ collection: 'users', id: 'user-123' });
await adapter.list({ collection: 'users' });
await adapter.deleteMany({ collection: 'users', ids: ['id1', 'id2'] });
// ... etc

Differences

Feature json-db-adapter dynamo-db-adapter
Storage Local JSON file AWS DynamoDB
Persistence Manual save or auto Automatic
save() Writes to disk No-op (always persisted)
reload() Reloads from disk No-op (always fresh)
Scalability Single file Cloud-scale
Concurrency File locking Optimistic locking

Best Practices

  1. Batch Operations - Use batch operations when working with multiple items
// Good: Single batch operation
await adapter.createMany({ collection: 'users', data: users });

// Avoid: Multiple single operations
for (const user of users) {
  await adapter.createOne({ collection: 'users', data: user });
}
  1. Populate Wisely - Only populate fields you need
// Good: Select specific fields
await adapter.getOneById({
  collection: 'posts',
  id: 'post-123',
  populate: [{ field: 'authorId', select: ['id', 'name'] }],
});

// Avoid: Populating entire large objects
await adapter.getOneById({
  collection: 'posts',
  id: 'post-123',
  populate: ['authorId'], // Fetches all author fields
});
  1. Use Filters Efficiently - Filter in application code for complex logic
const result = await adapter.list({
  collection: 'users',
  filter: (user) => user.createdAt > '2024-01-01' && user.status === 'active',
});

Error Handling

All operations throw errors on failure:

try {
  await adapter.getOneById({ collection: 'users', id: 'invalid-id' });
} catch (error) {
  console.error('Operation failed:', error);
}

License

MIT

Author

Viktor Kuzmenko

Repository

https://github.com/vitkuz/test-claude-code-skills/tree/main/dynamo-db-adapter