Skip to content

Commit 2c5a608

Browse files
feat(bun): add Bun.serve handler wrapper for idempotency
Adds @idempot/bun-handler package with idempotency() wrapper for Bun.serve. Supports wrapping route handlers with Redis, PostgreSQL, MySQL, SQLite, or Bun SQL stores.
1 parent f1c8b64 commit 2c5a608

12 files changed

Lines changed: 690 additions & 3 deletions

File tree

ARCHITECTURE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ idempot-js/
4646
│ ├── frameworks/ # Framework adapters
4747
│ │ ├── hono/
4848
│ │ ├── express/
49+
│ │ ├── bun/
4950
│ │ └── fastify/
5051
│ │
5152
│ └── stores/ # Storage backend implementations
@@ -129,6 +130,15 @@ Each framework adapter implements the idempotency middleware pattern for its fra
129130
- Handles async handlers properly
130131
- Supports `preHandler` style for Fastify compatibility
131132

133+
### Bun Adapter (`packages/frameworks/bun/`)
134+
135+
**Key characteristics:**
136+
137+
- Uses Bun's `Request => Response` handler pattern
138+
- Returns a wrapper function that wraps handlers with idempotency enforcement
139+
- Re-creates the request after body consumption so handlers can read the body
140+
- Exposes circuit breaker via `wrap.circuit`
141+
132142
### Fastify Adapter (`packages/frameworks/fastify/`)
133143

134144
**Key characteristics:**

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ This approach simplifies maintenance while giving TypeScript users an excellent
3939
| Category | Options |
4040
| -------------- | ------------------------------------------------------------------- |
4141
| **Runtimes** | Node.js, Bun, Deno (Lambda & Cloudflare Workers planned) |
42-
| **Frameworks** | Express, Hono, Fastify |
42+
| **Frameworks** | Express, Hono, Fastify, Bun Server |
4343
| **Stores** | Redis, PostgreSQL, MySQL, SQLite (DynamoDB & Cloudflare KV planned) |
4444

4545
## Response Headers

docs/.vitepress/config.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const docsSidebar = [
2020
items: [
2121
{ text: "Express", link: "/frameworks/express" },
2222
{ text: "Fastify", link: "/frameworks/fastify" },
23-
{ text: "Hono", link: "/frameworks/hono" }
23+
{ text: "Hono", link: "/frameworks/hono" },
24+
{ text: "Bun", link: "/frameworks/bun" }
2425
]
2526
},
2627
{

docs/frameworks/bun.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
title: Bun Handler - idempot-js
3+
description: Add IETF-compliant idempotency to Bun.serve applications. Wraps native Request/Response handlers with Redis, PostgreSQL, MySQL, SQLite, or Bun SQL storage backends.
4+
---
5+
6+
# Bun
7+
8+
## Installation
9+
10+
```bash
11+
bun add @idempot/bun-handler @idempot/bun-sql-store
12+
```
13+
14+
## Usage
15+
16+
```javascript
17+
import { idempotency } from "@idempot/bun-handler";
18+
import { BunSqlIdempotencyStore } from "@idempot/bun-sql-store";
19+
20+
const store = new BunSqlIdempotencyStore("sqlite://:memory:");
21+
const withIdempotency = idempotency({ store });
22+
23+
Bun.serve({
24+
routes: {
25+
"/orders": withIdempotency(async (req) => {
26+
const body = await req.json();
27+
const orderId = crypto.randomUUID();
28+
return Response.json({ id: orderId, ...body }, { status: 201 });
29+
})
30+
}
31+
});
32+
```
33+
34+
Unlike Express/Fastify/Hono middleware, `idempotency()` returns a **handler wrapper** rather than a middleware function. Call it once per route to wrap the handler directly.
35+
36+
## API
37+
38+
### `idempotency(options)`
39+
40+
Creates a handler wrapper for idempotency enforcement.
41+
42+
**Options:**
43+
44+
- `store` (required): Storage backend implementing `IdempotencyStore`
45+
- `required`: Whether idempotency key is required (default: `true`)
46+
- `ttlMs`: Time-to-live for idempotency records in milliseconds
47+
- `excludeFields`: Fields to exclude from fingerprint calculation
48+
- `resilience`: Circuit breaker and retry options
49+
50+
**Returns:** A function `(handler) => handler` that wraps a `Request => Response` handler. The returned wrapper function has a `circuit` property for circuit-breaker monitoring.
51+
52+
## Multiple Routes
53+
54+
Call `idempotency()` once and reuse the wrapper across routes:
55+
56+
```javascript
57+
import { idempotency } from "@idempot/bun-handler";
58+
import { BunSqlIdempotencyStore } from "@idempot/bun-sql-store";
59+
60+
const store = new BunSqlIdempotencyStore("sqlite://:memory:");
61+
const withIdempotency = idempotency({ store });
62+
63+
Bun.serve({
64+
routes: {
65+
"/orders": withIdempotency(async (req) => {
66+
const body = await req.json();
67+
return Response.json(
68+
{ id: crypto.randomUUID(), ...body },
69+
{ status: 201 }
70+
);
71+
}),
72+
73+
"/payments": withIdempotency(async (req) => {
74+
const body = await req.json();
75+
return Response.json(
76+
{ id: crypto.randomUUID(), ...body },
77+
{ status: 201 }
78+
);
79+
})
80+
}
81+
});
82+
```
83+
84+
## Circuit Breaker
85+
86+
The wrapper exposes the underlying circuit breaker for monitoring:
87+
88+
```javascript
89+
const withIdempotency = idempotency({ store });
90+
91+
console.log(withIdempotency.circuit.stats);
92+
```

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,10 @@
107107
"*.{json,md}": [
108108
"prettier --write"
109109
]
110-
}
110+
},
111+
"workspaces": [
112+
"packages/*",
113+
"packages/frameworks/*",
114+
"packages/stores/*"
115+
]
111116
}

packages/frameworks/bun/LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
(The BSD License)
2+
3+
Copyright (c) 2026, Morgan Roderick, morgan@roderick.dk
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without modification,
7+
are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice,
10+
this list of conditions and the following disclaimer.
11+
* Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
* Neither the name of Morgan Roderick nor the names of contributors
15+
may be used to endorse or promote products derived from this software
16+
without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

packages/frameworks/bun/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# @idempot/bun-handler
2+
3+
Bun.serve handler wrapper for idempotency.
4+
5+
## Installation
6+
7+
```bash
8+
npm install @idempot/bun-handler
9+
```
10+
11+
### Available Stores
12+
13+
Choose a storage backend that fits your infrastructure:
14+
15+
- `@idempot/sqlite-store` - great for development and single-node deployments
16+
- `@idempot/redis-store`
17+
- `@idempot/postgres-store`
18+
- `@idempot/mysql-store`
19+
- `@idempot/bun-sql-store` - Bun runtime
20+
21+
## Usage
22+
23+
```javascript
24+
import { idempotency } from "@idempot/bun-handler";
25+
import { SqliteIdempotencyStore } from "@idempot/sqlite-store";
26+
27+
const store = new SqliteIdempotencyStore({ path: ":memory:" });
28+
const withIdempotency = idempotency({ store });
29+
30+
Bun.serve({
31+
routes: {
32+
"/orders": withIdempotency(async (req) => {
33+
const orderId = crypto.randomUUID();
34+
const body = await req.json();
35+
return Response.json({ id: orderId, ...body }, { status: 201 });
36+
})
37+
}
38+
});
39+
```
40+
41+
## API
42+
43+
### `idempotency(options)`
44+
45+
Creates a handler wrapper for idempotency.
46+
47+
**Options:**
48+
49+
- `store` (required): Storage backend implementing `IdempotencyStore`
50+
- `headerName`: Header name for idempotency key (default: `"Idempotency-Key"`)
51+
- `required`: Whether idempotency key is required (default: `false`)
52+
- `ttlMs`: Time-to-live for idempotency records in milliseconds
53+
- `excludeFields`: Fields to exclude from fingerprint calculation
54+
- `resilience`: Circuit breaker and retry options
55+
56+
**Returns:** A wrapper function that accepts a `Request => Response` handler. The wrapper has a `circuit` property for monitoring.
57+
58+
## TypeScript Support
59+
60+
This library uses JavaScript with JSDoc comments for type information. Enable `allowJs` in your TypeScript configuration to use these types directly—no separate .d.ts files needed.
61+
62+
To use this library in a TypeScript project:
63+
64+
1. Add these settings to your `tsconfig.json`:
65+
66+
```json
67+
{
68+
"allowJs": true,
69+
"checkJs": true
70+
}
71+
```
72+
73+
2. Import the library as you normally would:
74+
75+
```typescript
76+
import { idempotency } from "@idempot/bun-handler";
77+
```
78+
79+
3. JSDoc comments provide full type safety: parameter types, return types, and detailed documentation in your IDE.
80+
81+
This approach simplifies maintenance while giving TypeScript users an excellent developer experience.
82+
83+
## License
84+
85+
BSD-3-Clause

0 commit comments

Comments
 (0)