Skip to content
reaatechREAATECH

@reaatech/idempotency-middleware-adapter-dynamodb

pending npm

Provides a DynamoDB storage adapter for `@reaatech/idempotency-middleware` that uses conditional writes for distributed locking and native TTL for automatic record expiration. It exports a `DynamoDBAdapter` class that accepts an AWS SDK v3 `DynamoDBClient` instance.

@reaatech/idempotency-middleware-adapter-dynamodb

npm version License: MIT CI

AWS DynamoDB storage adapter for @reaatech/idempotency-middleware. Uses conditional writes for distributed locking with TTL-compatible expiresAt attributes, backed by the AWS SDK for JavaScript v3.

Installation

terminal
npm install @reaatech/idempotency-middleware-adapter-dynamodb @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb
# or
pnpm add @reaatech/idempotency-middleware-adapter-dynamodb @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb

Feature Overview

  • Conditional write lockingattribute_not_exists(cacheKey) OR expiresAt < :nowSec prevents concurrent lock acquisition across distributed instances
  • TTL-compatible expiresAt — epoch-second attribute compatible with DynamoDB’s native TTL feature for automatic record cleanup
  • Client-side TTL enforcement — explicit expiry check on get() as a safety net before DynamoDB TTL scavenging
  • Connectionless — DynamoDB client is ready immediately; connect() and disconnect() are no-ops
  • Implements StorageAdapter — drop-in replacement for any other adapter

Quick Start

typescript
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBAdapter } from '@reaatech/idempotency-middleware-adapter-dynamodb';
import { IdempotencyMiddleware } from '@reaatech/idempotency-middleware';
 
const client = new DynamoDBClient({ region: 'us-east-1' });
const storage = new DynamoDBAdapter(client, 'idempotency-cache');
 
const middleware = new IdempotencyMiddleware(storage, { ttl: 3_600_000 });
 
const result = await middleware.execute(
  'order-456',
  { method: 'POST', path: '/orders', body: { productId: 'abc' } },
  async () => ({ id: 1, status: 'created' }),
);

API Reference

DynamoDBAdapter

typescript
import { DynamoDBAdapter } from '@reaatech/idempotency-middleware-adapter-dynamodb';
 
const adapter = new DynamoDBAdapter(client, 'my-table');

Constructor

ParamTypeDefaultDescription
clientDynamoDBClient(required)Configured AWS SDK v3 DynamoDB client
tableNamestringidempotency-cacheThe DynamoDB table name

Table Schema

The adapter expects a table with a partition key cacheKey (type String). Each item stores:

AttributeTypeDescription
cacheKeySPartition key — the SHA-256 cache key
response(any)The cached response (serialized)
createdAtNEpoch milliseconds
ttlNTTL in milliseconds
expiresAtNEpoch seconds (createdAt + ttl) — compatible with DynamoDB TTL

Methods

Implements the full StorageAdapter interface:

MethodDynamoDB OperationDescription
connect()NoneNo-op (client is connectionless)
disconnect()NoneNo-op
get(key)GetItem with cacheKey partition keyReturns null if missing or expired
set(key, record)PutItem with marshalled data + expiresAtOverwrites existing entries
delete(key)DeleteItem with cacheKey partition keyRemoves the item
acquireLock(key, ttl)PutItem with ConditionExpressionReturns true on success, false on ConditionalCheckFailedException
releaseLock(key)DeleteItem on lock:<key>Removes the lock item
waitForLock(key, timeout, pollInterval)GetItem on lock:<key>Polls until lock disappears, expires, or timeout

Locking Design

The DynamoDB adapter uses conditional writes for distributed locking:

  1. Acquire: PutItem on lock:<key> with condition attribute_not_exists(cacheKey) OR expiresAt < :nowSec
  2. Release: DeleteItem on lock:<key>
  3. Wait: polls GetItem on lock:<key> — returns when the item is missing or its expiresAt has passed

Expired locks are automatically re-acquirable due to the expiresAt < :nowSec clause in the condition expression.

Usage Patterns

Enabling DynamoDB TTL

Enable TTL on your DynamoDB table pointing to the expiresAt attribute. This lets DynamoDB automatically delete expired items without consuming write capacity:

terminal
aws dynamodb update-time-to-live \
  --table-name idempotency-cache \
  --time-to-live-specification "AttributeName=expiresAt,Enabled=true"

IAM Permissions

The adapter requires these DynamoDB actions:

json
{
  "Effect": "Allow",
  "Action": [
    "dynamodb:GetItem",
    "dynamodb:PutItem",
    "dynamodb:DeleteItem"
  ],
  "Resource": "arn:aws:dynamodb:*:*:table/idempotency-cache"
}

Custom Table Name

typescript
const adapter = new DynamoDBAdapter(client, 'prod-idempotency-cache');

Distributed Workers

Multiple Lambda functions or ECS tasks sharing the same DynamoDB table coordinate via conditional writes:

typescript
// Lambda A and Lambda B both execute:
const result = await middleware.execute('same-key', ctx, handler);
// Only one handler executes. Both return the same cached result.

License

MIT