@veloxts/core 0.3.3 → 0.3.4

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
@@ -15,10 +15,10 @@ pnpm add @veloxts/core
15
15
  ## Quick Start
16
16
 
17
17
  ```typescript
18
- import { createVeloxApp } from '@veloxts/core';
18
+ import { veloxApp } from '@veloxts/core';
19
19
 
20
20
  // Create application
21
- const app = await createVeloxApp({
21
+ const app = await veloxApp({
22
22
  port: 3210,
23
23
  host: '0.0.0.0',
24
24
  logger: true,
@@ -31,7 +31,7 @@ console.log(`Server running on ${app.address}`);
31
31
 
32
32
  ## Core API
33
33
 
34
- ### `createVeloxApp(config?)`
34
+ ### `veloxApp(config?)`
35
35
 
36
36
  Creates a new VeloxTS application instance with sensible defaults.
37
37
 
@@ -49,7 +49,7 @@ interface VeloxAppConfig {
49
49
  **Example:**
50
50
 
51
51
  ```typescript
52
- const app = await createVeloxApp({
52
+ const app = await veloxApp({
53
53
  port: 4000,
54
54
  host: '127.0.0.1',
55
55
  logger: {
@@ -80,21 +80,21 @@ interface VeloxApp {
80
80
  start(): Promise<void>; // Start the server
81
81
  stop(): Promise<void>; // Stop the server gracefully
82
82
  register(plugin, options?): Promise<void>; // Register a plugin
83
- onShutdown(handler): void; // Add shutdown handler
83
+ beforeShutdown(handler): void; // Add shutdown handler
84
84
  }
85
85
  ```
86
86
 
87
87
  **Example:**
88
88
 
89
89
  ```typescript
90
- const app = await createVeloxApp();
90
+ const app = await veloxApp();
91
91
 
92
92
  // Register plugins
93
93
  await app.register(databasePlugin);
94
94
  await app.register(routerPlugin);
95
95
 
96
96
  // Add shutdown hook
97
- app.onShutdown(async () => {
97
+ app.beforeShutdown(async () => {
98
98
  console.log('Cleaning up resources...');
99
99
  });
100
100
 
@@ -205,7 +205,7 @@ declare module '@veloxts/core' {
205
205
  }
206
206
 
207
207
  // Use in app
208
- const app = await createVeloxApp();
208
+ const app = await veloxApp();
209
209
  await app.register(databasePlugin);
210
210
  ```
211
211
 
@@ -392,7 +392,7 @@ app.server.get('/users/:id', async (request, reply) => {
392
392
  ### Environment-Based Configuration
393
393
 
394
394
  ```typescript
395
- const app = await createVeloxApp({
395
+ const app = await veloxApp({
396
396
  port: Number(process.env.PORT) || 3210,
397
397
  host: process.env.HOST || '0.0.0.0',
398
398
  logger: process.env.NODE_ENV !== 'production',
@@ -405,7 +405,7 @@ const app = await createVeloxApp({
405
405
  ### Production Configuration
406
406
 
407
407
  ```typescript
408
- const app = await createVeloxApp({
408
+ const app = await veloxApp({
409
409
  port: 3210,
410
410
  host: '0.0.0.0',
411
411
  logger: {
@@ -426,10 +426,10 @@ const app = await createVeloxApp({
426
426
  ### Graceful Shutdown
427
427
 
428
428
  ```typescript
429
- const app = await createVeloxApp();
429
+ const app = await veloxApp();
430
430
 
431
431
  // Add custom shutdown handlers
432
- app.onShutdown(async () => {
432
+ app.beforeShutdown(async () => {
433
433
  console.log('Cleaning up resources...');
434
434
  await database.disconnect();
435
435
  await cache.flush();
@@ -494,7 +494,7 @@ const fastify = Fastify({
494
494
  trustProxy: true,
495
495
  });
496
496
 
497
- const app = await createVeloxApp({ fastify });
497
+ const app = await veloxApp({ fastify });
498
498
  ```
499
499
 
500
500
  ### Accessing Fastify Directly
@@ -502,7 +502,7 @@ const app = await createVeloxApp({ fastify });
502
502
  The underlying Fastify instance is available via `app.server`:
503
503
 
504
504
  ```typescript
505
- const app = await createVeloxApp();
505
+ const app = await veloxApp();
506
506
 
507
507
  // Add Fastify plugins
508
508
  await app.server.register(fastifyCors, {
@@ -520,7 +520,7 @@ app.server.get('/custom', async (request, reply) => {
520
520
  ### Complete Application Setup
521
521
 
522
522
  ```typescript
523
- import { createVeloxApp } from '@veloxts/core';
523
+ import { veloxApp } from '@veloxts/core';
524
524
  import { createDatabasePlugin } from '@veloxts/orm';
525
525
  import { registerRestRoutes } from '@veloxts/router';
526
526
  import { PrismaClient } from '@prisma/client';
@@ -528,7 +528,7 @@ import { userProcedures } from './procedures/users';
528
528
 
529
529
  // Initialize
530
530
  const prisma = new PrismaClient();
531
- const app = await createVeloxApp({
531
+ const app = await veloxApp({
532
532
  port: Number(process.env.PORT) || 3210,
533
533
  logger: true,
534
534
  });
@@ -558,6 +558,687 @@ await app.start();
558
558
  console.log(`Server running at ${app.address}`);
559
559
  ```
560
560
 
561
+ ## Dependency Injection
562
+
563
+ VeloxTS provides a powerful, type-safe dependency injection container inspired by Angular and NestJS. The DI system enables automatic constructor injection, lifecycle management, and clean separation of concerns.
564
+
565
+ ### Overview
566
+
567
+ The DI container manages service creation, dependency resolution, and lifecycle:
568
+
569
+ - **Automatic constructor injection** via TypeScript decorators
570
+ - **Multiple provider types**: classes, factories, values, aliases
571
+ - **Lifecycle scopes**: singleton, transient, request-scoped
572
+ - **Type safety**: Full TypeScript inference without code generation
573
+ - **Circular dependency detection**
574
+ - **Fastify integration** for request-scoped services
575
+
576
+ ### Accessing the Container
577
+
578
+ Every VeloxApp instance has a DI container available:
579
+
580
+ ```typescript
581
+ const app = await veloxApp();
582
+
583
+ // Access the container
584
+ app.container.register({ /* ... */ });
585
+ const service = app.container.resolve(MyService);
586
+ ```
587
+
588
+ ### Tokens
589
+
590
+ Tokens are unique identifiers for services. VeloxTS supports three token types:
591
+
592
+ #### Class Tokens
593
+
594
+ Use the class itself as a token:
595
+
596
+ ```typescript
597
+ @Injectable()
598
+ class UserService {
599
+ getUsers() { /* ... */ }
600
+ }
601
+
602
+ // Register with class token
603
+ app.container.register({
604
+ provide: UserService,
605
+ useClass: UserService
606
+ });
607
+
608
+ // Resolve with class token
609
+ const userService = app.container.resolve(UserService);
610
+ ```
611
+
612
+ #### String Tokens
613
+
614
+ Use string literals for named services:
615
+
616
+ ```typescript
617
+ import { token } from '@veloxts/core';
618
+
619
+ interface DatabaseClient {
620
+ query(sql: string): Promise<unknown>;
621
+ }
622
+
623
+ const DATABASE = token<DatabaseClient>('DATABASE');
624
+
625
+ app.container.register({
626
+ provide: DATABASE,
627
+ useFactory: () => createDatabaseClient()
628
+ });
629
+
630
+ const db = app.container.resolve(DATABASE);
631
+ ```
632
+
633
+ #### Symbol Tokens
634
+
635
+ Use symbols for guaranteed uniqueness:
636
+
637
+ ```typescript
638
+ import { token } from '@veloxts/core';
639
+
640
+ interface Logger {
641
+ log(message: string): void;
642
+ }
643
+
644
+ const LOGGER = token.symbol<Logger>('Logger');
645
+
646
+ app.container.register({
647
+ provide: LOGGER,
648
+ useClass: ConsoleLogger
649
+ });
650
+
651
+ const logger = app.container.resolve(LOGGER);
652
+ ```
653
+
654
+ ### Provider Types
655
+
656
+ The container supports four provider types for different use cases. For common patterns, VeloxTS provides succinct provider helpers that simplify registration.
657
+
658
+ #### Succinct Provider Helpers
659
+
660
+ VeloxTS provides convenient helpers for common provider patterns:
661
+
662
+ ```typescript
663
+ import { singleton, scoped, transient, value, factory } from '@veloxts/core';
664
+
665
+ // Singleton scope (one instance for entire app)
666
+ app.container.register(singleton(ConfigService));
667
+
668
+ // Request scope (one instance per HTTP request)
669
+ app.container.register(scoped(UserContext));
670
+
671
+ // Transient scope (new instance every time)
672
+ app.container.register(transient(RequestIdGenerator));
673
+
674
+ // Value provider (provide existing value)
675
+ app.container.register(value(CONFIG, { port: 3210 }));
676
+
677
+ // Factory provider (use factory function)
678
+ app.container.register(
679
+ factory(DATABASE, (config: ConfigService) => {
680
+ return new PrismaClient({ datasources: { db: { url: config.databaseUrl } } });
681
+ }, [ConfigService])
682
+ );
683
+ ```
684
+
685
+ These helpers are equivalent to the full provider syntax but more concise and readable.
686
+
687
+ #### Class Provider
688
+
689
+ Instantiate a class with automatic dependency injection:
690
+
691
+ ```typescript
692
+ @Injectable()
693
+ class UserService {
694
+ constructor(
695
+ private db: DatabaseClient,
696
+ private logger: Logger
697
+ ) {}
698
+ }
699
+
700
+ // Using succinct helper (recommended)
701
+ app.container.register(singleton(UserService));
702
+
703
+ // Or using full syntax
704
+ app.container.register({
705
+ provide: UserService,
706
+ useClass: UserService,
707
+ scope: Scope.SINGLETON
708
+ });
709
+ ```
710
+
711
+ #### Factory Provider
712
+
713
+ Use a factory function to create instances:
714
+
715
+ ```typescript
716
+ // Using succinct helper (recommended)
717
+ app.container.register(
718
+ factory(DATABASE, (config: ConfigService) => {
719
+ return createDatabaseClient(config.databaseUrl);
720
+ }, [ConfigService])
721
+ );
722
+
723
+ // Or using full syntax
724
+ app.container.register({
725
+ provide: DATABASE,
726
+ useFactory: (config: ConfigService) => {
727
+ return createDatabaseClient(config.databaseUrl);
728
+ },
729
+ inject: [ConfigService],
730
+ scope: Scope.SINGLETON
731
+ });
732
+ ```
733
+
734
+ #### Value Provider
735
+
736
+ Provide an existing value directly:
737
+
738
+ ```typescript
739
+ const CONFIG = token<AppConfig>('CONFIG');
740
+
741
+ // Using succinct helper (recommended)
742
+ app.container.register(
743
+ value(CONFIG, {
744
+ port: 3210,
745
+ host: 'localhost',
746
+ debug: true
747
+ })
748
+ );
749
+
750
+ // Or using full syntax
751
+ app.container.register({
752
+ provide: CONFIG,
753
+ useValue: {
754
+ port: 3210,
755
+ host: 'localhost',
756
+ debug: true
757
+ }
758
+ });
759
+ ```
760
+
761
+ #### Existing Provider (Alias)
762
+
763
+ Create an alias to another token:
764
+
765
+ ```typescript
766
+ // Register concrete implementation
767
+ app.container.register({
768
+ provide: ConsoleLogger,
769
+ useClass: ConsoleLogger
770
+ });
771
+
772
+ // Create an alias
773
+ app.container.register({
774
+ provide: LOGGER,
775
+ useExisting: ConsoleLogger
776
+ });
777
+
778
+ // Both resolve to the same instance
779
+ const logger1 = app.container.resolve(LOGGER);
780
+ const logger2 = app.container.resolve(ConsoleLogger);
781
+ // logger1 === logger2
782
+ ```
783
+
784
+ ### Lifecycle Scopes
785
+
786
+ Scopes determine how service instances are created and shared.
787
+
788
+ #### Singleton Scope
789
+
790
+ One instance for the entire application (default):
791
+
792
+ ```typescript
793
+ @Injectable({ scope: Scope.SINGLETON })
794
+ class ConfigService {
795
+ // Shared across all requests
796
+ }
797
+
798
+ // Register using succinct helper
799
+ app.container.register(singleton(ConfigService));
800
+ ```
801
+
802
+ **Best for:**
803
+ - Configuration services
804
+ - Database connection pools
805
+ - Cache clients
806
+ - Stateless utility services
807
+
808
+ #### Transient Scope
809
+
810
+ New instance on every resolution:
811
+
812
+ ```typescript
813
+ @Injectable({ scope: Scope.TRANSIENT })
814
+ class RequestIdGenerator {
815
+ readonly id = crypto.randomUUID();
816
+ }
817
+
818
+ // Register using succinct helper
819
+ app.container.register(transient(RequestIdGenerator));
820
+ ```
821
+
822
+ **Best for:**
823
+ - Services with mutable state
824
+ - Factories that produce unique objects
825
+ - Services where isolation is critical
826
+
827
+ #### Request Scope
828
+
829
+ One instance per HTTP request:
830
+
831
+ ```typescript
832
+ @Injectable({ scope: Scope.REQUEST })
833
+ class UserContext {
834
+ constructor(private request: FastifyRequest) {}
835
+
836
+ get userId(): string {
837
+ return this.request.user?.id;
838
+ }
839
+ }
840
+
841
+ // Register using succinct helper
842
+ app.container.register(scoped(UserContext));
843
+ ```
844
+
845
+ **Best for:**
846
+ - User context/session data
847
+ - Request-specific caching
848
+ - Transaction management
849
+ - Audit logging with request context
850
+
851
+ **Note:** Request-scoped services require a Fastify request context. Resolving them outside a request handler throws an error.
852
+
853
+ ### Decorators
854
+
855
+ Use TypeScript decorators for automatic dependency injection.
856
+
857
+ #### Prerequisites
858
+
859
+ Enable decorators in `tsconfig.json`:
860
+
861
+ ```json
862
+ {
863
+ "compilerOptions": {
864
+ "experimentalDecorators": true,
865
+ "emitDecoratorMetadata": true
866
+ }
867
+ }
868
+ ```
869
+
870
+ Import `reflect-metadata` at your app's entry point:
871
+
872
+ ```typescript
873
+ import 'reflect-metadata';
874
+ import { veloxApp } from '@veloxts/core';
875
+ ```
876
+
877
+ #### @Injectable()
878
+
879
+ Marks a class as injectable:
880
+
881
+ ```typescript
882
+ @Injectable()
883
+ class UserService {
884
+ constructor(private db: DatabaseClient) {}
885
+ }
886
+
887
+ @Injectable({ scope: Scope.REQUEST })
888
+ class RequestLogger {
889
+ constructor(private request: FastifyRequest) {}
890
+ }
891
+ ```
892
+
893
+ #### @Inject()
894
+
895
+ Explicitly specifies the injection token:
896
+
897
+ ```typescript
898
+ const DATABASE = token<DatabaseClient>('DATABASE');
899
+
900
+ @Injectable()
901
+ class UserService {
902
+ constructor(
903
+ @Inject(DATABASE) private db: DatabaseClient,
904
+ private config: ConfigService // Auto-injected by class token
905
+ ) {}
906
+ }
907
+ ```
908
+
909
+ Use `@Inject()` when:
910
+ - Injecting by string or symbol token
911
+ - Injecting an interface (TypeScript interfaces are erased at runtime)
912
+ - Automatic type resolution doesn't work
913
+
914
+ #### @Optional()
915
+
916
+ Marks a dependency as optional:
917
+
918
+ ```typescript
919
+ @Injectable()
920
+ class NotificationService {
921
+ constructor(
922
+ @Optional() private emailService?: EmailService,
923
+ @Optional() @Inject(SMS_SERVICE) private smsService?: SmsService
924
+ ) {}
925
+
926
+ notify(message: string) {
927
+ // Gracefully handle missing services
928
+ this.emailService?.send(message);
929
+ this.smsService?.send(message);
930
+ }
931
+ }
932
+ ```
933
+
934
+ If an optional dependency cannot be resolved, `undefined` is injected instead of throwing an error.
935
+
936
+ ### Container API
937
+
938
+ #### Registering Services
939
+
940
+ Register a single provider:
941
+
942
+ ```typescript
943
+ // Using succinct helper (recommended)
944
+ app.container.register(scoped(UserService));
945
+
946
+ // Or using full syntax
947
+ app.container.register({
948
+ provide: UserService,
949
+ useClass: UserService,
950
+ scope: Scope.REQUEST
951
+ });
952
+ ```
953
+
954
+ Register multiple providers:
955
+
956
+ ```typescript
957
+ // Using succinct helpers (recommended)
958
+ app.container.registerMany([
959
+ singleton(UserService),
960
+ singleton(PostService),
961
+ value(CONFIG, appConfig)
962
+ ]);
963
+
964
+ // Or using full syntax
965
+ app.container.registerMany([
966
+ { provide: UserService, useClass: UserService },
967
+ { provide: PostService, useClass: PostService },
968
+ { provide: CONFIG, useValue: appConfig }
969
+ ]);
970
+ ```
971
+
972
+ #### Resolving Services
973
+
974
+ Synchronous resolution:
975
+
976
+ ```typescript
977
+ const userService = app.container.resolve(UserService);
978
+ ```
979
+
980
+ Asynchronous resolution (for async factories):
981
+
982
+ ```typescript
983
+ // Using succinct helper (recommended)
984
+ app.container.register(
985
+ factory(DATABASE, async (config: ConfigService) => {
986
+ const client = createClient(config.dbUrl);
987
+ await client.connect();
988
+ return client;
989
+ }, [ConfigService])
990
+ );
991
+
992
+ const db = await app.container.resolveAsync(DATABASE);
993
+ ```
994
+
995
+ Optional resolution (returns `undefined` if not found):
996
+
997
+ ```typescript
998
+ const service = app.container.resolveOptional(OptionalService);
999
+ if (service) {
1000
+ service.doSomething();
1001
+ }
1002
+ ```
1003
+
1004
+ #### Request Context
1005
+
1006
+ Resolve request-scoped services with context:
1007
+
1008
+ ```typescript
1009
+ app.server.get('/users', async (request, reply) => {
1010
+ const ctx = Container.createContext(request);
1011
+ const userContext = app.container.resolve(UserContext, ctx);
1012
+
1013
+ return { userId: userContext.userId };
1014
+ });
1015
+ ```
1016
+
1017
+ ### Integration with VeloxApp
1018
+
1019
+ The container is automatically attached to the Fastify server for request-scoped services:
1020
+
1021
+ ```typescript
1022
+ const app = await veloxApp();
1023
+
1024
+ // Container is already attached to app.server
1025
+ // Request-scoped services work automatically
1026
+
1027
+ @Injectable({ scope: Scope.REQUEST })
1028
+ class RequestLogger {
1029
+ constructor(private request: FastifyRequest) {}
1030
+ }
1031
+
1032
+ // Register using succinct helper
1033
+ app.container.register(scoped(RequestLogger));
1034
+
1035
+ app.server.get('/log', async (request, reply) => {
1036
+ const ctx = Container.createContext(request);
1037
+ const logger = app.container.resolve(RequestLogger, ctx);
1038
+ logger.log('Request received');
1039
+ return { ok: true };
1040
+ });
1041
+ ```
1042
+
1043
+ ### Practical Examples
1044
+
1045
+ #### Complete Service Layer
1046
+
1047
+ ```typescript
1048
+ import 'reflect-metadata';
1049
+ import {
1050
+ veloxApp,
1051
+ Injectable,
1052
+ Inject,
1053
+ Scope,
1054
+ token,
1055
+ singleton,
1056
+ scoped,
1057
+ factory
1058
+ } from '@veloxts/core';
1059
+ import { PrismaClient } from '@prisma/client';
1060
+
1061
+ // Define tokens
1062
+ const DATABASE = token<PrismaClient>('DATABASE');
1063
+ const CONFIG = token<AppConfig>('CONFIG');
1064
+
1065
+ // Configuration service
1066
+ @Injectable()
1067
+ class ConfigService {
1068
+ get databaseUrl(): string {
1069
+ return process.env.DATABASE_URL!;
1070
+ }
1071
+ }
1072
+
1073
+ // Database service
1074
+ @Injectable()
1075
+ class DatabaseService {
1076
+ constructor(@Inject(DATABASE) private db: PrismaClient) {}
1077
+
1078
+ async findUser(id: string) {
1079
+ return this.db.user.findUnique({ where: { id } });
1080
+ }
1081
+ }
1082
+
1083
+ // Business logic service
1084
+ @Injectable({ scope: Scope.REQUEST })
1085
+ class UserService {
1086
+ constructor(
1087
+ private database: DatabaseService,
1088
+ private config: ConfigService
1089
+ ) {}
1090
+
1091
+ async getUser(id: string) {
1092
+ return this.database.findUser(id);
1093
+ }
1094
+ }
1095
+
1096
+ // Create app
1097
+ const app = await veloxApp();
1098
+
1099
+ // Register services using succinct helpers
1100
+ app.container.register(
1101
+ factory(DATABASE, (config: ConfigService) => {
1102
+ return new PrismaClient({
1103
+ datasources: { db: { url: config.databaseUrl } }
1104
+ });
1105
+ }, [ConfigService])
1106
+ );
1107
+
1108
+ app.container.register(singleton(ConfigService));
1109
+ app.container.register(singleton(DatabaseService));
1110
+ app.container.register(scoped(UserService));
1111
+
1112
+ // Use in routes
1113
+ app.server.get('/users/:id', async (request, reply) => {
1114
+ const ctx = Container.createContext(request);
1115
+ const userService = app.container.resolve(UserService, ctx);
1116
+
1117
+ const user = await userService.getUser(request.params.id);
1118
+ return user;
1119
+ });
1120
+
1121
+ await app.start();
1122
+ ```
1123
+
1124
+ #### Testing with Child Containers
1125
+
1126
+ ```typescript
1127
+ import { createContainer, asClass } from '@veloxts/core';
1128
+
1129
+ // Create a child container for testing
1130
+ const testContainer = app.container.createChild();
1131
+
1132
+ // Override services with mocks
1133
+ class MockDatabaseService {
1134
+ async findUser(id: string) {
1135
+ return { id, name: 'Test User' };
1136
+ }
1137
+ }
1138
+
1139
+ testContainer.register({
1140
+ provide: DatabaseService,
1141
+ useClass: MockDatabaseService
1142
+ });
1143
+
1144
+ // Test with mocked dependencies
1145
+ const userService = testContainer.resolve(UserService);
1146
+ const user = await userService.getUser('123');
1147
+ // user comes from MockDatabaseService
1148
+ ```
1149
+
1150
+ #### Auto-Registration
1151
+
1152
+ Enable auto-registration to automatically register `@Injectable` classes:
1153
+
1154
+ ```typescript
1155
+ const app = await createVeloxApp();
1156
+ const autoContainer = createContainer({ autoRegister: true });
1157
+
1158
+ @Injectable()
1159
+ class AutoService {}
1160
+
1161
+ // No need to manually register
1162
+ const service = autoContainer.resolve(AutoService);
1163
+ // Automatically registers and resolves
1164
+ ```
1165
+
1166
+ ### Advanced Features
1167
+
1168
+ #### Circular Dependency Detection
1169
+
1170
+ The container detects circular dependencies:
1171
+
1172
+ ```typescript
1173
+ @Injectable()
1174
+ class ServiceA {
1175
+ constructor(private b: ServiceB) {}
1176
+ }
1177
+
1178
+ @Injectable()
1179
+ class ServiceB {
1180
+ constructor(private a: ServiceA) {}
1181
+ }
1182
+
1183
+ // Throws: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
1184
+ const service = app.container.resolve(ServiceA);
1185
+ ```
1186
+
1187
+ #### Container Hierarchy
1188
+
1189
+ Create parent-child container hierarchies:
1190
+
1191
+ ```typescript
1192
+ const parentContainer = createContainer();
1193
+ parentContainer.register({
1194
+ provide: ConfigService,
1195
+ useClass: ConfigService
1196
+ });
1197
+
1198
+ const childContainer = parentContainer.createChild();
1199
+ // Child inherits ConfigService from parent
1200
+ // Can override with its own providers
1201
+
1202
+ childContainer.register({
1203
+ provide: UserService,
1204
+ useClass: UserService
1205
+ });
1206
+ ```
1207
+
1208
+ #### Debug Information
1209
+
1210
+ Get container statistics:
1211
+
1212
+ ```typescript
1213
+ const info = app.container.getDebugInfo();
1214
+ console.log(info);
1215
+ // {
1216
+ // providerCount: 5,
1217
+ // providers: [
1218
+ // 'class(UserService, request)',
1219
+ // 'factory(DATABASE, singleton)',
1220
+ // 'value(CONFIG)',
1221
+ // 'existing(LOGGER => ConsoleLogger)'
1222
+ // ],
1223
+ // hasParent: false,
1224
+ // autoRegister: false
1225
+ // }
1226
+ ```
1227
+
1228
+ ### Best Practices
1229
+
1230
+ 1. **Use tokens for interfaces**: Create string or symbol tokens for interface types since TypeScript interfaces don't exist at runtime.
1231
+
1232
+ 2. **Prefer singleton scope**: Use `Scope.SINGLETON` by default for stateless services to improve performance.
1233
+
1234
+ 3. **Use request scope for user context**: Store user authentication, request-specific state, and transactions in request-scoped services.
1235
+
1236
+ 4. **Avoid circular dependencies**: Refactor shared logic into a third service to break circular dependencies.
1237
+
1238
+ 5. **Use `@Inject()` for clarity**: Explicitly specify tokens with `@Inject()` to make dependencies clear and avoid runtime type resolution issues.
1239
+
1240
+ 6. **Test with child containers**: Create child containers in tests to override services with mocks without affecting the main container.
1241
+
561
1242
  ## Related Packages
562
1243
 
563
1244
  - [@veloxts/router](/packages/router) - Procedure-based routing