Skip to main content
Version: Current

IdempotencyStore

@rotorsoft/act-root


@rotorsoft/act-root / act-ops/src/idempotency / IdempotencyStore

Interface: IdempotencyStore

Defined in: libs/act-ops/src/idempotency/port.ts:26

Receiver-side idempotency contract: atomically claim a key as processed-by-this-caller, report whether the claim succeeded.

The verb mirrors "@rotorsoft/act".Store.claim โ€” both are atomic acquire-or-fail operations on a contested resource. There, competing workers race for the right to drain a stream; here, competing requests race for the right to be processed as the canonical first-time delivery for an Idempotency-Key. One caller wins; the others see the claim has already been made and treat their request as a duplicate.

Not a Cache. In this codebase Cache means "rebuildable from a source of truth" (e.g. snapshot cache). Dedup state is authoritative โ€” losing it allows duplicate side effects, not just a rebuild. Hence Store. Implementations should preserve records for at least the sender's full retry envelope; see external integration for TTL sizing guidance (the matching helper lands in #747).

Implementations may be sync (in-memory) or async (durable adapters backed by Postgres, Redis, etc.). The middleware that consumes this port (#744) awaits unconditionally, so either shape composes cleanly with framework-agnostic receivers.

Methodsโ€‹

claim()โ€‹

claim(key, now?): boolean | Promise<boolean>

Defined in: libs/act-ops/src/idempotency/port.ts:54

Atomically claim key for this caller. Returns true if the caller won the claim (the key was fresh and is now recorded), false if another caller already claimed it โ€” the request should be treated as a duplicate.

The claim is tentative until the caller confirms the outcome with commit or release. A tentative claim still dedups a concurrent duplicate that arrives while the handler is in flight โ€” the second caller sees false and must not run the handler โ€” but it is not durable across the caller's own retries until committed. This two-phase shape is what stops a transient handler failure from permanently dropping a delivery: the sender retries with the same key, and because the failed attempt released (or never committed) the claim, the retry re-processes instead of being deduped into a silent success.

A false says another caller holds the key, not that its delivery succeeded. While the holder is still running, answering the duplicate's sender "accepted, stop retrying" can lose the delivery if the holder then fails and releases. Ask the sender to retry instead, as the receiver builder in @rotorsoft/act-http does with a 503.

now is exposed for tests; production callers should leave it undefined so wall-clock is used.

Parametersโ€‹

keyโ€‹

string

now?โ€‹

number

Returnsโ€‹

boolean | Promise<boolean>


commit()โ€‹

commit(key, now?): void | Promise<void>

Defined in: libs/act-ops/src/idempotency/port.ts:67

Promote a tentative claim to a durable record so every later delivery of the same key dedups. Call this once the business handler has succeeded. Committing a key that was never claimed records it too, so a durable adapter that lost the tentative reservation (process crash between claim and commit) still lands in a consistent "seen" state.

now is exposed for tests; production callers should leave it undefined so wall-clock is used.

Parametersโ€‹

keyโ€‹

string

now?โ€‹

number

Returnsโ€‹

void | Promise<void>


release()โ€‹

release(key): void | Promise<void>

Defined in: libs/act-ops/src/idempotency/port.ts:76

Drop a tentative claim so a retry re-processes. Call this when the business handler failed transiently and the delivery should be re-attempted under the same key. Releasing a key that has already been committed is a no-op โ€” a successful delivery stays deduped even if a late release arrives.

Parametersโ€‹

keyโ€‹

string

Returnsโ€‹

void | Promise<void>