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