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