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