npm package: @node-idempotency/nestjs
Description: Nestjs Plugin that provides Race-Condition free idempotency for HTTP requests, preventing unintended duplicate operations.
Author: Mahendra Hegde
Latest version: 1.0.9

#### @node-idempotency/nestjs

Nestjs wrapper for Node-Idempotency.
Implements `@node-idempotency/core` as a nestjs interceptor.

---

#### Why?

---

Network requests are unpredictable; clients/proxies may send duplicate or concurrent requests due to retries or network issues. To ensure smooth operation, servers must process each request only once. <i>**This package detects and handles duplicates, preventing issues like double charging the customer**</i>. It's:

- <i>Race Condition free: </i> Ensures consistent behavior even during concurrent requests.
- <i>Modular:</i> Easily integrates with your storage or existing implementation, adding a decorator makes the endpoint idempotent.
- <i>Customizable:</i> options to tweak the library as per your need.
- <i>[RFC](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/) compliant: </i> Adheres to standards for compatibility with other systems/clients.

---

#### How?

![No Image](https://raw.githubusercontent.com/mahendraHegde/node-idempotency/main/flow.png)

---

##### instal

```bash
npm i @node-idempotency/nestjs
```

##### usage

1. Register the module

```ts
import { NodeIdempotencyModule } from '@node-idempotency/nestjs';

@Module({
  imports: [
    NodeIdempotencyModule.forRootAsync({
      storage: {
        adapter: StorageAdapterEnum.memory, // or 'redis'
        options: /* adapter options */,
      },
      ...idempotencyOptions, // additional idempotency options
    }),
  ],
})
export class AppModule {}

// OR using factory method

import { NodeIdempotencyModule } from '@node-idempotency/nestjs';
import { type DynamicModule, Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";

@Module({})
export class AppModule {
    static forRootAsync(): DynamicModule {
    return {
      global: true,
      module: AppModule,
      controllers: [AppController],
      imports: [
        NodeIdempotencyModule.forRootAsync({
          imports: [ConfigModule.forRoot()],
          inject: [ConfigService],
          useFactory: async (configService: ConfigService) => {
            return {
              storage: {
                adapter: StorageAdapterEnum.redis,
                options: {
                  url: configService.get("REDIS_URL"),
                },
              },
              cacheTTLMS: configService.get("CACHE_TTL"),
              ...idempotencyOptions, // additional idempotency options
            };
          },
        }),
      ],
    };
  }
}


```

- `storage.adapter` can either be `memory`, `redis` or an instance of [`Storage`](https://github.com/mahendraHegde/node-idempotency/tree/main/packages/storage) interface.
- `storage.options` are options to the storage client, required for `redis`, is client options of [redis client](https://www.npmjs.com/package/redis).
- `idempotencyOptions` are the [`IdempotencyOptions`](https://github.com/mahendraHegde/node-idempotency/blob/main/packages/core/docs/interfaces/IdempotencyOptions.md) passed to `@node-idempotency/core/Idempotency`

2. Decorate controllers or handlers

- Decorating controllers

```ts
import { Controller, Get, Post, HttpCode, Body } from "@nestjs/common";
import { Idempotent } from "@node-idempotency/nestjs";

@Controller()
@Idempotent({ ...idempotencyOptions }) // Override module-level options
export class CounterController {
  counter = 0;

  @Get()
  getNumber(): number {
    return this.counter++;
  }

  @Post()
  @HttpCode(201)
  async addNumber(@Body() { number }: { number: number }): Promise<number> {
    this.counter += number;
    return this.counter;
  }
}
```

- Decorating selected handlers

```ts
import { Controller, Get, Post, HttpCode, Body } from "@nestjs/common";
import { Idempotent } from "@node-idempotency/nestjs";

@Controller()
export class CounterController {
  counter = 0;

  @Idempotent({ ...idempotencyOptions }) // Override module-level options
  @Get()
  getNumber(): number {
    return this.counter++;
  }

  @Post()
  @HttpCode(201)
  async addNumber(@Body() { number }: { number: number }): Promise<number> {
    this.counter += number;
    return this.counter;
  }
}
```

Library also exports the interceptor, you can use it like you would use any nestjs interceptors(ex: registering globaly)

```ts
import { NodeIdempotencyInterceptor } from "@node-idempotency/nestjs";
```