ts-ioc-container 46.7.0 → 46.8.0

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
@@ -32,6 +32,7 @@
32
32
  - [Instances](#instances)
33
33
  - [Dispose](#dispose)
34
34
  - [Lazy](#lazy) `lazy`
35
+ - [Lazy with registerPipe](#lazy-with-registerpipe) `lazy()`
35
36
  - [Injector](#injector)
36
37
  - [Metadata](#metadata) `@inject`
37
38
  - [Simple](#simple)
@@ -533,6 +534,107 @@ describe('Instances', function () {
533
534
 
534
535
  ```
535
536
 
537
+ ### Check Registration
538
+ Sometimes you want to check if a registration with a specific key exists in the container. This is useful for conditional registration logic, validation, and debugging.
539
+
540
+ - `hasRegistration(key)` checks if a registration exists in the current container or parent containers
541
+ - Checks both the current container's registrations and parent container registrations
542
+ - Works with string keys, symbol keys, and token keys
543
+ - Returns false after container disposal
544
+
545
+ ```typescript
546
+ import { Container, Registration as R, bindTo, register, SingleToken } from 'ts-ioc-container';
547
+
548
+ /**
549
+ * Container Registration Checking - hasRegistration
550
+ *
551
+ * The `hasRegistration` method allows you to check if a registration with a specific key
552
+ * exists in the current container. This is useful for conditional registration logic,
553
+ * validation, and debugging.
554
+ *
555
+ * Key points:
556
+ * - Checks only the current container's registrations (not parent containers)
557
+ * - Works with string keys, symbol keys, and token keys
558
+ * - Returns false after container disposal
559
+ * - Useful for conditional registration patterns
560
+ */
561
+ describe('hasRegistration', function () {
562
+ const createAppContainer = () => new Container({ tags: ['application'] });
563
+
564
+ it('should return true when registration exists with string key', function () {
565
+ const container = createAppContainer();
566
+ container.addRegistration(R.fromValue('production').bindToKey('Environment'));
567
+
568
+ expect(container.hasRegistration('Environment')).toBe(true);
569
+ });
570
+
571
+ it('should return false when registration does not exist', function () {
572
+ const container = createAppContainer();
573
+
574
+ expect(container.hasRegistration('NonExistentService')).toBe(false);
575
+ });
576
+
577
+ it('should work with symbol keys', function () {
578
+ const container = createAppContainer();
579
+ const serviceKey = Symbol('IService');
580
+ container.addRegistration(R.fromValue({ name: 'Service' }).bindToKey(serviceKey));
581
+
582
+ expect(container.hasRegistration(serviceKey)).toBe(true);
583
+ });
584
+
585
+ it('should work with token keys', function () {
586
+ const container = createAppContainer();
587
+ const loggerToken = new SingleToken<{ log: (msg: string) => void }>('ILogger');
588
+ container.addRegistration(R.fromValue({ log: () => {} }).bindTo(loggerToken));
589
+
590
+ expect(container.hasRegistration(loggerToken.token)).toBe(true);
591
+ });
592
+
593
+ it('should check current container and parent registrations', function () {
594
+ // Parent container has a registration
595
+ const parent = createAppContainer();
596
+ parent.addRegistration(R.fromValue('parent-config').bindToKey('Config'));
597
+
598
+ // Child scope does not have the registration
599
+ const child = parent.createScope();
600
+ child.addRegistration(R.fromValue('child-service').bindToKey('Service'));
601
+
602
+ // Child should see parent's registration (checks parent as well)
603
+ expect(child.hasRegistration('Config')).toBe(true);
604
+ // Child should see its own registration
605
+ expect(child.hasRegistration('Service')).toBe(true);
606
+ // Parent should see its own registration
607
+ expect(parent.hasRegistration('Config')).toBe(true);
608
+ });
609
+
610
+ it('should work with class-based registrations', function () {
611
+ @register(bindTo('ILogger'))
612
+ class Logger {}
613
+
614
+ const container = createAppContainer();
615
+ container.addRegistration(R.fromClass(Logger));
616
+
617
+ expect(container.hasRegistration('ILogger')).toBe(true);
618
+ });
619
+
620
+ it('should be useful for conditional registration patterns', function () {
621
+ const container = createAppContainer();
622
+
623
+ // Register a base service
624
+ container.addRegistration(R.fromValue('base-service').bindToKey('BaseService'));
625
+
626
+ // Conditionally register an extension only if base exists
627
+ if (container.hasRegistration('BaseService')) {
628
+ container.addRegistration(R.fromValue('extension-service').bindToKey('ExtensionService'));
629
+ }
630
+
631
+ expect(container.hasRegistration('BaseService')).toBe(true);
632
+ expect(container.hasRegistration('ExtensionService')).toBe(true);
633
+ });
634
+ });
635
+
636
+ ```
637
+
536
638
  ### Dispose
537
639
  Sometimes you want to dispose container and all its scopes. For example, when you want to prevent memory leaks. Or you want to ensure that nobody can use container after it was disposed.
538
640
 
@@ -766,6 +868,453 @@ describe('lazy provider', () => {
766
868
 
767
869
  ```
768
870
 
871
+ ### Lazy with registerPipe
872
+ The `lazy()` registerPipe can be used in two ways: with the `@register` decorator or directly on the `Provider` pipe. This allows you to defer expensive service initialization until first access.
873
+
874
+ **Use cases:**
875
+ - Defer expensive initialization (database connections, SMTP, external APIs)
876
+ - Conditional features that may not be used
877
+ - Breaking circular dependencies
878
+ - Memory optimization for optional services
879
+
880
+ **Two approaches:**
881
+
882
+ 1. **With @register decorator**: Use `lazy()` as a registerPipe in the decorator
883
+ 2. **With Provider pipe**: Use `Provider.fromClass().pipe(lazy())` directly
884
+
885
+ ```typescript
886
+ import 'reflect-metadata';
887
+ import { bindTo, Container, inject, lazy, Provider, register, Registration as R, singleton } from 'ts-ioc-container';
888
+
889
+ /**
890
+ * Lazy Loading with registerPipe
891
+ *
892
+ * The lazy() registerPipe can be used in two ways:
893
+ * 1. With @register decorator - lazy()
894
+ * 2. Directly on provider - provider.lazy()
895
+ *
896
+ * Both approaches defer instantiation until first access,
897
+ * improving startup time and memory usage.
898
+ */
899
+ describe('lazy registerPipe', () => {
900
+ // Track initialization for testing
901
+ const initLog: string[] = [];
902
+
903
+ beforeEach(() => {
904
+ initLog.length = 0;
905
+ });
906
+
907
+ /**
908
+ * Example 1: Using lazy() with @register decorator
909
+ *
910
+ * The lazy() registerPipe defers service instantiation until first use.
911
+ * Perfect for expensive services that may not always be needed.
912
+ */
913
+ describe('with @register decorator', () => {
914
+ // Database connection pool - expensive to initialize
915
+ @register(bindTo('DatabasePool'), singleton())
916
+ class DatabasePool {
917
+ constructor() {
918
+ initLog.push('DatabasePool initialized');
919
+ }
920
+
921
+ query(sql: string): string[] {
922
+ return [`Results for: ${sql}`];
923
+ }
924
+ }
925
+
926
+ // Analytics service - expensive, but only used occasionally
927
+ @register(bindTo('AnalyticsService'), lazy(), singleton())
928
+ class AnalyticsService {
929
+ constructor(@inject('DatabasePool') private db: DatabasePool) {
930
+ initLog.push('AnalyticsService initialized');
931
+ }
932
+
933
+ trackEvent(event: string): void {
934
+ this.db.query(`INSERT INTO events VALUES ('${event}')`);
935
+ }
936
+
937
+ generateReport(): string {
938
+ return 'Analytics Report';
939
+ }
940
+ }
941
+
942
+ // Application service - always used
943
+ class AppService {
944
+ constructor(@inject('AnalyticsService') public analytics: AnalyticsService) {
945
+ initLog.push('AppService initialized');
946
+ }
947
+
948
+ handleRequest(path: string): void {
949
+ // Most requests don't need analytics
950
+ if (path.includes('/admin')) {
951
+ // Only admin requests use analytics
952
+ this.analytics.trackEvent(`Admin access: ${path}`);
953
+ }
954
+ }
955
+ }
956
+
957
+ it('should defer AnalyticsService initialization until first access', () => {
958
+ const container = new Container()
959
+ .addRegistration(R.fromClass(DatabasePool))
960
+ .addRegistration(R.fromClass(AnalyticsService))
961
+ .addRegistration(R.fromClass(AppService));
962
+
963
+ // Resolve AppService
964
+ const app = container.resolve<AppService>(AppService);
965
+
966
+ // AppService is initialized, but AnalyticsService is NOT (it's lazy)
967
+ // DatabasePool is also not initialized because AnalyticsService hasn't been accessed
968
+ expect(initLog).toEqual(['AppService initialized']);
969
+
970
+ // Handle non-admin request - analytics not used
971
+ app.handleRequest('/api/users');
972
+ expect(initLog).toEqual(['AppService initialized']);
973
+ });
974
+
975
+ it('should initialize lazy service when first accessed', () => {
976
+ const container = new Container()
977
+ .addRegistration(R.fromClass(DatabasePool))
978
+ .addRegistration(R.fromClass(AnalyticsService))
979
+ .addRegistration(R.fromClass(AppService));
980
+
981
+ const app = container.resolve<AppService>(AppService);
982
+
983
+ // Handle admin request - now analytics IS used
984
+ app.handleRequest('/admin/dashboard');
985
+
986
+ // AnalyticsService was initialized on first access (DatabasePool too, as a dependency)
987
+ expect(initLog).toEqual(['AppService initialized', 'DatabasePool initialized', 'AnalyticsService initialized']);
988
+ });
989
+
990
+ it('should create only one instance even with multiple accesses', () => {
991
+ const container = new Container()
992
+ .addRegistration(R.fromClass(DatabasePool))
993
+ .addRegistration(R.fromClass(AnalyticsService))
994
+ .addRegistration(R.fromClass(AppService));
995
+
996
+ const app = container.resolve<AppService>(AppService);
997
+
998
+ // Access analytics multiple times
999
+ app.handleRequest('/admin/dashboard');
1000
+ app.analytics.generateReport();
1001
+ app.analytics.trackEvent('test');
1002
+
1003
+ // AnalyticsService initialized only once (singleton + lazy)
1004
+ const analyticsCount = initLog.filter((msg) => msg === 'AnalyticsService initialized').length;
1005
+ expect(analyticsCount).toBe(1);
1006
+ });
1007
+ });
1008
+
1009
+ /**
1010
+ * Example 2: Using lazy() directly on provider
1011
+ *
1012
+ * For manual registration, call .lazy() on the provider pipe.
1013
+ * This gives fine-grained control over lazy loading per dependency.
1014
+ */
1015
+ describe('with pure provider', () => {
1016
+ // Email service - expensive SMTP connection
1017
+ class EmailService {
1018
+ constructor() {
1019
+ initLog.push('EmailService initialized - SMTP connected');
1020
+ }
1021
+
1022
+ send(to: string, subject: string): string {
1023
+ return `Email sent to ${to}: ${subject}`;
1024
+ }
1025
+ }
1026
+
1027
+ // SMS service - expensive gateway connection
1028
+ class SmsService {
1029
+ constructor() {
1030
+ initLog.push('SmsService initialized - Gateway connected');
1031
+ }
1032
+
1033
+ send(to: string, message: string): string {
1034
+ return `SMS sent to ${to}: ${message}`;
1035
+ }
1036
+ }
1037
+
1038
+ // Notification service - uses email and SMS, but maybe not both
1039
+ class NotificationService {
1040
+ constructor(
1041
+ @inject('EmailService') public email: EmailService,
1042
+ @inject('SmsService') public sms: SmsService,
1043
+ ) {
1044
+ initLog.push('NotificationService initialized');
1045
+ }
1046
+
1047
+ notifyByEmail(user: string, message: string): string {
1048
+ return this.email.send(user, message);
1049
+ }
1050
+
1051
+ notifyBySms(phone: string, message: string): string {
1052
+ return this.sms.send(phone, message);
1053
+ }
1054
+ }
1055
+
1056
+ it('should allow selective lazy loading - email lazy, SMS eager', () => {
1057
+ const container = new Container()
1058
+ // EmailService is lazy - won't connect to SMTP until used
1059
+ .addRegistration(
1060
+ R.fromClass(EmailService)
1061
+ .bindToKey('EmailService')
1062
+ .pipe(singleton(), (p) => p.lazy()),
1063
+ )
1064
+ // SmsService is eager - connects to gateway immediately
1065
+ .addRegistration(R.fromClass(SmsService).bindToKey('SmsService').pipe(singleton()))
1066
+ .addRegistration(R.fromClass(NotificationService));
1067
+
1068
+ // Resolve NotificationService
1069
+ const notifications = container.resolve<NotificationService>(NotificationService);
1070
+
1071
+ // SmsService initialized immediately (eager)
1072
+ // EmailService NOT initialized yet (lazy)
1073
+ expect(initLog).toEqual(['SmsService initialized - Gateway connected', 'NotificationService initialized']);
1074
+
1075
+ // Send SMS - already initialized
1076
+ notifications.notifyBySms('555-1234', 'Test');
1077
+ expect(initLog).toEqual(['SmsService initialized - Gateway connected', 'NotificationService initialized']);
1078
+ });
1079
+
1080
+ it('should initialize lazy email service when first accessed', () => {
1081
+ const container = new Container()
1082
+ .addRegistration(
1083
+ R.fromClass(EmailService)
1084
+ .bindToKey('EmailService')
1085
+ .pipe(singleton(), (p) => p.lazy()),
1086
+ )
1087
+ .addRegistration(R.fromClass(SmsService).bindToKey('SmsService').pipe(singleton()))
1088
+ .addRegistration(R.fromClass(NotificationService));
1089
+
1090
+ const notifications = container.resolve<NotificationService>(NotificationService);
1091
+
1092
+ // Send email - NOW EmailService is initialized
1093
+ const result = notifications.notifyByEmail('user@example.com', 'Welcome!');
1094
+
1095
+ expect(result).toBe('Email sent to user@example.com: Welcome!');
1096
+ expect(initLog).toContain('EmailService initialized - SMTP connected');
1097
+ });
1098
+
1099
+ it('should work with multiple lazy providers', () => {
1100
+ const container = new Container()
1101
+ // Both services are lazy
1102
+ .addRegistration(
1103
+ R.fromClass(EmailService)
1104
+ .bindToKey('EmailService')
1105
+ .pipe(singleton(), (p) => p.lazy()),
1106
+ )
1107
+ .addRegistration(
1108
+ R.fromClass(SmsService)
1109
+ .bindToKey('SmsService')
1110
+ .pipe(singleton(), (p) => p.lazy()),
1111
+ )
1112
+ .addRegistration(R.fromClass(NotificationService));
1113
+
1114
+ const notifications = container.resolve<NotificationService>(NotificationService);
1115
+
1116
+ // Neither service initialized yet
1117
+ expect(initLog).toEqual(['NotificationService initialized']);
1118
+
1119
+ // Use SMS - only SMS initialized
1120
+ notifications.notifyBySms('555-1234', 'Test');
1121
+ expect(initLog).toEqual(['NotificationService initialized', 'SmsService initialized - Gateway connected']);
1122
+
1123
+ // Use Email - now Email initialized
1124
+ notifications.notifyByEmail('user@example.com', 'Test');
1125
+ expect(initLog).toEqual([
1126
+ 'NotificationService initialized',
1127
+ 'SmsService initialized - Gateway connected',
1128
+ 'EmailService initialized - SMTP connected',
1129
+ ]);
1130
+ });
1131
+ });
1132
+
1133
+ /**
1134
+ * Example 3: Pure Provider usage (without Registration)
1135
+ *
1136
+ * Use Provider.fromClass() directly with lazy() for maximum flexibility.
1137
+ */
1138
+ describe('with pure Provider', () => {
1139
+ class CacheService {
1140
+ constructor() {
1141
+ initLog.push('CacheService initialized - Redis connected');
1142
+ }
1143
+
1144
+ get(key: string): string | null {
1145
+ return `cached:${key}`;
1146
+ }
1147
+ }
1148
+
1149
+ class ApiService {
1150
+ constructor(@inject('CacheService') private cache: CacheService) {
1151
+ initLog.push('ApiService initialized');
1152
+ }
1153
+
1154
+ fetchData(id: string): string {
1155
+ const cached = this.cache.get(id);
1156
+ return cached || `fresh:${id}`;
1157
+ }
1158
+ }
1159
+
1160
+ it('should use Provider.fromClass with lazy() helper', () => {
1161
+ // Create pure provider with lazy loading
1162
+ const cacheProvider = Provider.fromClass(CacheService).pipe(lazy(), singleton());
1163
+
1164
+ const container = new Container();
1165
+ container.register('CacheService', cacheProvider);
1166
+ container.addRegistration(R.fromClass(ApiService));
1167
+
1168
+ const api = container.resolve<ApiService>(ApiService);
1169
+
1170
+ // CacheService not initialized yet (lazy)
1171
+ expect(initLog).toEqual(['ApiService initialized']);
1172
+
1173
+ // Access cache - NOW it's initialized
1174
+ api.fetchData('user:1');
1175
+ expect(initLog).toContain('CacheService initialized - Redis connected');
1176
+ });
1177
+
1178
+ it('should allow importing lazy as named export', () => {
1179
+ // Demonstrate that lazy() is imported from the library
1180
+ const cacheProvider = Provider.fromClass(CacheService).pipe(lazy());
1181
+
1182
+ const container = new Container();
1183
+ container.register('CacheService', cacheProvider);
1184
+
1185
+ const cache = container.resolve<CacheService>('CacheService');
1186
+
1187
+ // Not initialized until accessed
1188
+ expect(initLog).toEqual([]);
1189
+ cache.get('test');
1190
+ expect(initLog).toEqual(['CacheService initialized - Redis connected']);
1191
+ });
1192
+ });
1193
+
1194
+ /**
1195
+ * Example 4: Combining lazy with other pipes
1196
+ *
1197
+ * lazy() works seamlessly with other provider transformations.
1198
+ */
1199
+ describe('combining with other pipes', () => {
1200
+ class ConfigService {
1201
+ constructor(
1202
+ public apiUrl: string,
1203
+ public timeout: number,
1204
+ ) {
1205
+ initLog.push(`ConfigService initialized with ${apiUrl}`);
1206
+ }
1207
+ }
1208
+
1209
+ it('should combine lazy with args and singleton', () => {
1210
+ const container = new Container().addRegistration(
1211
+ R.fromClass(ConfigService)
1212
+ .bindToKey('Config')
1213
+ .pipe(
1214
+ (p) => p.setArgs(() => ['https://api.example.com', 5000]),
1215
+ (p) => p.lazy(),
1216
+ )
1217
+ .pipe(singleton()),
1218
+ );
1219
+
1220
+ // Config not initialized yet
1221
+ expect(initLog).toEqual([]);
1222
+
1223
+ // Resolve - still not initialized (lazy)
1224
+ const config1 = container.resolve<ConfigService>('Config');
1225
+ expect(initLog).toEqual([]);
1226
+
1227
+ // Access property - NOW initialized
1228
+ const url = config1.apiUrl;
1229
+ expect(url).toBe('https://api.example.com');
1230
+ expect(initLog).toEqual(['ConfigService initialized with https://api.example.com']);
1231
+
1232
+ // Resolve again - same instance (singleton)
1233
+ const config2 = container.resolve<ConfigService>('Config');
1234
+ expect(config2).toBe(config1);
1235
+ expect(initLog.length).toBe(1); // Still only one initialization
1236
+ });
1237
+ });
1238
+
1239
+ /**
1240
+ * Example 5: Real-world use case - Resource Management
1241
+ *
1242
+ * Lazy loading is ideal for:
1243
+ * - Database connections
1244
+ * - File handles
1245
+ * - External API clients
1246
+ * - Report generators
1247
+ */
1248
+ describe('real-world example - feature flags', () => {
1249
+ class FeatureFlagService {
1250
+ constructor() {
1251
+ initLog.push('FeatureFlagService initialized');
1252
+ }
1253
+
1254
+ isEnabled(feature: string): boolean {
1255
+ return feature === 'premium';
1256
+ }
1257
+ }
1258
+
1259
+ @register(bindTo('PremiumFeature'), lazy(), singleton())
1260
+ class PremiumFeature {
1261
+ constructor() {
1262
+ initLog.push('PremiumFeature initialized - expensive operation');
1263
+ }
1264
+
1265
+ execute(): string {
1266
+ return 'Premium feature executed';
1267
+ }
1268
+ }
1269
+
1270
+ class Application {
1271
+ constructor(
1272
+ @inject('FeatureFlagService') private flags: FeatureFlagService,
1273
+ @inject('PremiumFeature') private premium: PremiumFeature,
1274
+ ) {
1275
+ initLog.push('Application initialized');
1276
+ }
1277
+
1278
+ handleRequest(feature: string): string {
1279
+ if (this.flags.isEnabled(feature)) {
1280
+ return this.premium.execute();
1281
+ }
1282
+ return 'Standard feature';
1283
+ }
1284
+ }
1285
+
1286
+ it('should not initialize premium features for standard users', () => {
1287
+ const container = new Container()
1288
+ .addRegistration(R.fromClass(FeatureFlagService).bindToKey('FeatureFlagService').pipe(singleton()))
1289
+ .addRegistration(R.fromClass(PremiumFeature))
1290
+ .addRegistration(R.fromClass(Application));
1291
+
1292
+ const app = container.resolve<Application>(Application);
1293
+
1294
+ // Standard request - premium feature not initialized
1295
+ const result = app.handleRequest('standard');
1296
+ expect(result).toBe('Standard feature');
1297
+ expect(initLog).not.toContain('PremiumFeature initialized - expensive operation');
1298
+ });
1299
+
1300
+ it('should initialize premium features only for premium users', () => {
1301
+ const container = new Container()
1302
+ .addRegistration(R.fromClass(FeatureFlagService).bindToKey('FeatureFlagService').pipe(singleton()))
1303
+ .addRegistration(R.fromClass(PremiumFeature))
1304
+ .addRegistration(R.fromClass(Application));
1305
+
1306
+ const app = container.resolve<Application>(Application);
1307
+
1308
+ // Premium request - NOW premium feature is initialized
1309
+ const result = app.handleRequest('premium');
1310
+ expect(result).toBe('Premium feature executed');
1311
+ expect(initLog).toContain('PremiumFeature initialized - expensive operation');
1312
+ });
1313
+ });
1314
+ });
1315
+
1316
+ ```
1317
+
769
1318
  ## Injector
770
1319
  `IInjector` is used to describe how dependencies should be injected to constructor.
771
1320
 
@@ -107,6 +107,9 @@ class Container {
107
107
  getRegistrations() {
108
108
  return [...this.parent.getRegistrations(), ...this.registrations];
109
109
  }
110
+ hasRegistration(key) {
111
+ return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
112
+ }
110
113
  addOnConstructHook(...hooks) {
111
114
  this.onConstructHookList.push(...hooks);
112
115
  return this;
@@ -35,6 +35,9 @@ class EmptyContainer {
35
35
  getRegistrations() {
36
36
  return [];
37
37
  }
38
+ hasRegistration(key) {
39
+ return false;
40
+ }
38
41
  removeScope() { }
39
42
  useModule(module) {
40
43
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
@@ -79,5 +79,11 @@ class Registration {
79
79
  const provider = this.createProvider();
80
80
  container.register(this.key, provider.pipe(...this.mappers), { aliases: [...this.aliases] });
81
81
  }
82
+ getKeyOrFail() {
83
+ if (!this.key) {
84
+ throw new DependencyMissingKeyError_1.DependencyMissingKeyError('No key provided for registration');
85
+ }
86
+ return this.key;
87
+ }
82
88
  }
83
89
  exports.Registration = Registration;
@@ -104,6 +104,9 @@ export class Container {
104
104
  getRegistrations() {
105
105
  return [...this.parent.getRegistrations(), ...this.registrations];
106
106
  }
107
+ hasRegistration(key) {
108
+ return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
109
+ }
107
110
  addOnConstructHook(...hooks) {
108
111
  this.onConstructHookList.push(...hooks);
109
112
  return this;
@@ -32,6 +32,9 @@ export class EmptyContainer {
32
32
  getRegistrations() {
33
33
  return [];
34
34
  }
35
+ hasRegistration(key) {
36
+ return false;
37
+ }
35
38
  removeScope() { }
36
39
  useModule(module) {
37
40
  throw new MethodNotImplementedError();
@@ -76,4 +76,10 @@ export class Registration {
76
76
  const provider = this.createProvider();
77
77
  container.register(this.key, provider.pipe(...this.mappers), { aliases: [...this.aliases] });
78
78
  }
79
+ getKeyOrFail() {
80
+ if (!this.key) {
81
+ throw new DependencyMissingKeyError('No key provided for registration');
82
+ }
83
+ return this.key;
84
+ }
79
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "46.7.0",
3
+ "version": "46.8.0",
4
4
  "description": "Typescript IoC container",
5
5
  "workspaces": [
6
6
  "docs"
@@ -63,6 +63,8 @@
63
63
  "docs:preview": "pnpm --filter ts-ioc-container-docs run preview"
64
64
  },
65
65
  "devDependencies": {
66
+ "@commitlint/cli": "^20.2.0",
67
+ "@commitlint/config-conventional": "^20.2.0",
66
68
  "@semantic-release/changelog": "^6.0.3",
67
69
  "@semantic-release/git": "^10.0.1",
68
70
  "@semantic-release/github": "^12.0.2",
@@ -30,6 +30,7 @@ export declare class Container implements IContainer {
30
30
  dispose(): void;
31
31
  addRegistration(registration: IRegistration): this;
32
32
  getRegistrations(): IRegistration[];
33
+ hasRegistration(key: DependencyKey): boolean;
33
34
  addOnConstructHook(...hooks: OnConstructHook[]): this;
34
35
  addOnDisposeHook(...hooks: OnDisposeHook[]): this;
35
36
  addInstance(instance: Instance): void;
@@ -16,6 +16,7 @@ export declare class EmptyContainer implements IContainer {
16
16
  hasTag(tag: Tag): boolean;
17
17
  addTags(...tags: Tag[]): void;
18
18
  getRegistrations(): never[];
19
+ hasRegistration(key: DependencyKey): boolean;
19
20
  removeScope(): void;
20
21
  useModule(module: IContainerModule): this;
21
22
  addRegistration(registration: IRegistration): this;
@@ -38,6 +38,7 @@ export interface IContainer extends Tagged {
38
38
  register(key: DependencyKey, value: IProvider, options?: RegisterOptions): this;
39
39
  addRegistration(registration: IRegistration): this;
40
40
  getRegistrations(): IRegistration[];
41
+ hasRegistration(key: DependencyKey): boolean;
41
42
  resolve<T>(target: constructor<T> | DependencyKey, options?: ResolveOneOptions): T;
42
43
  resolveByAlias<T>(alias: DependencyKey, options?: ResolveManyOptions): T[];
43
44
  resolveOneByAlias<T>(alias: DependencyKey, options?: ResolveOneOptions): T;
@@ -6,6 +6,7 @@ import { MapFn } from '../utils/fp';
6
6
  import { type constructor } from '../utils/basic';
7
7
  export type ScopeMatchRule = (s: IContainer, prev?: boolean) => boolean;
8
8
  export interface IRegistration<T = any> extends IContainerModule {
9
+ getKeyOrFail(): DependencyKey;
9
10
  when(...predicates: ScopeMatchRule[]): this;
10
11
  bindToKey(key: DependencyKey): this;
11
12
  bindTo(key: DependencyKey | BindToken): this;
@@ -7,7 +7,7 @@ import { type MapFn } from '../utils/fp';
7
7
  import { type constructor } from '../utils/basic';
8
8
  export declare class Registration<T = any> implements IRegistration<T> {
9
9
  private createProvider;
10
- private key?;
10
+ key?: DependencyKey | undefined;
11
11
  private scopeRules;
12
12
  static fromClass<T>(Target: constructor<T>): IRegistration<any>;
13
13
  static fromValue<T>(value: T): IRegistration<any> | Registration<T>;
@@ -23,4 +23,5 @@ export declare class Registration<T = any> implements IRegistration<T> {
23
23
  bindTo(key: DependencyKey | BindToken): this;
24
24
  private matchScope;
25
25
  applyTo(container: IContainer): void;
26
+ getKeyOrFail(): DependencyKey;
26
27
  }