@lppedd/di-wise-neo 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -28,7 +28,7 @@ function isConstructor(token) {
28
28
  }
29
29
 
30
30
  // @internal
31
- function assert(condition, message) {
31
+ function check(condition, message) {
32
32
  if (!condition) {
33
33
  throw new Error(tag(typeof message === "string" ? message : message()));
34
34
  }
@@ -38,9 +38,10 @@ function expectNever(value) {
38
38
  throw new TypeError(tag(`unexpected value ${String(value)}`));
39
39
  }
40
40
  // @internal
41
- function throwUnregisteredError(token) {
41
+ function throwUnregisteredError(token, name) {
42
42
  const type = isConstructor(token) ? "class" : "token";
43
- throw new Error(tag(`unregistered ${type} ${token.name}`));
43
+ const spec = name !== undefined ? `[name=${name}]` : "";
44
+ throw new Error(tag(`unregistered ${type} ${token.name}${spec}`));
44
45
  }
45
46
  // @internal
46
47
  function throwExistingUnregisteredError(sourceToken, targetTokenOrError) {
@@ -59,13 +60,6 @@ function untag(message) {
59
60
  return message.startsWith("[di-wise-neo]") ? message.substring(13).trimStart() : message;
60
61
  }
61
62
 
62
- // @internal
63
- function invariant(condition) {
64
- if (!condition) {
65
- throw new Error("invariant violation");
66
- }
67
- }
68
-
69
63
  // @internal
70
64
  class KeyedStack {
71
65
  has(key) {
@@ -76,7 +70,7 @@ class KeyedStack {
76
70
  return entry?.value;
77
71
  }
78
72
  push(key, value) {
79
- invariant(!this.has(key));
73
+ check(!this.has(key), "invariant violation");
80
74
  this.myKeys.add(key);
81
75
  this.myEntries.push({
82
76
  key,
@@ -104,9 +98,10 @@ class WeakRefMap {
104
98
  }
105
99
  this.myMap.delete(key);
106
100
  }
101
+ return undefined;
107
102
  }
108
103
  set(key, value) {
109
- invariant(!this.get(key));
104
+ check(!this.get(key), "invariant violation");
110
105
  this.myMap.set(key, new WeakRef(value));
111
106
  return ()=>{
112
107
  this.myMap.delete(key);
@@ -130,7 +125,7 @@ const [provideInjectionContext, useInjectionContext] = createInjectionContext();
130
125
  // @internal
131
126
  function ensureInjectionContext(name) {
132
127
  const context = useInjectionContext();
133
- assert(context, `${name} can only be invoked within an injection context`);
128
+ check(context, `${name} can only be invoked within an injection context`);
134
129
  return context;
135
130
  }
136
131
  function createInjectionContext() {
@@ -360,13 +355,13 @@ class TokenRegistry {
360
355
  ] || this.getAllFromParent(token, name);
361
356
  }
362
357
  set(token, registration) {
363
- assert(!internals.has(token), `cannot register reserved token ${token.name}`);
358
+ check(!internals.has(token), `cannot register reserved token ${token.name}`);
364
359
  let registrations = this.myMap.get(token);
365
360
  if (!registrations) {
366
361
  this.myMap.set(token, registrations = []);
367
362
  } else if (registration.name !== undefined) {
368
363
  const existing = registrations.filter((r)=>r.name === registration.name);
369
- assert(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
364
+ check(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
370
365
  }
371
366
  registrations.push(registration);
372
367
  }
@@ -420,7 +415,7 @@ class TokenRegistry {
420
415
  let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
421
416
  if (registrations && name !== undefined) {
422
417
  registrations = registrations.filter((r)=>r.name === name);
423
- assert(registrations.length < 2, `internal error: more than one registration named '${name}'`);
418
+ check(registrations.length < 2, `internal error: more than one registration named '${name}'`);
424
419
  }
425
420
  return registrations ?? [];
426
421
  }
@@ -561,7 +556,7 @@ function isDisposable(value) {
561
556
  const [token, provider, options] = args;
562
557
  const existingProvider = isExistingProvider(provider);
563
558
  const name = existingProvider ? undefined : provider.name;
564
- assert(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
559
+ check(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
565
560
  if (isClassProvider(provider)) {
566
561
  const metadata = getMetadata(provider.useClass);
567
562
  const registration = {
@@ -582,7 +577,7 @@ function isDisposable(value) {
582
577
  }
583
578
  } else {
584
579
  if (existingProvider) {
585
- assert(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
580
+ check(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
586
581
  }
587
582
  this.myTokenRegistry.set(token, {
588
583
  name: name,
@@ -615,29 +610,30 @@ function isDisposable(value) {
615
610
  localOptional = optionalOrName;
616
611
  localName = name;
617
612
  }
618
- const registration = this.myTokenRegistry.get(token, localName);
613
+ let registration = this.myTokenRegistry.get(token, localName);
614
+ if (!registration && isConstructor(token)) {
615
+ registration = this.autoRegisterClass(token, localName);
616
+ }
619
617
  if (registration) {
620
618
  return this.resolveRegistration(token, registration, localName);
621
619
  }
622
- if (isConstructor(token)) {
623
- return this.instantiateClass(token, localOptional);
624
- }
625
- return localOptional ? undefined : throwUnregisteredError(token);
620
+ return localOptional ? undefined : throwUnregisteredError(token, localName);
626
621
  }
627
622
  resolveAll(token, optional) {
628
623
  this.checkDisposed();
629
- const registrations = this.myTokenRegistry.getAll(token);
624
+ let registrations = this.myTokenRegistry.getAll(token);
625
+ if (registrations.length === 0 && isConstructor(token)) {
626
+ const registration = this.autoRegisterClass(token);
627
+ if (registration) {
628
+ registrations = [
629
+ registration
630
+ ];
631
+ }
632
+ }
630
633
  if (registrations.length > 0) {
631
634
  return registrations //
632
635
  .map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
633
636
  }
634
- if (isConstructor(token)) {
635
- const instance = this.instantiateClass(token, optional);
636
- return instance === undefined // = could not resolve, but since it is optional
637
- ? [] : [
638
- instance
639
- ];
640
- }
641
637
  return optional ? [] : throwUnregisteredError(token);
642
638
  }
643
639
  dispose() {
@@ -687,40 +683,25 @@ function isDisposable(value) {
687
683
  throw e;
688
684
  }
689
685
  }
690
- instantiateClass(Class, optional) {
686
+ autoRegisterClass(Class, name) {
691
687
  const metadata = getMetadata(Class);
692
- if (metadata.autoRegister ?? this.myOptions.autoRegister) {
693
- // Temporarily set eagerInstantiate to false to avoid resolving the class two times:
694
- // one inside register(), and the other just below
688
+ const autoRegister = metadata.autoRegister ?? this.myOptions.autoRegister;
689
+ if (autoRegister && (name === undefined || metadata.name === name)) {
690
+ // Temporarily set eagerInstantiate to false to avoid potentially resolving
691
+ // the class inside register()
695
692
  const eagerInstantiate = metadata.eagerInstantiate;
696
693
  metadata.eagerInstantiate = false;
697
694
  try {
698
695
  this.register(Class);
699
- return this.resolve(Class);
696
+ return this.myTokenRegistry.get(Class, name ?? metadata.name);
700
697
  } finally{
701
698
  metadata.eagerInstantiate = eagerInstantiate;
702
699
  }
703
700
  }
704
- const scope = this.resolveScope(metadata.scope?.value);
705
- if (optional && scope === Scope.Container) {
706
- // It would not be possible to resolve the class in container scope,
707
- // as that would require prior registration.
708
- // However, since resolution is marked optional, we simply return undefined.
709
- return undefined;
710
- }
711
- assert(scope !== Scope.Container, `unregistered class ${Class.name} cannot be resolved in container scope`);
712
- const registration = {
713
- provider: metadata.provider,
714
- options: {
715
- scope: scope
716
- },
717
- dependencies: metadata.dependencies
718
- };
719
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
720
- return this.resolveScopedValue(registration, (args)=>new Class(...args));
701
+ return undefined;
721
702
  }
722
703
  resolveProviderValue(registration, provider) {
723
- assert(registration.provider === provider, "internal error: mismatching provider");
704
+ check(registration.provider === provider, "internal error: mismatching provider");
724
705
  if (isClassProvider(provider)) {
725
706
  const Class = provider.useClass;
726
707
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
@@ -733,9 +714,7 @@ function isDisposable(value) {
733
714
  if (isValueProvider(provider)) {
734
715
  return provider.useValue;
735
716
  }
736
- if (isExistingProvider(provider)) {
737
- assert(false, "internal error: unexpected ExistingProvider");
738
- }
717
+ check(!isExistingProvider(provider), "internal error: unexpected ExistingProvider");
739
718
  expectNever(provider);
740
719
  }
741
720
  resolveScopedValue(registration, factory) {
@@ -751,7 +730,7 @@ function isDisposable(value) {
751
730
  const options = registration.options;
752
731
  if (resolution.stack.has(provider)) {
753
732
  const dependentRef = resolution.dependents.get(provider);
754
- assert(dependentRef, "circular dependency detected");
733
+ check(dependentRef, "circular dependency detected");
755
734
  return dependentRef.current;
756
735
  }
757
736
  const scope = this.resolveScope(options?.scope, context);
@@ -810,13 +789,13 @@ function isDisposable(value) {
810
789
  resolveConstructorDependencies(registration) {
811
790
  const dependencies = registration.dependencies;
812
791
  if (dependencies) {
813
- assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
792
+ check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
814
793
  const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
815
794
  if (ctorDeps.length > 0) {
816
795
  // Let's check if all necessary constructor parameters are decorated.
817
796
  // If not, we cannot safely create an instance.
818
797
  const ctor = registration.provider.useClass;
819
- assert(ctor.length === ctorDeps.length, ()=>{
798
+ check(ctor.length === ctorDeps.length, ()=>{
820
799
  const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
821
800
  return msg + `, but found ${ctorDeps.length}`;
822
801
  });
@@ -840,7 +819,7 @@ function isDisposable(value) {
840
819
  injectDependencies(registration, instance) {
841
820
  const dependencies = registration.dependencies;
842
821
  if (dependencies) {
843
- assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
822
+ check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
844
823
  const ctor = registration.provider.useClass;
845
824
  // Perform method injection
846
825
  for (const entry of dependencies.methods){
@@ -850,7 +829,7 @@ function isDisposable(value) {
850
829
  // If not, we cannot safely invoke the method.
851
830
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
852
831
  const method = instance[key];
853
- assert(methodDeps.length === method.length, ()=>{
832
+ check(methodDeps.length === method.length, ()=>{
854
833
  const msg = `expected ${method.length} decorated method parameters`;
855
834
  return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
856
835
  });
@@ -874,7 +853,7 @@ function isDisposable(value) {
874
853
  return instance;
875
854
  }
876
855
  checkDisposed() {
877
- assert(!this.myDisposed, "the container is disposed");
856
+ check(!this.myDisposed, "the container is disposed");
878
857
  }
879
858
  }
880
859
 
@@ -930,7 +909,7 @@ function isDisposable(value) {
930
909
  return function(Class) {
931
910
  const metadata = getMetadata(Class);
932
911
  const currentScope = metadata.scope;
933
- assert(!currentScope || currentScope.value === Scope.Container, ()=>{
912
+ check(!currentScope || currentScope.value === Scope.Container, ()=>{
934
913
  const { value, appliedBy } = currentScope;
935
914
  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.`;
936
915
  });
@@ -955,7 +934,7 @@ function forwardRef(token) {
955
934
  },
956
935
  getRefToken: ()=>{
957
936
  const tokenOrTokens = token();
958
- assert(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
937
+ check(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
959
938
  return tokenOrTokens;
960
939
  }
961
940
  };
@@ -975,7 +954,7 @@ function isTokenRef(value) {
975
954
  function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
976
955
  // Error out immediately if the decorator has been applied to a static method
977
956
  if (propertyKey !== undefined && typeof target === "function") {
978
- assert(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
957
+ check(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
979
958
  }
980
959
  if (propertyKey === undefined) {
981
960
  // Constructor
@@ -995,11 +974,30 @@ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex,
995
974
  //
996
975
  // @internal
997
976
  function checkSingleDecorator(dependency, target, propertyKey, parameterIndex) {
998
- assert(!dependency.appliedBy, ()=>{
999
- const where = propertyKey === undefined ? `${target.name} constructor` : `${target.constructor.name}.${String(propertyKey)}`;
1000
- return `${where} parameter ${parameterIndex} declares multiple injection decorators, but only one is allowed`;
977
+ check(dependency.appliedBy === undefined, ()=>{
978
+ const location = getLocation(target, propertyKey, parameterIndex);
979
+ return `multiple injection decorators on ${location}, but only one is allowed`;
980
+ });
981
+ }
982
+ // Checks that the `@Named` decorator is not used in combination with
983
+ // `@InjectAll` or `@OptionalAll`, which ignore the name qualification.
984
+ //
985
+ // @internal
986
+ function checkNamedDecorator(dependency, target, propertyKey, parameterIndex) {
987
+ const { appliedBy, name } = dependency;
988
+ check(name === undefined || appliedBy !== "InjectAll" && appliedBy !== "OptionalAll", ()=>{
989
+ const location = getLocation(target, propertyKey, parameterIndex);
990
+ return `@Named has no effect on ${location} when used with @${appliedBy}`;
1001
991
  });
1002
992
  }
993
+ // Returns a human-readable description of the parameter location.
994
+ // For example: "Wizard constructor parameter 2" or "Wizard.set parameter 0"
995
+ //
996
+ // @internal
997
+ function getLocation(target, propertyKey, parameterIndex) {
998
+ const location = propertyKey === undefined ? `${target.name} constructor` : `${target.constructor.name}.${String(propertyKey)}`;
999
+ return `${location} parameter ${parameterIndex}`;
1000
+ }
1003
1001
 
1004
1002
  function Inject(token) {
1005
1003
  return function(target, propertyKey, parameterIndex) {
@@ -1037,6 +1035,7 @@ function InjectAll(token) {
1037
1035
  checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
1038
1036
  dependency.appliedBy = "InjectAll";
1039
1037
  dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1038
+ checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
1040
1039
  });
1041
1040
  };
1042
1041
  }
@@ -1059,21 +1058,23 @@ function InjectAll(token) {
1059
1058
  *
1060
1059
  * @__NO_SIDE_EFFECTS__
1061
1060
  */ function Named(name) {
1062
- if (!name.trim()) {
1063
- assert(false, "the @Named qualifier cannot be empty or blank");
1064
- }
1061
+ check(name.trim(), "the @Named qualifier cannot be empty or blank");
1065
1062
  return function(target, propertyKey, parameterIndex) {
1066
1063
  if (parameterIndex === undefined) {
1067
1064
  // The decorator has been applied to the class
1068
1065
  const ctor = target;
1069
1066
  const metadata = getMetadata(ctor);
1070
- assert(!metadata.name, `a @Named('${metadata.name}') qualifier has already been applied to ${ctor.name}`);
1067
+ check(metadata.name === undefined, `multiple @Named decorators on class ${ctor.name}, but only one is allowed`);
1071
1068
  metadata.name = name;
1072
1069
  } else {
1073
1070
  // The decorator has been applied to a method parameter
1074
1071
  updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
1075
- assert(!dependency.name, `a @Named('${dependency.name}') qualifier has already been applied to the parameter`);
1072
+ check(dependency.name === undefined, ()=>{
1073
+ const location = getLocation(target, propertyKey, parameterIndex);
1074
+ return `multiple @Named decorators on ${location}, but only one is allowed`;
1075
+ });
1076
1076
  dependency.name = name;
1077
+ checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
1077
1078
  });
1078
1079
  }
1079
1080
  };
@@ -1095,6 +1096,7 @@ function OptionalAll(token) {
1095
1096
  checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
1096
1097
  dependency.appliedBy = "OptionalAll";
1097
1098
  dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1099
+ checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
1098
1100
  });
1099
1101
  };
1100
1102
  }
@@ -1124,7 +1126,7 @@ function OptionalAll(token) {
1124
1126
  return function(Class) {
1125
1127
  const metadata = getMetadata(Class);
1126
1128
  const currentScope = metadata.scope;
1127
- assert(!currentScope || currentScope.value === scope, ()=>{
1129
+ check(!currentScope || currentScope.value === scope, ()=>{
1128
1130
  const { value, appliedBy } = currentScope;
1129
1131
  const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
1130
1132
  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.`;