Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions docs/frameworks/fastify.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Fastify Plugin - idempot-js
description: Add IETF-compliant idempotency to Fastify applications using preHandler hooks. Supports Redis, PostgreSQL, MySQL, and SQLite storage backends.
description: Add IETF-compliant idempotency to Fastify applications using a Fastify plugin. Supports Redis, PostgreSQL, MySQL, and SQLite storage backends.
---

# Fastify
Expand All @@ -21,23 +21,32 @@ import { SqliteIdempotencyStore } from "@idempot/sqlite-store";
const fastify = Fastify();
const store = new SqliteIdempotencyStore({ path: ":memory:" });

fastify.post(
"/orders",
{ preHandler: idempotency({ store }) },
async (request, reply) => {
const orderId = crypto.randomUUID();
return { id: orderId, ...request.body };
}
);
// Register as a plugin - applies to all routes in this scope
fastify.register(idempotency, { store });

fastify.post("/orders", async (request, reply) => {
const orderId = crypto.randomUUID();
return { id: orderId, ...request.body };
});

fastify.listen({ port: 3000 });
```

### Migration from v1.x

```javascript
// v1.x (deprecated)
fastify.addHook("preHandler", idempotency({ store }));

// v2.x
fastify.register(idempotency, { store });
```

## API

### `idempotency(options)`

Creates Fastify preHandler hook for idempotency.
Creates a Fastify plugin for idempotency. The plugin registers `preHandler` and `onSend` hooks internally.

**Options:**

Expand All @@ -47,4 +56,4 @@ Creates Fastify preHandler hook for idempotency.
- `excludeFields`: Fields to exclude from fingerprint calculation
- `resilience`: Circuit breaker and retry options

**Returns:** Fastify preHandler hook function with `circuit` property for monitoring.
**Returns:** Fastify plugin with `circuit` property for monitoring.
33 changes: 15 additions & 18 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,25 +186,22 @@ const store = new PostgresIdempotencyStore({
database: "payments"
});

fastify.post(
"/payments",
{
preHandler: idempotency({
store,
ttlMs: 7 * 24 * 60 * 60 * 1000, // 7 days
excludeFields: ["timestamp", "$.audit.clientTimestamp"],
resilience: {
timeoutMs: 2000, // Allow slower database operations
maxRetries: 5, // More retries for critical operations
resetTimeoutMs: 60000 // 1 minute recovery window
}
})
},
async (request, reply) => {
const payment = await processPayment(request.body);
return payment;
// Register as plugin
fastify.register(idempotency, {
store,
ttlMs: 7 * 24 * 60 * 60 * 1000, // 7 days
excludeFields: ["timestamp", "$.audit.clientTimestamp"],
resilience: {
timeoutMs: 2000, // Allow slower database operations
maxRetries: 5, // More retries for critical operations
resetTimeoutMs: 60000 // 1 minute recovery window
}
);
});

fastify.post("/payments", async (request, reply) => {
const payment = await processPayment(request.body);
return payment;
});
```

## Choosing a Storage Backend
Expand Down
44 changes: 44 additions & 0 deletions examples/fastify-sqlite-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Fastify from "fastify";
import { idempotency } from "../packages/frameworks/fastify/index.js";
import { SqliteIdempotencyStore } from "../packages/stores/sqlite/index.js";

const fastify = Fastify();
const store = new SqliteIdempotencyStore({ path: "./examples/idempotency.db" });

fastify.register(idempotency, { store });

fastify.post("/orders", async (request, reply) => {
const orderId = crypto.randomUUID();
console.log(`Creating order: ${orderId}`);
return reply
.code(201)
.send({ id: orderId, status: "created", ...request.body });
});

fastify.listen({ port: 3000 }, (err, address) => {
if (err) {
console.error(err);
process.exit(1);
}
console.log(`Server running at ${address}`);
console.log("");
console.log("Try these requests:");
console.log("");
console.log("# Create order (optional idempotency-key)");
console.log("curl -X POST http://localhost:3000/orders \\");
console.log(' -H "Content-Type: application/json" \\');
console.log(' -H "idempotency-key: order-123" \\');
console.log(' -d \'{"item": "widget", "quantity": 5}\'');
console.log("");
console.log("# Replay - same key and body returns cached response");
console.log("curl -X POST http://localhost:3000/orders \\");
console.log(' -H "Content-Type: application/json" \\');
console.log(' -H "idempotency-key: order-123" \\');
console.log(' -d \'{"item": "widget", "quantity": 5}\'');
});

process.on("SIGINT", () => {
console.log("Closing database...");
store.close();
process.exit(0);
});
34 changes: 22 additions & 12 deletions packages/frameworks/fastify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,44 @@ import { SqliteIdempotencyStore } from "@idempot/sqlite-store";
const fastify = Fastify();
const store = new SqliteIdempotencyStore({ path: ":memory:" });

fastify.post(
"/orders",
{ preHandler: idempotency({ store }) },
async (request, reply) => {
const orderId = crypto.randomUUID();
return { id: orderId, ...request.body };
}
);
// Register as a plugin - applies to all routes in this scope
fastify.register(idempotency, { store });

fastify.post("/orders", async (request, reply) => {
const orderId = crypto.randomUUID();
return { id: orderId, ...request.body };
});

fastify.listen({ port: 3000 });
```

### Migration from v1.x

If upgrading from v1.x, update your code:

```javascript
// v1.x (deprecated)
fastify.addHook("preHandler", idempotency({ store }));

// v2.x
fastify.register(idempotency, { store });
```

## API

### `idempotency(options)`

Creates Fastify preHandler hook for idempotency.
Creates a Fastify plugin for idempotency. The plugin registers `preHandler` and `onSend` hooks internally.

**Options:**

- `store` (required): Storage backend implementing `IdempotencyStore`
- `headerName`: Header name for idempotency key (default: `"Idempotency-Key"`)
- `required`: Whether idempotency key is required (default: `false`)
- `required`: Whether idempotency key is required (default: `true`)
- `ttlMs`: Time-to-live for idempotency records in milliseconds
- `excludeFields`: Fields to exclude from fingerprint calculation
- `resilience`: Circuit breaker and retry options

**Returns:** Fastify preHandler hook function with `circuit` property for monitoring.
**Returns:** Fastify plugin with `circuit` property for monitoring.

## TypeScript Support

Expand Down
Loading