Idempotency utility for AWS Lambda functions that prevents duplicate executions by tracking request payloads in DynamoDB or cache stores, with support for function wrappers, decorators, and Middy middleware.
The makeIdempotent function is a higher-order function that wraps any function to make it idempotent. It works with both Lambda handlers and arbitrary functions.
Wraps a function to make it idempotent by tracking executions using a persistence store.
/**
* Function wrapper to make any function idempotent.
*
* By default, the entire first argument is hashed to create the idempotency key.
* Use eventKeyJmesPath to hash only a subset of the payload, or dataIndexArgument
* to hash a different function argument.
*
* @param fn - The function to make idempotent
* @param options - Configuration options for idempotency behavior
* @returns A new function with idempotency behavior
*/
function makeIdempotent<Func extends AnyFunction>(
fn: Func,
options: ItempotentFunctionOptions<Parameters<Func>>
): (...args: Parameters<Func>) => ReturnType<Func>;
type AnyFunction = (...args: Array<any>) => any;
type ItempotentFunctionOptions<T extends Array<any>> = T[1] extends Context
? IdempotencyLambdaHandlerOptions
: IdempotencyLambdaHandlerOptions & {
dataIndexArgument?: number;
};
interface IdempotencyLambdaHandlerOptions {
/** Persistence layer to store idempotency records */
persistenceStore: BasePersistenceLayer;
/** Optional configuration for idempotency behavior */
config?: IdempotencyConfig;
/** Optional custom prefix for idempotency keys */
keyPrefix?: string;
}Usage Examples:
Lambda Handler Idempotency
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import type { Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const myHandler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
// Your business logic
return {
statusCode: 200,
body: JSON.stringify({ message: 'Success' }),
};
};
// Wrap the handler to make it idempotent
// The event object (first argument) is automatically used as the idempotency payload
export const handler = makeIdempotent(myHandler, { persistenceStore });Arbitrary Function Idempotency
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import type { SQSEvent, SQSRecord } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
// Make an arbitrary function idempotent
const processRecord = async (record: SQSRecord): Promise<void> => {
// Process the record
console.log('Processing:', record.body);
};
const processIdempotently = makeIdempotent(processRecord, {
persistenceStore,
});
export const handler = async (event: SQSEvent) => {
for (const record of event.Records) {
await processIdempotently(record);
}
};Using Subset of Payload with JMESPath
import { makeIdempotent, IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const myHandler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<void> => {
// Your logic
};
// Use only the 'user' field from requestContext.identity for idempotency key
export const handler = makeIdempotent(myHandler, {
persistenceStore,
config: new IdempotencyConfig({
eventKeyJmesPath: 'requestContext.identity.user',
}),
});Using JMESPath Built-in Functions
import { makeIdempotent, IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const myHandler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<void> => {
// Your logic
};
// Use powertools_json() to decode JSON body and extract specific fields
export const handler = makeIdempotent(myHandler, {
persistenceStore,
config: new IdempotencyConfig({
eventKeyJmesPath: 'powertools_json(body).["user", "productId"]',
}),
});Multi-Parameter Functions with dataIndexArgument
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import type { Context, SQSEvent, SQSRecord } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const processRecord = async (
record: SQSRecord,
customerId: string
): Promise<void> => {
// Your processing logic
console.log('Processing for customer:', customerId);
};
// Use the second argument (customerId) as the idempotency key
const processIdempotently = makeIdempotent(processRecord, {
persistenceStore,
dataIndexArgument: 1, // Index is zero-based, so 1 means second argument
});
export const handler = async (event: SQSEvent, context: Context) => {
for (const record of event.Records) {
await processIdempotently(record, 'customer-123');
}
};With Custom Key Prefix
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const myHandler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<void> => {
// Your logic
};
// Add a custom prefix to idempotency keys
export const handler = makeIdempotent(myHandler, {
persistenceStore,
keyPrefix: 'my-custom-prefix',
});The function may throw various idempotency errors:
IdempotencyKeyError - When no idempotency key can be extracted and throwOnNoIdempotencyKey is trueIdempotencyItemAlreadyExistsError - When a duplicate request is detectedIdempotencyAlreadyInProgressError - When a request is already being processedIdempotencyValidationError - When payload validation failsInstall with Tessl CLI
npx tessl i tessl/npm-aws-lambda-powertools--idempotency