Skip to content

@Dedupe ​

Proxy-based decorator (same mechanism as @WithLock): works on any @Injectable method.

typescript
import { Injectable } from '@nestjs/common';
import { Dedupe } from '@nestjs-redisx/dedup';
import { StripeEvent } from './types';

@Injectable()
export class WebhookService {
  // Providers redeliver: each event id is handled once per 5 minutes,
  // fleet-wide. Duplicate deliveries resolve to undefined without running
  // the body. A THROWN first attempt releases the marker, so the provider's
  // retry is not locked out for the rest of the window.
  @Dedupe({ key: 'stripe:{0}', window: 300_000 })
  async handleStripeEvent(eventId: string, event: StripeEvent): Promise<void> {
    // ...apply the event exactly once per window...
  }
}

Options ​

OptionTypeDescription
keystring | (...args) => stringDedup key; {n} interpolates the n-th argument
windownumber?Window in ms (default: plugin defaultWindow)

Semantics ​

  • The first call (per key, across all instances) runs the body and returns its result; repeats within the window resolve undefined without running it.
  • A thrown body releases this caller's marker - the retry is not locked out for the rest of the window.
  • Store unavailability follows the plugin errorPolicy.

Composing with @Cron ​

Both orders are safe - the wrapper carries over metadata stamped by decorators below it, so @Dedupe() above @Cron() does not unregister the job.

Without the Plugin ​

If DedupPlugin is not registered, the method executes without deduplication and logs a warning - fail-open: a possible duplicate is recoverable, a silently skipped business method is not.

Return Type Caveat ​

Callers of a deduplicated method must accept undefined (the duplicate outcome). If every caller needs the value, deduplicate at the event boundary (the webhook controller) rather than deep in the call chain.

Released under the MIT License.