Column.Idempotency (Column v1.0.0)

Copy Markdown View Source

Idempotency key generation and management.

Column supports idempotent POST requests via the Idempotency-Key header. Keys are stored for 30 days and must be:

  • Maximum 255 characters
  • ASCII printable characters only

The Column client auto-generates a random key for every POST request. Use this module when you need deterministic, business-logic-derived keys.

Strategies

UUID (default — random, non-deterministic)

key = Column.Idempotency.uuid()
Column.ACH.create(%{...}, idempotency_key: key)

Deterministic (content-addressed)

Use when the same business operation must map to the same key, even across process restarts or retries from a job queue:

key = Column.Idempotency.for_transfer(:ach, order_id: "ord_123", attempt: 1)
# => "ach:ord_123:1"

key = Column.Idempotency.hash("ach", %{order_id: "ord_123", amount: 10_000})
# => "ach:a3f2c8..." (SHA256-derived, always same for same inputs)

Namespaced

key = Column.Idempotency.namespaced("payroll", "run_456", "june-2024")
# => "payroll:run_456:june-2024"

Validation

Column.Idempotency.valid?("my-key")  # => true
Column.Idempotency.valid?("x" <> String.duplicate("a", 255))  # => false (too long)

Summary

Functions

Build a key from keyword list of fields.

Build a deterministic content-addressed key from a namespace and a map of params.

Build a namespaced idempotency key from parts.

Generate a random UUID v4 idempotency key.

Returns true if the key is valid for use as an idempotency key.

Functions

for_transfer(type, fields)

@spec for_transfer(
  atom(),
  keyword()
) :: String.t()

Build a key from keyword list of fields.

hash(namespace, params)

@spec hash(String.t(), map()) :: String.t()

Build a deterministic content-addressed key from a namespace and a map of params.

The map is JSON-encoded, SHA256-hashed, and prefixed with the namespace. This produces the same key for the same logical operation across retries.

Column.Idempotency.hash("ach_credit", %{order_id: "ord_123", amount: 5000})
# => "ach_credit:3d2e1f..."

namespaced(namespace, id, qualifier)

@spec namespaced(String.t(), String.t(), String.t()) :: String.t()

Build a namespaced idempotency key from parts.

All parts are joined with : and validated.

Column.Idempotency.namespaced("payroll", "run_456", "2024-06")
# => "payroll:run_456:2024-06"

uuid()

@spec uuid() :: String.t()

Generate a random UUID v4 idempotency key.

valid?(key)

@spec valid?(String.t()) :: boolean()

Returns true if the key is valid for use as an idempotency key.