Skip to content

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.

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) {}
}

new IdempotencyBehavior(store, defaults?, logger?): IdempotencyBehavior

Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:219

IdempotencyStore

IdempotencyBehaviorOptions

PipelineLogger

IdempotencyBehavior

readonly static [PIPELINE_BEHAVIOR_CONTRACT]: IPipelineBehaviorContract

Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:159

handle(context, next): Promise<unknown>

Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:239

IPipelineContext

NextDelegate

Promise<unknown>

IPipelineBehavior.handle


resolveEffectiveOptions(options?): IdempotencyBehaviorOptions

Defined in: packages/pipeline-idempotency/src/idempotency.behavior.ts:470

Shallow-merges pipeline-level options over the constructor defaults.

IdempotencyBehaviorOptions

IdempotencyBehaviorOptions

IPipelineBehaviorOptionsResolver.resolveEffectiveOptions