IdempotencyBehavior
Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:154
Pipeline behavior that deduplicates concurrent requests sharing an idempotency key and replays the stored response after a successful execution.
For each in-scope request it derives a key (IdempotencyBehaviorOptions.keyFactory), then atomically claims it in a pluggable IdempotencyStore:
- first claim → run the handler and store the completed response;
- duplicate, completed → return the stored response (no re-execution);
- duplicate, in progress → throw IdempotencyConflictError (
409); - key reused with a different payload → throw it as
key_reuse(422); - completed under a different authorization scope → throw it as
replay_scope(409), when a IdempotencyBehaviorOptions.replayScopeFactory is configured. The record is kept and the handler is not re-executed, so a permission change cannot duplicate the effect.
Each claim carries a unique owner token. Completion and release compare that token atomically, so an execution that outlives its TTL cannot overwrite or delete a newer claim after the key is reclaimed.
When the handler throws, releaseOnError controls whether the key remains
claimed. It defaults to true, so failed executions release the key and a
later retry may execute the handler again. Set it to false when retaining
the claim after a failure is preferable to retryability.
Store-agnostic by design: memory (default), Redis, Postgres, or your own are one-line swaps of the store passed to the constructor. When no key is produced, the handler runs normally.
Example
Section titled “Example”Per-handler idempotency
Keyed off an optional Idempotency-Key header and partitioned by tenant and principal:
class CreatePaymentHandler { @pipeline.wrap({ kind: 'command' }, [IdempotencyBehavior, { keyFactory: createPartitionedIdempotencyKeyFactory({ principal: (c) => c.items.get('currentUserId') as string | undefined, operation: (c) => c.items.get('idempotencyKey') as string | undefined, onMissingOperation: 'skip', }), ttl: 86_400_000, }]) async handle(command: CreatePaymentCommand) {}}Implements
Section titled “Implements”Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new IdempotencyBehavior(
store,defaults?,logger?):IdempotencyBehavior
Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:219
Parameters
Section titled “Parameters”defaults?
Section titled “defaults?”logger?
Section titled “logger?”Returns
Section titled “Returns”IdempotencyBehavior
Properties
Section titled “Properties”[PIPELINE_BEHAVIOR_CONTRACT]
Section titled “[PIPELINE_BEHAVIOR_CONTRACT]”
readonlystatic[PIPELINE_BEHAVIOR_CONTRACT]:IPipelineBehaviorContract
Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:159
Methods
Section titled “Methods”handle()
Section titled “handle()”handle(
context,next):Promise<unknown>
Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:239
Parameters
Section titled “Parameters”context
Section titled “context”Returns
Section titled “Returns”Promise<unknown>
Implementation of
Section titled “Implementation of”resolveEffectiveOptions()
Section titled “resolveEffectiveOptions()”resolveEffectiveOptions(
options?):IdempotencyBehaviorOptions
Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:470
Shallow-merges pipeline-level options over the constructor defaults.