@lppedd/di-wise-neo 0.5.3 → 0.7.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/dist/es/index.mjs CHANGED
@@ -127,23 +127,23 @@ function ensureInjectionContext(fn) {
127
127
  return context;
128
128
  }
129
129
 
130
- function inject(token) {
130
+ function inject(token, name) {
131
131
  const context = ensureInjectionContext(inject);
132
- return context.container.resolve(token);
132
+ return context.container.resolve(token, name);
133
133
  }
134
- function injectBy(thisArg, token) {
134
+ function injectBy(thisArg, token, name) {
135
135
  const context = ensureInjectionContext(injectBy);
136
136
  const resolution = context.resolution;
137
137
  const currentFrame = resolution.stack.peek();
138
138
  if (!currentFrame) {
139
- return inject(token);
139
+ return inject(token, name);
140
140
  }
141
141
  const currentRef = {
142
142
  current: thisArg
143
143
  };
144
144
  const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
145
145
  try {
146
- return inject(token);
146
+ return inject(token, name);
147
147
  } finally{
148
148
  cleanup();
149
149
  }
@@ -154,41 +154,59 @@ function injectAll(token) {
154
154
  return context.container.resolveAll(token);
155
155
  }
156
156
 
157
- /**
158
- * Registers a mapping between a generated (e.g., decorated or proxied) constructor
159
- * and its original, underlying constructor.
160
- *
161
- * This allows libraries or consumers that manipulate constructors, such as through
162
- * class decorators, to inform the DI system about the real "identity" of a class.
163
- *
164
- * @param transformedClass The constructor function returned by a class decorator or factory.
165
- * @param originalClass The original constructor function.
166
- *
167
- * @remarks
168
- * This API affects the core class identity resolution mechanism of the DI system.
169
- * Incorrect usage may cause metadata to be misassociated, leading to subtle errors.
170
- * Use only when manipulating constructors (e.g., via decorators or proxies),
171
- * and ensure the mapping is correct.
172
- */ function setClassIdentityMapping(transformedClass, originalClass) {
173
- classIdentityMap.set(transformedClass, originalClass);
157
+ // @internal
158
+ class Metadata {
159
+ constructor(Class){
160
+ this.dependencies = {
161
+ constructor: [],
162
+ methods: new Map()
163
+ };
164
+ this.tokensRef = {
165
+ getRefTokens: ()=>new Set()
166
+ };
167
+ this.provider = {
168
+ useClass: Class
169
+ };
170
+ }
171
+ get name() {
172
+ return this.provider.name;
173
+ }
174
+ set name(name) {
175
+ this.provider.name = name;
176
+ }
177
+ getConstructorDependency(index) {
178
+ const i = this.dependencies.constructor.findIndex((d)=>d.index === index);
179
+ if (i > -1) {
180
+ return this.dependencies.constructor[i];
181
+ }
182
+ const dependency = {
183
+ index: index
184
+ };
185
+ this.dependencies.constructor.push(dependency);
186
+ return dependency;
187
+ }
188
+ getMethodDependency(method, index) {
189
+ let methodDeps = this.dependencies.methods.get(method);
190
+ if (!methodDeps) {
191
+ this.dependencies.methods.set(method, methodDeps = []);
192
+ }
193
+ const i = methodDeps.findIndex((d)=>d.index === index);
194
+ if (i > -1) {
195
+ return methodDeps[i];
196
+ }
197
+ const dependency = {
198
+ index: index
199
+ };
200
+ methodDeps.push(dependency);
201
+ return dependency;
202
+ }
174
203
  }
175
204
  // @internal
176
205
  function getMetadata(Class) {
177
206
  const originalClass = classIdentityMap.get(Class) ?? Class;
178
207
  let metadata = metadataMap.get(originalClass);
179
208
  if (!metadata) {
180
- metadataMap.set(originalClass, metadata = {
181
- tokensRef: {
182
- getRefTokens: ()=>new Set()
183
- },
184
- provider: {
185
- useClass: originalClass
186
- },
187
- dependencies: {
188
- constructor: [],
189
- methods: new Map()
190
- }
191
- });
209
+ metadataMap.set(originalClass, metadata = new Metadata(originalClass));
192
210
  }
193
211
  if (metadata.provider.useClass !== Class) {
194
212
  // This is part of the class identity mapping API (see setClassIdentityMapping).
@@ -202,32 +220,48 @@ function getMetadata(Class) {
202
220
  // We must update useClass to be the extender class B so that instances created by the
203
221
  // DI container match the consumer's registered class. Without this update, the DI
204
222
  // system would instantiate the original class A, causing behavior inconsistencies.
205
- //
206
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
207
223
  metadata.provider.useClass = Class;
208
224
  }
209
225
  return metadata;
210
226
  }
227
+ /**
228
+ * Registers a mapping between a generated (e.g., decorated or proxied) constructor
229
+ * and its original, underlying constructor.
230
+ *
231
+ * This allows libraries or consumers that manipulate constructors, such as through
232
+ * class decorators, to inform the DI system about the real "identity" of a class.
233
+ *
234
+ * @param transformedClass The constructor function returned by a class decorator or factory.
235
+ * @param originalClass The original constructor function.
236
+ *
237
+ * @remarks
238
+ * This API affects the core class identity resolution mechanism of the DI system.
239
+ * Incorrect usage may cause metadata to be misassociated, leading to subtle errors.
240
+ * Use only when manipulating constructors (e.g., via decorators or proxies),
241
+ * and ensure the mapping is correct.
242
+ */ function setClassIdentityMapping(transformedClass, originalClass) {
243
+ classIdentityMap.set(transformedClass, originalClass);
244
+ }
211
245
  const classIdentityMap = new WeakMap();
212
246
  const metadataMap = new WeakMap();
213
247
 
214
- function optional(token) {
248
+ function optional(token, name) {
215
249
  const context = ensureInjectionContext(optional);
216
- return context.container.resolve(token, true);
250
+ return context.container.resolve(token, true, name);
217
251
  }
218
- function optionalBy(thisArg, token) {
252
+ function optionalBy(thisArg, token, name) {
219
253
  const context = ensureInjectionContext(optionalBy);
220
254
  const resolution = context.resolution;
221
255
  const currentFrame = resolution.stack.peek();
222
256
  if (!currentFrame) {
223
- return optional(token);
257
+ return optional(token, name);
224
258
  }
225
259
  const currentRef = {
226
260
  current: thisArg
227
261
  };
228
262
  const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
229
263
  try {
230
- return optional(token);
264
+ return optional(token, name);
231
265
  } finally{
232
266
  cleanup();
233
267
  }
@@ -286,28 +320,29 @@ const Scope = {
286
320
  }
287
321
  // @internal
288
322
  function isConstructor(token) {
289
- return typeof token == "function";
323
+ return typeof token === "function";
290
324
  }
291
325
 
292
326
  // @internal
293
327
  function getTypeName(value) {
294
- if (typeof value == "string") {
295
- return `"${value}"`;
296
- }
297
- if (typeof value == "function") {
298
- return value.name && `typeof ${value.name}` || "Function";
299
- }
300
- if (typeof value == "object") {
301
- if (value === null) {
302
- return "null";
303
- }
304
- const proto = Object.getPrototypeOf(value);
305
- if (proto && proto !== Object.prototype) {
306
- const constructor = proto.constructor;
307
- if (typeof constructor == "function" && constructor.name) {
308
- return constructor.name;
328
+ switch(typeof value){
329
+ case "string":
330
+ return `"${value}"`;
331
+ case "function":
332
+ return value.name && `typeof ${value.name}` || "Function";
333
+ case "object":
334
+ {
335
+ if (value === null) {
336
+ return "null";
337
+ }
338
+ const proto = Object.getPrototypeOf(value);
339
+ if (proto && proto !== Object.prototype) {
340
+ const constructor = proto.constructor;
341
+ if (typeof constructor === "function" && constructor.name) {
342
+ return constructor.name;
343
+ }
344
+ }
309
345
  }
310
- }
311
346
  }
312
347
  return typeof value;
313
348
  }
@@ -318,28 +353,46 @@ class TokenRegistry {
318
353
  this.myMap = new Map();
319
354
  this.myParent = parent;
320
355
  }
321
- get(token) {
356
+ get(token, name) {
322
357
  // To clarify, at(-1) means we take the last added registration for this token
323
- return this.getAll(token)?.at(-1);
358
+ return this.getAll(token, name).at(-1);
324
359
  }
325
- getAll(token) {
326
- const internal = internals.get(token);
360
+ getAll(token, name) {
361
+ // Internal registrations cannot have a name
362
+ const internal = name !== undefined ? undefined : internals.get(token);
327
363
  return internal && [
328
364
  internal
329
- ] || this.getAllFromParent(token);
365
+ ] || this.getAllFromParent(token, name);
330
366
  }
331
367
  set(token, registration) {
332
368
  assert(!internals.has(token), `cannot register reserved token ${token.name}`);
333
369
  let registrations = this.myMap.get(token);
334
370
  if (!registrations) {
335
371
  this.myMap.set(token, registrations = []);
372
+ } else if (registration.name !== undefined) {
373
+ const existing = registrations.filter((r)=>r.name === registration.name);
374
+ assert(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
336
375
  }
337
376
  registrations.push(registration);
338
377
  }
339
- delete(token) {
378
+ delete(token, name) {
340
379
  const registrations = this.myMap.get(token);
341
- this.myMap.delete(token);
342
- return registrations;
380
+ if (registrations) {
381
+ if (name !== undefined) {
382
+ const removedRegistrations = [];
383
+ const newRegistrations = [];
384
+ for (const registration of registrations){
385
+ const array = registration.name === name ? removedRegistrations : newRegistrations;
386
+ array.push(registration);
387
+ }
388
+ if (removedRegistrations.length > 0) {
389
+ this.myMap.set(token, newRegistrations);
390
+ return removedRegistrations;
391
+ }
392
+ }
393
+ this.myMap.delete(token);
394
+ }
395
+ return registrations ?? [];
343
396
  }
344
397
  deleteAll() {
345
398
  const tokens = Array.from(this.myMap.keys());
@@ -367,9 +420,14 @@ class TokenRegistry {
367
420
  }
368
421
  return Array.from(values);
369
422
  }
370
- getAllFromParent(token) {
371
- const registrations = this.myMap.get(token);
372
- return registrations || this.myParent?.getAllFromParent(token);
423
+ getAllFromParent(token, name) {
424
+ const thisRegistrations = this.myMap.get(token);
425
+ let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
426
+ if (registrations && name !== undefined) {
427
+ registrations = registrations.filter((r)=>r.name === name);
428
+ assert(registrations.length < 2, `internal error: more than one registration named '${name}'`);
429
+ }
430
+ return registrations ?? [];
373
431
  }
374
432
  }
375
433
  // @internal
@@ -467,9 +525,6 @@ function isDisposable(value) {
467
525
  getAllCached(token) {
468
526
  this.checkDisposed();
469
527
  const registrations = this.myTokenRegistry.getAll(token);
470
- if (!registrations) {
471
- return [];
472
- }
473
528
  const values = new Set();
474
529
  for (const registration of registrations){
475
530
  const value = registration.value;
@@ -491,50 +546,21 @@ function isDisposable(value) {
491
546
  }
492
547
  return Array.from(values);
493
548
  }
494
- isRegistered(token) {
549
+ isRegistered(token, name) {
495
550
  this.checkDisposed();
496
- return this.myTokenRegistry.get(token) !== undefined;
497
- }
498
- registerClass(token, Class, options) {
499
- // This mess will go away once/if we remove the register method
500
- // in favor of the multiple specialized ones
501
- if (Class) {
502
- const ctor = Class ?? token;
503
- this.register(token, {
504
- useClass: ctor
505
- }, options);
506
- } else {
507
- this.register(token);
508
- }
509
- }
510
- registerFactory(token, factory, options) {
511
- this.register(token, {
512
- useFactory: factory
513
- }, options);
514
- }
515
- registerValue(token, value) {
516
- this.register(token, {
517
- useValue: value
518
- });
519
- }
520
- registerAlias(targetToken, aliasTokens) {
521
- // De-duplicate tokens
522
- for (const alias of new Set(aliasTokens)){
523
- this.register(alias, {
524
- useExisting: targetToken
525
- });
526
- }
551
+ return this.myTokenRegistry.get(token, name) !== undefined;
527
552
  }
528
553
  register(...args) {
529
554
  this.checkDisposed();
530
- if (args.length == 1) {
555
+ if (args.length === 1) {
531
556
  const Class = args[0];
532
557
  const metadata = getMetadata(Class);
533
558
  const registration = {
559
+ name: metadata.name,
534
560
  // The provider is of type ClassProvider, initialized by getMetadata
535
561
  provider: metadata.provider,
536
562
  options: {
537
- scope: metadata.scope ?? this.myOptions.defaultScope
563
+ scope: metadata.scope?.value ?? this.myOptions.defaultScope
538
564
  },
539
565
  dependencies: metadata.dependencies
540
566
  };
@@ -551,18 +577,19 @@ function isDisposable(value) {
551
577
  }
552
578
  // Eager-instantiate only if the class is container-scoped
553
579
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
554
- this.resolve(Class);
580
+ this.resolveProviderValue(registration, registration.provider);
555
581
  }
556
582
  } else {
557
583
  const [token, provider, options] = args;
558
584
  if (isClassProvider(provider)) {
559
585
  const metadata = getMetadata(provider.useClass);
560
586
  const registration = {
587
+ // An explicit provider name overrides what is specified via @Named
588
+ name: metadata.name ?? provider.name,
561
589
  provider: metadata.provider,
562
590
  options: {
563
- // The explicit registration options override what is specified
564
- // via class decorators (e.g., @Scoped)
565
- scope: metadata.scope ?? this.myOptions.defaultScope,
591
+ // Explicit registration options override what is specified via class decorators (e.g., @Scoped)
592
+ scope: metadata.scope?.value ?? this.myOptions.defaultScope,
566
593
  ...options
567
594
  },
568
595
  dependencies: metadata.dependencies
@@ -570,13 +597,15 @@ function isDisposable(value) {
570
597
  this.myTokenRegistry.set(token, registration);
571
598
  // Eager-instantiate only if the provided class is container-scoped
572
599
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
573
- this.resolve(token);
600
+ this.resolveProviderValue(registration, registration.provider);
574
601
  }
575
602
  } else {
576
- if (isExistingProvider(provider)) {
603
+ const existingProvider = isExistingProvider(provider);
604
+ if (existingProvider) {
577
605
  assert(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
578
606
  }
579
607
  this.myTokenRegistry.set(token, {
608
+ name: existingProvider ? undefined : provider.name,
580
609
  provider: provider,
581
610
  options: options
582
611
  });
@@ -584,12 +613,9 @@ function isDisposable(value) {
584
613
  }
585
614
  return this;
586
615
  }
587
- unregister(token) {
616
+ unregister(token, name) {
588
617
  this.checkDisposed();
589
- const registrations = this.myTokenRegistry.delete(token);
590
- if (!registrations) {
591
- return [];
592
- }
618
+ const registrations = this.myTokenRegistry.delete(token, name);
593
619
  const values = new Set();
594
620
  for (const registration of registrations){
595
621
  const value = registration.value;
@@ -599,22 +625,31 @@ function isDisposable(value) {
599
625
  }
600
626
  return Array.from(values);
601
627
  }
602
- resolve(token, optional) {
628
+ resolve(token, optionalOrName, name) {
603
629
  this.checkDisposed();
604
- const registration = this.myTokenRegistry.get(token);
630
+ let localOptional;
631
+ let localName;
632
+ if (typeof optionalOrName === "string") {
633
+ localName = optionalOrName;
634
+ } else {
635
+ localOptional = optionalOrName;
636
+ localName = name;
637
+ }
638
+ const registration = this.myTokenRegistry.get(token, localName);
605
639
  if (registration) {
606
- return this.resolveRegistration(token, registration);
640
+ return this.resolveRegistration(token, registration, localName);
607
641
  }
608
642
  if (isConstructor(token)) {
609
- return this.instantiateClass(token, optional);
643
+ return this.instantiateClass(token, localOptional);
610
644
  }
611
- return optional ? undefined : throwUnregisteredError(token);
645
+ return optionalOrName ? undefined : throwUnregisteredError(token);
612
646
  }
613
647
  resolveAll(token, optional) {
614
648
  this.checkDisposed();
615
649
  const registrations = this.myTokenRegistry.getAll(token);
616
- if (registrations) {
617
- return registrations.map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
650
+ if (registrations.length > 0) {
651
+ return registrations //
652
+ .map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
618
653
  }
619
654
  if (isConstructor(token)) {
620
655
  const instance = this.instantiateClass(token, optional);
@@ -650,12 +685,12 @@ function isDisposable(value) {
650
685
  // Allow values to be GCed
651
686
  disposedRefs.clear();
652
687
  }
653
- resolveRegistration(token, registration) {
688
+ resolveRegistration(token, registration, name) {
654
689
  let currRegistration = registration;
655
690
  let currProvider = currRegistration.provider;
656
691
  while(isExistingProvider(currProvider)){
657
692
  const targetToken = currProvider.useExisting;
658
- currRegistration = this.myTokenRegistry.get(targetToken);
693
+ currRegistration = this.myTokenRegistry.get(targetToken, name);
659
694
  if (!currRegistration) {
660
695
  throwExistingUnregisteredError(token, targetToken);
661
696
  }
@@ -686,7 +721,7 @@ function isDisposable(value) {
686
721
  metadata.eagerInstantiate = eagerInstantiate;
687
722
  }
688
723
  }
689
- const scope = this.resolveScope(metadata.scope);
724
+ const scope = this.resolveScope(metadata.scope?.value);
690
725
  if (optional && scope === Scope.Container) {
691
726
  // It would not be possible to resolve the class in container scope,
692
727
  // as that would require prior registration.
@@ -786,7 +821,7 @@ function isDisposable(value) {
786
821
  }
787
822
  }
788
823
  resolveScope(scope = this.myOptions.defaultScope, context = useInjectionContext()) {
789
- if (scope == Scope.Inherited) {
824
+ if (scope === Scope.Inherited) {
790
825
  const dependentFrame = context?.resolution.stack.peek();
791
826
  return dependentFrame?.scope || Scope.Transient;
792
827
  }
@@ -796,7 +831,7 @@ function isDisposable(value) {
796
831
  const dependencies = registration.dependencies;
797
832
  if (dependencies) {
798
833
  assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
799
- const ctorDeps = dependencies.constructor;
834
+ const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
800
835
  if (ctorDeps.length > 0) {
801
836
  // Let's check if all necessary constructor parameters are decorated.
802
837
  // If not, we cannot safely create an instance.
@@ -807,13 +842,13 @@ function isDisposable(value) {
807
842
  });
808
843
  return ctorDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
809
844
  const token = dep.tokenRef.getRefToken();
810
- switch(dep.decorator){
845
+ switch(dep.appliedBy){
811
846
  case "Inject":
812
- return this.resolve(token);
847
+ return this.resolve(token, dep.name);
813
848
  case "InjectAll":
814
849
  return this.resolveAll(token);
815
850
  case "Optional":
816
- return this.resolve(token, true);
851
+ return this.resolve(token, true, dep.name);
817
852
  case "OptionalAll":
818
853
  return this.resolveAll(token, true);
819
854
  }
@@ -828,7 +863,9 @@ function isDisposable(value) {
828
863
  assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
829
864
  const ctor = registration.provider.useClass;
830
865
  // Perform method injection
831
- for (const [key, methodDeps] of dependencies.methods){
866
+ for (const entry of dependencies.methods){
867
+ const key = entry[0];
868
+ const methodDeps = entry[1].filter((d)=>d.appliedBy);
832
869
  // Let's check if all necessary method parameters are decorated.
833
870
  // If not, we cannot safely invoke the method.
834
871
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@@ -839,13 +876,13 @@ function isDisposable(value) {
839
876
  });
840
877
  const args = methodDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
841
878
  const token = dep.tokenRef.getRefToken();
842
- switch(dep.decorator){
879
+ switch(dep.appliedBy){
843
880
  case "Inject":
844
- return injectBy(instance, token);
881
+ return injectBy(instance, token, dep.name);
845
882
  case "InjectAll":
846
883
  return injectAll(token);
847
884
  case "Optional":
848
- return optionalBy(instance, token);
885
+ return optionalBy(instance, token, dep.name);
849
886
  case "OptionalAll":
850
887
  return optionalAll(token);
851
888
  }
@@ -876,7 +913,7 @@ function isDisposable(value) {
876
913
  *
877
914
  * @example
878
915
  * ```ts
879
- * @AutoRegister
916
+ * @AutoRegister()
880
917
  * class Wizard {}
881
918
  *
882
919
  * const wizard = container.resolve(Wizard);
@@ -884,32 +921,45 @@ function isDisposable(value) {
884
921
  * ```
885
922
  *
886
923
  * @__NO_SIDE_EFFECTS__
887
- */ function AutoRegister(Class) {
888
- const metadata = getMetadata(Class);
889
- metadata.autoRegister = true;
924
+ */ function AutoRegister() {
925
+ return function(Class) {
926
+ const metadata = getMetadata(Class);
927
+ metadata.autoRegister = true;
928
+ };
890
929
  }
891
930
 
892
931
  /**
893
- * Class decorator that enables eager instantiation of a class when it is registered
894
- * in the container with `Scope.Container`.
932
+ * Class decorator that sets the class scope to **Container** and enables
933
+ * eager instantiation when the class is registered in the container.
895
934
  *
896
935
  * This causes the container to immediately create and cache the instance of the class,
897
936
  * instead of deferring instantiation until the first resolution.
898
937
  *
899
938
  * @example
900
939
  * ```ts
901
- * @EagerInstantiate
902
- * @Scoped(Scope.Container)
940
+ * @EagerInstantiate()
903
941
  * class Wizard {}
904
942
  *
905
- * // A Wizard instance is immediately created and cached by the container
906
- * const wizard = container.register(Wizard);
943
+ * // Wizard is registered with Container scope, and an instance
944
+ * // is immediately created and cached by the container
945
+ * container.register(Wizard);
907
946
  * ```
908
947
  *
909
948
  * @__NO_SIDE_EFFECTS__
910
- */ function EagerInstantiate(Class) {
911
- const metadata = getMetadata(Class);
912
- metadata.eagerInstantiate = true;
949
+ */ function EagerInstantiate() {
950
+ return function(Class) {
951
+ const metadata = getMetadata(Class);
952
+ const currentScope = metadata.scope;
953
+ assert(!currentScope || currentScope.value === Scope.Container, ()=>{
954
+ const { value, appliedBy } = currentScope;
955
+ return `class ${Class.name}: Scope.${value} was already set by @${appliedBy},\n ` + `but @EagerInstantiate is trying to set a conflicting Scope.Container.\n ` + `Only one decorator should set the class scope, or all must agree on the same value.`;
956
+ });
957
+ metadata.eagerInstantiate = true;
958
+ metadata.scope = {
959
+ value: Scope.Container,
960
+ appliedBy: "EagerInstantiate"
961
+ };
962
+ };
913
963
  }
914
964
 
915
965
  function forwardRef(token) {
@@ -941,41 +991,31 @@ function isTokenRef(value) {
941
991
  return value && typeof value === "object" && typeof value.getRefToken === "function";
942
992
  }
943
993
 
944
- function processDecoratedParameter(decorator, token, target, propertyKey, parameterIndex) {
945
- // Error out immediately if the decorator has been applied
946
- // to a static property or a static method
994
+ // @internal
995
+ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
996
+ // Error out immediately if the decorator has been applied to a static method
947
997
  if (propertyKey !== undefined && typeof target === "function") {
948
- assert(false, `@${decorator} cannot be used on static member ${target.name}.${String(propertyKey)}`);
998
+ assert(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
949
999
  }
950
- const tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
951
1000
  if (propertyKey === undefined) {
952
1001
  // Constructor
953
1002
  const metadata = getMetadata(target);
954
- metadata.dependencies.constructor.push({
955
- decorator: decorator,
956
- tokenRef: tokenRef,
957
- index: parameterIndex
958
- });
1003
+ const dependency = metadata.getConstructorDependency(parameterIndex);
1004
+ updateFn(dependency);
959
1005
  } else {
960
- // Normal instance method
1006
+ // Instance method
961
1007
  const metadata = getMetadata(target.constructor);
962
- const methods = metadata.dependencies.methods;
963
- let dep = methods.get(propertyKey);
964
- if (dep === undefined) {
965
- dep = [];
966
- methods.set(propertyKey, dep);
967
- }
968
- dep.push({
969
- decorator: decorator,
970
- tokenRef: tokenRef,
971
- index: parameterIndex
972
- });
1008
+ const dependency = metadata.getMethodDependency(propertyKey, parameterIndex);
1009
+ updateFn(dependency);
973
1010
  }
974
1011
  }
975
1012
 
976
1013
  function Inject(token) {
977
1014
  return function(target, propertyKey, parameterIndex) {
978
- processDecoratedParameter("Inject", token, target, propertyKey, parameterIndex);
1015
+ updateParameterMetadata("Inject", target, propertyKey, parameterIndex, (dependency)=>{
1016
+ dependency.appliedBy = "Inject";
1017
+ dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1018
+ });
979
1019
  };
980
1020
  }
981
1021
 
@@ -1001,19 +1041,66 @@ function Inject(token) {
1001
1041
 
1002
1042
  function InjectAll(token) {
1003
1043
  return function(target, propertyKey, parameterIndex) {
1004
- processDecoratedParameter("InjectAll", token, target, propertyKey, parameterIndex);
1044
+ updateParameterMetadata("InjectAll", target, propertyKey, parameterIndex, (dependency)=>{
1045
+ dependency.appliedBy = "InjectAll";
1046
+ dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1047
+ });
1048
+ };
1049
+ }
1050
+
1051
+ /**
1052
+ * Qualifies a class or an injected parameter with a unique name.
1053
+ *
1054
+ * This allows the container to distinguish between multiple implementations
1055
+ * of the same interface or type during registration and injection.
1056
+ *
1057
+ * @example
1058
+ * ```ts
1059
+ * @Named("dumbledore")
1060
+ * class Dumbledore implements Wizard {}
1061
+ *
1062
+ * // Register Dumbledore with Type<Wizard>
1063
+ * container.register(IWizard, { useClass: Dumbledore });
1064
+ * const dumbledore = container.resolve(IWizard, "dumbledore");
1065
+ * ```
1066
+ *
1067
+ * @__NO_SIDE_EFFECTS__
1068
+ */ function Named(name) {
1069
+ if (!name.trim()) {
1070
+ assert(false, "the @Named qualifier cannot be empty or blank");
1071
+ }
1072
+ return function(target, propertyKey, parameterIndex) {
1073
+ if (parameterIndex === undefined) {
1074
+ // The decorator has been applied to the class
1075
+ const ctor = target;
1076
+ const metadata = getMetadata(ctor);
1077
+ assert(!metadata.name, `a @Named('${metadata.name}') qualifier has already been applied to ${ctor.name}`);
1078
+ metadata.name = name;
1079
+ } else {
1080
+ // The decorator has been applied to a method parameter
1081
+ updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
1082
+ assert(!dependency.name, `a @Named('${dependency.name}') qualifier has already been applied to the parameter`);
1083
+ dependency.name = name;
1084
+ });
1085
+ }
1005
1086
  };
1006
1087
  }
1007
1088
 
1008
1089
  function Optional(token) {
1009
1090
  return function(target, propertyKey, parameterIndex) {
1010
- processDecoratedParameter("Optional", token, target, propertyKey, parameterIndex);
1091
+ updateParameterMetadata("Optional", target, propertyKey, parameterIndex, (dependency)=>{
1092
+ dependency.appliedBy = "Optional";
1093
+ dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1094
+ });
1011
1095
  };
1012
1096
  }
1013
1097
 
1014
1098
  function OptionalAll(token) {
1015
1099
  return function(target, propertyKey, parameterIndex) {
1016
- processDecoratedParameter("OptionalAll", token, target, propertyKey, parameterIndex);
1100
+ updateParameterMetadata("OptionalAll", target, propertyKey, parameterIndex, (dependency)=>{
1101
+ dependency.appliedBy = "OptionalAll";
1102
+ dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1103
+ });
1017
1104
  };
1018
1105
  }
1019
1106
 
@@ -1041,7 +1128,16 @@ function OptionalAll(token) {
1041
1128
  */ function Scoped(scope) {
1042
1129
  return function(Class) {
1043
1130
  const metadata = getMetadata(Class);
1044
- metadata.scope = scope;
1131
+ const currentScope = metadata.scope;
1132
+ assert(!currentScope || currentScope.value === scope, ()=>{
1133
+ const { value, appliedBy } = currentScope;
1134
+ const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
1135
+ return `class ${Class.name}: Scope.${value} was already set by ${by},\n ` + `but @Scoped is trying to set a conflicting Scope.${scope}.\n ` + `Only one decorator should set the class scope, or all must agree on the same value.`;
1136
+ });
1137
+ metadata.scope = {
1138
+ value: scope,
1139
+ appliedBy: "Scoped"
1140
+ };
1045
1141
  };
1046
1142
  }
1047
1143
 
@@ -1136,5 +1232,5 @@ function OptionalAll(token) {
1136
1232
  return container;
1137
1233
  }
1138
1234
 
1139
- export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Optional, OptionalAll, Scope, Scoped, applyMiddleware, build, createContainer, createType, forwardRef, inject, injectAll, injectBy, setClassIdentityMapping };
1235
+ export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Named, Optional, OptionalAll, Scope, Scoped, applyMiddleware, build, createContainer, createType, forwardRef, inject, injectAll, injectBy, setClassIdentityMapping };
1140
1236
  //# sourceMappingURL=index.mjs.map