icetea-middleware 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -0
- package/dist/index.cjs +1 -2
- package/dist/index.d.ts +0 -7
- package/dist/index.js +2 -2
- package/package.json +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Ice Tea Middleware
|
|
2
|
+
|
|
3
|
+
A lightweight, flexible middleware pipeline for **Next.js** applications. Ice Tea Middleware provides a clean and extensible way to manage request/response middleware in your Next.js projects using class-based middleware instances with signal support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Simple & Clean API** – Register and execute middlewares with minimal boilerplate
|
|
8
|
+
- **Signal-Based Early Termination** – Halt middleware execution when needed using signals
|
|
9
|
+
- **Route-Based Filtering** – Conditionally execute middlewares based on request matching
|
|
10
|
+
- **Zero-Config Setup** – Works out of the box with Next.js App Router and Pages Router
|
|
11
|
+
- **Response Migration** – Seamlessly transfer headers and cookies between responses
|
|
12
|
+
- **Core Middleware Support** – Execute essential middlewares on every request regardless of route
|
|
13
|
+
- **Async/Await Ready** – Built for modern asynchronous middleware execution
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install icetea-middleware
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yarn add icetea-middleware
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add icetea-middleware
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Create a Middleware Instance
|
|
32
|
+
|
|
33
|
+
Each middleware is an object implementing the `MiddlewareInstance` interface:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
37
|
+
import { ISignal } from "icetea-middleware";
|
|
38
|
+
|
|
39
|
+
export const loggerMiddleware = {
|
|
40
|
+
name: "logger",
|
|
41
|
+
route: () => true, // applies to all requests
|
|
42
|
+
executor: async (
|
|
43
|
+
request: NextRequest,
|
|
44
|
+
response: NextResponse,
|
|
45
|
+
signal: ISignal,
|
|
46
|
+
) => {
|
|
47
|
+
console.log(`[${request.method}] ${request.nextUrl.pathname}`);
|
|
48
|
+
return response;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Set Up the Middleware Pipeline
|
|
54
|
+
|
|
55
|
+
Create a `middleware.ts` file at the root of your Next.js project:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// middleware.ts
|
|
59
|
+
import { NextRequest } from "next/server";
|
|
60
|
+
import { MiddlewareWrapper } from "icetea-middleware";
|
|
61
|
+
|
|
62
|
+
export async function middleware(request: NextRequest) {
|
|
63
|
+
const wrapper = new MiddlewareWrapper(request);
|
|
64
|
+
|
|
65
|
+
wrapper.register([
|
|
66
|
+
loggerMiddleware,
|
|
67
|
+
// ... other middlewares
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
return await wrapper.execute();
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
### `MiddlewareWrapper`
|
|
77
|
+
|
|
78
|
+
The core class that orchestrates middleware execution.
|
|
79
|
+
|
|
80
|
+
#### `constructor(request: NextRequest)`
|
|
81
|
+
|
|
82
|
+
Creates a new middleware pipeline instance.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const wrapper = new MiddlewareWrapper(request);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### `register(middlewares: MiddlewareInstance[]): void`
|
|
89
|
+
|
|
90
|
+
Registers an array of middlewares. Core middlewares (those with a boolean `route`) are executed on every request, while route-based middlewares are filtered by their `route` function.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
wrapper.register([
|
|
94
|
+
{ name: "auth", route: true, executor: async (req, res, signal) => res },
|
|
95
|
+
{
|
|
96
|
+
name: "logging",
|
|
97
|
+
route: (req) => req.nextUrl.pathname.startsWith("/api"),
|
|
98
|
+
executor: async (req, res, signal) => res,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### `execute(): Promise<NextResponse | null>`
|
|
104
|
+
|
|
105
|
+
Executes the registered middlewares sequentially. Returns a `NextResponse` or `null`.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const response = await wrapper.execute();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### `static switchResponse(newResponse: NextResponse, response: NextResponse): void`
|
|
112
|
+
|
|
113
|
+
Copies headers and cookies from one response to another. Useful when you need to create a new response but preserve existing headers/cookies.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const newResponse = NextResponse.redirect(new URL("/login", request.url));
|
|
117
|
+
MiddlewareWrapper.switchResponse(newResponse, response);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `Signal`
|
|
121
|
+
|
|
122
|
+
A simple signal mechanism to stop middleware execution early.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { Signal } from "icetea-middleware";
|
|
126
|
+
|
|
127
|
+
const signal = new Signal();
|
|
128
|
+
signal.set(); // triggers the signal
|
|
129
|
+
const isStopped = signal.get(); // boolean
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Types
|
|
133
|
+
|
|
134
|
+
### `MiddlewareInstance`
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
interface MiddlewareInstance {
|
|
138
|
+
name: string;
|
|
139
|
+
route: boolean | ((request: NextRequest) => boolean);
|
|
140
|
+
executor: (
|
|
141
|
+
request: NextRequest,
|
|
142
|
+
response: NextResponse,
|
|
143
|
+
signal: ISignal,
|
|
144
|
+
) => Promise<NextResponse>;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Property | Type | Description |
|
|
149
|
+
| ---------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
150
|
+
| `name` | `string` | A descriptive name for the middleware (used for debugging/logging) |
|
|
151
|
+
| `route` | `boolean \| ((request) => boolean)` | If `true`, runs on every request. If a function, executes when it returns `true` |
|
|
152
|
+
| `executor` | `(request, response, signal) => Promise<NextResponse>` | The middleware logic. Receive the request, current response, and a signal to stop execution |
|
|
153
|
+
|
|
154
|
+
### `ISignal`
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
interface ISignal {
|
|
158
|
+
set: () => void;
|
|
159
|
+
get: () => boolean;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Examples
|
|
164
|
+
|
|
165
|
+
### Authentication Middleware
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
169
|
+
import { ISignal } from "icetea-middleware";
|
|
170
|
+
|
|
171
|
+
export const authMiddleware = {
|
|
172
|
+
name: "auth",
|
|
173
|
+
route: (request: NextRequest) => {
|
|
174
|
+
const protectedPaths = ["/dashboard", "/profile", "/admin"];
|
|
175
|
+
return protectedPaths.some((path) =>
|
|
176
|
+
request.nextUrl.pathname.startsWith(path),
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
executor: async (
|
|
180
|
+
request: NextRequest,
|
|
181
|
+
response: NextResponse,
|
|
182
|
+
signal: ISignal,
|
|
183
|
+
) => {
|
|
184
|
+
const token = request.cookies.get("session-token");
|
|
185
|
+
|
|
186
|
+
if (!token) {
|
|
187
|
+
signal.set();
|
|
188
|
+
return NextResponse.redirect(new URL("/login", request.url));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return response;
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Rate Limiting Middleware
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
200
|
+
import { ISignal } from "icetea-middleware";
|
|
201
|
+
|
|
202
|
+
const rateLimit = new Map<string, { count: number; timestamp: number }>();
|
|
203
|
+
|
|
204
|
+
export const rateLimitMiddleware = {
|
|
205
|
+
name: "rate-limit",
|
|
206
|
+
route: (request: NextRequest) => request.nextUrl.pathname.startsWith("/api"),
|
|
207
|
+
executor: async (
|
|
208
|
+
request: NextRequest,
|
|
209
|
+
response: NextResponse,
|
|
210
|
+
signal: ISignal,
|
|
211
|
+
) => {
|
|
212
|
+
const ip = request.ip ?? "unknown";
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
const windowMs = 60_000; // 1 minute
|
|
215
|
+
const maxRequests = 100;
|
|
216
|
+
|
|
217
|
+
const entry = rateLimit.get(ip);
|
|
218
|
+
|
|
219
|
+
if (!entry || now - entry.timestamp > windowMs) {
|
|
220
|
+
rateLimit.set(ip, { count: 1, timestamp: now });
|
|
221
|
+
return response;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
entry.count++;
|
|
225
|
+
|
|
226
|
+
if (entry.count > maxRequests) {
|
|
227
|
+
signal.set();
|
|
228
|
+
return new NextResponse("Too Many Requests", { status: 429 });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return response;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## How It Works
|
|
237
|
+
|
|
238
|
+
1. **Initialization** – `MiddlewareWrapper` is instantiated with the incoming `NextRequest`.
|
|
239
|
+
2. **Registration** – Middlewares are registered via `register()`. They are split into two groups:
|
|
240
|
+
- **Core middlewares** (`route: boolean`) – Always executed.
|
|
241
|
+
- **Route middlewares** (`route: function`) – Executed only when the route function returns `true` for the current request.
|
|
242
|
+
3. **Execution** – `execute()` runs each middleware in order, passing the request, current response, and a `Signal` instance.
|
|
243
|
+
4. **Early Termination** – If any middleware calls `signal.set()`, the pipeline stops immediately and returns the current response.
|
|
244
|
+
5. **Response** – If execution completes without interruption, the default `NextResponse.next()` is returned.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT © De Wibisana
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
<p align="center">Built with ❤️ for Next.js</p>
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let next_server_js = require("next/server.js");
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/utils/signal.ts
|
|
4
4
|
var Signal = class {
|
|
5
5
|
constructor() {
|
|
6
6
|
this.signal = false;
|
|
@@ -55,4 +55,3 @@ var MiddlewareWrapper = class {
|
|
|
55
55
|
};
|
|
56
56
|
//#endregion
|
|
57
57
|
exports.MiddlewareWrapper = MiddlewareWrapper;
|
|
58
|
-
exports.Signal = Signal;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,11 +24,4 @@ export declare class MiddlewareWrapper {
|
|
|
24
24
|
static switchResponse(newResponse: NextResponse, response: NextResponse): void;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export declare class Signal implements ISignal {
|
|
28
|
-
private signal;
|
|
29
|
-
constructor();
|
|
30
|
-
set(value: boolean): void;
|
|
31
|
-
get(): boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
27
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from "next/server.js";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/utils/signal.ts
|
|
3
3
|
var Signal = class {
|
|
4
4
|
constructor() {
|
|
5
5
|
this.signal = false;
|
|
@@ -53,4 +53,4 @@ var MiddlewareWrapper = class {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
//#endregion
|
|
56
|
-
export { MiddlewareWrapper
|
|
56
|
+
export { MiddlewareWrapper };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icetea-middleware",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A simple middleware for nextjs",
|
|
6
6
|
"main": "./dist/index.umd.cjs",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"dev": "vite",
|
|
21
21
|
"build": "tsc && vite build",
|
|
22
|
+
"publish": "npm version patch && tsc && vite build && npm publish",
|
|
22
23
|
"preview": "vite preview",
|
|
23
24
|
"test": "echo \"No tests yet\""
|
|
24
25
|
},
|