@shadow-library/fastify 0.0.8 → 0.0.9

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 CHANGED
@@ -444,6 +444,257 @@ export class ProductController {
444
444
  }
445
445
  ```
446
446
 
447
+ ## Context Service
448
+
449
+ The `ContextService` provides request-scoped context management using Node.js AsyncLocalStorage. It allows you to access request-specific data from anywhere in your application without explicitly passing it through function parameters.
450
+
451
+ **Important**: Context is automatically initialized for all HTTP requests and is always available within the request-response lifecycle (controllers, middleware, guards, services called during request processing). The `isInitialized()` method is primarily useful for methods that might be called both within and outside the request-response scope, such as during application startup, migrations, or background tasks.
452
+
453
+ ### Accessing Context Service
454
+
455
+ ```typescript
456
+ import { ContextService } from '@shadow-library/fastify';
457
+
458
+ @HttpController('/api')
459
+ export class ExampleController {
460
+ constructor(private readonly contextService: ContextService) {}
461
+
462
+ @Get('/current-request')
463
+ getCurrentRequestInfo() {
464
+ const request = this.contextService.getRequest();
465
+ const rid = this.contextService.getRID();
466
+
467
+ return {
468
+ method: request.method,
469
+ url: request.url,
470
+ requestId: rid,
471
+ userAgent: request.headers['user-agent'],
472
+ };
473
+ }
474
+ }
475
+ ```
476
+
477
+ ### Core Methods
478
+
479
+ #### Context State Management
480
+
481
+ ```typescript
482
+ // Check if context is initialized
483
+ // Useful for methods that may be called outside request-response scope
484
+ // (e.g., during migrations, startup tasks, background jobs)
485
+ contextService.isInitialized(): boolean
486
+
487
+ // Check if running in a child context (for nested operations)
488
+ contextService.isChildContext(): boolean
489
+ ```
490
+
491
+ #### Request/Response Access
492
+
493
+ ```typescript
494
+ // Get the current HTTP request object
495
+ contextService.getRequest(): FastifyRequest
496
+ contextService.getRequest(false): FastifyRequest | null
497
+
498
+ // Get the current HTTP response object
499
+ contextService.getResponse(): FastifyReply
500
+ contextService.getResponse(false): FastifyReply | null
501
+
502
+ // Get the current request ID
503
+ contextService.getRID(): string
504
+ contextService.getRID(false): string | null
505
+ ```
506
+
507
+ #### Data Storage
508
+
509
+ ```typescript
510
+ // Store data in current context
511
+ contextService.set('user', userData);
512
+ contextService.set('startTime', Date.now());
513
+
514
+ // Retrieve data from current context
515
+ const user = contextService.get('user');
516
+ const startTime = contextService.get('startTime', true); // throws if missing
517
+
518
+ // Store data in parent context (when in child context)
519
+ contextService.setInParent('sharedData', value);
520
+
521
+ // Get data from parent context
522
+ const parentData = contextService.getFromParent('sharedData');
523
+
524
+ // Resolve data (checks current context first, then parent)
525
+ const resolvedData = contextService.resolve('someKey');
526
+ ```
527
+
528
+ ### Practical Examples
529
+
530
+ #### Service Used in Multiple Contexts
531
+
532
+ ```typescript
533
+ @Injectable()
534
+ export class UserService {
535
+ constructor(private readonly contextService: ContextService) {}
536
+
537
+ async getUserInfo(userId: string) {
538
+ // This service method might be called during HTTP requests
539
+ // OR during migrations/background tasks
540
+ if (this.contextService.isInitialized()) {
541
+ // We're in a request context - can access request-specific data
542
+ const requestId = this.contextService.getRID();
543
+ console.log(`Fetching user ${userId} for request ${requestId}`);
544
+
545
+ // Maybe add audit trail with request context
546
+ const request = this.contextService.getRequest();
547
+ await this.auditLog.log({
548
+ action: 'getUserInfo',
549
+ userId,
550
+ requestId,
551
+ userAgent: request.headers['user-agent'],
552
+ ip: request.ip,
553
+ });
554
+ } else {
555
+ // We're outside request context (migration, background task, etc.)
556
+ console.log(`Fetching user ${userId} outside request context`);
557
+ }
558
+
559
+ return this.database.findUser(userId);
560
+ }
561
+ }
562
+ ```
563
+
564
+ #### Migration Script Example
565
+
566
+ ```typescript
567
+ // During migrations, context is not initialized
568
+ class UserMigration {
569
+ constructor(private readonly userService: UserService) {}
570
+
571
+ async migrateBulkUsers() {
572
+ // Context is NOT initialized here
573
+ console.log('Context initialized:', this.contextService.isInitialized()); // false
574
+
575
+ const users = await this.userService.getAllUsers(); // Works fine
576
+ // Process users...
577
+ }
578
+ }
579
+ ```
580
+
581
+ #### Request Logging Middleware
582
+
583
+ ```typescript
584
+ @Middleware({ type: 'onRequest', weight: 100 })
585
+ export class RequestLoggerMiddleware {
586
+ constructor(private readonly contextService: ContextService) {}
587
+
588
+ use(request: FastifyRequest, reply: FastifyReply, done: Function) {
589
+ // Context is ALWAYS initialized in middleware during requests
590
+ // No need to check isInitialized() here
591
+ this.contextService.set('startTime', Date.now());
592
+ this.contextService.set('userIP', request.ip);
593
+ done();
594
+ }
595
+ }
596
+
597
+ @Middleware({ type: 'onResponse', weight: 100 })
598
+ export class ResponseLoggerMiddleware {
599
+ constructor(private readonly contextService: ContextService) {}
600
+
601
+ use(request: FastifyRequest, reply: FastifyReply, done: Function) {
602
+ // Context is ALWAYS initialized in middleware during requests
603
+ const startTime = this.contextService.get<number>('startTime');
604
+ const duration = startTime ? Date.now() - startTime : 0;
605
+
606
+ console.log({
607
+ requestId: this.contextService.getRID(),
608
+ method: request.method,
609
+ url: request.url,
610
+ statusCode: reply.statusCode,
611
+ duration: `${duration}ms`,
612
+ userIP: this.contextService.get('userIP')
613
+ });
614
+ done();
615
+ }
616
+ }
617
+
618
+ console.log({
619
+ requestId: this.contextService.getRID(),
620
+ method: request.method,
621
+ url: request.url,
622
+ statusCode: reply.statusCode,
623
+ duration: `${duration}ms`,
624
+ userIP: this.contextService.get('userIP'),
625
+ });
626
+ }
627
+ done();
628
+ }
629
+ }
630
+ ```
631
+
632
+ #### Authentication Context
633
+
634
+ ```typescript
635
+ @Middleware({ type: 'preHandler', weight: 90 })
636
+ export class AuthMiddleware {
637
+ constructor(private readonly contextService: ContextService) {}
638
+
639
+ async use(request: FastifyRequest, reply: FastifyReply) {
640
+ const token = request.headers.authorization?.replace('Bearer ', '');
641
+
642
+ if (token) {
643
+ // Context is always available in middleware during requests
644
+ const user = await this.validateToken(token);
645
+ if (user) {
646
+ // Store authenticated user in context
647
+ this.contextService.set('currentUser', user);
648
+ this.contextService.set('isAuthenticated', true);
649
+ }
650
+ }
651
+ }
652
+ }
653
+
654
+ // Use in any controller or service
655
+ @HttpController('/api/profile')
656
+ export class ProfileController {
657
+ constructor(private readonly contextService: ContextService) {}
658
+
659
+ @Get()
660
+ getProfile() {
661
+ // Context is always available in controllers during requests
662
+ const isAuthenticated = this.contextService.get<boolean>('isAuthenticated');
663
+ if (!isAuthenticated) {
664
+ throw new ServerError(ServerErrorCode.UNAUTHORIZED);
665
+ }
666
+
667
+ const currentUser = this.contextService.get('currentUser');
668
+ return { user: currentUser };
669
+ }
670
+ }
671
+ ```
672
+
673
+ #### Child Context Usage
674
+
675
+ ```typescript
676
+ @HttpController('/api')
677
+ export class DataController {
678
+ constructor(
679
+ private readonly contextService: ContextService,
680
+ @Inject(Router) private readonly fastifyRouter: FastifyRouter,
681
+ ) {}
682
+
683
+ @Get('/aggregate')
684
+ async getAggregateData() {
685
+ const results = [];
686
+
687
+ // Each child route call creates a new child context
688
+ for (const endpoint of ['/users', '/posts', '/comments']) {
689
+ const result = await this.fastifyRouter.resolveChildRoute(endpoint);
690
+ results.push(result);
691
+ }
692
+
693
+ return { aggregated: results };
694
+ }
695
+ }
696
+ ```
697
+
447
698
  ## Examples
448
699
 
449
700
  Check out the [examples](./examples) directory for complete working examples:
@@ -5,6 +5,7 @@ export declare class ContextService {
5
5
  static readonly name = "ContextService";
6
6
  private readonly storage;
7
7
  init(): onRequestHookHandler;
8
+ isInitialized(): boolean;
8
9
  get<T>(key: Key, throwOnMissing: true): T;
9
10
  get<T>(key: Key, throwOnMissing?: boolean): T | null;
10
11
  getFromParent<T>(key: Key, throwOnMissing: true): T;
@@ -37,6 +37,9 @@ let ContextService = class ContextService {
37
37
  this.storage.run(store, done);
38
38
  };
39
39
  }
40
+ isInitialized() {
41
+ return this.storage.getStore() !== undefined;
42
+ }
40
43
  get(key, throwOnMissing) {
41
44
  const store = this.storage.getStore();
42
45
  if (!store)
@@ -5,6 +5,7 @@ export declare class ContextService {
5
5
  static readonly name = "ContextService";
6
6
  private readonly storage;
7
7
  init(): onRequestHookHandler;
8
+ isInitialized(): boolean;
8
9
  get<T>(key: Key, throwOnMissing: true): T;
9
10
  get<T>(key: Key, throwOnMissing?: boolean): T | null;
10
11
  getFromParent<T>(key: Key, throwOnMissing: true): T;
@@ -34,6 +34,9 @@ let ContextService = class ContextService {
34
34
  this.storage.run(store, done);
35
35
  };
36
36
  }
37
+ isInitialized() {
38
+ return this.storage.getStore() !== undefined;
39
+ }
37
40
  get(key, throwOnMissing) {
38
41
  const store = this.storage.getStore();
39
42
  if (!store)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shadow-library/fastify",
3
3
  "type": "module",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "sideEffects": false,
6
6
  "description": "A Fastify wrapper featuring decorator-based routing, middleware and error handling",
7
7
  "repository": {