npm package: @idempotix/next
Description: Next.js integration for Idempotix idempotency
Author: stacks0x
Latest version: 1.0.3

# @idempotix/next

Next.js integration for HTTP idempotency. Supports App Router and Pages Router.

[![npm version](https://img.shields.io/npm/v/@idempotix/next.svg)](https://www.npmjs.com/package/@idempotix/next)

## Installation

```bash
npm install @idempotix/core @idempotix/next
```

## App Router

```typescript
// app/api/orders/route.ts
import { next } from '@idempotix/next';

export const POST = next()(async (req) => {
  const order = await createOrder(await req.json());
  return Response.json(order, { status: 201 });
});
```

## Pages Router

```typescript
// pages/api/orders.ts
import { pages } from '@idempotix/next/pages';

export default pages()(async (req, res) => {
  const order = await createOrder(req.body);
  res.status(201).json(order);
});
```

## Configuration

Both `next()` and `pages()` accept the same options:

```typescript
export const POST = next({
  storage: upstash(), // Storage adapter
  ttl: '1h', // Cache duration
  required: true, // Require Idempotency-Key header
  failOpen: true, // Continue if storage fails
  getKey: (req) => req.headers.get('x-request-id'), // Custom key extraction
  getHash: async (req) => hashObject(await req.json()), // Custom hash
  onHit: (key, response) => {}, // Cache hit callback
  onMiss: (key) => {}, // Cache miss callback
  onError: (key, error) => {}, // Error callback
})(handler);
```

## Pre-configured Factory

Share configuration across routes:

```typescript
// lib/idempotency.ts
import { configure } from '@idempotix/next';
import { upstash } from '@idempotix/upstash';

export const idempotent = configure({
  storage: upstash(),
  ttl: '1h',
});

// app/api/orders/route.ts
import { idempotent } from '@/lib/idempotency';

export const POST = idempotent()(handler);
```

Pages Router has its own configure:

```typescript
// lib/idempotency.ts
import { configure } from '@idempotix/next/pages';

export const idempotent = configure({
  storage: upstash(),
  ttl: '1h',
});

// pages/api/orders.ts
import { idempotent } from '@/lib/idempotency';

export default idempotent()(handler);
```

## Headers

| Header               | Direction | Description                           |
| -------------------- | --------- | ------------------------------------- |
| `Idempotency-Key`    | Request   | Client-provided unique key            |
| `Idempotency-Key`    | Response  | Echo of the key used                  |
| `Idempotency-Replay` | Response  | `true` when returning cached response |

## Error Responses

All errors follow [RFC 7807 Problem Details](https://tools.ietf.org/html/rfc7807):

| Status | Type           | Cause                               |
| ------ | -------------- | ----------------------------------- |
| 400    | `key-required` | Missing key when `required: true`   |
| 409    | `conflict`     | Request with same key in progress   |
| 422    | `mismatch`     | Same key reused with different body |

## With Upstash (Serverless)

```typescript
import { upstash } from '@idempotix/upstash';

export const POST = next({ storage: upstash() })(handler);
```

## License

MIT