@lppedd/di-wise-neo 0.9.2 → 0.9.3

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
@@ -1,30 +1,3 @@
1
- /**
2
- * Type API.
3
- */ /**
4
- * Creates a type token.
5
- *
6
- * @example
7
- * ```ts
8
- * const ISpell = createType<Spell>("Spell");
9
- * ```
10
- *
11
- * @__NO_SIDE_EFFECTS__
12
- */ function createType(typeName) {
13
- const type = {
14
- name: `Type<${typeName}>`,
15
- inter: createType,
16
- union: createType,
17
- toString () {
18
- return type.name;
19
- }
20
- };
21
- return type;
22
- }
23
- // @internal
24
- function isConstructor(token) {
25
- return typeof token === "function";
26
- }
27
-
28
1
  // @internal
29
2
  function check(condition, message) {
30
3
  if (!condition) {
@@ -37,18 +10,35 @@ function expectNever(value) {
37
10
  }
38
11
  // @internal
39
12
  function throwUnregisteredError(token, name) {
40
- const type = isConstructor(token) ? "class" : "token";
41
13
  const spec = name !== undefined ? `[name=${name}]` : "";
42
- throw new Error(tag(`unregistered ${type} ${token.name}${spec}`));
14
+ throw new Error(tag(`unregistered token ${getTokenName(token)}${spec}`));
15
+ }
16
+ // @internal
17
+ function throwExistingUnregisteredError(token, cause) {
18
+ const message = tag(`failed to resolve token ${getTokenName(token)}`);
19
+ throw isError(cause) ? new Error(`${message}\n [cause] ${untag(cause.message)}`, {
20
+ cause
21
+ }) : new Error(`${message}\n [cause] the aliased token ${getTokenName(cause)} is not registered`);
43
22
  }
44
23
  // @internal
45
- function throwExistingUnregisteredError(sourceToken, targetTokenOrError) {
46
- let message = tag(`token resolution error encountered while resolving ${sourceToken.name}`);
47
- message += isError(targetTokenOrError) ? `\n [cause] ${untag(targetTokenOrError.message)}` : `\n [cause] the aliased token ${targetTokenOrError.name} is not registered`;
48
- throw new Error(message);
24
+ function throwParameterResolutionError(ctor, methodKey, dependency, cause) {
25
+ const location = getLocation(ctor, methodKey);
26
+ const token = dependency.tokenRef.getRefToken();
27
+ const message = tag(`failed to resolve dependency for ${location}(parameter #${dependency.index}: ${token.name})`);
28
+ throw new Error(`${message}\n [cause] ${untag(cause.message)}`, {
29
+ cause
30
+ });
31
+ }
32
+ // @internal
33
+ function getLocation(ctor, methodKey) {
34
+ const ctorName = ctor.name || "<unnamed>";
35
+ return methodKey ? `${ctorName}.${String(methodKey)}` : ctorName;
36
+ }
37
+ // @internal
38
+ function getTokenName(token) {
39
+ return token.name || "<unnamed>";
49
40
  }
50
41
  function isError(value) {
51
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
52
42
  return value && value.stack && value.message && typeof value.message === "string";
53
43
  }
54
44
  function tag(message) {
@@ -174,7 +164,7 @@ function injectAll(token) {
174
164
  class Metadata {
175
165
  constructor(Class){
176
166
  this.dependencies = {
177
- constructor: [],
167
+ ctor: [],
178
168
  methods: new Map()
179
169
  };
180
170
  this.tokensRef = {
@@ -190,15 +180,15 @@ class Metadata {
190
180
  set name(name) {
191
181
  this.provider.name = name;
192
182
  }
193
- getConstructorDependency(index) {
194
- const i = this.dependencies.constructor.findIndex((d)=>d.index === index);
183
+ getCtorDependency(index) {
184
+ const i = this.dependencies.ctor.findIndex((d)=>d.index === index);
195
185
  if (i > -1) {
196
- return this.dependencies.constructor[i];
186
+ return this.dependencies.ctor[i];
197
187
  }
198
188
  const dependency = {
199
189
  index: index
200
190
  };
201
- this.dependencies.constructor.push(dependency);
191
+ this.dependencies.ctor.push(dependency);
202
192
  return dependency;
203
193
  }
204
194
  getMethodDependency(method, index) {
@@ -312,6 +302,33 @@ const Scope = {
312
302
  Container: "Container"
313
303
  };
314
304
 
305
+ /**
306
+ * Type API.
307
+ */ /**
308
+ * Creates a type token.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * const ISpell = createType<Spell>("Spell");
313
+ * ```
314
+ *
315
+ * @__NO_SIDE_EFFECTS__
316
+ */ function createType(typeName) {
317
+ const type = {
318
+ name: `Type<${typeName}>`,
319
+ inter: createType,
320
+ union: createType,
321
+ toString () {
322
+ return type.name;
323
+ }
324
+ };
325
+ return type;
326
+ }
327
+ // @internal
328
+ function isConstructor(token) {
329
+ return typeof token === "function";
330
+ }
331
+
315
332
  // @internal
316
333
  function getTypeName(value) {
317
334
  switch(typeof value){
@@ -326,9 +343,9 @@ function getTypeName(value) {
326
343
  }
327
344
  const proto = Object.getPrototypeOf(value);
328
345
  if (proto && proto !== Object.prototype) {
329
- const constructor = proto.constructor;
330
- if (typeof constructor === "function" && constructor.name) {
331
- return constructor.name;
346
+ const ctor = proto.constructor;
347
+ if (typeof ctor === "function" && ctor.name) {
348
+ return ctor.name;
332
349
  }
333
350
  }
334
351
  }
@@ -444,7 +461,6 @@ const builders = new WeakSet();
444
461
  // @internal
445
462
  // @internal
446
463
  function isDisposable(value) {
447
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
448
464
  return value && typeof value === "object" && typeof value.dispose === "function";
449
465
  }
450
466
 
@@ -555,7 +571,7 @@ function isDisposable(value) {
555
571
  const [token, provider, options] = args;
556
572
  const existingProvider = isExistingProvider(provider);
557
573
  const name = existingProvider ? undefined : provider.name;
558
- check(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
574
+ check(name === undefined || name.trim(), `the name qualifier for token ${getTokenName(token)} must not be empty`);
559
575
  if (isClassProvider(provider)) {
560
576
  const metadata = getMetadata(provider.useClass);
561
577
  const registration = {
@@ -701,7 +717,6 @@ function isDisposable(value) {
701
717
  const provider = registration.provider;
702
718
  if (isClassProvider(provider)) {
703
719
  const Class = provider.useClass;
704
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
705
720
  return this.resolveScopedValue(token, registration, (args)=>new Class(...args));
706
721
  }
707
722
  if (isFactoryProvider(provider)) {
@@ -728,8 +743,8 @@ function isDisposable(value) {
728
743
  if (resolution.stack.has(provider)) {
729
744
  const dependentRef = resolution.dependents.get(provider);
730
745
  check(dependentRef, ()=>{
731
- const tokenStack = resolution.tokenStack.map((t)=>t.name).concat(token.name).join(" → ");
732
- return `circular dependency detected while resolving ${tokenStack}`;
746
+ const path = resolution.tokenStack.map((t)=>t.name).join(" → ");
747
+ return `circular dependency detected while resolving ${path} → ${token.name}`;
733
748
  });
734
749
  return dependentRef.current;
735
750
  }
@@ -750,8 +765,8 @@ function isDisposable(value) {
750
765
  if (valueRef) {
751
766
  return valueRef.current;
752
767
  }
753
- const args = this.resolveConstructorDependencies(registration);
754
- const value = this.injectDependencies(registration, factory(args));
768
+ const args = this.resolveCtorDependencies(registration);
769
+ const value = this.injectMethodDependencies(registration, factory(args));
755
770
  registration.value = {
756
771
  current: value
757
772
  };
@@ -763,8 +778,8 @@ function isDisposable(value) {
763
778
  if (valueRef) {
764
779
  return valueRef.current;
765
780
  }
766
- const args = this.resolveConstructorDependencies(registration);
767
- const value = this.injectDependencies(registration, factory(args));
781
+ const args = this.resolveCtorDependencies(registration);
782
+ const value = this.injectMethodDependencies(registration, factory(args));
768
783
  resolution.values.set(provider, {
769
784
  current: value
770
785
  });
@@ -772,8 +787,8 @@ function isDisposable(value) {
772
787
  }
773
788
  case Scope.Transient:
774
789
  {
775
- const args = this.resolveConstructorDependencies(registration);
776
- return this.injectDependencies(registration, factory(args));
790
+ const args = this.resolveCtorDependencies(registration);
791
+ return this.injectMethodDependencies(registration, factory(args));
777
792
  }
778
793
  }
779
794
  } finally{
@@ -787,72 +802,74 @@ function isDisposable(value) {
787
802
  }
788
803
  return scope;
789
804
  }
790
- resolveConstructorDependencies(registration) {
805
+ resolveCtorDependencies(registration) {
791
806
  const dependencies = registration.dependencies;
792
807
  if (dependencies) {
793
808
  check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
794
- const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
809
+ const ctorDeps = dependencies.ctor.filter((d)=>d.appliedBy);
795
810
  if (ctorDeps.length > 0) {
796
811
  // Let's check if all necessary constructor parameters are decorated.
797
812
  // If not, we cannot safely create an instance.
798
813
  const ctor = registration.provider.useClass;
799
814
  check(ctor.length === ctorDeps.length, ()=>{
800
- const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
815
+ const location = getLocation(ctor);
816
+ const msg = `${location} expected ${ctor.length} decorated constructor parameters`;
801
817
  return msg + `, but found ${ctorDeps.length}`;
802
818
  });
803
- return ctorDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
804
- const token = dep.tokenRef.getRefToken();
805
- switch(dep.appliedBy){
806
- case "Inject":
807
- return this.resolve(token, dep.name);
808
- case "InjectAll":
809
- return this.resolveAll(token);
810
- case "Optional":
811
- return this.resolve(token, true, dep.name);
812
- case "OptionalAll":
813
- return this.resolveAll(token, true);
814
- }
815
- });
819
+ return this.resolveArgs(ctorDeps, ctor);
816
820
  }
817
821
  }
818
822
  return [];
819
823
  }
820
- injectDependencies(registration, instance) {
824
+ injectMethodDependencies(registration, instance) {
821
825
  const dependencies = registration.dependencies;
822
826
  if (dependencies) {
823
827
  check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
824
828
  const ctor = registration.provider.useClass;
825
829
  // Perform method injection
826
830
  for (const entry of dependencies.methods){
827
- const key = entry[0];
831
+ const methodKey = entry[0];
828
832
  const methodDeps = entry[1].filter((d)=>d.appliedBy);
829
833
  // Let's check if all necessary method parameters are decorated.
830
834
  // If not, we cannot safely invoke the method.
831
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
832
- const method = instance[key];
835
+ const method = instance[methodKey];
833
836
  check(methodDeps.length === method.length, ()=>{
834
- const msg = `expected ${method.length} decorated method parameters`;
835
- return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
836
- });
837
- const args = methodDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
838
- const token = dep.tokenRef.getRefToken();
839
- switch(dep.appliedBy){
840
- case "Inject":
841
- return injectBy(instance, token, dep.name);
842
- case "InjectAll":
843
- return injectAll(token);
844
- case "Optional":
845
- return optionalBy(instance, token, dep.name);
846
- case "OptionalAll":
847
- return optionalAll(token);
848
- }
837
+ const location = getLocation(ctor, methodKey);
838
+ const msg = `${location} expected ${method.length} decorated method parameters`;
839
+ return msg + `, but found ${methodDeps.length}`;
849
840
  });
850
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
841
+ const args = this.resolveArgs(methodDeps, ctor, instance, methodKey);
851
842
  method.bind(instance)(...args);
852
843
  }
853
844
  }
854
845
  return instance;
855
846
  }
847
+ resolveArgs(deps, ctor, instance, methodKey) {
848
+ const sortedDeps = deps.sort((a, b)=>a.index - b.index);
849
+ const args = [];
850
+ for (const dep of sortedDeps){
851
+ try {
852
+ args.push(this.resolveDependency(dep, instance));
853
+ } catch (e) {
854
+ throwParameterResolutionError(ctor, methodKey, dep, e);
855
+ }
856
+ }
857
+ return args;
858
+ }
859
+ resolveDependency(dependency, instance) {
860
+ const token = dependency.tokenRef.getRefToken();
861
+ const name = dependency.name;
862
+ switch(dependency.appliedBy){
863
+ case "Inject":
864
+ return instance ? injectBy(instance, token, name) : this.resolve(token, name);
865
+ case "InjectAll":
866
+ return instance ? injectAll(token) : this.resolveAll(token);
867
+ case "Optional":
868
+ return instance ? optionalBy(instance, token, name) : this.resolve(token, true, name);
869
+ case "OptionalAll":
870
+ return instance ? optionalAll(token) : this.resolveAll(token, true);
871
+ }
872
+ }
856
873
  checkDisposed() {
857
874
  check(!this.myDisposed, "the container is disposed");
858
875
  }
@@ -908,11 +925,13 @@ function isDisposable(value) {
908
925
  * @__NO_SIDE_EFFECTS__
909
926
  */ function EagerInstantiate() {
910
927
  return function(Class) {
911
- const metadata = getMetadata(Class);
928
+ const ctor = Class;
929
+ const metadata = getMetadata(ctor);
912
930
  const currentScope = metadata.scope;
913
931
  check(!currentScope || currentScope.value === Scope.Container, ()=>{
914
932
  const { value, appliedBy } = currentScope;
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.`;
933
+ const className = getTokenName(ctor);
934
+ return `class ${className}: 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.`;
916
935
  });
917
936
  metadata.eagerInstantiate = true;
918
937
  metadata.scope = {
@@ -942,30 +961,28 @@ function forwardRef(token) {
942
961
  }
943
962
  // @internal
944
963
  function isTokensRef(value) {
945
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
946
964
  return value && typeof value === "object" && typeof value.getRefTokens === "function";
947
965
  }
948
966
  // @internal
949
967
  function isTokenRef(value) {
950
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
951
968
  return value && typeof value === "object" && typeof value.getRefToken === "function";
952
969
  }
953
970
 
954
971
  // @internal
955
- function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
972
+ function updateParameterMetadata(decorator, target, methodKey, parameterIndex, updateFn) {
956
973
  // Error out immediately if the decorator has been applied to a static method
957
- if (propertyKey !== undefined && typeof target === "function") {
958
- check(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
974
+ if (methodKey !== undefined && typeof target === "function") {
975
+ check(false, `@${decorator} cannot be used on static method ${target.name}.${String(methodKey)}`);
959
976
  }
960
- if (propertyKey === undefined) {
977
+ if (methodKey === undefined) {
961
978
  // Constructor
962
979
  const metadata = getMetadata(target);
963
- const dependency = metadata.getConstructorDependency(parameterIndex);
980
+ const dependency = metadata.getCtorDependency(parameterIndex);
964
981
  updateFn(dependency);
965
982
  } else {
966
983
  // Instance method
967
984
  const metadata = getMetadata(target.constructor);
968
- const dependency = metadata.getMethodDependency(propertyKey, parameterIndex);
985
+ const dependency = metadata.getMethodDependency(methodKey, parameterIndex);
969
986
  updateFn(dependency);
970
987
  }
971
988
  }
@@ -974,30 +991,30 @@ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex,
974
991
  // understand which one "wins", unless the user is aware of the decorator evaluation order.
975
992
  //
976
993
  // @internal
977
- function checkSingleDecorator(dependency, target, propertyKey, parameterIndex) {
994
+ function checkSingleDecorator(dependency, target, methodKey, parameterIndex) {
978
995
  check(dependency.appliedBy === undefined, ()=>{
979
- const location = getLocation(target, propertyKey, parameterIndex);
980
- return `multiple injection decorators on ${location}, but only one is allowed`;
996
+ const param = describeParam(target, methodKey, parameterIndex);
997
+ return `multiple injection decorators on ${param}, but only one is allowed`;
981
998
  });
982
999
  }
983
1000
  // Checks that the `@Named` decorator is not used in combination with
984
1001
  // `@InjectAll` or `@OptionalAll`, which ignore the name qualification.
985
1002
  //
986
1003
  // @internal
987
- function checkNamedDecorator(dependency, target, propertyKey, parameterIndex) {
1004
+ function checkNamedDecorator(dependency, target, methodKey, parameterIndex) {
988
1005
  const { appliedBy, name } = dependency;
989
1006
  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}`;
1007
+ const param = describeParam(target, methodKey, parameterIndex);
1008
+ return `@Named has no effect on ${param} when used with @${appliedBy}`;
992
1009
  });
993
1010
  }
994
1011
  // Returns a human-readable description of the parameter location.
995
1012
  // For example: "Wizard constructor parameter 2" or "Wizard.set parameter 0"
996
1013
  //
997
1014
  // @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}`;
1015
+ function describeParam(target, methodKey, parameterIndex) {
1016
+ const location = methodKey === undefined ? target.name : `${target.constructor.name}.${String(methodKey)}`;
1017
+ return `${location}(parameter #${parameterIndex})`;
1001
1018
  }
1002
1019
 
1003
1020
  function Inject(token) {
@@ -1059,20 +1076,21 @@ function InjectAll(token) {
1059
1076
  *
1060
1077
  * @__NO_SIDE_EFFECTS__
1061
1078
  */ function Named(name) {
1062
- check(name.trim(), "the @Named qualifier cannot be empty or blank");
1079
+ check(name.trim(), "the @Named qualifier must not be empty");
1063
1080
  return function(target, propertyKey, parameterIndex) {
1064
1081
  if (parameterIndex === undefined) {
1065
1082
  // The decorator has been applied to the class
1066
1083
  const ctor = target;
1067
1084
  const metadata = getMetadata(ctor);
1068
- check(metadata.name === undefined, `multiple @Named decorators on class ${ctor.name}, but only one is allowed`);
1085
+ const className = getTokenName(ctor);
1086
+ check(metadata.name === undefined, `multiple @Named decorators on class ${className}, but only one is allowed`);
1069
1087
  metadata.name = name;
1070
1088
  } else {
1071
1089
  // The decorator has been applied to a method parameter
1072
1090
  updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
1073
1091
  check(dependency.name === undefined, ()=>{
1074
- const location = getLocation(target, propertyKey, parameterIndex);
1075
- return `multiple @Named decorators on ${location}, but only one is allowed`;
1092
+ const param = describeParam(target, propertyKey, parameterIndex);
1093
+ return `multiple @Named decorators on ${param}, but only one is allowed`;
1076
1094
  });
1077
1095
  dependency.name = name;
1078
1096
  checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
@@ -1125,12 +1143,14 @@ function OptionalAll(token) {
1125
1143
  * @__NO_SIDE_EFFECTS__
1126
1144
  */ function Scoped(scope) {
1127
1145
  return function(Class) {
1128
- const metadata = getMetadata(Class);
1146
+ const ctor = Class;
1147
+ const metadata = getMetadata(ctor);
1129
1148
  const currentScope = metadata.scope;
1130
1149
  check(!currentScope || currentScope.value === scope, ()=>{
1131
1150
  const { value, appliedBy } = currentScope;
1132
1151
  const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
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.`;
1152
+ const className = getTokenName(ctor);
1153
+ return `class ${className}: 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.`;
1134
1154
  });
1135
1155
  metadata.scope = {
1136
1156
  value: scope,
@@ -1212,10 +1232,7 @@ function OptionalAll(token) {
1212
1232
  use (key, wrap) {
1213
1233
  // We need to bind the 'this' context of the function to the container
1214
1234
  // before passing it to the middleware wrapper.
1215
- //
1216
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
1217
1235
  const fn = container[key].bind(container);
1218
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1219
1236
  container[key] = wrap(fn);
1220
1237
  return composer;
1221
1238
  }