Siglo provides first-class idempotency guarantees at both the workflow level and individual step level.
1. Idempotency Store Interface
TheIdempotencyStore abstraction governs key claiming, caching, and completion:
get(key) and getResult(key) are supported for reading completed results across store implementations (e.g. RedisIdempotencyStore, PostgresIdempotencyStore, InMemoryIdempotencyStore).
Claim States
'claimed': Key was successfully acquired. Execution may proceed.'completed': Key was previously executed to completion. The orchestrator returns the cached output immediately without re-executing steps.'in-progress': Another execution is currently running with the same key. The orchestrator throwsIdempotencyConflictException(or waits ifsettleTimeoutMsis configured).
InMemoryIdempotencyStore is provided by default. Custom stores (such as Redis or PostgreSQL) can be plugged in seamlessly.
2. Key Scoping and Namespacing (buildIdempotencyKey)
To prevent key collisions across namespaces or versions, keys are automatically scoped:
3. Workflow-Level Idempotency
Workflow-level deduplication wraps the entire flow execution. Subsequent requests with identical idempotency keys return the prior result:4. Step-Level Idempotency (Atomic Lifecycle Commitment)
Individual steps (including array.reduce() steps) can declare idempotency with .idempotent(...).
Fine-grained deduplication on individual steps prevents redundant side effects (e.g. charging a payment gateway twice):
Atomic Commitment Rule
A step’s idempotency record transitions to'completed' atomically only after all post-execution hooks (onAfter) successfully pass. This prevents dirty cached reads by concurrent executions if post-processing fails.
If a step action or hook fails, the idempotency claim is automatically released via store.fail(key), allowing safe retries.
5. Wait-and-Settle Thundering Herd Defense
When high-frequency webhooks (e.g. Stripe, Shopify) or distributed messaging queues (Kafka, BullMQ, SQS) deliver duplicate messages concurrently within milliseconds, failing fast withIdempotencyConflictException can cause unnecessary client errors.
Configure Wait-and-Settle on idempotent steps:
- When the second concurrent request arrives while the first is
'in-progress', it pauses and polls the store everypollIntervalMs. - If the first request finishes within
settleTimeoutMs, the second request returns the cached result cleanly without re-executing. - If the first request does not finish within
settleTimeoutMs,IdempotencyConflictExceptionis thrown.
6. Two-Tier Error Classification
When an idempotent step throws, Siglo differentiates between permanent business declines and transient infrastructure faults:- Permanent Business Errors (e.g.
CardDeclined,AccountNotFound,InvalidCurrency): CallidempotencyStore.complete(key, { error: 'CardDeclined' })or configure error predicates so that subsequent retries return the cached decline immediately without re-invoking payment gateways. - Transient Infrastructure Errors (e.g.
ETIMEDOUT,503 Service Unavailable,ECONNRESET): Siglo invokesidempotencyStore.fail(key), releasing the claim so subsequent retry attempts or backoff poller sweeps can re-execute cleanly.
