@lppedd/di-wise-neo 0.9.0 → 0.9.2
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 +67 -50
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +67 -50
- package/dist/es/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
@@ -28,7 +28,7 @@ function isConstructor(token) {
|
|
28
28
|
}
|
29
29
|
|
30
30
|
// @internal
|
31
|
-
function
|
31
|
+
function check(condition, message) {
|
32
32
|
if (!condition) {
|
33
33
|
throw new Error(tag(typeof message === "string" ? message : message()));
|
34
34
|
}
|
@@ -60,13 +60,6 @@ function untag(message) {
|
|
60
60
|
return message.startsWith("[di-wise-neo]") ? message.substring(13).trimStart() : message;
|
61
61
|
}
|
62
62
|
|
63
|
-
// @internal
|
64
|
-
function invariant(condition) {
|
65
|
-
if (!condition) {
|
66
|
-
throw new Error("invariant violation");
|
67
|
-
}
|
68
|
-
}
|
69
|
-
|
70
63
|
// @internal
|
71
64
|
class KeyedStack {
|
72
65
|
has(key) {
|
@@ -77,7 +70,7 @@ class KeyedStack {
|
|
77
70
|
return entry?.value;
|
78
71
|
}
|
79
72
|
push(key, value) {
|
80
|
-
|
73
|
+
check(!this.has(key), "invariant violation");
|
81
74
|
this.myKeys.add(key);
|
82
75
|
this.myEntries.push({
|
83
76
|
key,
|
@@ -89,7 +82,7 @@ class KeyedStack {
|
|
89
82
|
};
|
90
83
|
}
|
91
84
|
constructor(){
|
92
|
-
this.myEntries =
|
85
|
+
this.myEntries = [];
|
93
86
|
this.myKeys = new WeakSet();
|
94
87
|
}
|
95
88
|
}
|
@@ -108,7 +101,7 @@ class WeakRefMap {
|
|
108
101
|
return undefined;
|
109
102
|
}
|
110
103
|
set(key, value) {
|
111
|
-
|
104
|
+
check(!this.get(key), "invariant violation");
|
112
105
|
this.myMap.set(key, new WeakRef(value));
|
113
106
|
return ()=>{
|
114
107
|
this.myMap.delete(key);
|
@@ -122,6 +115,7 @@ class WeakRefMap {
|
|
122
115
|
// @internal
|
123
116
|
function createResolution() {
|
124
117
|
return {
|
118
|
+
tokenStack: [],
|
125
119
|
stack: new KeyedStack(),
|
126
120
|
values: new WeakRefMap(),
|
127
121
|
dependents: new WeakRefMap()
|
@@ -132,7 +126,7 @@ const [provideInjectionContext, useInjectionContext] = createInjectionContext();
|
|
132
126
|
// @internal
|
133
127
|
function ensureInjectionContext(name) {
|
134
128
|
const context = useInjectionContext();
|
135
|
-
|
129
|
+
check(context, `${name} can only be invoked within an injection context`);
|
136
130
|
return context;
|
137
131
|
}
|
138
132
|
function createInjectionContext() {
|
@@ -362,13 +356,13 @@ class TokenRegistry {
|
|
362
356
|
] || this.getAllFromParent(token, name);
|
363
357
|
}
|
364
358
|
set(token, registration) {
|
365
|
-
|
359
|
+
check(!internals.has(token), `cannot register reserved token ${token.name}`);
|
366
360
|
let registrations = this.myMap.get(token);
|
367
361
|
if (!registrations) {
|
368
362
|
this.myMap.set(token, registrations = []);
|
369
363
|
} else if (registration.name !== undefined) {
|
370
364
|
const existing = registrations.filter((r)=>r.name === registration.name);
|
371
|
-
|
365
|
+
check(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
|
372
366
|
}
|
373
367
|
registrations.push(registration);
|
374
368
|
}
|
@@ -422,7 +416,7 @@ class TokenRegistry {
|
|
422
416
|
let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
|
423
417
|
if (registrations && name !== undefined) {
|
424
418
|
registrations = registrations.filter((r)=>r.name === name);
|
425
|
-
|
419
|
+
check(registrations.length < 2, `internal error: more than one registration named '${name}'`);
|
426
420
|
}
|
427
421
|
return registrations ?? [];
|
428
422
|
}
|
@@ -557,13 +551,13 @@ function isDisposable(value) {
|
|
557
551
|
}
|
558
552
|
// Eager-instantiate only if the class is container-scoped
|
559
553
|
if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
|
560
|
-
this.resolveProviderValue(
|
554
|
+
this.resolveProviderValue(Class, registration);
|
561
555
|
}
|
562
556
|
} else {
|
563
557
|
const [token, provider, options] = args;
|
564
558
|
const existingProvider = isExistingProvider(provider);
|
565
559
|
const name = existingProvider ? undefined : provider.name;
|
566
|
-
|
560
|
+
check(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
|
567
561
|
if (isClassProvider(provider)) {
|
568
562
|
const metadata = getMetadata(provider.useClass);
|
569
563
|
const registration = {
|
@@ -580,11 +574,11 @@ function isDisposable(value) {
|
|
580
574
|
this.myTokenRegistry.set(token, registration);
|
581
575
|
// Eager-instantiate only if the provided class is container-scoped
|
582
576
|
if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
|
583
|
-
this.resolveProviderValue(
|
577
|
+
this.resolveProviderValue(token, registration);
|
584
578
|
}
|
585
579
|
} else {
|
586
580
|
if (existingProvider) {
|
587
|
-
|
581
|
+
check(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
|
588
582
|
}
|
589
583
|
this.myTokenRegistry.set(token, {
|
590
584
|
name: name,
|
@@ -670,17 +664,15 @@ function isDisposable(value) {
|
|
670
664
|
}
|
671
665
|
resolveRegistration(token, registration, name) {
|
672
666
|
let currRegistration = registration;
|
673
|
-
|
674
|
-
|
675
|
-
const targetToken = currProvider.useExisting;
|
667
|
+
while(isExistingProvider(currRegistration.provider)){
|
668
|
+
const targetToken = currRegistration.provider.useExisting;
|
676
669
|
currRegistration = this.myTokenRegistry.get(targetToken, name);
|
677
670
|
if (!currRegistration) {
|
678
671
|
throwExistingUnregisteredError(token, targetToken);
|
679
672
|
}
|
680
|
-
currProvider = currRegistration.provider;
|
681
673
|
}
|
682
674
|
try {
|
683
|
-
return this.resolveProviderValue(
|
675
|
+
return this.resolveProviderValue(token, currRegistration);
|
684
676
|
} catch (e) {
|
685
677
|
// If we were trying to resolve a token registered via ExistingProvider,
|
686
678
|
// we must add the cause of the error to the message
|
@@ -707,26 +699,24 @@ function isDisposable(value) {
|
|
707
699
|
}
|
708
700
|
return undefined;
|
709
701
|
}
|
710
|
-
resolveProviderValue(
|
711
|
-
|
702
|
+
resolveProviderValue(token, registration) {
|
703
|
+
const provider = registration.provider;
|
712
704
|
if (isClassProvider(provider)) {
|
713
705
|
const Class = provider.useClass;
|
714
706
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
715
|
-
return this.resolveScopedValue(registration, (args)=>new Class(...args));
|
707
|
+
return this.resolveScopedValue(token, registration, (args)=>new Class(...args));
|
716
708
|
}
|
717
709
|
if (isFactoryProvider(provider)) {
|
718
710
|
const factory = provider.useFactory;
|
719
|
-
return this.resolveScopedValue(registration, factory);
|
711
|
+
return this.resolveScopedValue(token, registration, factory);
|
720
712
|
}
|
721
713
|
if (isValueProvider(provider)) {
|
722
714
|
return provider.useValue;
|
723
715
|
}
|
724
|
-
|
725
|
-
assert(false, "internal error: unexpected ExistingProvider");
|
726
|
-
}
|
716
|
+
check(!isExistingProvider(provider), "internal error: unexpected ExistingProvider");
|
727
717
|
expectNever(provider);
|
728
718
|
}
|
729
|
-
resolveScopedValue(registration, factory) {
|
719
|
+
resolveScopedValue(token, registration, factory) {
|
730
720
|
let context = useInjectionContext();
|
731
721
|
if (!context || context.container !== this) {
|
732
722
|
context = {
|
@@ -739,12 +729,16 @@ function isDisposable(value) {
|
|
739
729
|
const options = registration.options;
|
740
730
|
if (resolution.stack.has(provider)) {
|
741
731
|
const dependentRef = resolution.dependents.get(provider);
|
742
|
-
|
732
|
+
check(dependentRef, ()=>{
|
733
|
+
const tokenStack = resolution.tokenStack.map((t)=>t.name).concat(token.name).join(" → ");
|
734
|
+
return `circular dependency detected while resolving ${tokenStack}`;
|
735
|
+
});
|
743
736
|
return dependentRef.current;
|
744
737
|
}
|
745
738
|
const scope = this.resolveScope(options?.scope, context);
|
746
739
|
const cleanups = [
|
747
740
|
provideInjectionContext(context),
|
741
|
+
resolution.tokenStack.push(token) && (()=>resolution.tokenStack.pop()),
|
748
742
|
!isBuilder(provider) && resolution.stack.push(provider, {
|
749
743
|
provider,
|
750
744
|
scope
|
@@ -798,13 +792,13 @@ function isDisposable(value) {
|
|
798
792
|
resolveConstructorDependencies(registration) {
|
799
793
|
const dependencies = registration.dependencies;
|
800
794
|
if (dependencies) {
|
801
|
-
|
795
|
+
check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
802
796
|
const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
|
803
797
|
if (ctorDeps.length > 0) {
|
804
798
|
// Let's check if all necessary constructor parameters are decorated.
|
805
799
|
// If not, we cannot safely create an instance.
|
806
800
|
const ctor = registration.provider.useClass;
|
807
|
-
|
801
|
+
check(ctor.length === ctorDeps.length, ()=>{
|
808
802
|
const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
|
809
803
|
return msg + `, but found ${ctorDeps.length}`;
|
810
804
|
});
|
@@ -828,7 +822,7 @@ function isDisposable(value) {
|
|
828
822
|
injectDependencies(registration, instance) {
|
829
823
|
const dependencies = registration.dependencies;
|
830
824
|
if (dependencies) {
|
831
|
-
|
825
|
+
check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
832
826
|
const ctor = registration.provider.useClass;
|
833
827
|
// Perform method injection
|
834
828
|
for (const entry of dependencies.methods){
|
@@ -838,7 +832,7 @@ function isDisposable(value) {
|
|
838
832
|
// If not, we cannot safely invoke the method.
|
839
833
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
840
834
|
const method = instance[key];
|
841
|
-
|
835
|
+
check(methodDeps.length === method.length, ()=>{
|
842
836
|
const msg = `expected ${method.length} decorated method parameters`;
|
843
837
|
return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
|
844
838
|
});
|
@@ -862,7 +856,7 @@ function isDisposable(value) {
|
|
862
856
|
return instance;
|
863
857
|
}
|
864
858
|
checkDisposed() {
|
865
|
-
|
859
|
+
check(!this.myDisposed, "the container is disposed");
|
866
860
|
}
|
867
861
|
}
|
868
862
|
|
@@ -918,7 +912,7 @@ function isDisposable(value) {
|
|
918
912
|
return function(Class) {
|
919
913
|
const metadata = getMetadata(Class);
|
920
914
|
const currentScope = metadata.scope;
|
921
|
-
|
915
|
+
check(!currentScope || currentScope.value === Scope.Container, ()=>{
|
922
916
|
const { value, appliedBy } = currentScope;
|
923
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.`;
|
924
918
|
});
|
@@ -943,7 +937,7 @@ function forwardRef(token) {
|
|
943
937
|
},
|
944
938
|
getRefToken: ()=>{
|
945
939
|
const tokenOrTokens = token();
|
946
|
-
|
940
|
+
check(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
|
947
941
|
return tokenOrTokens;
|
948
942
|
}
|
949
943
|
};
|
@@ -963,7 +957,7 @@ function isTokenRef(value) {
|
|
963
957
|
function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
|
964
958
|
// Error out immediately if the decorator has been applied to a static method
|
965
959
|
if (propertyKey !== undefined && typeof target === "function") {
|
966
|
-
|
960
|
+
check(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
|
967
961
|
}
|
968
962
|
if (propertyKey === undefined) {
|
969
963
|
// Constructor
|
@@ -983,11 +977,30 @@ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex,
|
|
983
977
|
//
|
984
978
|
// @internal
|
985
979
|
function checkSingleDecorator(dependency, target, propertyKey, parameterIndex) {
|
986
|
-
|
987
|
-
const
|
988
|
-
return
|
980
|
+
check(dependency.appliedBy === undefined, ()=>{
|
981
|
+
const location = getLocation(target, propertyKey, parameterIndex);
|
982
|
+
return `multiple injection decorators on ${location}, but only one is allowed`;
|
989
983
|
});
|
990
984
|
}
|
985
|
+
// Checks that the `@Named` decorator is not used in combination with
|
986
|
+
// `@InjectAll` or `@OptionalAll`, which ignore the name qualification.
|
987
|
+
//
|
988
|
+
// @internal
|
989
|
+
function checkNamedDecorator(dependency, target, propertyKey, parameterIndex) {
|
990
|
+
const { appliedBy, name } = dependency;
|
991
|
+
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}`;
|
994
|
+
});
|
995
|
+
}
|
996
|
+
// Returns a human-readable description of the parameter location.
|
997
|
+
// For example: "Wizard constructor parameter 2" or "Wizard.set parameter 0"
|
998
|
+
//
|
999
|
+
// @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}`;
|
1003
|
+
}
|
991
1004
|
|
992
1005
|
function Inject(token) {
|
993
1006
|
return function(target, propertyKey, parameterIndex) {
|
@@ -1025,6 +1038,7 @@ function InjectAll(token) {
|
|
1025
1038
|
checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
|
1026
1039
|
dependency.appliedBy = "InjectAll";
|
1027
1040
|
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1041
|
+
checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
|
1028
1042
|
});
|
1029
1043
|
};
|
1030
1044
|
}
|
@@ -1047,21 +1061,23 @@ function InjectAll(token) {
|
|
1047
1061
|
*
|
1048
1062
|
* @__NO_SIDE_EFFECTS__
|
1049
1063
|
*/ function Named(name) {
|
1050
|
-
|
1051
|
-
assert(false, "the @Named qualifier cannot be empty or blank");
|
1052
|
-
}
|
1064
|
+
check(name.trim(), "the @Named qualifier cannot be empty or blank");
|
1053
1065
|
return function(target, propertyKey, parameterIndex) {
|
1054
1066
|
if (parameterIndex === undefined) {
|
1055
1067
|
// The decorator has been applied to the class
|
1056
1068
|
const ctor = target;
|
1057
1069
|
const metadata = getMetadata(ctor);
|
1058
|
-
|
1070
|
+
check(metadata.name === undefined, `multiple @Named decorators on class ${ctor.name}, but only one is allowed`);
|
1059
1071
|
metadata.name = name;
|
1060
1072
|
} else {
|
1061
1073
|
// The decorator has been applied to a method parameter
|
1062
1074
|
updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
|
1063
|
-
|
1075
|
+
check(dependency.name === undefined, ()=>{
|
1076
|
+
const location = getLocation(target, propertyKey, parameterIndex);
|
1077
|
+
return `multiple @Named decorators on ${location}, but only one is allowed`;
|
1078
|
+
});
|
1064
1079
|
dependency.name = name;
|
1080
|
+
checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
|
1065
1081
|
});
|
1066
1082
|
}
|
1067
1083
|
};
|
@@ -1083,6 +1099,7 @@ function OptionalAll(token) {
|
|
1083
1099
|
checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
|
1084
1100
|
dependency.appliedBy = "OptionalAll";
|
1085
1101
|
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1102
|
+
checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
|
1086
1103
|
});
|
1087
1104
|
};
|
1088
1105
|
}
|
@@ -1112,7 +1129,7 @@ function OptionalAll(token) {
|
|
1112
1129
|
return function(Class) {
|
1113
1130
|
const metadata = getMetadata(Class);
|
1114
1131
|
const currentScope = metadata.scope;
|
1115
|
-
|
1132
|
+
check(!currentScope || currentScope.value === scope, ()=>{
|
1116
1133
|
const { value, appliedBy } = currentScope;
|
1117
1134
|
const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
|
1118
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.`;
|