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