@lppedd/di-wise-neo 0.9.0 → 0.9.2

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
  }
@@ -60,13 +60,6 @@ function untag(message) {
60
60
  return message.startsWith("[di-wise-neo]") ? message.substring(13).trimStart() : message;
61
61
  }
62
62
 
63
- // @internal
64
- function invariant(condition) {
65
- if (!condition) {
66
- throw new Error("invariant violation");
67
- }
68
- }
69
-
70
63
  // @internal
71
64
  class KeyedStack {
72
65
  has(key) {
@@ -77,7 +70,7 @@ class KeyedStack {
77
70
  return entry?.value;
78
71
  }
79
72
  push(key, value) {
80
- invariant(!this.has(key));
73
+ check(!this.has(key), "invariant violation");
81
74
  this.myKeys.add(key);
82
75
  this.myEntries.push({
83
76
  key,
@@ -89,7 +82,7 @@ class KeyedStack {
89
82
  };
90
83
  }
91
84
  constructor(){
92
- this.myEntries = new Array();
85
+ this.myEntries = [];
93
86
  this.myKeys = new WeakSet();
94
87
  }
95
88
  }
@@ -108,7 +101,7 @@ class WeakRefMap {
108
101
  return undefined;
109
102
  }
110
103
  set(key, value) {
111
- invariant(!this.get(key));
104
+ check(!this.get(key), "invariant violation");
112
105
  this.myMap.set(key, new WeakRef(value));
113
106
  return ()=>{
114
107
  this.myMap.delete(key);
@@ -122,6 +115,7 @@ class WeakRefMap {
122
115
  // @internal
123
116
  function createResolution() {
124
117
  return {
118
+ tokenStack: [],
125
119
  stack: new KeyedStack(),
126
120
  values: new WeakRefMap(),
127
121
  dependents: new WeakRefMap()
@@ -132,7 +126,7 @@ const [provideInjectionContext, useInjectionContext] = createInjectionContext();
132
126
  // @internal
133
127
  function ensureInjectionContext(name) {
134
128
  const context = useInjectionContext();
135
- assert(context, `${name} can only be invoked within an injection context`);
129
+ check(context, `${name} can only be invoked within an injection context`);
136
130
  return context;
137
131
  }
138
132
  function createInjectionContext() {
@@ -362,13 +356,13 @@ class TokenRegistry {
362
356
  ] || this.getAllFromParent(token, name);
363
357
  }
364
358
  set(token, registration) {
365
- assert(!internals.has(token), `cannot register reserved token ${token.name}`);
359
+ check(!internals.has(token), `cannot register reserved token ${token.name}`);
366
360
  let registrations = this.myMap.get(token);
367
361
  if (!registrations) {
368
362
  this.myMap.set(token, registrations = []);
369
363
  } else if (registration.name !== undefined) {
370
364
  const existing = registrations.filter((r)=>r.name === registration.name);
371
- assert(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
365
+ check(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
372
366
  }
373
367
  registrations.push(registration);
374
368
  }
@@ -422,7 +416,7 @@ class TokenRegistry {
422
416
  let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
423
417
  if (registrations && name !== undefined) {
424
418
  registrations = registrations.filter((r)=>r.name === name);
425
- assert(registrations.length < 2, `internal error: more than one registration named '${name}'`);
419
+ check(registrations.length < 2, `internal error: more than one registration named '${name}'`);
426
420
  }
427
421
  return registrations ?? [];
428
422
  }
@@ -557,13 +551,13 @@ function isDisposable(value) {
557
551
  }
558
552
  // Eager-instantiate only if the class is container-scoped
559
553
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
560
- this.resolveProviderValue(registration, registration.provider);
554
+ this.resolveProviderValue(Class, registration);
561
555
  }
562
556
  } else {
563
557
  const [token, provider, options] = args;
564
558
  const existingProvider = isExistingProvider(provider);
565
559
  const name = existingProvider ? undefined : provider.name;
566
- assert(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
560
+ check(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
567
561
  if (isClassProvider(provider)) {
568
562
  const metadata = getMetadata(provider.useClass);
569
563
  const registration = {
@@ -580,11 +574,11 @@ function isDisposable(value) {
580
574
  this.myTokenRegistry.set(token, registration);
581
575
  // Eager-instantiate only if the provided class is container-scoped
582
576
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
583
- this.resolveProviderValue(registration, registration.provider);
577
+ this.resolveProviderValue(token, registration);
584
578
  }
585
579
  } else {
586
580
  if (existingProvider) {
587
- assert(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
581
+ check(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
588
582
  }
589
583
  this.myTokenRegistry.set(token, {
590
584
  name: name,
@@ -670,17 +664,15 @@ function isDisposable(value) {
670
664
  }
671
665
  resolveRegistration(token, registration, name) {
672
666
  let currRegistration = registration;
673
- let currProvider = currRegistration.provider;
674
- while(isExistingProvider(currProvider)){
675
- const targetToken = currProvider.useExisting;
667
+ while(isExistingProvider(currRegistration.provider)){
668
+ const targetToken = currRegistration.provider.useExisting;
676
669
  currRegistration = this.myTokenRegistry.get(targetToken, name);
677
670
  if (!currRegistration) {
678
671
  throwExistingUnregisteredError(token, targetToken);
679
672
  }
680
- currProvider = currRegistration.provider;
681
673
  }
682
674
  try {
683
- return this.resolveProviderValue(currRegistration, currProvider);
675
+ return this.resolveProviderValue(token, currRegistration);
684
676
  } catch (e) {
685
677
  // If we were trying to resolve a token registered via ExistingProvider,
686
678
  // we must add the cause of the error to the message
@@ -707,26 +699,24 @@ function isDisposable(value) {
707
699
  }
708
700
  return undefined;
709
701
  }
710
- resolveProviderValue(registration, provider) {
711
- assert(registration.provider === provider, "internal error: mismatching provider");
702
+ resolveProviderValue(token, registration) {
703
+ const provider = registration.provider;
712
704
  if (isClassProvider(provider)) {
713
705
  const Class = provider.useClass;
714
706
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
715
- return this.resolveScopedValue(registration, (args)=>new Class(...args));
707
+ return this.resolveScopedValue(token, registration, (args)=>new Class(...args));
716
708
  }
717
709
  if (isFactoryProvider(provider)) {
718
710
  const factory = provider.useFactory;
719
- return this.resolveScopedValue(registration, factory);
711
+ return this.resolveScopedValue(token, registration, factory);
720
712
  }
721
713
  if (isValueProvider(provider)) {
722
714
  return provider.useValue;
723
715
  }
724
- if (isExistingProvider(provider)) {
725
- assert(false, "internal error: unexpected ExistingProvider");
726
- }
716
+ check(!isExistingProvider(provider), "internal error: unexpected ExistingProvider");
727
717
  expectNever(provider);
728
718
  }
729
- resolveScopedValue(registration, factory) {
719
+ resolveScopedValue(token, registration, factory) {
730
720
  let context = useInjectionContext();
731
721
  if (!context || context.container !== this) {
732
722
  context = {
@@ -739,12 +729,16 @@ function isDisposable(value) {
739
729
  const options = registration.options;
740
730
  if (resolution.stack.has(provider)) {
741
731
  const dependentRef = resolution.dependents.get(provider);
742
- assert(dependentRef, "circular dependency detected");
732
+ check(dependentRef, ()=>{
733
+ const tokenStack = resolution.tokenStack.map((t)=>t.name).concat(token.name).join(" → ");
734
+ return `circular dependency detected while resolving ${tokenStack}`;
735
+ });
743
736
  return dependentRef.current;
744
737
  }
745
738
  const scope = this.resolveScope(options?.scope, context);
746
739
  const cleanups = [
747
740
  provideInjectionContext(context),
741
+ resolution.tokenStack.push(token) && (()=>resolution.tokenStack.pop()),
748
742
  !isBuilder(provider) && resolution.stack.push(provider, {
749
743
  provider,
750
744
  scope
@@ -798,13 +792,13 @@ function isDisposable(value) {
798
792
  resolveConstructorDependencies(registration) {
799
793
  const dependencies = registration.dependencies;
800
794
  if (dependencies) {
801
- assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
795
+ check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
802
796
  const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
803
797
  if (ctorDeps.length > 0) {
804
798
  // Let's check if all necessary constructor parameters are decorated.
805
799
  // If not, we cannot safely create an instance.
806
800
  const ctor = registration.provider.useClass;
807
- assert(ctor.length === ctorDeps.length, ()=>{
801
+ check(ctor.length === ctorDeps.length, ()=>{
808
802
  const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
809
803
  return msg + `, but found ${ctorDeps.length}`;
810
804
  });
@@ -828,7 +822,7 @@ function isDisposable(value) {
828
822
  injectDependencies(registration, instance) {
829
823
  const dependencies = registration.dependencies;
830
824
  if (dependencies) {
831
- assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
825
+ check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
832
826
  const ctor = registration.provider.useClass;
833
827
  // Perform method injection
834
828
  for (const entry of dependencies.methods){
@@ -838,7 +832,7 @@ function isDisposable(value) {
838
832
  // If not, we cannot safely invoke the method.
839
833
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
840
834
  const method = instance[key];
841
- assert(methodDeps.length === method.length, ()=>{
835
+ check(methodDeps.length === method.length, ()=>{
842
836
  const msg = `expected ${method.length} decorated method parameters`;
843
837
  return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
844
838
  });
@@ -862,7 +856,7 @@ function isDisposable(value) {
862
856
  return instance;
863
857
  }
864
858
  checkDisposed() {
865
- assert(!this.myDisposed, "the container is disposed");
859
+ check(!this.myDisposed, "the container is disposed");
866
860
  }
867
861
  }
868
862
 
@@ -918,7 +912,7 @@ function isDisposable(value) {
918
912
  return function(Class) {
919
913
  const metadata = getMetadata(Class);
920
914
  const currentScope = metadata.scope;
921
- assert(!currentScope || currentScope.value === Scope.Container, ()=>{
915
+ check(!currentScope || currentScope.value === Scope.Container, ()=>{
922
916
  const { value, appliedBy } = currentScope;
923
917
  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.`;
924
918
  });
@@ -943,7 +937,7 @@ function forwardRef(token) {
943
937
  },
944
938
  getRefToken: ()=>{
945
939
  const tokenOrTokens = token();
946
- assert(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
940
+ check(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
947
941
  return tokenOrTokens;
948
942
  }
949
943
  };
@@ -963,7 +957,7 @@ function isTokenRef(value) {
963
957
  function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
964
958
  // Error out immediately if the decorator has been applied to a static method
965
959
  if (propertyKey !== undefined && typeof target === "function") {
966
- assert(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
960
+ check(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
967
961
  }
968
962
  if (propertyKey === undefined) {
969
963
  // Constructor
@@ -983,11 +977,30 @@ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex,
983
977
  //
984
978
  // @internal
985
979
  function checkSingleDecorator(dependency, target, propertyKey, parameterIndex) {
986
- assert(!dependency.appliedBy, ()=>{
987
- const where = propertyKey === undefined ? `${target.name} constructor` : `${target.constructor.name}.${String(propertyKey)}`;
988
- return `${where} parameter ${parameterIndex} declares multiple injection decorators, but only one is allowed`;
980
+ check(dependency.appliedBy === undefined, ()=>{
981
+ const location = getLocation(target, propertyKey, parameterIndex);
982
+ return `multiple injection decorators on ${location}, but only one is allowed`;
989
983
  });
990
984
  }
985
+ // Checks that the `@Named` decorator is not used in combination with
986
+ // `@InjectAll` or `@OptionalAll`, which ignore the name qualification.
987
+ //
988
+ // @internal
989
+ function checkNamedDecorator(dependency, target, propertyKey, parameterIndex) {
990
+ const { appliedBy, name } = dependency;
991
+ check(name === undefined || appliedBy !== "InjectAll" && appliedBy !== "OptionalAll", ()=>{
992
+ const location = getLocation(target, propertyKey, parameterIndex);
993
+ return `@Named has no effect on ${location} when used with @${appliedBy}`;
994
+ });
995
+ }
996
+ // Returns a human-readable description of the parameter location.
997
+ // For example: "Wizard constructor parameter 2" or "Wizard.set parameter 0"
998
+ //
999
+ // @internal
1000
+ function getLocation(target, propertyKey, parameterIndex) {
1001
+ const location = propertyKey === undefined ? `${target.name} constructor` : `${target.constructor.name}.${String(propertyKey)}`;
1002
+ return `${location} parameter ${parameterIndex}`;
1003
+ }
991
1004
 
992
1005
  function Inject(token) {
993
1006
  return function(target, propertyKey, parameterIndex) {
@@ -1025,6 +1038,7 @@ function InjectAll(token) {
1025
1038
  checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
1026
1039
  dependency.appliedBy = "InjectAll";
1027
1040
  dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1041
+ checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
1028
1042
  });
1029
1043
  };
1030
1044
  }
@@ -1047,21 +1061,23 @@ function InjectAll(token) {
1047
1061
  *
1048
1062
  * @__NO_SIDE_EFFECTS__
1049
1063
  */ function Named(name) {
1050
- if (!name.trim()) {
1051
- assert(false, "the @Named qualifier cannot be empty or blank");
1052
- }
1064
+ check(name.trim(), "the @Named qualifier cannot be empty or blank");
1053
1065
  return function(target, propertyKey, parameterIndex) {
1054
1066
  if (parameterIndex === undefined) {
1055
1067
  // The decorator has been applied to the class
1056
1068
  const ctor = target;
1057
1069
  const metadata = getMetadata(ctor);
1058
- assert(!metadata.name, `a @Named('${metadata.name}') qualifier has already been applied to ${ctor.name}`);
1070
+ check(metadata.name === undefined, `multiple @Named decorators on class ${ctor.name}, but only one is allowed`);
1059
1071
  metadata.name = name;
1060
1072
  } else {
1061
1073
  // The decorator has been applied to a method parameter
1062
1074
  updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
1063
- assert(!dependency.name, `a @Named('${dependency.name}') qualifier has already been applied to the parameter`);
1075
+ check(dependency.name === undefined, ()=>{
1076
+ const location = getLocation(target, propertyKey, parameterIndex);
1077
+ return `multiple @Named decorators on ${location}, but only one is allowed`;
1078
+ });
1064
1079
  dependency.name = name;
1080
+ checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
1065
1081
  });
1066
1082
  }
1067
1083
  };
@@ -1083,6 +1099,7 @@ function OptionalAll(token) {
1083
1099
  checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
1084
1100
  dependency.appliedBy = "OptionalAll";
1085
1101
  dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
1102
+ checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
1086
1103
  });
1087
1104
  };
1088
1105
  }
@@ -1112,7 +1129,7 @@ function OptionalAll(token) {
1112
1129
  return function(Class) {
1113
1130
  const metadata = getMetadata(Class);
1114
1131
  const currentScope = metadata.scope;
1115
- assert(!currentScope || currentScope.value === scope, ()=>{
1132
+ check(!currentScope || currentScope.value === scope, ()=>{
1116
1133
  const { value, appliedBy } = currentScope;
1117
1134
  const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
1118
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.`;