@ooneex/middleware 0.0.1 → 0.3.0
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 +412 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +4 -3
- package/package.json +20 -7
- package/dist/ooneex-middleware-0.0.1.tgz +0 -0
package/README.md
CHANGED
|
@@ -1 +1,413 @@
|
|
|
1
1
|
# @ooneex/middleware
|
|
2
|
+
|
|
3
|
+
A middleware framework for TypeScript applications with decorators for request/response pipeline processing. This package provides interfaces and utilities for creating HTTP and WebSocket middleware that intercept and transform requests before they reach controllers.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
✅ **HTTP Middleware** - Intercept and process HTTP requests and responses
|
|
14
|
+
|
|
15
|
+
✅ **WebSocket Middleware** - Handle WebSocket connection processing
|
|
16
|
+
|
|
17
|
+
✅ **Decorator Support** - Register middleware with decorators for clean code
|
|
18
|
+
|
|
19
|
+
✅ **Context Access** - Full access to request, response, and application context
|
|
20
|
+
|
|
21
|
+
✅ **Container Integration** - Works seamlessly with dependency injection
|
|
22
|
+
|
|
23
|
+
✅ **Type-Safe** - Full TypeScript support with proper type definitions
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
### Bun
|
|
28
|
+
```bash
|
|
29
|
+
bun add @ooneex/middleware
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### pnpm
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add @ooneex/middleware
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Yarn
|
|
38
|
+
```bash
|
|
39
|
+
yarn add @ooneex/middleware
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### npm
|
|
43
|
+
```bash
|
|
44
|
+
npm install @ooneex/middleware
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Basic HTTP Middleware
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { decorator } from '@ooneex/middleware';
|
|
53
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
54
|
+
|
|
55
|
+
@decorator.middleware()
|
|
56
|
+
class LoggingMiddleware implements IMiddleware {
|
|
57
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
58
|
+
console.log(`${context.method} ${context.request.path}`);
|
|
59
|
+
|
|
60
|
+
// Continue to next middleware/controller
|
|
61
|
+
return context;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Authentication Middleware
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { decorator } from '@ooneex/middleware';
|
|
70
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
71
|
+
import { Jwt } from '@ooneex/jwt';
|
|
72
|
+
|
|
73
|
+
@decorator.middleware()
|
|
74
|
+
class AuthMiddleware implements IMiddleware {
|
|
75
|
+
private readonly jwt = new Jwt();
|
|
76
|
+
|
|
77
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
78
|
+
const authHeader = context.header.get('Authorization');
|
|
79
|
+
|
|
80
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
81
|
+
context.response.exception('Missing authorization header', {
|
|
82
|
+
status: 401
|
|
83
|
+
});
|
|
84
|
+
return context;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const token = authHeader.substring(7);
|
|
88
|
+
const isValid = await this.jwt.isValid(token);
|
|
89
|
+
|
|
90
|
+
if (!isValid) {
|
|
91
|
+
context.response.exception('Invalid or expired token', {
|
|
92
|
+
status: 401
|
|
93
|
+
});
|
|
94
|
+
return context;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Set user in context
|
|
98
|
+
const payload = this.jwt.getPayload<{ userId: string; role: string }>(token);
|
|
99
|
+
context.user = { id: payload.userId, role: payload.role };
|
|
100
|
+
|
|
101
|
+
return context;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### WebSocket Middleware
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { decorator } from '@ooneex/middleware';
|
|
110
|
+
import type { ISocketMiddleware, ContextType as SocketContextType } from '@ooneex/middleware';
|
|
111
|
+
|
|
112
|
+
@decorator.middleware()
|
|
113
|
+
class SocketAuthMiddleware implements ISocketMiddleware {
|
|
114
|
+
public async handle(context: SocketContextType): Promise<SocketContextType> {
|
|
115
|
+
const token = context.queries.token;
|
|
116
|
+
|
|
117
|
+
if (!token) {
|
|
118
|
+
context.response.exception('Missing authentication token', {
|
|
119
|
+
status: 401
|
|
120
|
+
});
|
|
121
|
+
return context;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Validate token and set user
|
|
125
|
+
context.user = await this.validateToken(token);
|
|
126
|
+
|
|
127
|
+
return context;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Request Validation Middleware
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { decorator } from '@ooneex/middleware';
|
|
136
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
137
|
+
|
|
138
|
+
@decorator.middleware()
|
|
139
|
+
class ValidationMiddleware implements IMiddleware {
|
|
140
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
141
|
+
// Validate content type for POST/PUT requests
|
|
142
|
+
if (['POST', 'PUT', 'PATCH'].includes(context.method)) {
|
|
143
|
+
const contentType = context.header.get('Content-Type');
|
|
144
|
+
|
|
145
|
+
if (!contentType?.includes('application/json')) {
|
|
146
|
+
context.response.exception('Content-Type must be application/json', {
|
|
147
|
+
status: 415
|
|
148
|
+
});
|
|
149
|
+
return context;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return context;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Registering Middleware with App
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { App } from '@ooneex/app';
|
|
162
|
+
import { AuthMiddleware, LoggingMiddleware } from './middleware';
|
|
163
|
+
|
|
164
|
+
const app = new App({
|
|
165
|
+
middlewares: [LoggingMiddleware, AuthMiddleware],
|
|
166
|
+
// ... other config
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await app.run();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API Reference
|
|
173
|
+
|
|
174
|
+
### Interfaces
|
|
175
|
+
|
|
176
|
+
#### `IMiddleware`
|
|
177
|
+
|
|
178
|
+
Interface for HTTP middleware implementations.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
interface IMiddleware<T extends ContextConfigType = ContextConfigType> {
|
|
182
|
+
handle: (context: ContextType<T>) => Promise<ContextType<T>> | ContextType<T>;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Methods:**
|
|
187
|
+
|
|
188
|
+
##### `handle(context: ContextType): Promise<ContextType> | ContextType`
|
|
189
|
+
|
|
190
|
+
Processes the request context and returns the (potentially modified) context.
|
|
191
|
+
|
|
192
|
+
**Parameters:**
|
|
193
|
+
- `context` - The request context containing request, response, and app data
|
|
194
|
+
|
|
195
|
+
**Returns:** The context object (modified or unchanged)
|
|
196
|
+
|
|
197
|
+
**Example:**
|
|
198
|
+
```typescript
|
|
199
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
200
|
+
// Pre-processing
|
|
201
|
+
console.log('Before controller');
|
|
202
|
+
|
|
203
|
+
// Return context to continue processing
|
|
204
|
+
return context;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
#### `ISocketMiddleware`
|
|
211
|
+
|
|
212
|
+
Interface for WebSocket middleware implementations.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface ISocketMiddleware<T extends SocketContextConfigType = SocketContextConfigType> {
|
|
216
|
+
handle: (context: SocketContextType<T>) => Promise<SocketContextType<T>> | SocketContextType<T>;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Methods:**
|
|
221
|
+
|
|
222
|
+
##### `handle(context: SocketContextType): Promise<SocketContextType> | SocketContextType`
|
|
223
|
+
|
|
224
|
+
Processes the WebSocket context and returns the (potentially modified) context.
|
|
225
|
+
|
|
226
|
+
**Parameters:**
|
|
227
|
+
- `context` - The WebSocket context containing connection data and channel methods
|
|
228
|
+
|
|
229
|
+
**Returns:** The context object (modified or unchanged)
|
|
230
|
+
|
|
231
|
+
### Types
|
|
232
|
+
|
|
233
|
+
#### `MiddlewareClassType`
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
type MiddlewareClassType = new (...args: any[]) => IMiddleware;
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `SocketMiddlewareClassType`
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
type SocketMiddlewareClassType = new (...args: any[]) => ISocketMiddleware;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Decorators
|
|
246
|
+
|
|
247
|
+
#### `@decorator.middleware()`
|
|
248
|
+
|
|
249
|
+
Registers a class as middleware with the dependency injection container.
|
|
250
|
+
|
|
251
|
+
**Example:**
|
|
252
|
+
```typescript
|
|
253
|
+
@decorator.middleware()
|
|
254
|
+
class MyMiddleware implements IMiddleware {
|
|
255
|
+
// Implementation
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Advanced Usage
|
|
260
|
+
|
|
261
|
+
### CORS Middleware
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { decorator } from '@ooneex/middleware';
|
|
265
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
266
|
+
|
|
267
|
+
@decorator.middleware()
|
|
268
|
+
class CorsMiddleware implements IMiddleware {
|
|
269
|
+
private readonly allowedOrigins = ['https://example.com', 'https://app.example.com'];
|
|
270
|
+
|
|
271
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
272
|
+
const origin = context.header.get('Origin');
|
|
273
|
+
|
|
274
|
+
if (origin && this.allowedOrigins.includes(origin)) {
|
|
275
|
+
context.response.header.set('Access-Control-Allow-Origin', origin);
|
|
276
|
+
context.response.header.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
277
|
+
context.response.header.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Handle preflight requests
|
|
281
|
+
if (context.method === 'OPTIONS') {
|
|
282
|
+
context.response.json({}, 204);
|
|
283
|
+
return context;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return context;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Rate Limiting Middleware
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { decorator } from '@ooneex/middleware';
|
|
295
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
296
|
+
import { container } from '@ooneex/container';
|
|
297
|
+
import type { IRateLimiter } from '@ooneex/rate-limit';
|
|
298
|
+
|
|
299
|
+
@decorator.middleware()
|
|
300
|
+
class RateLimitMiddleware implements IMiddleware {
|
|
301
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
302
|
+
const rateLimiter = container.get<IRateLimiter>('rateLimiter');
|
|
303
|
+
const clientIp = context.ip || 'unknown';
|
|
304
|
+
|
|
305
|
+
const result = await rateLimiter.check(clientIp, 100, 60); // 100 requests per minute
|
|
306
|
+
|
|
307
|
+
context.response.header.set('X-RateLimit-Limit', result.total.toString());
|
|
308
|
+
context.response.header.set('X-RateLimit-Remaining', result.remaining.toString());
|
|
309
|
+
context.response.header.set('X-RateLimit-Reset', result.resetAt.toISOString());
|
|
310
|
+
|
|
311
|
+
if (result.limited) {
|
|
312
|
+
context.response.exception('Too many requests', {
|
|
313
|
+
status: 429,
|
|
314
|
+
data: { retryAfter: result.resetAt }
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return context;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Middleware with Dependencies
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { decorator } from '@ooneex/middleware';
|
|
327
|
+
import { injectable, inject } from '@ooneex/container';
|
|
328
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
329
|
+
import type { ILogger } from '@ooneex/logger';
|
|
330
|
+
|
|
331
|
+
@decorator.middleware()
|
|
332
|
+
@injectable()
|
|
333
|
+
class LoggingMiddleware implements IMiddleware {
|
|
334
|
+
constructor(
|
|
335
|
+
@inject('logger') private readonly logger: ILogger
|
|
336
|
+
) {}
|
|
337
|
+
|
|
338
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
339
|
+
const start = Date.now();
|
|
340
|
+
|
|
341
|
+
this.logger.info('Request started', {
|
|
342
|
+
method: context.method,
|
|
343
|
+
path: context.request.path,
|
|
344
|
+
ip: context.ip
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Store start time for response logging
|
|
348
|
+
context.request.startTime = start;
|
|
349
|
+
|
|
350
|
+
return context;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Error Handling Middleware
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { decorator } from '@ooneex/middleware';
|
|
359
|
+
import type { IMiddleware, ContextType } from '@ooneex/middleware';
|
|
360
|
+
import { Exception } from '@ooneex/exception';
|
|
361
|
+
|
|
362
|
+
@decorator.middleware()
|
|
363
|
+
class ErrorMiddleware implements IMiddleware {
|
|
364
|
+
public async handle(context: ContextType): Promise<ContextType> {
|
|
365
|
+
try {
|
|
366
|
+
return context;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error instanceof Exception) {
|
|
369
|
+
context.logger.error(error);
|
|
370
|
+
context.response.exception(error.message, {
|
|
371
|
+
status: error.status,
|
|
372
|
+
data: error.data
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
context.logger.error('Unexpected error', {
|
|
376
|
+
error: error instanceof Error ? error.message : String(error)
|
|
377
|
+
});
|
|
378
|
+
context.response.exception('Internal server error', {
|
|
379
|
+
status: 500
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return context;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
392
|
+
|
|
393
|
+
## Contributing
|
|
394
|
+
|
|
395
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
396
|
+
|
|
397
|
+
### Development Setup
|
|
398
|
+
|
|
399
|
+
1. Clone the repository
|
|
400
|
+
2. Install dependencies: `bun install`
|
|
401
|
+
3. Run tests: `bun run test`
|
|
402
|
+
4. Build the project: `bun run build`
|
|
403
|
+
|
|
404
|
+
### Guidelines
|
|
405
|
+
|
|
406
|
+
- Write tests for new features
|
|
407
|
+
- Follow the existing code style
|
|
408
|
+
- Update documentation for API changes
|
|
409
|
+
- Ensure all tests pass before submitting PR
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
Made with ❤️ by the Ooneex team
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { EContainerScope } from "@ooneex/container";
|
|
2
|
+
import { ContextConfigType, ContextType } from "@ooneex/controller";
|
|
3
|
+
import { ContextConfigType as SocketContextConfigType, ContextType as SocketContextType } from "@ooneex/socket";
|
|
1
4
|
type MiddlewareClassType = new (...args: any[]) => IMiddleware;
|
|
2
|
-
|
|
3
|
-
|
|
5
|
+
type SocketMiddlewareClassType = new (...args: any[]) => ISocketMiddleware;
|
|
6
|
+
interface IMiddleware<T extends ContextConfigType = ContextConfigType> {
|
|
7
|
+
handle: (context: ContextType<T>) => Promise<ContextType<T>> | ContextType<T>;
|
|
4
8
|
}
|
|
5
|
-
|
|
9
|
+
interface ISocketMiddleware<T extends SocketContextConfigType = SocketContextConfigType> {
|
|
10
|
+
handle: (context: SocketContextType<T>) => Promise<SocketContextType<T>> | SocketContextType<T>;
|
|
11
|
+
}
|
|
12
|
+
declare const decorator: {
|
|
13
|
+
middleware: (scope?: EContainerScope) => (target: MiddlewareClassType | SocketMiddlewareClassType) => void;
|
|
14
|
+
};
|
|
15
|
+
export { decorator, SocketMiddlewareClassType, MiddlewareClassType, ISocketMiddleware, IMiddleware };
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [],
|
|
3
|
+
"sources": ["src/decorators.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
+
"import { container, EContainerScope } from \"@ooneex/container\";\nimport type { MiddlewareClassType, SocketMiddlewareClassType } from \"./types\";\n\nexport const decorator = {\n middleware: (scope: EContainerScope = EContainerScope.Singleton) => {\n return (target: MiddlewareClassType | SocketMiddlewareClassType): void => {\n container.add(target, scope);\n };\n },\n};\n"
|
|
5
6
|
],
|
|
6
|
-
"mappings": "",
|
|
7
|
-
"debugId": "
|
|
7
|
+
"mappings": "AAAA,oBAAS,qBAAW,0BAGb,IAAM,EAAY,CACvB,WAAY,CAAC,EAAyB,EAAgB,YAAc,CAClE,MAAO,CAAC,IAAkE,CACxE,EAAU,IAAI,EAAQ,CAAK,GAGjC",
|
|
8
|
+
"debugId": "99D3A80723DBEBF264756E2164756E21",
|
|
8
9
|
"names": []
|
|
9
10
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ooneex/middleware",
|
|
3
|
-
"description": "",
|
|
4
|
-
"version": "0.0
|
|
3
|
+
"description": "Middleware framework with decorators for request/response pipeline processing in HTTP and WebSocket contexts",
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -25,10 +25,23 @@
|
|
|
25
25
|
"test": "bun test tests",
|
|
26
26
|
"build": "bunup",
|
|
27
27
|
"lint": "tsgo --noEmit && bunx biome lint",
|
|
28
|
-
"publish
|
|
29
|
-
"publish:pack": "bun pm pack --destination ./dist",
|
|
30
|
-
"publish:dry": "bun publish --dry-run"
|
|
28
|
+
"publish": "bun publish --access public || true"
|
|
31
29
|
},
|
|
32
|
-
"
|
|
33
|
-
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@ooneex/container": "0.0.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@ooneex/controller": "0.0.1",
|
|
35
|
+
"@ooneex/socket": "0.0.1"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"bun",
|
|
39
|
+
"decorator",
|
|
40
|
+
"handler",
|
|
41
|
+
"http",
|
|
42
|
+
"middleware",
|
|
43
|
+
"ooneex",
|
|
44
|
+
"pipeline",
|
|
45
|
+
"typescript"
|
|
46
|
+
]
|
|
34
47
|
}
|
|
Binary file
|