9.2 KiB
Capability Spec: idempotency
- Capability:
idempotency - Package:
@suaveplan/idempotency - Change: enterprise-idempotency
- Date: 2026-04-19
ADDED Requirements
All requirements in this section are new for genesis. The main-branch package provides the baseline public-API surface; everything below is either a carry-over preserved verbatim or a genesis-specific enhancement.
REQ-IDMP-001 — Structured error hierarchy
The package MUST define an IdempotencyError base class that extends SuaveplanError from @suaveplan/error. All error types thrown by the package MUST extend IdempotencyError. Error codes MUST follow the IDEMPOTENCY.* dotted namespace and be registered with defineCodes / registerIdempotencyErrorCodes() on the shared ErrorCodes singleton.
Required codes:
| Code | HTTP | Severity | Retryable |
|---|---|---|---|
IDEMPOTENCY.KEY_CONFLICT |
409 | warning | false |
IDEMPOTENCY.RESPONSE_MISMATCH |
409 | error | false |
IDEMPOTENCY.STORE_ERROR |
503 | error | true (exponential, max 3) |
IDEMPOTENCY.KEY_EXPIRED |
410 | warning | false |
IDEMPOTENCY.KEY_INVALID |
400 | warning | false |
IDEMPOTENCY.LOCK_ACQUISITION_FAILED |
503 | error | true |
IDEMPOTENCY.VERSION_CONFLICT |
409 | warning | false |
REQ-IDMP-002 — Zod schemas for all public config types
Every public configuration and record type MUST have a corresponding Zod schema exported from the package. Schemas must be usable for runtime validation at the boundary (e.g. HTTP middleware deserialization). Schema exports:
IdempotencyStatusSchemaIdempotencyRecordSchemaIdempotencyGuardConfigSchemaDistributedGuardConfigSchemaKeyGeneratorConfigSchemaKeyVersioningConfigSchemaIdempotencyMetricsConfigSchemaIdempotencyMiddlewareConfigSchema
REQ-IDMP-003 — Injectable clock and RNG
No public API may call Date.now() or Math.random() directly. Every time-dependent and random-dependent operation MUST accept an optional clock?: Clock and rng?: Rng (from @suaveplan/types) so tests can run deterministically with FakeClock and a seeded RNG. Affected components: IdempotencyGuard, DistributedIdempotencyGuard, IdempotencyMetrics, generateIdempotencyKey.
REQ-IDMP-004 — AbortSignal support throughout
Every async public method that performs IO or waits on an internal lock MUST accept an optional { signal?: AbortSignal } option. On abort, pending waits MUST reject with a DOMException named AbortError (or an IdempotencyError wrapping it). The signal MUST be propagated to the user-supplied fn where possible. Affected: IdempotencyGuard.execute, DistributedIdempotencyGuard.execute.
REQ-IDMP-005 — OTel instrumentation via @opentelemetry/api
The package MUST instrument all externally-visible operations using @opentelemetry/api meters. Instrumentation MUST be a no-op when no MeterProvider is registered. Required instruments:
| Instrument | Type | Labels |
|---|---|---|
idempotency.request.total |
Counter | outcome: hit | miss | conflict | mismatch | error |
idempotency.execution.duration_ms |
Histogram | — |
idempotency.lock.acquisition.total |
Counter | result: acquired | failed | retried |
idempotency.lock.wait_ms |
Histogram | — |
idempotency.store.operation.total |
Counter | operation: get | set | delete | cleanup |
REQ-IDMP-006 — Property-based tests with fast-check
The following properties MUST be verified with fast-check arbitraries:
compositeKeyoutput is deterministic and stable for equal inputs across arbitrary string arrays.fingerprintPayloadproduces distinct hex strings for structurally distinct payloads with overwhelming probability (collision resistance property).generateIdempotencyKeynever produces duplicate keys across N calls when supplied a deterministic RNG.IdempotencyGuard.executenever invokesfnmore than once for the same key when called concurrently from multiple coroutines.KeyVersioningnever retains more thanmaxVersionsentries regardless of call ordering.
REQ-IDMP-007 — Benchmarks with mitata
A benchmark suite MUST be maintained under src/__benchmarks__/. Baseline numbers MUST be published in README.md. Required benchmarks:
MemoryIdempotencyStore.gethot path — 10 000 cached reads.IdempotencyGuard.executewith cached hit — zero-execution overhead.compositeKeythroughput — 1 000 000 iterations.fingerprintPayloadthroughput — 10 000 small payloads.
REQ-IDMP-008 — Store adapter extensibility
IdempotencyStoreAdapter<T> MUST remain a structural interface (not a class) so that downstream packages can provide custom implementations without inheriting from a base class. The four required methods are: get(key) → Promise<IdempotencyRecord<T> | undefined>, set(key, record) → Promise<void>, delete(key) → Promise<boolean>, cleanup(beforeTimestamp) → Promise<number>.
Similarly, LockProvider MUST remain a structural interface with acquire(resource, ttlMs) → Promise<boolean> and release(resource) → Promise<void>. Implementations (memory, Redis) are provided by the package as conveniences, not as required base classes.
REQ-IDMP-009 — Real services for integration tests; no mock stores in unit tests
MemoryIdempotencyStore and MemoryLockProvider are full real implementations and MUST be used directly in unit tests — not as mocks. RedisIdempotencyStore, RedisLockProvider, and DatabaseIdempotencyStore MUST be tested against real services. Integration tests MUST be placed in tests/integration/ and MUST be guarded by environment variables (TEST_REDIS_URL, TEST_DATABASE_URL). No vi.mock or jest.mock of store or lock provider internals is permitted.
REQ-IDMP-010 — 100% test coverage (lines / statements / branches / functions)
As a Layer 1 foundation package, @suaveplan/idempotency MUST achieve 100/100/100/100 coverage (Istanbul provider). The coverage threshold MUST be enforced in vitest.config.ts. The bunx turbo test:coverage --filter=@suaveplan/idempotency command MUST exit non-zero if any threshold is not met.
Public Exports
Classes
| Export | Module |
|---|---|
IdempotencyGuard |
src/guard/idempotency-guard |
DistributedIdempotencyGuard |
src/distributed/distributed-guard |
MemoryLockProvider |
src/distributed/memory-lock-provider |
RedisLockProvider |
src/distributed/redis-lock-provider |
MemoryIdempotencyStore |
src/store/memory-store |
DatabaseIdempotencyStore |
src/store/database-store |
RedisIdempotencyStore |
src/store/redis-store |
KeyVersioning |
src/versioning/key-versioning |
IdempotencyMetrics |
src/metrics/idempotency-metrics |
Error classes
| Export | Extends |
|---|---|
IdempotencyError |
SuaveplanError |
IdempotencyConflictError |
IdempotencyError |
IdempotencyMismatchError |
IdempotencyError |
LockAcquisitionError |
IdempotencyError |
VersionConflictError |
IdempotencyError |
Functions
| Export | Signature |
|---|---|
compositeKey |
(...parts: string[]) → string |
fingerprintPayload |
(input: unknown, hashFn?: (data: Uint8Array) => string) → string |
generateIdempotencyKey |
(config?: KeyGeneratorConfig) → string |
createIdempotencyMiddleware |
<TReq, TRes>(config: IdempotencyMiddlewareConfig<TReq, TRes>) → MiddlewareHandler |
registerIdempotencyErrorCodes |
() → void |
Types
| Export | Kind |
|---|---|
IdempotencyStatus |
type alias |
ConflictResolutionStrategy |
type alias |
IdempotencyRecord<T> |
interface |
IdempotencyStoreAdapter<T> |
interface |
IdempotencyDatabaseAdapter |
interface |
RedisClientAdapter |
interface |
LockProvider |
interface |
IdempotencyGuardConfig |
interface |
DistributedGuardConfig |
interface |
DatabaseIdempotencyStoreConfig |
interface |
RedisIdempotencyStoreConfig |
interface |
RedisLockProviderConfig |
interface |
KeyGeneratorConfig |
interface |
KeyVersioningConfig |
interface |
IdempotencyMetricsConfig |
interface |
IdempotencyMetricsSnapshot |
interface |
MetricsReporter |
type alias |
MiddlewareResult<TRes> |
type alias (discriminated union) |
VersionEntry<T> |
interface |
IdempotencyErrorCode |
type alias (union) |
Constants / registries
| Export | Kind |
|---|---|
IDEMPOTENCY_ERROR_METADATA |
Readonly<Record<IdempotencyErrorCode, CodeMetadata>> |
Zod schemas
| Export | Validates |
|---|---|
IdempotencyStatusSchema |
IdempotencyStatus |
IdempotencyRecordSchema |
IdempotencyRecord |
IdempotencyGuardConfigSchema |
IdempotencyGuardConfig |
DistributedGuardConfigSchema |
DistributedGuardConfig |
KeyGeneratorConfigSchema |
KeyGeneratorConfig |
KeyVersioningConfigSchema |
KeyVersioningConfig |
IdempotencyMetricsConfigSchema |
IdempotencyMetricsConfig |
IdempotencyMiddlewareConfigSchema |
IdempotencyMiddlewareConfig |
MODIFIED Requirements
None — this is a greenfield package on genesis; there are no prior spec entries to modify.
REMOVED Requirements
None.