or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cache-persistence.mdconfiguration.mddecorator.mddynamodb-persistence.mderrors.mdfunction-wrapper.mdindex.mdmiddleware.mdtypes.md
tile.json

function-wrapper.mddocs/

Function Wrapper

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.

Capabilities

Make Idempotent Function

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',
});

Behavior

  1. First Invocation: When called with new data, the function executes normally and saves the result
  2. Duplicate Invocation: If called again with the same data (before expiry), returns the saved result without re-executing
  3. Concurrent Invocations: If multiple invocations happen simultaneously, only one executes while others wait or fail
  4. Expired Records: After the expiry time, the function executes again and creates a new record
  5. Lambda Context: For Lambda handlers, automatically registers the Lambda context for timeout tracking

Error Handling

The function may throw various idempotency errors:

  • IdempotencyKeyError - When no idempotency key can be extracted and throwOnNoIdempotencyKey is true
  • IdempotencyItemAlreadyExistsError - When a duplicate request is detected
  • IdempotencyAlreadyInProgressError - When a request is already being processed
  • IdempotencyValidationError - When payload validation fails