nestify-js 0.4.2 → 0.5.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/README.md +72 -15
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -167,6 +167,46 @@ Defines a module with providers, controllers, imports, and exports:
|
|
|
167
167
|
class UserModule {}
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
### Custom Decorators and Metadata
|
|
171
|
+
|
|
172
|
+
`createDecorator(key)` creates a Stage 3 class/method decorator that stores custom metadata. Metadata getters now receive the controller class directly instead of an `ExecutionContext`:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const Roles = createDecorator<string[]>('roles');
|
|
176
|
+
|
|
177
|
+
@Roles(['admin'])
|
|
178
|
+
@Controller('/admin')
|
|
179
|
+
class AdminController {
|
|
180
|
+
@Get('/audit')
|
|
181
|
+
@Roles(['auditor'])
|
|
182
|
+
getAuditLog() {
|
|
183
|
+
// ...
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@Guard()
|
|
188
|
+
class RolesGuard extends NestifyGuard {
|
|
189
|
+
canActivate(context: ExecutionContext) {
|
|
190
|
+
const controller = context.getClass();
|
|
191
|
+
const handler = context.getHandler();
|
|
192
|
+
const roles =
|
|
193
|
+
getMethodMetadata<string[]>(controller, handler.name, 'roles') ??
|
|
194
|
+
getClassMetadata<string[]>(controller, 'roles');
|
|
195
|
+
|
|
196
|
+
const currentRole = 'admin'; // Read this from the authenticated request or a service
|
|
197
|
+
return !roles?.length || roles.includes(currentRole);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The available helpers are:
|
|
203
|
+
|
|
204
|
+
- `createDecorator<T>(key)` — creates a decorator usable on classes and methods.
|
|
205
|
+
- `getClassMetadata<T>(controller, key)` — reads class metadata.
|
|
206
|
+
- `getMethodMetadata<T>(controller, methodName, key)` — reads method metadata.
|
|
207
|
+
- `setClassMetadata(context, key, value)` / `setMethodMetadata(context, key, value)` — write metadata from a custom Stage 3 decorator implementation.
|
|
208
|
+
- `SymbolMetadata` — exposes the low-level Stage 3 metadata symbol for advanced use; prefer the helpers above.
|
|
209
|
+
|
|
170
210
|
### Middleware System
|
|
171
211
|
|
|
172
212
|
There are four kinds of middleware: **Guards**, **Interceptors**, **Pipes** and **Filters**.
|
|
@@ -174,8 +214,8 @@ There are four kinds of middleware: **Guards**, **Interceptors**, **Pipes** and
|
|
|
174
214
|
Execution order of a single request:
|
|
175
215
|
|
|
176
216
|
```
|
|
177
|
-
Request → Interceptor(enter) →
|
|
178
|
-
|
|
217
|
+
Request → Guard → Interceptor(enter) → Pipe → Controller method → Interceptor(leave) → Response
|
|
218
|
+
└──────────────────── Unhandled exception → Filter ────────────────────┘
|
|
179
219
|
```
|
|
180
220
|
|
|
181
221
|
#### Registration Rules (Important)
|
|
@@ -183,7 +223,7 @@ Request → Interceptor(enter) → Guard → Pipe → Controller method → Inte
|
|
|
183
223
|
- **Built-in middlewares are auto-registered**: the framework's preset pipes (`PipeBody` / `PipeQuery` / `PipeParams` / `PipeIp` / `PipeRaw` / `PipeFile`) and `JwtGuard` are automatically instantiated during `apply()`. They work out of the box, no configuration needed.
|
|
184
224
|
- **Custom middlewares must be registered**: like NestJS, classes decorated by `@Guard()` / `@Interceptor()` / `@Pipe()` / `@Filter()` must appear in some module's `providers`, otherwise route registration fails with `Cannot find class for token`.
|
|
185
225
|
- **Where to apply**: `@UseGuards` / `@UseInterceptors` / `@UsePipes` / `@UseFilters` can be applied on a **controller class** (affects all its routes) or on a **method** (affects only that route). Middlewares of the same kind run in order: global → controller → method.
|
|
186
|
-
- Middleware classes are `Injectable
|
|
226
|
+
- Custom middleware classes must **extend** `NestifyGuard`, `NestifyInterceptor`, `NestifyPipe`, or `NestifyFilter`; the decorators validate the base class at runtime. Middleware classes are also `Injectable`, so `@Inject` property injection works inside them.
|
|
187
227
|
|
|
188
228
|
#### Guards
|
|
189
229
|
|
|
@@ -191,7 +231,7 @@ Guards control access to routes. Returning `false` or throwing from `canActivate
|
|
|
191
231
|
|
|
192
232
|
```typescript
|
|
193
233
|
@Guard()
|
|
194
|
-
class AuthGuard
|
|
234
|
+
class AuthGuard extends NestifyGuard {
|
|
195
235
|
// Dependency injection works
|
|
196
236
|
@Inject(AuthService)
|
|
197
237
|
authService: AuthService;
|
|
@@ -235,19 +275,27 @@ class AppModule {}
|
|
|
235
275
|
|
|
236
276
|
#### Interceptors
|
|
237
277
|
|
|
238
|
-
|
|
278
|
+
An interceptor receives `(context, next)` before pipes and the controller method run. It must return `next`; register reverse-phase callbacks with its Promise-like `.then()` and `.catch()` methods:
|
|
239
279
|
|
|
240
280
|
```typescript
|
|
241
281
|
@Interceptor()
|
|
242
|
-
class LoggingInterceptor
|
|
243
|
-
intercept(context: ExecutionContext) {
|
|
282
|
+
class LoggingInterceptor extends NestifyInterceptor {
|
|
283
|
+
intercept(context: ExecutionContext, next: InterceptorNextHandler) {
|
|
244
284
|
const start = Date.now();
|
|
245
285
|
console.log('Request started');
|
|
246
286
|
|
|
247
|
-
return
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
287
|
+
return next
|
|
288
|
+
.then((result: any) => {
|
|
289
|
+
console.log(`Request completed in ${Date.now() - start}ms`);
|
|
290
|
+
return {
|
|
291
|
+
data: result,
|
|
292
|
+
elapsed: Date.now() - start,
|
|
293
|
+
}; // Passed to the next outer interceptor, then used as the response
|
|
294
|
+
})
|
|
295
|
+
.catch((error: unknown) => {
|
|
296
|
+
console.error('Response mapping failed', error);
|
|
297
|
+
throw error;
|
|
298
|
+
});
|
|
251
299
|
}
|
|
252
300
|
}
|
|
253
301
|
|
|
@@ -256,11 +304,19 @@ class LoggingInterceptor implements NestifyInterceptor {
|
|
|
256
304
|
class ApiController {
|
|
257
305
|
@Get('/data')
|
|
258
306
|
getData() {
|
|
259
|
-
return {
|
|
307
|
+
return { value: 'example' };
|
|
260
308
|
}
|
|
261
309
|
}
|
|
262
310
|
```
|
|
263
311
|
|
|
312
|
+
Important interceptor semantics:
|
|
313
|
+
|
|
314
|
+
- Interceptors enter in registration order: global → controller → method.
|
|
315
|
+
- Their `next.then(...)` callbacks run in reverse order: method → controller → global.
|
|
316
|
+
- Each `.then()` receives the current result, and its return value is passed to the next outer interceptor.
|
|
317
|
+
- If an interceptor's `.then()` callback throws or rejects, its matching `.catch()` callback can recover by returning a value or continue the failure by throwing.
|
|
318
|
+
- `intercept()` must return the provided `next` handler; returning a standalone function is no longer supported.
|
|
319
|
+
|
|
264
320
|
Global interceptor: `{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }`.
|
|
265
321
|
|
|
266
322
|
#### Pipes
|
|
@@ -271,7 +327,7 @@ Pipes validate and transform input data. Each pipe's return value becomes the ne
|
|
|
271
327
|
|
|
272
328
|
```typescript
|
|
273
329
|
@Pipe()
|
|
274
|
-
class TrimPipe
|
|
330
|
+
class TrimPipe extends NestifyPipe {
|
|
275
331
|
async transform(context: ExecutionContext, input: any[], schema?: PipeFullSchema) {
|
|
276
332
|
// `input` comes from the previous step; the return value goes to the next pipe or the handler
|
|
277
333
|
return input.map((v) => (typeof v === 'string' ? v.trim() : v));
|
|
@@ -342,7 +398,7 @@ Filters handle exceptions thrown by routes. Specify the exception classes to cat
|
|
|
342
398
|
|
|
343
399
|
```typescript
|
|
344
400
|
@Filter(HttpException)
|
|
345
|
-
class HttpExceptionFilter
|
|
401
|
+
class HttpExceptionFilter extends NestifyFilter {
|
|
346
402
|
catch(context: ExecutionContext, exception: HttpException) {
|
|
347
403
|
const response = context.switchToHttp().getReply();
|
|
348
404
|
response.status(exception.status).send({
|
|
@@ -466,6 +522,7 @@ import {
|
|
|
466
522
|
Params,
|
|
467
523
|
UseGuards,
|
|
468
524
|
Guard,
|
|
525
|
+
NestifyGuard,
|
|
469
526
|
nestify,
|
|
470
527
|
} from 'nestify-js';
|
|
471
528
|
|
|
@@ -494,7 +551,7 @@ class UserService {
|
|
|
494
551
|
|
|
495
552
|
// Guard
|
|
496
553
|
@Guard()
|
|
497
|
-
class AuthGuard {
|
|
554
|
+
class AuthGuard extends NestifyGuard {
|
|
498
555
|
canActivate(context) {
|
|
499
556
|
// Simple auth check
|
|
500
557
|
const request = context.switchToHttp().getRequest();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestify-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A NestJS like fastify plugin for dependency injection",
|
|
5
5
|
"description_zh": "一个类似于 NestJS 的 Fastify 依赖注入插件",
|
|
6
6
|
"purpose": "npm",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@fastify/sensible": "^6.0.4",
|
|
42
42
|
"@fastify/static": "^9.1.3",
|
|
43
43
|
"fastify": "^5.0.0",
|
|
44
|
-
"@nestify-js/core": "0.
|
|
45
|
-
"@nestify-js/shared": "0.
|
|
44
|
+
"@nestify-js/core": "0.5.1",
|
|
45
|
+
"@nestify-js/shared": "0.5.1"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@fastify/multipart": "^9.3.0"
|