@nestarc/idempotency
Classes
IdempotencyInterceptor
Defined in: src/idempotency.interceptor.ts:100
The core idempotency interceptor.
Reads @Idempotent() metadata off the handler, extracts the configured idempotency header, computes a request body fingerprint, and dispatches the storage state machine: replay COMPLETED, conflict on PROCESSING, mismatch on differing fingerprint, otherwise lock + delegate + capture response under token-based compare-and-set.
Implements the IETF draft httpapi-idempotency-key-header-07 semantics for 400 / 409 / 422 responses.
Implements
NestInterceptor
Constructors
Constructor
new IdempotencyInterceptor(
reflector,
storage,
moduleOptions): IdempotencyInterceptor;Defined in: src/idempotency.interceptor.ts:103
Parameters
| Parameter | Type |
|---|---|
reflector | Reflector |
storage | IdempotencyStorage |
moduleOptions | IdempotencyOptions |
Returns
Methods
intercept()
intercept(context, next): Observable<unknown>;Defined in: src/idempotency.interceptor.ts:119
Method to implement a custom interceptor.
Parameters
| Parameter | Type | Description |
|---|---|---|
context | ExecutionContext | an ExecutionContext object providing methods to access the route handler and class about to be invoked. |
next | CallHandler | a reference to the CallHandler, which provides access to an Observable representing the response stream from the route handler. |
Returns
Observable<unknown>
Implementation of
NestInterceptor.interceptIdempotencyModule
Defined in: src/idempotency.module.ts:35
NestJS dynamic module exposing the IdempotencyInterceptor and the configured IdempotencyStorage.
The module does not auto-register the interceptor as APP_INTERCEPTOR — consumers opt in via one of three patterns:
- App-global:
providers: [{ provide: APP_INTERCEPTOR, useClass: IdempotencyInterceptor }] - Controller-scoped:
@UseInterceptors(IdempotencyInterceptor)on the class - Method-scoped:
@UseInterceptors(IdempotencyInterceptor)on the handler
The module is registered as global by default so consumers can wire any of the three patterns without re-importing it everywhere.
Constructors
Constructor
new IdempotencyModule(): IdempotencyModule;Returns
Methods
forRoot()
static forRoot(options): DynamicModule;Defined in: src/idempotency.module.ts:36
Parameters
| Parameter | Type |
|---|---|
options | IdempotencyOptions |
Returns
DynamicModule
forRootAsync()
static forRootAsync(options): DynamicModule;Defined in: src/idempotency.module.ts:58
Parameters
| Parameter | Type |
|---|---|
options | IdempotencyAsyncOptions |
Returns
DynamicModule
MemoryStorage
Defined in: src/storage/memory.storage.ts:25
In-memory implementation of IdempotencyStorage.
Backed by a Map with per-entry setTimeout expirations. Suitable for tests and single-instance development. Not safe for production: state is lost on restart and not shared across processes — two replicas would each enforce idempotency independently, letting duplicates slip through.
Implements
IdempotencyStorageOnModuleDestroy
Constructors
Constructor
new MemoryStorage(): MemoryStorage;Returns
Methods
complete()
complete(
key,
token,
response,
ttlSeconds): Promise<MutateResult>;Defined in: src/storage/memory.storage.ts:66
Transitions a PROCESSING record to COMPLETED and stores the captured response, but ONLY if the stored record's token matches the caller's token. Returns 'stale' if the token does not match — meaning the original record was evicted and a newer one exists under this key. The caller's response must not overwrite the newer record.
On 'ok', implementations must refresh the TTL to ttlSeconds.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
response | CompleteResponse |
ttlSeconds | number |
Returns
Promise<MutateResult>
Implementation of
create()
create(
key,
fingerprint,
ttlSeconds): Promise<CreateResult>;Defined in: src/storage/memory.storage.ts:41
Atomically creates a PROCESSING record. On success, returns an opaque token that the caller MUST pass back to complete() / delete().
Parameters
| Parameter | Type | Description |
|---|---|---|
key | string | the idempotency key from the client header (already scoped by the interceptor to include endpoint identity) |
fingerprint | string | undefined | SHA-256 of the request body, or undefined if fingerprinting is off |
ttlSeconds | number | lifetime of the lock; the interceptor passes the resolved TTL |
Returns
Promise<CreateResult>
Implementation of
delete()
delete(key, token): Promise<MutateResult>;Defined in: src/storage/memory.storage.ts:105
Removes a record, but ONLY if the caller's token matches. Returns 'ok' if the record was removed OR was already absent (idempotent cleanup), and 'stale' only if a DIFFERENT record (with a different token) is currently stored under this key.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
Returns
Promise<MutateResult>
Implementation of
get()
get(key): Promise<IdempotencyRecord | null>;Defined in: src/storage/memory.storage.ts:28
Fetches a record by key. Returns null if the key does not exist or has expired.
Parameters
| Parameter | Type |
|---|---|
key | string |
Returns
Promise<IdempotencyRecord | null>
Implementation of
onModuleDestroy()
onModuleDestroy(): Promise<void>;Defined in: src/storage/memory.storage.ts:122
Lifecycle hook: clear all pending eviction timers when the module is torn down. Prevents leaked timers from keeping the Node event loop alive in long test runs.
Returns
Promise<void>
Implementation of
OnModuleDestroy.onModuleDestroyPostgresStorage
Defined in: src/storage/postgres.storage.ts:72
Postgres-backed implementation of IdempotencyStorage.
Stores each record as a row in idempotency_records (override via tableName). Atomic NX is enforced by the primary-key constraint on key combined with INSERT ... ON CONFLICT DO UPDATE WHERE expires_at < now(). Token-based compare-and-set is enforced by WHERE token = $ clauses on complete() and delete(). Lazy expiration is enforced by WHERE expires_at > now() in get().
For active cleanup of expired rows see PostgresSweepService.
Implements
IdempotencyStorageOnModuleDestroy
Constructors
Constructor
new PostgresStorage(options): PostgresStorage;Defined in: src/storage/postgres.storage.ts:78
Parameters
| Parameter | Type |
|---|---|
options | PostgresStorageOptions |
Returns
Methods
close()
close(): Promise<void>;Defined in: src/storage/postgres.storage.ts:233
Returns
Promise<void>
complete()
complete(
key,
token,
response,
ttlSeconds): Promise<MutateResult>;Defined in: src/storage/postgres.storage.ts:172
Transitions a PROCESSING record to COMPLETED and stores the captured response, but ONLY if the stored record's token matches the caller's token. Returns 'stale' if the token does not match — meaning the original record was evicted and a newer one exists under this key. The caller's response must not overwrite the newer record.
On 'ok', implementations must refresh the TTL to ttlSeconds.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
response | CompleteResponse |
ttlSeconds | number |
Returns
Promise<MutateResult>
Implementation of
create()
create(
key,
fingerprint,
ttlSeconds): Promise<CreateResult>;Defined in: src/storage/postgres.storage.ts:143
Atomically creates a PROCESSING record. On success, returns an opaque token that the caller MUST pass back to complete() / delete().
Parameters
| Parameter | Type | Description |
|---|---|---|
key | string | the idempotency key from the client header (already scoped by the interceptor to include endpoint identity) |
fingerprint | string | undefined | SHA-256 of the request body, or undefined if fingerprinting is off |
ttlSeconds | number | lifetime of the lock; the interceptor passes the resolved TTL |
Returns
Promise<CreateResult>
Implementation of
createSchema()
static createSchema(pool, tableName?): Promise<void>;Defined in: src/storage/postgres.storage.ts:248
Idempotently creates the records table and supporting index. Safe to call multiple times. Used by autoCreateSchema=true and available as a public helper for code-driven migrations.
Parameters
| Parameter | Type | Default value |
|---|---|---|
pool | Pool | undefined |
tableName | string | DEFAULT_TABLE_NAME |
Returns
Promise<void>
delete()
delete(key, token): Promise<MutateResult>;Defined in: src/storage/postgres.storage.ts:209
Removes a record, but ONLY if the caller's token matches. Returns 'ok' if the record was removed OR was already absent (idempotent cleanup), and 'stale' only if a DIFFERENT record (with a different token) is currently stored under this key.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
Returns
Promise<MutateResult>
Implementation of
get()
get(key): Promise<IdempotencyRecord | null>;Defined in: src/storage/postgres.storage.ts:110
Fetches a record by key. Returns null if the key does not exist or has expired.
Parameters
| Parameter | Type |
|---|---|
key | string |
Returns
Promise<IdempotencyRecord | null>
Implementation of
onModuleDestroy()
onModuleDestroy(): Promise<void>;Defined in: src/storage/postgres.storage.ts:239
Returns
Promise<void>
Implementation of
OnModuleDestroy.onModuleDestroyonModuleInit()
onModuleInit(): Promise<void>;Defined in: src/storage/postgres.storage.ts:104
Returns
Promise<void>
PostgresSweepService
Defined in: src/services/postgres-sweep.service.ts:34
Optional service that periodically deletes expired idempotency records.
Lazy expiration in PostgresStorage.get already guarantees correctness; this service exists only to keep disk usage and dead tuples bounded in long-running deployments.
Multi-instance safety: each sweep wraps DELETE in pg_try_advisory_lock(hashtext('idempotency-sweep')). Concurrent replicas will see a lock contention and skip — no DELETE storms.
Implements
OnModuleInitOnModuleDestroy
Constructors
Constructor
new PostgresSweepService(storage, options?): PostgresSweepService;Defined in: src/services/postgres-sweep.service.ts:38
Parameters
| Parameter | Type |
|---|---|
storage | PostgresStorage |
options | SweepOptions |
Returns
Methods
onModuleDestroy()
onModuleDestroy(): Promise<void>;Defined in: src/services/postgres-sweep.service.ts:56
Returns
Promise<void>
Implementation of
OnModuleDestroy.onModuleDestroyonModuleInit()
onModuleInit(): Promise<void>;Defined in: src/services/postgres-sweep.service.ts:45
Returns
Promise<void>
Implementation of
OnModuleInit.onModuleInitsweep()
sweep(): Promise<{
deleted: number;
}>;Defined in: src/services/postgres-sweep.service.ts:64
Runs one sweep cycle. Returns the number of rows deleted (0 if another replica holds the advisory lock for this cycle).
Returns
Promise<{ deleted: number; }>
RedisStorage
Defined in: src/storage/redis.storage.ts:76
Redis-backed implementation of IdempotencyStorage.
Stores each record as a Redis Hash under ${keyPrefix}${key} with two fields: token (opaque UUID owned by the creating caller) and payload (JSON-serialized SerializedPayload). All mutations go through Lua scripts registered with defineCommand so the compare-and-set logic runs atomically on the Redis server — closing the race window that a GET-then-SET pattern would leave open.
Implements
IdempotencyStorageOnModuleDestroy
Constructors
Constructor
new RedisStorage(options): RedisStorage;Defined in: src/storage/redis.storage.ts:81
Parameters
| Parameter | Type |
|---|---|
options | RedisStorageOptions |
Returns
Methods
close()
close(): Promise<void>;Defined in: src/storage/redis.storage.ts:202
Closes the internally-managed Redis client. No-op if the client was supplied by the consumer (they own its lifecycle).
Normally called automatically via onModuleDestroy() during Nest's shutdown. Exposed publicly so non-Nest consumers (or manual teardown in tests) can trigger the cleanup without going through the module lifecycle.
Returns
Promise<void>
complete()
complete(
key,
token,
response,
ttlSeconds): Promise<MutateResult>;Defined in: src/storage/redis.storage.ts:156
Transitions a PROCESSING record to COMPLETED and stores the captured response, but ONLY if the stored record's token matches the caller's token. Returns 'stale' if the token does not match — meaning the original record was evicted and a newer one exists under this key. The caller's response must not overwrite the newer record.
On 'ok', implementations must refresh the TTL to ttlSeconds.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
response | CompleteResponse |
ttlSeconds | number |
Returns
Promise<MutateResult>
Implementation of
create()
create(
key,
fingerprint,
ttlSeconds): Promise<CreateResult>;Defined in: src/storage/redis.storage.ts:131
Atomically creates a PROCESSING record. On success, returns an opaque token that the caller MUST pass back to complete() / delete().
Parameters
| Parameter | Type | Description |
|---|---|---|
key | string | the idempotency key from the client header (already scoped by the interceptor to include endpoint identity) |
fingerprint | string | undefined | SHA-256 of the request body, or undefined if fingerprinting is off |
ttlSeconds | number | lifetime of the lock; the interceptor passes the resolved TTL |
Returns
Promise<CreateResult>
Implementation of
delete()
delete(key, token): Promise<MutateResult>;Defined in: src/storage/redis.storage.ts:188
Removes a record, but ONLY if the caller's token matches. Returns 'ok' if the record was removed OR was already absent (idempotent cleanup), and 'stale' only if a DIFFERENT record (with a different token) is currently stored under this key.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
Returns
Promise<MutateResult>
Implementation of
get()
get(key): Promise<IdempotencyRecord | null>;Defined in: src/storage/redis.storage.ts:112
Fetches a record by key. Returns null if the key does not exist or has expired.
Parameters
| Parameter | Type |
|---|---|
key | string |
Returns
Promise<IdempotencyRecord | null>
Implementation of
onModuleDestroy()
onModuleDestroy(): Promise<void>;Defined in: src/storage/redis.storage.ts:217
Nest lifecycle hook — fires automatically when the host module is destroyed (e.g. during app.close()). Delegates to close so consumers who pass only connection options (letting this class own the client) get graceful teardown without manual bookkeeping.
If the consumer supplied their own client, this hook is a no-op: they remain responsible for closing what they created.
Returns
Promise<void>
Implementation of
OnModuleDestroy.onModuleDestroyInterfaces
CompleteResponse
Defined in: src/interfaces/idempotency-storage.interface.ts:6
The response payload captured by the interceptor and persisted by storage.
Properties
body?
optional body?: string;Defined in: src/interfaces/idempotency-storage.interface.ts:11
JSON-serialized response body, or undefined for empty bodies (e.g. 204).
headers?
optional headers?: Record<string, string>;Defined in: src/interfaces/idempotency-storage.interface.ts:14
Lowercase HTTP response headers captured for replay.
statusCode
statusCode: number;Defined in: src/interfaces/idempotency-storage.interface.ts:8
HTTP status code emitted by the original handler.
CreateResult
Defined in: src/interfaces/idempotency-storage.interface.ts:28
Return shape of IdempotencyStorage.create.
acquired === true means this caller successfully created a new PROCESSING record and was given an opaque token that uniquely identifies that record. The caller MUST pass this token back to complete() / delete() so the storage can verify it still owns the record before mutating it.
acquired === false means a record already existed (NX semantics). No token is issued in this case.
Properties
acquired
acquired: boolean;Defined in: src/interfaces/idempotency-storage.interface.ts:29
token?
optional token?: string;Defined in: src/interfaces/idempotency-storage.interface.ts:30
IdempotencyAsyncOptions
Defined in: src/interfaces/idempotency-options.interface.ts:184
Async configuration passed to IdempotencyModule.forRootAsync. Mirrors the standard NestJS async-module pattern (useFactory / useClass / useExisting).
Extends
Pick<ModuleMetadata,"imports">
Properties
imports?
optional imports?: (
| DynamicModule
| Type<any>
| Promise<DynamicModule>
| ForwardReference<any>)[];Defined in: node_modules/@nestjs/common/interfaces/modules/module-metadata.interface.d.ts:18
Optional list of imported modules that export the providers which are required in this module.
Inherited from
Pick.importsinject?
optional inject?: any[];Defined in: src/interfaces/idempotency-options.interface.ts:190
isGlobal?
optional isGlobal?: boolean;Defined in: src/interfaces/idempotency-options.interface.ts:191
useClass?
optional useClass?: Type<IdempotencyOptionsFactory>;Defined in: src/interfaces/idempotency-options.interface.ts:186
useExisting?
optional useExisting?: Type<IdempotencyOptionsFactory>;Defined in: src/interfaces/idempotency-options.interface.ts:185
useFactory?
optional useFactory?: (...args) =>
| IdempotencyOptions
| Promise<IdempotencyOptions>;Defined in: src/interfaces/idempotency-options.interface.ts:187
Parameters
| Parameter | Type |
|---|---|
...args | any[] |
Returns
| IdempotencyOptions | Promise<IdempotencyOptions>
IdempotencyOptions
Defined in: src/interfaces/idempotency-options.interface.ts:79
Module-level configuration passed to IdempotencyModule.forRoot.
Properties
fingerprint?
optional fingerprint?: boolean | IdempotencyFingerprintResolver;Defined in: src/interfaces/idempotency-options.interface.ts:136
When true, the interceptor computes a SHA-256 fingerprint of the request body and verifies it on subsequent requests. Pass a resolver function to provide an application-specific semantic fingerprint. A mismatch produces HTTP 422.
Default
trueheaderName?
optional headerName?: string;Defined in: src/interfaces/idempotency-options.interface.ts:114
The HTTP header name carrying the idempotency key. Override only if you need to deviate from the IETF draft default.
Default
'Idempotency-Key'isGlobal?
optional isGlobal?: boolean;Defined in: src/interfaces/idempotency-options.interface.ts:168
When true, the module is registered as a global module (no need to import it into every consumer module).
Default
truekeyResolver?
optional keyResolver?: IdempotencyKeyResolver;Defined in: src/interfaces/idempotency-options.interface.ts:120
Optional application-level idempotency key resolver. When configured, its return value is used instead of reading the configured header.
maxKeyLength?
optional maxKeyLength?: number;Defined in: src/interfaces/idempotency-options.interface.ts:127
Maximum accepted idempotency key length, in characters.
Default
255observability?
optional observability?: IdempotencyObservabilityOptions;Defined in: src/interfaces/idempotency-options.interface.ts:160
Optional operational hooks and client-visible status headers.
processingTtl?
optional processingTtl?: number;Defined in: src/interfaces/idempotency-options.interface.ts:106
Optional time-to-live for in-flight PROCESSING records, in seconds. When omitted, ttl is used for both processing locks and completed replay records. Per-handler @Idempotent({ processingTtl }) overrides this.
Configure this only when you want stuck in-flight records to expire sooner than completed replay records. Values shorter than the endpoint's real processing time can allow duplicate execution.
replayHeaders?
optional replayHeaders?: ReplayHeadersOption;Defined in: src/interfaces/idempotency-options.interface.ts:155
Controls which response headers are captured and replayed.
true or undefined uses the conservative default allowlist. false disables header replay. A string array uses an explicit allowlist, still filtered through the unsafe header denylist.
Default
truescope?
optional scope?: IdempotencyScope;Defined in: src/interfaces/idempotency-options.interface.ts:143
How storage keys are namespaced. See IdempotencyScope.
Default
'endpoint'storage
storage: IdempotencyStorage;Defined in: src/interfaces/idempotency-options.interface.ts:85
The storage adapter instance to use. Construct it yourself (e.g. new MemoryStorage() or new RedisStorage({ host, port })) for full type-safe control over adapter wiring.
ttl?
optional ttl?: number;Defined in: src/interfaces/idempotency-options.interface.ts:95
Default time-to-live for idempotency records, in seconds. Per-handler @Idempotent({ ttl }) overrides this. Completed replay records use this TTL. In-flight PROCESSING records also use this TTL unless processingTtl is configured.
Default
86400 (24 hours)IdempotencyOptionsFactory
Defined in: src/interfaces/idempotency-options.interface.ts:174
Factory contract for useClass / useExisting async registration paths.
Methods
createIdempotencyOptions()
createIdempotencyOptions():
| IdempotencyOptions
| Promise<IdempotencyOptions>;Defined in: src/interfaces/idempotency-options.interface.ts:175
Returns
| IdempotencyOptions | Promise<IdempotencyOptions>
IdempotencyRecord
Defined in: src/interfaces/idempotency-record.interface.ts:14
The persisted shape of an idempotency record across all storage adapters.
Properties
createdAt
createdAt: Date;Defined in: src/interfaces/idempotency-record.interface.ts:54
When the record was first created by IdempotencyStorage.create().
Invariant: this field is IMMUTABLE over the lifetime of a record. complete() and any other mutation MUST preserve the original value. Storage adapters that rewrite createdAt on update are non-conformant and WILL break consumers who use it for monitoring (e.g. first-seen timestamps in metrics / audit trails).
expiresAt
expiresAt: Date;Defined in: src/interfaces/idempotency-record.interface.ts:61
When the record will be evicted by the storage adapter. Unlike createdAt, this field IS mutated on complete() when the adapter refreshes the TTL window to the new (typically longer) value.
fingerprint?
optional fingerprint?: string;Defined in: src/interfaces/idempotency-record.interface.ts:31
SHA-256 of the request body, used to detect a key being reused with a different payload (which produces HTTP 422 per the IETF draft). Undefined when fingerprinting is disabled.
key
key: string;Defined in: src/interfaces/idempotency-record.interface.ts:16
The exact value of the Idempotency-Key header from the original request.
responseBody?
optional responseBody?: string;Defined in: src/interfaces/idempotency-record.interface.ts:40
JSON-serialized response body, ready to be parsed and replayed.
responseHeaders?
optional responseHeaders?: Record<string, string>;Defined in: src/interfaces/idempotency-record.interface.ts:43
Lowercase HTTP response headers captured for replay.
status
status: IdempotencyStatus;Defined in: src/interfaces/idempotency-record.interface.ts:34
Current lifecycle state.
statusCode?
optional statusCode?: number;Defined in: src/interfaces/idempotency-record.interface.ts:37
Captured HTTP status code of the original handler response.
token
token: string;Defined in: src/interfaces/idempotency-record.interface.ts:24
Opaque token issued by IdempotencyStorage.create() that uniquely identifies THIS record across its lifetime. Used by complete() / delete() to compare-and-set so that a slow caller cannot clobber a newer caller's record after TTL eviction.
IdempotencyStorage
Defined in: src/interfaces/idempotency-storage.interface.ts:74
Pluggable storage contract for idempotency records.
Implementations must guarantee:
- Atomic creation (
NXsemantics) — two concurrentcreate()calls for the same key must result in exactly oneacquired: trueand oneacquired: false. - Token-based compare-and-set on
complete()/delete()— a caller can only mutate a record whose stored token matches the token they received from their owncreate()call. This prevents the TTL-eviction race where a slow caller would otherwise clobber a newer caller's record. createdAtimmutability —complete()and any other mutation MUST preserve thecreatedAtfield of the original PROCESSING record. See IdempotencyRecord.createdAt.
Lifecycle
Storage adapters that hold external resources (Redis clients, DB connections, timers) SHOULD implement Nest's OnModuleDestroy hook so the resources are released when the host application shuts down. Both built-in adapters (MemoryStorage, RedisStorage) do this — a custom adapter is free to opt in the same way.
A cross-adapter contract suite that exercises every requirement of this interface lives at test/support/shared-storage-contract.ts — new adapters should be plugged into it to guarantee LSP-level uniformity.
Methods
complete()
complete(
key,
token,
response,
ttlSeconds): Promise<MutateResult>;Defined in: src/interfaces/idempotency-storage.interface.ts:104
Transitions a PROCESSING record to COMPLETED and stores the captured response, but ONLY if the stored record's token matches the caller's token. Returns 'stale' if the token does not match — meaning the original record was evicted and a newer one exists under this key. The caller's response must not overwrite the newer record.
On 'ok', implementations must refresh the TTL to ttlSeconds.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
response | CompleteResponse |
ttlSeconds | number |
Returns
Promise<MutateResult>
create()
create(
key,
fingerprint,
ttlSeconds): Promise<CreateResult>;Defined in: src/interfaces/idempotency-storage.interface.ts:89
Atomically creates a PROCESSING record. On success, returns an opaque token that the caller MUST pass back to complete() / delete().
Parameters
| Parameter | Type | Description |
|---|---|---|
key | string | the idempotency key from the client header (already scoped by the interceptor to include endpoint identity) |
fingerprint | string | undefined | SHA-256 of the request body, or undefined if fingerprinting is off |
ttlSeconds | number | lifetime of the lock; the interceptor passes the resolved TTL |
Returns
Promise<CreateResult>
delete()
delete(key, token): Promise<MutateResult>;Defined in: src/interfaces/idempotency-storage.interface.ts:117
Removes a record, but ONLY if the caller's token matches. Returns 'ok' if the record was removed OR was already absent (idempotent cleanup), and 'stale' only if a DIFFERENT record (with a different token) is currently stored under this key.
Parameters
| Parameter | Type |
|---|---|
key | string |
token | string |
Returns
Promise<MutateResult>
get()
get(key): Promise<IdempotencyRecord | null>;Defined in: src/interfaces/idempotency-storage.interface.ts:78
Fetches a record by key. Returns null if the key does not exist or has expired.
Parameters
| Parameter | Type |
|---|---|
key | string |
Returns
Promise<IdempotencyRecord | null>
IdempotentMetadata
Defined in: src/interfaces/idempotency-options.interface.ts:238
The metadata shape persisted via SetMetadata by the Idempotent decorator. The enabled: true flag lets the interceptor distinguish "decorator applied with no overrides" from "no decorator at all".
Extends
Properties
enabled
enabled: true;Defined in: src/interfaces/idempotency-options.interface.ts:239
fingerprint?
optional fingerprint?: boolean | IdempotencyFingerprintResolver;Defined in: src/interfaces/idempotency-options.interface.ts:230
Override the module-level fingerprint setting for this handler.
Inherited from
keyResolver?
optional keyResolver?: IdempotencyKeyResolver;Defined in: src/interfaces/idempotency-options.interface.ts:220
Override the module-level key resolver for this handler.
Inherited from
maxKeyLength?
optional maxKeyLength?: number;Defined in: src/interfaces/idempotency-options.interface.ts:225
Override the module-level maximum key length for this handler.
Inherited from
IdempotentOptions.maxKeyLength
processingTtl?
optional processingTtl?: number;Defined in: src/interfaces/idempotency-options.interface.ts:215
Override the module-level processing TTL for this handler (in seconds).
Inherited from
IdempotentOptions.processingTtl
required?
optional required?: boolean;Defined in: src/interfaces/idempotency-options.interface.ts:205
When true, the Idempotency-Key header is mandatory and a missing header produces HTTP 400. When false, requests without the header pass through normally (no idempotency check).
Default
trueInherited from
ttl?
optional ttl?: number;Defined in: src/interfaces/idempotency-options.interface.ts:210
Override the module-level TTL for this handler (in seconds).
Inherited from
IdempotentOptions
Defined in: src/interfaces/idempotency-options.interface.ts:197
Per-handler overrides accepted by the Idempotent decorator.
Extended by
Properties
fingerprint?
optional fingerprint?: boolean | IdempotencyFingerprintResolver;Defined in: src/interfaces/idempotency-options.interface.ts:230
Override the module-level fingerprint setting for this handler.
keyResolver?
optional keyResolver?: IdempotencyKeyResolver;Defined in: src/interfaces/idempotency-options.interface.ts:220
Override the module-level key resolver for this handler.
maxKeyLength?
optional maxKeyLength?: number;Defined in: src/interfaces/idempotency-options.interface.ts:225
Override the module-level maximum key length for this handler.
processingTtl?
optional processingTtl?: number;Defined in: src/interfaces/idempotency-options.interface.ts:215
Override the module-level processing TTL for this handler (in seconds).
required?
optional required?: boolean;Defined in: src/interfaces/idempotency-options.interface.ts:205
When true, the Idempotency-Key header is mandatory and a missing header produces HTTP 400. When false, requests without the header pass through normally (no idempotency check).
Default
truettl?
optional ttl?: number;Defined in: src/interfaces/idempotency-options.interface.ts:210
Override the module-level TTL for this handler (in seconds).
PostgresStorageOptions
Defined in: src/storage/postgres.storage.ts:21
Constructor options for PostgresStorage.
Provide either a pre-built pool (recommended — lets the consumer manage connection lifecycle) OR a connection config that the storage uses to lazily build its own pool.
Properties
autoCreateSchema?
optional autoCreateSchema?: boolean;Defined in: src/storage/postgres.storage.ts:37
If true, run CREATE TABLE IF NOT EXISTS and matching index on module init. Defaults to false. Recommended only for development.
connection?
optional connection?: PoolConfig;Defined in: src/storage/postgres.storage.ts:25
pg PoolConfig used to lazily construct an internal pool.
pool?
optional pool?: Pool;Defined in: src/storage/postgres.storage.ts:23
A pre-built pg Pool. Wins over connection if both are supplied.
poolFactory?
optional poolFactory?: (connection) => Pool;Defined in: src/storage/postgres.storage.ts:27
Test-only seam: custom factory used in place of new Pool(connection).
Parameters
| Parameter | Type |
|---|---|
connection | PoolConfig |
Returns
Pool
tableName?
optional tableName?: string;Defined in: src/storage/postgres.storage.ts:32
Table name used for idempotency records.
Default
'idempotency_records'RedisStorageOptions
Defined in: src/storage/redis.storage.ts:20
Constructor options for RedisStorage.
Provide either a pre-built client (recommended — lets the consumer manage connection lifecycle) OR a connection options object that the storage uses to lazily build its own client.
Properties
client?
optional client?: Redis;Defined in: src/storage/redis.storage.ts:22
A pre-built ioredis client. Wins over connection if both are supplied.
clientFactory?
optional clientFactory?: (connection) => Redis;Defined in: src/storage/redis.storage.ts:26
Test-only seam: custom factory used in place of new Redis(connection).
Parameters
| Parameter | Type |
|---|---|
connection | RedisOptions |
Returns
Redis
connection?
optional connection?: RedisOptions;Defined in: src/storage/redis.storage.ts:24
ioredis connection options used to lazily construct an internal client.
keyPrefix?
optional keyPrefix?: string;Defined in: src/storage/redis.storage.ts:31
Prefix prepended to every idempotency key in Redis.
Default
'idempotency:'SweepOptions
Defined in: src/services/postgres-sweep.service.ts:13
Properties
enabled
enabled: boolean;Defined in: src/services/postgres-sweep.service.ts:15
When false, the service is wired up but never schedules a sweep.
intervalMs?
optional intervalMs?: number;Defined in: src/services/postgres-sweep.service.ts:17
Sweep cadence. Defaults to 60_000 (1 minute).
Type Aliases
IdempotencyScope
type IdempotencyScope = "endpoint" | "global" | ((context) => string);Defined in: src/interfaces/idempotency-options.interface.ts:21
How the interceptor derives the storage-key namespace from the request.
'endpoint'(default) — scope by actual HTTP method + request path when available, falling back to Nest route metadata and then controller class + handler method name. Two different endpoints using the SAMEIdempotency-Keyvalue will NOT collide. Matches the IETF draft recommendation that the key be unique per (key, request URI) tuple.'global'— legacy behavior: use the raw header value as the storage key with no namespace. Safe only if clients guarantee globally-unique keys across all endpoints (e.g. fresh UUIDs per request).A function
(ctx) => string— fully custom scoping. Useful in multi-tenant systems where the scope should include the tenant ID. The returned string will be combined with the raw header value.
IdempotencyStatus
type IdempotencyStatus = "PROCESSING" | "COMPLETED";Defined in: src/interfaces/idempotency-record.interface.ts:9
Lifecycle state of an idempotency record.
PROCESSING: a request with this key is currently being handled. A duplicate request arriving in this state should receive HTTP 409 Conflict.COMPLETED: the request finished and its response is cached. A duplicate request with the same fingerprint should be replayed from the stored response.
MutateResult
type MutateResult = "ok" | "stale";Defined in: src/interfaces/idempotency-storage.interface.ts:45
Return shape of IdempotencyStorage.complete and IdempotencyStorage.delete.
'ok': the operation succeeded — the caller's token matched the stored record (or, for delete, the record was already absent).'stale': the caller's token does NOT match the record currently stored under this key. This happens when the original PROCESSING record was evicted by TTL and a newer caller has since created a fresh record. The original caller MUST NOT touch the newer record; storage silently refused the write.
ReplayHeadersOption
type ReplayHeadersOption = boolean | string[];Defined in: src/interfaces/idempotency-options.interface.ts:26
Variables
DEFAULT_HEADER_NAME
const DEFAULT_HEADER_NAME: "Idempotency-Key" = 'Idempotency-Key';Defined in: src/idempotency.constants.ts:29
Default HTTP header name carrying the idempotency key. Matches the IETF draft httpapi-idempotency-key-header-07.
DEFAULT_TTL_SECONDS
const DEFAULT_TTL_SECONDS: 86400 = 86_400;Defined in: src/idempotency.constants.ts:34
Default time-to-live for idempotency records, in seconds (24 hours).
IDEMPOTENCY_OPTIONS
const IDEMPOTENCY_OPTIONS: typeof IDEMPOTENCY_OPTIONS;Defined in: src/idempotency.constants.ts:4
Injection token for the resolved IdempotencyOptions instance.
IDEMPOTENCY_STORAGE
const IDEMPOTENCY_STORAGE: typeof IDEMPOTENCY_STORAGE;Defined in: src/idempotency.constants.ts:9
Injection token for the IdempotencyStorage instance the interceptor uses.
IDEMPOTENCY_SWEEP_OPTIONS
const IDEMPOTENCY_SWEEP_OPTIONS: typeof IDEMPOTENCY_SWEEP_OPTIONS;Defined in: src/idempotency.constants.ts:15
Injection token for SweepOptions. Optional — when not provided the PostgresSweepService runs in disabled mode (no scheduled cleanup).
IDEMPOTENT_METADATA_KEY
const IDEMPOTENT_METADATA_KEY: "nestarc:idempotent" = 'nestarc:idempotent';Defined in: src/idempotency.constants.ts:23
Reflector metadata key carrying the per-handler IdempotentMetadata.
Stored as a plain string (not Symbol) for maximum compatibility with Nest's Reflector.get and Reflect.getMetadata.
Functions
Idempotent()
function Idempotent(options?): MethodDecorator;Defined in: src/idempotency.decorator.ts:29
Marks a NestJS controller handler as idempotent.
The IdempotencyInterceptor reads this metadata to decide whether to apply duplicate-request protection: extracting the Idempotency-Key header, computing a request fingerprint, and replaying cached responses for repeats.
Parameters
| Parameter | Type |
|---|---|
options? | IdempotentOptions |
Returns
MethodDecorator
Examples
Basic usage — header is required, body fingerprinted, default TTL.
@Post()
@Idempotent()
createPayment(@Body() dto: CreatePaymentDto) { ... }Per-handler overrides.
@Post('refunds')
@Idempotent({ ttl: 3600, fingerprint: false })
createRefund(@Body() dto: CreateRefundDto) { ... }