IdempotencyKit.Store.Ecto (idempotency_kit v0.1.0)

Copy Markdown View Source

Generic Ecto/Postgres helper functions for idempotency stores.

This module is adapter glue and does not implement a behaviour by itself. Host applications typically create a thin module that implements their local store behaviour and delegates to these functions with app-specific Repo, request schema, and config.

claim_request/7 options:

  • :processing_stale_after_seconds (positive integer, default 300)
  • :retention_days (positive integer, default 14)
  • :create_changeset_fun optional function (schema_module, attrs) -> Ecto.Changeset.t() used to build the insert changeset.

Summary

Functions

claim_request(repo, request_schema, user_id, scope, idempotency_key, request_hash, opts \\ [])

@spec claim_request(
  module(),
  module(),
  integer(),
  String.t(),
  String.t(),
  String.t(),
  keyword()
) ::
  {:execute, IdempotencyKit.Store.request_record()}
  | {:processing, IdempotencyKit.Store.request_record()}
  | {:replay, IdempotencyKit.Store.request_record()}
  | {:error,
     :invalid_key
     | :invalid_scope
     | :invalid_request_hash
     | :payload_mismatch
     | :idempotency_unavailable}

complete_request(repo, request_schema, request, status, response_status, response_body)

@spec complete_request(
  module(),
  module(),
  IdempotencyKit.Store.request_record(),
  String.t(),
  pos_integer(),
  map()
) ::
  {:ok, IdempotencyKit.Store.request_record()}
  | {:error, :idempotency_unavailable}

purge_stale_requests(repo, request_schema, opts \\ [])

@spec purge_stale_requests(module(), module(), keyword()) ::
  {non_neg_integer(), nil | [term()]}

replay_candidate?(repo, request_schema, user_id, scope, idempotency_key, request_payload)

@spec replay_candidate?(module(), module(), integer(), String.t(), String.t(), term()) ::
  boolean()

Return whether a request is an exact idempotency-key + payload match.

This is a read-only pre-check for callers that need policy decisions before claiming, for example skipping rate-limit consumption for an exact retry. It returns false for invalid identifiers, missing records, and payload mismatches.

The result is advisory. Call claim_request/7 for the authoritative lifecycle result.

request_hash(payload)

@spec request_hash(term()) :: String.t()