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.
Files changed (2) hide show
  1. package/README.md +72 -15
  2. 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) → Guard → Pipe → Controller method → Interceptor(leave)
178
- └────────── Exception → Filter ──────────┘
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` too, so `@Inject` property injection works inside them.
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 implements NestifyGuard {
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
- Interceptors run before the controller method; the returned function is called after the method finishes (useful for logging, timing, response wrapping). The returned function receives the handler's return value (or the caught error):
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 implements NestifyInterceptor {
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 (result: any) => {
248
- console.log(`Request completed in ${Date.now() - start}ms`);
249
- return result; // Can be modified; the return value becomes the final response
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 { data: 'example' };
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 implements NestifyPipe {
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 implements NestifyFilter {
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.4.2",
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.4.2",
45
- "@nestify-js/shared": "0.4.2"
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"