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-adapterPrerequisites
- AWS credentials configured (environment variables, IAM role, or AWS config)
- DynamoDB table created with:
- Partition Key:
PK(String) - Sort Key:
SK(String)
- Partition Key:
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:createShell 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.shEnvironment 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:deleteShell 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.shSafety 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 entitiesSingle-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 falsecount - Count entities with optional filter
const result = await adapter.count({
collection: 'users',
filter: (user) => user.status === 'active', // Optional
});
console.log(result.count); // Number of entitiesclear - Remove all entities from a collection
const result = await adapter.clear({
collection: 'users',
});
console.log(result.deletedCount); // Number of deleted entitiessave - 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
- Installation
# Before
npm install @vitkuz/json-db-adapter
# After
npm install @vitkuz/dynamo-db-adapter- Import
// Before
import { createJsonDbAdapter } from '@vitkuz/json-db-adapter';
// After
import { createDynamoDbAdapter } from '@vitkuz/dynamo-db-adapter';- 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',
});- 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'] });
// ... etcDifferences
| 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
- 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 });
}- 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
});- 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