@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 +134 -117
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +134 -117
- package/dist/es/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
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
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
-
|
194
|
-
const i = this.dependencies.
|
183
|
+
getCtorDependency(index) {
|
184
|
+
const i = this.dependencies.ctor.findIndex((d)=>d.index === index);
|
195
185
|
if (i > -1) {
|
196
|
-
return this.dependencies.
|
186
|
+
return this.dependencies.ctor[i];
|
197
187
|
}
|
198
188
|
const dependency = {
|
199
189
|
index: index
|
200
190
|
};
|
201
|
-
this.dependencies.
|
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
|
330
|
-
if (typeof
|
331
|
-
return
|
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(),
|
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
|
732
|
-
return `circular dependency detected while resolving ${
|
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.
|
754
|
-
const value = this.
|
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.
|
767
|
-
const value = this.
|
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.
|
776
|
-
return this.
|
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
|
-
|
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.
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
832
|
-
const method = instance[key];
|
835
|
+
const method = instance[methodKey];
|
833
836
|
check(methodDeps.length === method.length, ()=>{
|
834
|
-
const
|
835
|
-
|
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
|
-
|
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
|
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
|
-
|
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,
|
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 (
|
958
|
-
check(false, `@${decorator} cannot be used on static method ${target.name}.${String(
|
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 (
|
977
|
+
if (methodKey === undefined) {
|
961
978
|
// Constructor
|
962
979
|
const metadata = getMetadata(target);
|
963
|
-
const dependency = metadata.
|
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(
|
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,
|
994
|
+
function checkSingleDecorator(dependency, target, methodKey, parameterIndex) {
|
978
995
|
check(dependency.appliedBy === undefined, ()=>{
|
979
|
-
const
|
980
|
-
return `multiple injection decorators on ${
|
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,
|
1004
|
+
function checkNamedDecorator(dependency, target, methodKey, parameterIndex) {
|
988
1005
|
const { appliedBy, name } = dependency;
|
989
1006
|
check(name === undefined || appliedBy !== "InjectAll" && appliedBy !== "OptionalAll", ()=>{
|
990
|
-
const
|
991
|
-
return `@Named has no effect on ${
|
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
|
999
|
-
const location =
|
1000
|
-
return `${location}
|
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
|
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
|
-
|
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
|
1075
|
-
return `multiple @Named decorators on ${
|
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
|
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
|
-
|
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
|
}
|