PaperTiger.Idempotency (PaperTiger v1.0.2)

Copy Markdown View Source

Implements Stripe's idempotency mechanism to prevent duplicate requests.

Stripe stores idempotency keys for 24 hours. Requests with the same Idempotency-Key header return the cached response without re-executing.

Usage

The PaperTiger.Plugs.Idempotency plug automatically handles this for POST requests.

Implementation

  • Stores responses in ETS keyed by idempotency key
  • TTL: 24 hours (matches Stripe)
  • Cleanup runs hourly to remove expired entries

Examples

# Client retries request with same key
headers = [{"idempotency-key", "req_123"}]

# First request executes and caches response
Stripe.Charge.create(%{...}, headers: headers)

# Second request returns cached response (no duplicate charge)
Stripe.Charge.create(%{...}, headers: headers)

Summary

Functions

Checks if a request with this idempotency key has been processed.

Returns a specification to start this module under a supervisor.

Clears all idempotency keys.

Clears idempotency keys for a specific namespace.

Starts the Idempotency GenServer.

Stores a response for the given idempotency key.

Functions

check(idempotency_key)

@spec check(String.t()) :: {:cached, map()} | :new_request | :in_progress

Checks if a request with this idempotency key has been processed.

Returns:

  • {:cached, response} - Key exists, return cached response
  • :new_request - Key doesn't exist, proceed with request (and reserves the key atomically)
  • :in_progress - Another request with this key is currently processing

Race Condition Protection

Uses atomic ETS insert_new to prevent race conditions. If two requests arrive simultaneously with the same key, only one will get :new_request, the other will get :in_progress and should retry or wait.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

clear()

@spec clear() :: :ok

Clears all idempotency keys.

Useful for test cleanup.

clear_namespace(namespace)

@spec clear_namespace(pid() | :global) :: :ok

Clears idempotency keys for a specific namespace.

Used by PaperTiger.Test to clean up after each test.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the Idempotency GenServer.

store(idempotency_key, response)

@spec store(String.t(), map()) :: :ok

Stores a response for the given idempotency key.

The response will be cached for 24 hours.