@lppedd/di-wise-neo 0.9.2 → 0.9.4

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}`));
45
17
  }
46
18
  // @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);
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`);
24
+ }
25
+ // @internal
26
+ function throwParameterResolutionError(ctor, methodKey, dependency, cause) {
27
+ const location = getLocation(ctor, methodKey);
28
+ const tokenName = getTokenName(dependency.tokenRef.getRefToken());
29
+ const message = tag(`failed to resolve dependency for ${location}(parameter #${dependency.index}: ${tokenName})`);
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
  }
@@ -358,11 +375,14 @@ class TokenRegistry {
358
375
  set(token, registration) {
359
376
  check(!internals.has(token), `cannot register reserved token ${token.name}`);
360
377
  let registrations = this.myMap.get(token);
361
- if (!registrations) {
378
+ if (registrations) {
379
+ const name = registration.name;
380
+ if (name !== undefined) {
381
+ const existing = registrations.filter((r)=>r.name === name);
382
+ check(existing.length === 0, `token ${getTokenName(token)} with name '${name}' is already registered`);
383
+ }
384
+ } else {
362
385
  this.myMap.set(token, registrations = []);
363
- } else if (registration.name !== undefined) {
364
- const existing = registrations.filter((r)=>r.name === registration.name);
365
- check(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
366
386
  }
367
387
  registrations.push(registration);
368
388
  }
@@ -446,7 +466,6 @@ const builders = new WeakSet();
446
466
  // @internal
447
467
  // @internal
448
468
  function isDisposable(value) {
449
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
450
469
  return value && typeof value === "object" && typeof value.dispose === "function";
451
470
  }
452
471
 
@@ -557,7 +576,7 @@ function isDisposable(value) {
557
576
  const [token, provider, options] = args;
558
577
  const existingProvider = isExistingProvider(provider);
559
578
  const name = existingProvider ? undefined : provider.name;
560
- check(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
579
+ check(name === undefined || name.trim(), `the name qualifier for token ${getTokenName(token)} must not be empty`);
561
580
  if (isClassProvider(provider)) {
562
581
  const metadata = getMetadata(provider.useClass);
563
582
  const registration = {
@@ -578,7 +597,7 @@ function isDisposable(value) {
578
597
  }
579
598
  } else {
580
599
  if (existingProvider) {
581
- check(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
600
+ check(token !== provider.useExisting, `token ${getTokenName(token)} cannot alias itself via useExisting`);
582
601
  }
583
602
  this.myTokenRegistry.set(token, {
584
603
  name: name,
@@ -703,7 +722,6 @@ function isDisposable(value) {
703
722
  const provider = registration.provider;
704
723
  if (isClassProvider(provider)) {
705
724
  const Class = provider.useClass;
706
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
707
725
  return this.resolveScopedValue(token, registration, (args)=>new Class(...args));
708
726
  }
709
727
  if (isFactoryProvider(provider)) {
@@ -730,8 +748,8 @@ function isDisposable(value) {
730
748
  if (resolution.stack.has(provider)) {
731
749
  const dependentRef = resolution.dependents.get(provider);
732
750
  check(dependentRef, ()=>{
733
- const tokenStack = resolution.tokenStack.map((t)=>t.name).concat(token.name).join(" → ");
734
- return `circular dependency detected while resolving ${tokenStack}`;
751
+ const path = resolution.tokenStack.map(getTokenName).join(" → ");
752
+ return `circular dependency detected while resolving ${path} → ${getTokenName(token)}`;
735
753
  });
736
754
  return dependentRef.current;
737
755
  }
@@ -752,8 +770,8 @@ function isDisposable(value) {
752
770
  if (valueRef) {
753
771
  return valueRef.current;
754
772
  }
755
- const args = this.resolveConstructorDependencies(registration);
756
- const value = this.injectDependencies(registration, factory(args));
773
+ const args = this.resolveCtorDependencies(registration);
774
+ const value = this.injectMethodDependencies(registration, factory(args));
757
775
  registration.value = {
758
776
  current: value
759
777
  };
@@ -765,8 +783,8 @@ function isDisposable(value) {
765
783
  if (valueRef) {
766
784
  return valueRef.current;
767
785
  }
768
- const args = this.resolveConstructorDependencies(registration);
769
- const value = this.injectDependencies(registration, factory(args));
786
+ const args = this.resolveCtorDependencies(registration);
787
+ const value = this.injectMethodDependencies(registration, factory(args));
770
788
  resolution.values.set(provider, {
771
789
  current: value
772
790
  });
@@ -774,8 +792,8 @@ function isDisposable(value) {
774
792
  }
775
793
  case Scope.Transient:
776
794
  {
777
- const args = this.resolveConstructorDependencies(registration);
778
- return this.injectDependencies(registration, factory(args));
795
+ const args = this.resolveCtorDependencies(registration);
796
+ return this.injectMethodDependencies(registration, factory(args));
779
797
  }
780
798
  }
781
799
  } finally{
@@ -789,72 +807,74 @@ function isDisposable(value) {
789
807
  }
790
808
  return scope;
791
809
  }
792
- resolveConstructorDependencies(registration) {
810
+ resolveCtorDependencies(registration) {
793
811
  const dependencies = registration.dependencies;
794
812
  if (dependencies) {
795
813
  check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
796
- const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
814
+ const ctorDeps = dependencies.ctor.filter((d)=>d.appliedBy);
797
815
  if (ctorDeps.length > 0) {
798
816
  // Let's check if all necessary constructor parameters are decorated.
799
817
  // If not, we cannot safely create an instance.
800
818
  const ctor = registration.provider.useClass;
801
819
  check(ctor.length === ctorDeps.length, ()=>{
802
- const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
820
+ const location = getLocation(ctor);
821
+ const msg = `${location} expected ${ctor.length} decorated constructor parameters`;
803
822
  return msg + `, but found ${ctorDeps.length}`;
804
823
  });
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
- });
824
+ return this.resolveArgs(ctorDeps, ctor);
818
825
  }
819
826
  }
820
827
  return [];
821
828
  }
822
- injectDependencies(registration, instance) {
829
+ injectMethodDependencies(registration, instance) {
823
830
  const dependencies = registration.dependencies;
824
831
  if (dependencies) {
825
832
  check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
826
833
  const ctor = registration.provider.useClass;
827
834
  // Perform method injection
828
835
  for (const entry of dependencies.methods){
829
- const key = entry[0];
836
+ const methodKey = entry[0];
830
837
  const methodDeps = entry[1].filter((d)=>d.appliedBy);
831
838
  // Let's check if all necessary method parameters are decorated.
832
839
  // If not, we cannot safely invoke the method.
833
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
834
- const method = instance[key];
840
+ const method = instance[methodKey];
835
841
  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}`;
842
+ const location = getLocation(ctor, methodKey);
843
+ const msg = `${location} expected ${method.length} decorated method parameters`;
844
+ return msg + `, but found ${methodDeps.length}`;
838
845
  });
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
- }
851
- });
852
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
846
+ const args = this.resolveArgs(methodDeps, ctor, instance, methodKey);
853
847
  method.bind(instance)(...args);
854
848
  }
855
849
  }
856
850
  return instance;
857
851
  }
852
+ resolveArgs(deps, ctor, instance, methodKey) {
853
+ const sortedDeps = deps.sort((a, b)=>a.index - b.index);
854
+ const args = [];
855
+ for (const dep of sortedDeps){
856
+ try {
857
+ args.push(this.resolveDependency(dep, instance));
858
+ } catch (e) {
859
+ throwParameterResolutionError(ctor, methodKey, dep, e);
860
+ }
861
+ }
862
+ return args;
863
+ }
864
+ resolveDependency(dependency, instance) {
865
+ const token = dependency.tokenRef.getRefToken();
866
+ const name = dependency.name;
867
+ switch(dependency.appliedBy){
868
+ case "Inject":
869
+ return instance ? injectBy(instance, token, name) : this.resolve(token, name);
870
+ case "InjectAll":
871
+ return instance ? injectAll(token) : this.resolveAll(token);
872
+ case "Optional":
873
+ return instance ? optionalBy(instance, token, name) : this.resolve(token, true, name);
874
+ case "OptionalAll":
875
+ return instance ? optionalAll(token) : this.resolveAll(token, true);
876
+ }
877
+ }
858
878
  checkDisposed() {
859
879
  check(!this.myDisposed, "the container is disposed");
860
880
  }
@@ -910,11 +930,13 @@ function isDisposable(value) {
910
930
  * @__NO_SIDE_EFFECTS__
911
931
  */ function EagerInstantiate() {
912
932
  return function(Class) {
913
- const metadata = getMetadata(Class);
933
+ const ctor = Class;
934
+ const metadata = getMetadata(ctor);
914
935
  const currentScope = metadata.scope;
915
936
  check(!currentScope || currentScope.value === Scope.Container, ()=>{
916
937
  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.`;
938
+ const className = getTokenName(ctor);
939
+ 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
940
  });
919
941
  metadata.eagerInstantiate = true;
920
942
  metadata.scope = {
@@ -944,30 +966,28 @@ function forwardRef(token) {
944
966
  }
945
967
  // @internal
946
968
  function isTokensRef(value) {
947
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
948
969
  return value && typeof value === "object" && typeof value.getRefTokens === "function";
949
970
  }
950
971
  // @internal
951
972
  function isTokenRef(value) {
952
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
953
973
  return value && typeof value === "object" && typeof value.getRefToken === "function";
954
974
  }
955
975
 
956
976
  // @internal
957
- function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
977
+ function updateParameterMetadata(decorator, target, methodKey, parameterIndex, updateFn) {
958
978
  // 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)}`);
979
+ if (methodKey !== undefined && typeof target === "function") {
980
+ check(false, `@${decorator} cannot be used on static method ${target.name}.${String(methodKey)}`);
961
981
  }
962
- if (propertyKey === undefined) {
982
+ if (methodKey === undefined) {
963
983
  // Constructor
964
984
  const metadata = getMetadata(target);
965
- const dependency = metadata.getConstructorDependency(parameterIndex);
985
+ const dependency = metadata.getCtorDependency(parameterIndex);
966
986
  updateFn(dependency);
967
987
  } else {
968
988
  // Instance method
969
989
  const metadata = getMetadata(target.constructor);
970
- const dependency = metadata.getMethodDependency(propertyKey, parameterIndex);
990
+ const dependency = metadata.getMethodDependency(methodKey, parameterIndex);
971
991
  updateFn(dependency);
972
992
  }
973
993
  }
@@ -976,30 +996,30 @@ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex,
976
996
  // understand which one "wins", unless the user is aware of the decorator evaluation order.
977
997
  //
978
998
  // @internal
979
- function checkSingleDecorator(dependency, target, propertyKey, parameterIndex) {
999
+ function checkSingleDecorator(dependency, target, methodKey, parameterIndex) {
980
1000
  check(dependency.appliedBy === undefined, ()=>{
981
- const location = getLocation(target, propertyKey, parameterIndex);
982
- return `multiple injection decorators on ${location}, but only one is allowed`;
1001
+ const param = describeParam(target, methodKey, parameterIndex);
1002
+ return `multiple injection decorators on ${param}, but only one is allowed`;
983
1003
  });
984
1004
  }
985
1005
  // Checks that the `@Named` decorator is not used in combination with
986
1006
  // `@InjectAll` or `@OptionalAll`, which ignore the name qualification.
987
1007
  //
988
1008
  // @internal
989
- function checkNamedDecorator(dependency, target, propertyKey, parameterIndex) {
1009
+ function checkNamedDecorator(dependency, target, methodKey, parameterIndex) {
990
1010
  const { appliedBy, name } = dependency;
991
1011
  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}`;
1012
+ const param = describeParam(target, methodKey, parameterIndex);
1013
+ return `@Named has no effect on ${param} when used with @${appliedBy}`;
994
1014
  });
995
1015
  }
996
1016
  // Returns a human-readable description of the parameter location.
997
1017
  // For example: "Wizard constructor parameter 2" or "Wizard.set parameter 0"
998
1018
  //
999
1019
  // @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}`;
1020
+ function describeParam(target, methodKey, parameterIndex) {
1021
+ const location = methodKey === undefined ? target.name : `${target.constructor.name}.${String(methodKey)}`;
1022
+ return `${location}(parameter #${parameterIndex})`;
1003
1023
  }
1004
1024
 
1005
1025
  function Inject(token) {
@@ -1061,20 +1081,21 @@ function InjectAll(token) {
1061
1081
  *
1062
1082
  * @__NO_SIDE_EFFECTS__
1063
1083
  */ function Named(name) {
1064
- check(name.trim(), "the @Named qualifier cannot be empty or blank");
1084
+ check(name.trim(), "the @Named qualifier must not be empty");
1065
1085
  return function(target, propertyKey, parameterIndex) {
1066
1086
  if (parameterIndex === undefined) {
1067
1087
  // The decorator has been applied to the class
1068
1088
  const ctor = target;
1069
1089
  const metadata = getMetadata(ctor);
1070
- check(metadata.name === undefined, `multiple @Named decorators on class ${ctor.name}, but only one is allowed`);
1090
+ const className = getTokenName(ctor);
1091
+ check(metadata.name === undefined, `multiple @Named decorators on class ${className}, but only one is allowed`);
1071
1092
  metadata.name = name;
1072
1093
  } else {
1073
1094
  // The decorator has been applied to a method parameter
1074
1095
  updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
1075
1096
  check(dependency.name === undefined, ()=>{
1076
- const location = getLocation(target, propertyKey, parameterIndex);
1077
- return `multiple @Named decorators on ${location}, but only one is allowed`;
1097
+ const param = describeParam(target, propertyKey, parameterIndex);
1098
+ return `multiple @Named decorators on ${param}, but only one is allowed`;
1078
1099
  });
1079
1100
  dependency.name = name;
1080
1101
  checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
@@ -1127,12 +1148,14 @@ function OptionalAll(token) {
1127
1148
  * @__NO_SIDE_EFFECTS__
1128
1149
  */ function Scoped(scope) {
1129
1150
  return function(Class) {
1130
- const metadata = getMetadata(Class);
1151
+ const ctor = Class;
1152
+ const metadata = getMetadata(ctor);
1131
1153
  const currentScope = metadata.scope;
1132
1154
  check(!currentScope || currentScope.value === scope, ()=>{
1133
1155
  const { value, appliedBy } = currentScope;
1134
1156
  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.`;
1157
+ const className = getTokenName(ctor);
1158
+ 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
1159
  });
1137
1160
  metadata.scope = {
1138
1161
  value: scope,
@@ -1214,10 +1237,7 @@ function OptionalAll(token) {
1214
1237
  use (key, wrap) {
1215
1238
  // We need to bind the 'this' context of the function to the container
1216
1239
  // 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
1240
  const fn = container[key].bind(container);
1220
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1221
1241
  container[key] = wrap(fn);
1222
1242
  return composer;
1223
1243
  }