icetea-middleware 0.1.2 → 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 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/middlewares/signal.ts
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
@@ -7,7 +7,6 @@ export declare interface ISignal {
7
7
  }
8
8
 
9
9
  export declare interface MiddlewareInstance {
10
- new (): MiddlewareInstance;
11
10
  route: (request: NextRequest) => boolean | boolean;
12
11
  executor: (request: NextRequest, response: NextResponse, signal: ISignal) => Promise<NextResponse>;
13
12
  }
@@ -21,15 +20,8 @@ export declare class MiddlewareWrapper {
21
20
  private tempResponse;
22
21
  constructor(request: NextRequest);
23
22
  register(middlewares: MiddlewareInstance[]): void;
24
- execute(): Promise<NextResponse<unknown> | null>;
23
+ execute(): Promise<NextResponse | null>;
25
24
  static switchResponse(newResponse: NextResponse, response: NextResponse): void;
26
25
  }
27
26
 
28
- export declare class Signal implements ISignal {
29
- private signal;
30
- constructor();
31
- set(value: boolean): void;
32
- get(): boolean;
33
- }
34
-
35
27
  export { }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from "next/server.js";
2
- //#region src/middlewares/signal.ts
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, Signal };
56
+ export { MiddlewareWrapper };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icetea-middleware",
3
- "version": "0.1.2",
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
  },