@hazeljs/core 0.2.0-alpha.1
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/LICENSE +192 -0
- package/README.md +560 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +1237 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +810 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +1183 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +166 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +538 -0
- package/dist/errors/http.error.d.ts +34 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +69 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +94 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +516 -0
- package/dist/hazel-module.d.ts +29 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +137 -0
- package/dist/hazel-response.d.ts +25 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +89 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +159 -0
- package/dist/interceptors/interceptor.d.ts +30 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +71 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +261 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +22 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +506 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# @hazeljs/core
|
|
2
|
+
|
|
3
|
+
**The foundation of HazelJS — DI, routing, and decorators that feel right.**
|
|
4
|
+
|
|
5
|
+
Stop wiring boilerplate. Build APIs with dependency injection, decorator-based routing, and middleware that just works. TypeScript-first, production-ready, zero Express dependency.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@hazeljs/core)
|
|
8
|
+
[](https://www.npmjs.com/package/@hazeljs/core)
|
|
9
|
+
[](https://www.apache.org/licenses/LICENSE-2.0)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🎯 **Dependency Injection** - Advanced DI with Singleton, Transient, and Request scopes
|
|
14
|
+
- 🎨 **Decorator-Based API** - Clean, intuitive programming model
|
|
15
|
+
- 🛣️ **Routing** - Express-based routing with parameter extraction
|
|
16
|
+
- 🔌 **Middleware Support** - Global and route-level middleware
|
|
17
|
+
- 🛡️ **Guards & Interceptors** - Request validation and transformation
|
|
18
|
+
- 🔧 **Pipes** - Data transformation and validation
|
|
19
|
+
- 🏥 **Health Checks** - Built-in liveness, readiness, and startup probes
|
|
20
|
+
- 🛑 **Graceful Shutdown** - Proper cleanup and connection draining
|
|
21
|
+
- 📊 **Logging** - Winston-based structured logging
|
|
22
|
+
- ✅ **Validation** - Automatic request validation with class-validator
|
|
23
|
+
- 🧪 **Testing Utilities** - Full testing support with TestingModule
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @hazeljs/core
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Create a Controller
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { Controller, Get, Post, Body, Param } from '@hazeljs/core';
|
|
37
|
+
|
|
38
|
+
@Controller('/users')
|
|
39
|
+
export class UserController {
|
|
40
|
+
@Get()
|
|
41
|
+
findAll() {
|
|
42
|
+
return { users: [] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Get('/:id')
|
|
46
|
+
findOne(@Param('id') id: string) {
|
|
47
|
+
return { id, name: 'John Doe' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Post()
|
|
51
|
+
create(@Body() createUserDto: CreateUserDto) {
|
|
52
|
+
return { message: 'User created', data: createUserDto };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Create a Service
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { Injectable } from '@hazeljs/core';
|
|
61
|
+
|
|
62
|
+
@Injectable()
|
|
63
|
+
export class UserService {
|
|
64
|
+
private users = [];
|
|
65
|
+
|
|
66
|
+
findAll() {
|
|
67
|
+
return this.users;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
findOne(id: string) {
|
|
71
|
+
return this.users.find(user => user.id === id);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
create(data: any) {
|
|
75
|
+
const user = { id: Date.now().toString(), ...data };
|
|
76
|
+
this.users.push(user);
|
|
77
|
+
return user;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Create a Module
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { HazelModule } from '@hazeljs/core';
|
|
86
|
+
import { UserController } from './user.controller';
|
|
87
|
+
import { UserService } from './user.service';
|
|
88
|
+
|
|
89
|
+
@HazelModule({
|
|
90
|
+
controllers: [UserController],
|
|
91
|
+
providers: [UserService],
|
|
92
|
+
})
|
|
93
|
+
export class AppModule {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. Bootstrap the Application
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { HazelApp, BuiltInHealthChecks } from '@hazeljs/core';
|
|
100
|
+
import { AppModule } from './app.module';
|
|
101
|
+
|
|
102
|
+
async function bootstrap() {
|
|
103
|
+
const app = await HazelApp.create(AppModule);
|
|
104
|
+
|
|
105
|
+
// Register health checks
|
|
106
|
+
app.registerHealthCheck(BuiltInHealthChecks.memoryCheck(500));
|
|
107
|
+
app.registerHealthCheck(BuiltInHealthChecks.eventLoopCheck(100));
|
|
108
|
+
|
|
109
|
+
// Register shutdown handlers
|
|
110
|
+
app.registerShutdownHandler({
|
|
111
|
+
name: 'cleanup',
|
|
112
|
+
handler: async () => {
|
|
113
|
+
console.log('Cleaning up resources...');
|
|
114
|
+
},
|
|
115
|
+
timeout: 5000,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await app.listen(3000);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
bootstrap();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Dependency Injection
|
|
125
|
+
|
|
126
|
+
### Scopes
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { Injectable, Scope } from '@hazeljs/core';
|
|
130
|
+
|
|
131
|
+
// Singleton (default) - one instance for entire app
|
|
132
|
+
@Injectable()
|
|
133
|
+
export class SingletonService {}
|
|
134
|
+
|
|
135
|
+
// Transient - new instance every time
|
|
136
|
+
@Injectable({ scope: Scope.TRANSIENT })
|
|
137
|
+
export class TransientService {}
|
|
138
|
+
|
|
139
|
+
// Request - one instance per HTTP request
|
|
140
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
141
|
+
export class RequestContextService {}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Constructor Injection
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
@Injectable()
|
|
148
|
+
export class OrderService {
|
|
149
|
+
constructor(
|
|
150
|
+
private userService: UserService,
|
|
151
|
+
private paymentService: PaymentService
|
|
152
|
+
) {}
|
|
153
|
+
|
|
154
|
+
async createOrder(userId: string) {
|
|
155
|
+
const user = await this.userService.findOne(userId);
|
|
156
|
+
const payment = await this.paymentService.process(user);
|
|
157
|
+
return { user, payment };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Routing & Decorators
|
|
163
|
+
|
|
164
|
+
### HTTP Methods
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { Controller, Get, Post, Put, Delete, Patch } from '@hazeljs/core';
|
|
168
|
+
|
|
169
|
+
@Controller('/api')
|
|
170
|
+
export class ApiController {
|
|
171
|
+
@Get('/items')
|
|
172
|
+
getItems() {}
|
|
173
|
+
|
|
174
|
+
@Post('/items')
|
|
175
|
+
createItem() {}
|
|
176
|
+
|
|
177
|
+
@Put('/items/:id')
|
|
178
|
+
updateItem() {}
|
|
179
|
+
|
|
180
|
+
@Patch('/items/:id')
|
|
181
|
+
patchItem() {}
|
|
182
|
+
|
|
183
|
+
@Delete('/items/:id')
|
|
184
|
+
deleteItem() {}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Parameter Decorators
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { Controller, Get, Post, Param, Query, Body, Headers, Req, Res } from '@hazeljs/core';
|
|
192
|
+
|
|
193
|
+
@Controller('/users')
|
|
194
|
+
export class UserController {
|
|
195
|
+
@Get('/:id')
|
|
196
|
+
findOne(
|
|
197
|
+
@Param('id') id: string,
|
|
198
|
+
@Query('include') include?: string
|
|
199
|
+
) {
|
|
200
|
+
return { id, include };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Post()
|
|
204
|
+
create(
|
|
205
|
+
@Body() createUserDto: CreateUserDto,
|
|
206
|
+
@Headers('authorization') auth: string
|
|
207
|
+
) {
|
|
208
|
+
return { data: createUserDto, auth };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@Get('/raw')
|
|
212
|
+
rawAccess(@Req() req: Request, @Res() res: Response) {
|
|
213
|
+
res.json({ message: 'Direct access to req/res' });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Custom metadata and parameter decorators
|
|
219
|
+
|
|
220
|
+
Attach custom metadata for guards or other layers with **SetMetadata** / **getMetadata**, and build your own parameter decorators with **createParamDecorator**:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { SetMetadata, getMetadata, createParamDecorator } from '@hazeljs/core';
|
|
224
|
+
|
|
225
|
+
// Custom metadata (e.g. for guards)
|
|
226
|
+
@SetMetadata('roles', ['admin'])
|
|
227
|
+
class AdminController {}
|
|
228
|
+
|
|
229
|
+
// Custom parameter decorator
|
|
230
|
+
const CurrentUser = createParamDecorator((_req, ctx) => ctx.user);
|
|
231
|
+
|
|
232
|
+
@Get('profile')
|
|
233
|
+
getProfile(@CurrentUser user: User) {
|
|
234
|
+
return user;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Use the custom param decorator **without** parentheses: `@CurrentUser`. See the [full API reference](https://hazeljs.com/docs/api-reference) for `ParamDecoratorContext` and `CUSTOM_METADATA_PREFIX`.
|
|
239
|
+
|
|
240
|
+
## Middleware
|
|
241
|
+
|
|
242
|
+
### Global Middleware
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { Middleware, MiddlewareContext } from '@hazeljs/core';
|
|
246
|
+
|
|
247
|
+
@Injectable()
|
|
248
|
+
export class LoggerMiddleware implements Middleware {
|
|
249
|
+
async use(context: MiddlewareContext, next: () => Promise<void>) {
|
|
250
|
+
console.log(`${context.request.method} ${context.request.url}`);
|
|
251
|
+
await next();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Register globally
|
|
256
|
+
const app = await HazelApp.create(AppModule);
|
|
257
|
+
app.useGlobalMiddleware(new LoggerMiddleware());
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Route-Level Middleware
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { Controller, Get, UseMiddleware } from '@hazeljs/core';
|
|
264
|
+
|
|
265
|
+
@Controller('/admin')
|
|
266
|
+
@UseMiddleware(AuthMiddleware)
|
|
267
|
+
export class AdminController {
|
|
268
|
+
@Get('/dashboard')
|
|
269
|
+
@UseMiddleware(RoleCheckMiddleware)
|
|
270
|
+
getDashboard() {
|
|
271
|
+
return { data: 'admin dashboard' };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Guards
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { Guard, GuardContext } from '@hazeljs/core';
|
|
280
|
+
|
|
281
|
+
@Injectable()
|
|
282
|
+
export class AuthGuard implements Guard {
|
|
283
|
+
canActivate(context: GuardContext): boolean | Promise<boolean> {
|
|
284
|
+
const token = context.request.headers.authorization;
|
|
285
|
+
return this.validateToken(token);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private validateToken(token: string): boolean {
|
|
289
|
+
// Validate JWT token
|
|
290
|
+
return !!token;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Use in controller
|
|
295
|
+
@Controller('/protected')
|
|
296
|
+
@UseGuard(AuthGuard)
|
|
297
|
+
export class ProtectedController {
|
|
298
|
+
@Get()
|
|
299
|
+
getData() {
|
|
300
|
+
return { message: 'Protected data' };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Interceptors
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { Interceptor, InterceptorContext } from '@hazeljs/core';
|
|
309
|
+
|
|
310
|
+
@Injectable()
|
|
311
|
+
export class TransformInterceptor implements Interceptor {
|
|
312
|
+
async intercept(context: InterceptorContext, next: () => Promise<any>) {
|
|
313
|
+
const result = await next();
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
data: result,
|
|
317
|
+
timestamp: new Date().toISOString(),
|
|
318
|
+
path: context.request.url,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Use globally or per route
|
|
324
|
+
@Controller('/api')
|
|
325
|
+
@UseInterceptor(TransformInterceptor)
|
|
326
|
+
export class ApiController {}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Pipes
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { Pipe, PipeContext } from '@hazeljs/core';
|
|
333
|
+
|
|
334
|
+
@Injectable()
|
|
335
|
+
export class ValidationPipe implements Pipe {
|
|
336
|
+
transform(value: any, context: PipeContext) {
|
|
337
|
+
if (!value) {
|
|
338
|
+
throw new Error('Value is required');
|
|
339
|
+
}
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Use in route
|
|
345
|
+
@Post()
|
|
346
|
+
create(@Body(ValidationPipe) createDto: CreateDto) {
|
|
347
|
+
return createDto;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Validation
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { IsEmail, IsString, MinLength } from 'class-validator';
|
|
355
|
+
|
|
356
|
+
export class CreateUserDto {
|
|
357
|
+
@IsEmail()
|
|
358
|
+
email: string;
|
|
359
|
+
|
|
360
|
+
@IsString()
|
|
361
|
+
@MinLength(8)
|
|
362
|
+
password: string;
|
|
363
|
+
|
|
364
|
+
@IsString()
|
|
365
|
+
name: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@Controller('/users')
|
|
369
|
+
export class UserController {
|
|
370
|
+
@Post()
|
|
371
|
+
create(@Body() createUserDto: CreateUserDto) {
|
|
372
|
+
// Automatically validated before reaching here
|
|
373
|
+
return createUserDto;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Health Checks
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { HazelApp, BuiltInHealthChecks } from '@hazeljs/core';
|
|
382
|
+
|
|
383
|
+
const app = await HazelApp.create(AppModule);
|
|
384
|
+
|
|
385
|
+
// Built-in checks
|
|
386
|
+
app.registerHealthCheck(BuiltInHealthChecks.memoryCheck(500)); // 500MB threshold
|
|
387
|
+
app.registerHealthCheck(BuiltInHealthChecks.eventLoopCheck(100)); // 100ms lag
|
|
388
|
+
|
|
389
|
+
// Custom health check
|
|
390
|
+
app.registerHealthCheck({
|
|
391
|
+
name: 'database',
|
|
392
|
+
check: async () => {
|
|
393
|
+
try {
|
|
394
|
+
await database.ping();
|
|
395
|
+
return { status: 'healthy' };
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return {
|
|
398
|
+
status: 'unhealthy',
|
|
399
|
+
message: error.message
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
critical: true,
|
|
404
|
+
timeout: 3000,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Endpoints available:
|
|
408
|
+
// GET /health - Liveness probe
|
|
409
|
+
// GET /ready - Readiness probe
|
|
410
|
+
// GET /startup - Startup probe
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Graceful Shutdown
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
const app = await HazelApp.create(AppModule);
|
|
417
|
+
|
|
418
|
+
app.registerShutdownHandler({
|
|
419
|
+
name: 'database',
|
|
420
|
+
handler: async () => {
|
|
421
|
+
await database.disconnect();
|
|
422
|
+
console.log('Database disconnected');
|
|
423
|
+
},
|
|
424
|
+
timeout: 5000,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
app.registerShutdownHandler({
|
|
428
|
+
name: 'cache',
|
|
429
|
+
handler: async () => {
|
|
430
|
+
await redis.quit();
|
|
431
|
+
console.log('Redis connection closed');
|
|
432
|
+
},
|
|
433
|
+
timeout: 3000,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
await app.listen(3000);
|
|
437
|
+
|
|
438
|
+
// On SIGTERM/SIGINT:
|
|
439
|
+
// 1. HTTP server stops accepting new connections
|
|
440
|
+
// 2. Existing requests complete (up to 10s)
|
|
441
|
+
// 3. Shutdown handlers execute in order
|
|
442
|
+
// 4. Process exits cleanly
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Exception Handling
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { ExceptionFilter, ExceptionContext } from '@hazeljs/core';
|
|
449
|
+
|
|
450
|
+
@Injectable()
|
|
451
|
+
export class HttpExceptionFilter implements ExceptionFilter {
|
|
452
|
+
catch(error: Error, context: ExceptionContext) {
|
|
453
|
+
const response = context.response;
|
|
454
|
+
|
|
455
|
+
response.status(500).json({
|
|
456
|
+
statusCode: 500,
|
|
457
|
+
message: error.message,
|
|
458
|
+
timestamp: new Date().toISOString(),
|
|
459
|
+
path: context.request.url,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Use globally
|
|
465
|
+
app.useGlobalExceptionFilter(new HttpExceptionFilter());
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Testing
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { TestingModule } from '@hazeljs/core';
|
|
472
|
+
import { UserController } from './user.controller';
|
|
473
|
+
import { UserService } from './user.service';
|
|
474
|
+
|
|
475
|
+
describe('UserController', () => {
|
|
476
|
+
let module: TestingModule;
|
|
477
|
+
let controller: UserController;
|
|
478
|
+
let service: UserService;
|
|
479
|
+
|
|
480
|
+
beforeEach(async () => {
|
|
481
|
+
module = await TestingModule.create({
|
|
482
|
+
controllers: [UserController],
|
|
483
|
+
providers: [
|
|
484
|
+
{
|
|
485
|
+
provide: UserService,
|
|
486
|
+
useValue: {
|
|
487
|
+
findAll: jest.fn().mockResolvedValue([]),
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
controller = module.get(UserController);
|
|
494
|
+
service = module.get(UserService);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
afterEach(async () => {
|
|
498
|
+
await module.close();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should return all users', async () => {
|
|
502
|
+
const result = await controller.findAll();
|
|
503
|
+
expect(result).toEqual([]);
|
|
504
|
+
expect(service.findAll).toHaveBeenCalled();
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Logging
|
|
510
|
+
|
|
511
|
+
HTTP requests are logged in following format: `METHOD path status duration` (e.g. `GET /api/health 200 3ms`).
|
|
512
|
+
|
|
513
|
+
| Env var | Default | Description |
|
|
514
|
+
|---------|---------|-------------|
|
|
515
|
+
| `LOG_LEVEL` | `info` | Log level (error, warn, info, debug) |
|
|
516
|
+
| `LOG_HTTP` | `true` | Set to `false` to disable HTTP request logs |
|
|
517
|
+
| `LOG_ENABLED` | `true` | Set to `false` to disable all logging |
|
|
518
|
+
|
|
519
|
+
Startup and registration logs (controllers, routes, providers) are at `debug` level. Use `LOG_LEVEL=debug` for troubleshooting.
|
|
520
|
+
|
|
521
|
+
## API Reference
|
|
522
|
+
|
|
523
|
+
### Decorators
|
|
524
|
+
|
|
525
|
+
- `@HazelModule(options)` - Define a module
|
|
526
|
+
- `@Controller(path)` - Define a controller
|
|
527
|
+
- `@Injectable(options?)` - Mark class as injectable
|
|
528
|
+
- `@Get(path?)`, `@Post(path?)`, `@Put(path?)`, `@Delete(path?)`, `@Patch(path?)` - HTTP methods
|
|
529
|
+
- `@Param(name)`, `@Query(name)`, `@Body()`, `@Headers(name)`, `@Req()`, `@Res()`, `@Ip()`, `@Host()` - Parameter extraction
|
|
530
|
+
- `@UseGuards(...guards)`, `@UseInterceptors(...interceptors)`, `@UsePipes(...pipes)` - Apply guards, interceptors, pipes
|
|
531
|
+
- `@Public()` / `@SkipAuth()` - Mark route as public
|
|
532
|
+
- `@Timeout(ms)`, `@Retry(options)`, `@Optional()`, `@Session()` - Per-route behavior
|
|
533
|
+
- `@ApiTags(...tags)`, `@ApiOperation(options)` - OpenAPI metadata
|
|
534
|
+
- **SetMetadata(key, value)** - Attach custom metadata to a class or method (read with `getMetadata(key, target, propertyKey?)`)
|
|
535
|
+
- **createParamDecorator(resolve)** - Build custom parameter decorators that inject values from `(req, context, container)`
|
|
536
|
+
|
|
537
|
+
### Classes
|
|
538
|
+
|
|
539
|
+
- `HazelApp` - Main application class
|
|
540
|
+
- `TestingModule` - Testing utilities
|
|
541
|
+
- `Logger` - Logging service
|
|
542
|
+
|
|
543
|
+
## Examples
|
|
544
|
+
|
|
545
|
+
See the [examples](../../example) directory for complete working examples.
|
|
546
|
+
|
|
547
|
+
## Contributing
|
|
548
|
+
|
|
549
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
550
|
+
|
|
551
|
+
## License
|
|
552
|
+
|
|
553
|
+
Apache 2.0 © [HazelJS](https://hazeljs.com)
|
|
554
|
+
|
|
555
|
+
## Links
|
|
556
|
+
|
|
557
|
+
- [Documentation](https://hazeljs.com/docs)
|
|
558
|
+
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
559
|
+
- [Issues](https://github.com/hazel-js/hazeljs/issues)
|
|
560
|
+
- [Discord](https://discord.com/channels/1448263814238965833/1448263814859456575)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/container.test.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,CAAC"}
|