PlaidEx.Sync.CursorStore (plaid_ex v1.0.0)

Copy Markdown View Source

Pluggable store for Plaid transaction sync cursors.

Cursors represent the position in Plaid's transaction event log. They MUST be persisted durably — if a cursor is lost, the next sync call will restart from the beginning (full historical replay).

Production persistence note

By default, cursors are persisted via PlaidEx.Sync.CursorStore.EtsBackend, which means cursors are LOST on application restart. For production systems, implement Behaviour with a database backend (Ecto, Redix, etc.) and configure it:

config :plaid_ex,
  cursor_store: MyApp.PlaidCursorStore

Custom cursor store

defmodule MyApp.PlaidCursorStore do
  @behaviour PlaidEx.Sync.CursorStore.Behaviour

  @impl true
  def get(item_id) do
    case Repo.get_by(PlaidItem, item_id: item_id) do
      nil -> nil
      item -> item.sync_cursor
    end
  end

  @impl true
  def put(item_id, cursor) do
    Repo.update_all(
      from(i in PlaidItem, where: i.item_id == ^item_id),
      set: [sync_cursor: cursor]
    )
    :ok
  end

  @impl true
  def delete(item_id) do
    Repo.delete_all(from(i in PlaidItem, where: i.item_id == ^item_id))
    :ok
  end
end

Summary

Functions

Deletes the cursor for an item, forcing a full re-sync on next run.

Retrieves the cursor for the given item ID. Returns nil if no cursor exists (fresh sync).

Persists a cursor for the given item ID.

Functions

delete(item_id)

@spec delete(String.t()) :: :ok

Deletes the cursor for an item, forcing a full re-sync on next run.

Use this when Plaid returns TRANSACTIONS_SYNC_MUTATION_DURING_PAGINATION — the cursor is invalidated and a fresh sync must begin from the start.

get(item_id)

@spec get(String.t()) :: String.t() | nil

Retrieves the cursor for the given item ID. Returns nil if no cursor exists (fresh sync).

put(item_id, cursor)

@spec put(String.t(), String.t()) :: :ok

Persists a cursor for the given item ID.

IMPORTANT: Call this BEFORE processing the page data. This ensures that on crash, the sync restarts from the correct cursor position (processing the same page again) rather than losing the cursor and starting over.