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

decorator.mddocs/

Decorator

The @idempotent decorator makes class methods idempotent. It's ideal for TypeScript class-based Lambda handlers and follows the decorator pattern.

Capabilities

Idempotent Decorator

Decorator function to make class methods idempotent by tracking executions using a persistence store.

/**
 * Decorator to make class methods idempotent.
 *
 * Can be used on Lambda handler methods or arbitrary class methods.
 * Configuration options are the same as makeIdempotent.
 *
 * @param options - Configuration options for idempotency behavior
 * @returns Decorator function that wraps the method
 */
function idempotent(
  options: ItempotentFunctionOptions<Parameters<AnyFunction>>
): (
  target: unknown,
  propertyKey: string,
  descriptor: PropertyDescriptor
) => PropertyDescriptor;

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 with Decorator

import { idempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: 'idempotencyTable',
});

class MyLambdaFunction implements LambdaInterface {
  @idempotent({ persistenceStore })
  public async handler(
    event: APIGatewayProxyEvent,
    context: Context
  ): Promise<APIGatewayProxyResult> {
    // Your business logic
    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Success' }),
    };
  }
}

const handlerClass = new MyLambdaFunction();
export const handler = handlerClass.handler.bind(handlerClass);

Arbitrary Method with Decorator

import { idempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context, SQSEvent, SQSRecord } from 'aws-lambda';

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: 'idempotencyTable',
});

class MyHandler implements LambdaInterface {
  public async handler(event: SQSEvent, context: Context): Promise<void> {
    for (const record of event.Records) {
      await this.processRecord(record);
    }
  }

  @idempotent({ persistenceStore })
  private async processRecord(record: SQSRecord): Promise<void> {
    // Process each record idempotently
    console.log('Processing:', record.body);
  }
}

const handlerClass = new MyHandler();
export const handler = handlerClass.handler.bind(handlerClass);

With Configuration Options

import { idempotent, IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: 'idempotencyTable',
});

const config = new IdempotencyConfig({
  eventKeyJmesPath: 'body',
  expiresAfterSeconds: 3600,
  useLocalCache: true,
});

class MyLambdaFunction implements LambdaInterface {
  @idempotent({ persistenceStore, config })
  public async handler(
    event: APIGatewayProxyEvent,
    context: Context
  ): Promise<void> {
    // Your logic
  }
}

const handlerClass = new MyLambdaFunction();
export const handler = handlerClass.handler.bind(handlerClass);

With Custom Key Prefix

import { idempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: 'idempotencyTable',
});

class MyLambdaFunction implements LambdaInterface {
  @idempotent({
    persistenceStore,
    keyPrefix: 'my-service',
  })
  public async handler(
    event: APIGatewayProxyEvent,
    context: Context
  ): Promise<void> {
    // Your logic
  }
}

const handlerClass = new MyLambdaFunction();
export const handler = handlerClass.handler.bind(handlerClass);

Multiple Decorated Methods

import { idempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context, SQSEvent, SQSRecord } from 'aws-lambda';

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: 'idempotencyTable',
});

class OrderProcessor implements LambdaInterface {
  public async handler(event: SQSEvent, context: Context): Promise<void> {
    for (const record of event.Records) {
      const order = JSON.parse(record.body);

      // Process different order types idempotently
      if (order.type === 'purchase') {
        await this.processPurchase(order);
      } else if (order.type === 'refund') {
        await this.processRefund(order);
      }
    }
  }

  @idempotent({ persistenceStore, keyPrefix: 'purchase' })
  private async processPurchase(order: any): Promise<void> {
    // Process purchase
  }

  @idempotent({ persistenceStore, keyPrefix: 'refund' })
  private async processRefund(order: any): Promise<void> {
    // Process refund
  }
}

const processor = new OrderProcessor();
export const handler = processor.handler.bind(processor);

Behavior

The decorator wraps the method with the same idempotency logic as makeIdempotent:

  1. First Invocation: Executes the method normally and saves the result
  2. Duplicate Invocation: Returns the saved result without re-executing the method
  3. Concurrent Invocations: Prevents concurrent execution of the same operation
  4. Context Binding: Maintains the this context of the class instance
  5. Expired Records: Re-executes the method after the expiry time

Important Notes

  • The decorator must be applied to methods of a class, not standalone functions
  • When exporting the handler, bind it to the class instance: handler.bind(handlerClass)
  • For Lambda handlers, the method should follow the signature (event, context) => Promise<T>
  • Configuration options are identical to makeIdempotent

Error Handling

The decorator may throw various idempotency errors:

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