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