@lppedd/di-wise-neo 0.8.1 → 0.9.1
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 +77 -75
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +77 -75
- 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
|
}
|
@@ -38,9 +38,10 @@ function expectNever(value) {
|
|
38
38
|
throw new TypeError(tag(`unexpected value ${String(value)}`));
|
39
39
|
}
|
40
40
|
// @internal
|
41
|
-
function throwUnregisteredError(token) {
|
41
|
+
function throwUnregisteredError(token, name) {
|
42
42
|
const type = isConstructor(token) ? "class" : "token";
|
43
|
-
|
43
|
+
const spec = name !== undefined ? `[name=${name}]` : "";
|
44
|
+
throw new Error(tag(`unregistered ${type} ${token.name}${spec}`));
|
44
45
|
}
|
45
46
|
// @internal
|
46
47
|
function throwExistingUnregisteredError(sourceToken, targetTokenOrError) {
|
@@ -59,13 +60,6 @@ function untag(message) {
|
|
59
60
|
return message.startsWith("[di-wise-neo]") ? message.substring(13).trimStart() : message;
|
60
61
|
}
|
61
62
|
|
62
|
-
// @internal
|
63
|
-
function invariant(condition) {
|
64
|
-
if (!condition) {
|
65
|
-
throw new Error("invariant violation");
|
66
|
-
}
|
67
|
-
}
|
68
|
-
|
69
63
|
// @internal
|
70
64
|
class KeyedStack {
|
71
65
|
has(key) {
|
@@ -76,7 +70,7 @@ class KeyedStack {
|
|
76
70
|
return entry?.value;
|
77
71
|
}
|
78
72
|
push(key, value) {
|
79
|
-
|
73
|
+
check(!this.has(key), "invariant violation");
|
80
74
|
this.myKeys.add(key);
|
81
75
|
this.myEntries.push({
|
82
76
|
key,
|
@@ -104,9 +98,10 @@ class WeakRefMap {
|
|
104
98
|
}
|
105
99
|
this.myMap.delete(key);
|
106
100
|
}
|
101
|
+
return undefined;
|
107
102
|
}
|
108
103
|
set(key, value) {
|
109
|
-
|
104
|
+
check(!this.get(key), "invariant violation");
|
110
105
|
this.myMap.set(key, new WeakRef(value));
|
111
106
|
return ()=>{
|
112
107
|
this.myMap.delete(key);
|
@@ -130,7 +125,7 @@ const [provideInjectionContext, useInjectionContext] = createInjectionContext();
|
|
130
125
|
// @internal
|
131
126
|
function ensureInjectionContext(name) {
|
132
127
|
const context = useInjectionContext();
|
133
|
-
|
128
|
+
check(context, `${name} can only be invoked within an injection context`);
|
134
129
|
return context;
|
135
130
|
}
|
136
131
|
function createInjectionContext() {
|
@@ -360,13 +355,13 @@ class TokenRegistry {
|
|
360
355
|
] || this.getAllFromParent(token, name);
|
361
356
|
}
|
362
357
|
set(token, registration) {
|
363
|
-
|
358
|
+
check(!internals.has(token), `cannot register reserved token ${token.name}`);
|
364
359
|
let registrations = this.myMap.get(token);
|
365
360
|
if (!registrations) {
|
366
361
|
this.myMap.set(token, registrations = []);
|
367
362
|
} else if (registration.name !== undefined) {
|
368
363
|
const existing = registrations.filter((r)=>r.name === registration.name);
|
369
|
-
|
364
|
+
check(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
|
370
365
|
}
|
371
366
|
registrations.push(registration);
|
372
367
|
}
|
@@ -420,7 +415,7 @@ class TokenRegistry {
|
|
420
415
|
let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
|
421
416
|
if (registrations && name !== undefined) {
|
422
417
|
registrations = registrations.filter((r)=>r.name === name);
|
423
|
-
|
418
|
+
check(registrations.length < 2, `internal error: more than one registration named '${name}'`);
|
424
419
|
}
|
425
420
|
return registrations ?? [];
|
426
421
|
}
|
@@ -561,7 +556,7 @@ function isDisposable(value) {
|
|
561
556
|
const [token, provider, options] = args;
|
562
557
|
const existingProvider = isExistingProvider(provider);
|
563
558
|
const name = existingProvider ? undefined : provider.name;
|
564
|
-
|
559
|
+
check(name === undefined || name.trim(), "the provider name qualifier cannot be empty or blank");
|
565
560
|
if (isClassProvider(provider)) {
|
566
561
|
const metadata = getMetadata(provider.useClass);
|
567
562
|
const registration = {
|
@@ -582,7 +577,7 @@ function isDisposable(value) {
|
|
582
577
|
}
|
583
578
|
} else {
|
584
579
|
if (existingProvider) {
|
585
|
-
|
580
|
+
check(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
|
586
581
|
}
|
587
582
|
this.myTokenRegistry.set(token, {
|
588
583
|
name: name,
|
@@ -615,29 +610,30 @@ function isDisposable(value) {
|
|
615
610
|
localOptional = optionalOrName;
|
616
611
|
localName = name;
|
617
612
|
}
|
618
|
-
|
613
|
+
let registration = this.myTokenRegistry.get(token, localName);
|
614
|
+
if (!registration && isConstructor(token)) {
|
615
|
+
registration = this.autoRegisterClass(token, localName);
|
616
|
+
}
|
619
617
|
if (registration) {
|
620
618
|
return this.resolveRegistration(token, registration, localName);
|
621
619
|
}
|
622
|
-
|
623
|
-
return this.instantiateClass(token, localOptional);
|
624
|
-
}
|
625
|
-
return localOptional ? undefined : throwUnregisteredError(token);
|
620
|
+
return localOptional ? undefined : throwUnregisteredError(token, localName);
|
626
621
|
}
|
627
622
|
resolveAll(token, optional) {
|
628
623
|
this.checkDisposed();
|
629
|
-
|
624
|
+
let registrations = this.myTokenRegistry.getAll(token);
|
625
|
+
if (registrations.length === 0 && isConstructor(token)) {
|
626
|
+
const registration = this.autoRegisterClass(token);
|
627
|
+
if (registration) {
|
628
|
+
registrations = [
|
629
|
+
registration
|
630
|
+
];
|
631
|
+
}
|
632
|
+
}
|
630
633
|
if (registrations.length > 0) {
|
631
634
|
return registrations //
|
632
635
|
.map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
|
633
636
|
}
|
634
|
-
if (isConstructor(token)) {
|
635
|
-
const instance = this.instantiateClass(token, optional);
|
636
|
-
return instance === undefined // = could not resolve, but since it is optional
|
637
|
-
? [] : [
|
638
|
-
instance
|
639
|
-
];
|
640
|
-
}
|
641
637
|
return optional ? [] : throwUnregisteredError(token);
|
642
638
|
}
|
643
639
|
dispose() {
|
@@ -687,40 +683,25 @@ function isDisposable(value) {
|
|
687
683
|
throw e;
|
688
684
|
}
|
689
685
|
}
|
690
|
-
|
686
|
+
autoRegisterClass(Class, name) {
|
691
687
|
const metadata = getMetadata(Class);
|
692
|
-
|
693
|
-
|
694
|
-
//
|
688
|
+
const autoRegister = metadata.autoRegister ?? this.myOptions.autoRegister;
|
689
|
+
if (autoRegister && (name === undefined || metadata.name === name)) {
|
690
|
+
// Temporarily set eagerInstantiate to false to avoid potentially resolving
|
691
|
+
// the class inside register()
|
695
692
|
const eagerInstantiate = metadata.eagerInstantiate;
|
696
693
|
metadata.eagerInstantiate = false;
|
697
694
|
try {
|
698
695
|
this.register(Class);
|
699
|
-
return this.
|
696
|
+
return this.myTokenRegistry.get(Class, name ?? metadata.name);
|
700
697
|
} finally{
|
701
698
|
metadata.eagerInstantiate = eagerInstantiate;
|
702
699
|
}
|
703
700
|
}
|
704
|
-
|
705
|
-
if (optional && scope === Scope.Container) {
|
706
|
-
// It would not be possible to resolve the class in container scope,
|
707
|
-
// as that would require prior registration.
|
708
|
-
// However, since resolution is marked optional, we simply return undefined.
|
709
|
-
return undefined;
|
710
|
-
}
|
711
|
-
assert(scope !== Scope.Container, `unregistered class ${Class.name} cannot be resolved in container scope`);
|
712
|
-
const registration = {
|
713
|
-
provider: metadata.provider,
|
714
|
-
options: {
|
715
|
-
scope: scope
|
716
|
-
},
|
717
|
-
dependencies: metadata.dependencies
|
718
|
-
};
|
719
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
720
|
-
return this.resolveScopedValue(registration, (args)=>new Class(...args));
|
701
|
+
return undefined;
|
721
702
|
}
|
722
703
|
resolveProviderValue(registration, provider) {
|
723
|
-
|
704
|
+
check(registration.provider === provider, "internal error: mismatching provider");
|
724
705
|
if (isClassProvider(provider)) {
|
725
706
|
const Class = provider.useClass;
|
726
707
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
@@ -733,9 +714,7 @@ function isDisposable(value) {
|
|
733
714
|
if (isValueProvider(provider)) {
|
734
715
|
return provider.useValue;
|
735
716
|
}
|
736
|
-
|
737
|
-
assert(false, "internal error: unexpected ExistingProvider");
|
738
|
-
}
|
717
|
+
check(!isExistingProvider(provider), "internal error: unexpected ExistingProvider");
|
739
718
|
expectNever(provider);
|
740
719
|
}
|
741
720
|
resolveScopedValue(registration, factory) {
|
@@ -751,7 +730,7 @@ function isDisposable(value) {
|
|
751
730
|
const options = registration.options;
|
752
731
|
if (resolution.stack.has(provider)) {
|
753
732
|
const dependentRef = resolution.dependents.get(provider);
|
754
|
-
|
733
|
+
check(dependentRef, "circular dependency detected");
|
755
734
|
return dependentRef.current;
|
756
735
|
}
|
757
736
|
const scope = this.resolveScope(options?.scope, context);
|
@@ -810,13 +789,13 @@ function isDisposable(value) {
|
|
810
789
|
resolveConstructorDependencies(registration) {
|
811
790
|
const dependencies = registration.dependencies;
|
812
791
|
if (dependencies) {
|
813
|
-
|
792
|
+
check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
814
793
|
const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
|
815
794
|
if (ctorDeps.length > 0) {
|
816
795
|
// Let's check if all necessary constructor parameters are decorated.
|
817
796
|
// If not, we cannot safely create an instance.
|
818
797
|
const ctor = registration.provider.useClass;
|
819
|
-
|
798
|
+
check(ctor.length === ctorDeps.length, ()=>{
|
820
799
|
const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
|
821
800
|
return msg + `, but found ${ctorDeps.length}`;
|
822
801
|
});
|
@@ -840,7 +819,7 @@ function isDisposable(value) {
|
|
840
819
|
injectDependencies(registration, instance) {
|
841
820
|
const dependencies = registration.dependencies;
|
842
821
|
if (dependencies) {
|
843
|
-
|
822
|
+
check(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
844
823
|
const ctor = registration.provider.useClass;
|
845
824
|
// Perform method injection
|
846
825
|
for (const entry of dependencies.methods){
|
@@ -850,7 +829,7 @@ function isDisposable(value) {
|
|
850
829
|
// If not, we cannot safely invoke the method.
|
851
830
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
852
831
|
const method = instance[key];
|
853
|
-
|
832
|
+
check(methodDeps.length === method.length, ()=>{
|
854
833
|
const msg = `expected ${method.length} decorated method parameters`;
|
855
834
|
return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
|
856
835
|
});
|
@@ -874,7 +853,7 @@ function isDisposable(value) {
|
|
874
853
|
return instance;
|
875
854
|
}
|
876
855
|
checkDisposed() {
|
877
|
-
|
856
|
+
check(!this.myDisposed, "the container is disposed");
|
878
857
|
}
|
879
858
|
}
|
880
859
|
|
@@ -930,7 +909,7 @@ function isDisposable(value) {
|
|
930
909
|
return function(Class) {
|
931
910
|
const metadata = getMetadata(Class);
|
932
911
|
const currentScope = metadata.scope;
|
933
|
-
|
912
|
+
check(!currentScope || currentScope.value === Scope.Container, ()=>{
|
934
913
|
const { value, appliedBy } = currentScope;
|
935
914
|
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
915
|
});
|
@@ -955,7 +934,7 @@ function forwardRef(token) {
|
|
955
934
|
},
|
956
935
|
getRefToken: ()=>{
|
957
936
|
const tokenOrTokens = token();
|
958
|
-
|
937
|
+
check(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
|
959
938
|
return tokenOrTokens;
|
960
939
|
}
|
961
940
|
};
|
@@ -975,7 +954,7 @@ function isTokenRef(value) {
|
|
975
954
|
function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
|
976
955
|
// Error out immediately if the decorator has been applied to a static method
|
977
956
|
if (propertyKey !== undefined && typeof target === "function") {
|
978
|
-
|
957
|
+
check(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
|
979
958
|
}
|
980
959
|
if (propertyKey === undefined) {
|
981
960
|
// Constructor
|
@@ -995,11 +974,30 @@ function updateParameterMetadata(decorator, target, propertyKey, parameterIndex,
|
|
995
974
|
//
|
996
975
|
// @internal
|
997
976
|
function checkSingleDecorator(dependency, target, propertyKey, parameterIndex) {
|
998
|
-
|
999
|
-
const
|
1000
|
-
return
|
977
|
+
check(dependency.appliedBy === undefined, ()=>{
|
978
|
+
const location = getLocation(target, propertyKey, parameterIndex);
|
979
|
+
return `multiple injection decorators on ${location}, but only one is allowed`;
|
980
|
+
});
|
981
|
+
}
|
982
|
+
// Checks that the `@Named` decorator is not used in combination with
|
983
|
+
// `@InjectAll` or `@OptionalAll`, which ignore the name qualification.
|
984
|
+
//
|
985
|
+
// @internal
|
986
|
+
function checkNamedDecorator(dependency, target, propertyKey, parameterIndex) {
|
987
|
+
const { appliedBy, name } = dependency;
|
988
|
+
check(name === undefined || appliedBy !== "InjectAll" && appliedBy !== "OptionalAll", ()=>{
|
989
|
+
const location = getLocation(target, propertyKey, parameterIndex);
|
990
|
+
return `@Named has no effect on ${location} when used with @${appliedBy}`;
|
1001
991
|
});
|
1002
992
|
}
|
993
|
+
// Returns a human-readable description of the parameter location.
|
994
|
+
// For example: "Wizard constructor parameter 2" or "Wizard.set parameter 0"
|
995
|
+
//
|
996
|
+
// @internal
|
997
|
+
function getLocation(target, propertyKey, parameterIndex) {
|
998
|
+
const location = propertyKey === undefined ? `${target.name} constructor` : `${target.constructor.name}.${String(propertyKey)}`;
|
999
|
+
return `${location} parameter ${parameterIndex}`;
|
1000
|
+
}
|
1003
1001
|
|
1004
1002
|
function Inject(token) {
|
1005
1003
|
return function(target, propertyKey, parameterIndex) {
|
@@ -1037,6 +1035,7 @@ function InjectAll(token) {
|
|
1037
1035
|
checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
|
1038
1036
|
dependency.appliedBy = "InjectAll";
|
1039
1037
|
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1038
|
+
checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
|
1040
1039
|
});
|
1041
1040
|
};
|
1042
1041
|
}
|
@@ -1059,21 +1058,23 @@ function InjectAll(token) {
|
|
1059
1058
|
*
|
1060
1059
|
* @__NO_SIDE_EFFECTS__
|
1061
1060
|
*/ function Named(name) {
|
1062
|
-
|
1063
|
-
assert(false, "the @Named qualifier cannot be empty or blank");
|
1064
|
-
}
|
1061
|
+
check(name.trim(), "the @Named qualifier cannot be empty or blank");
|
1065
1062
|
return function(target, propertyKey, parameterIndex) {
|
1066
1063
|
if (parameterIndex === undefined) {
|
1067
1064
|
// The decorator has been applied to the class
|
1068
1065
|
const ctor = target;
|
1069
1066
|
const metadata = getMetadata(ctor);
|
1070
|
-
|
1067
|
+
check(metadata.name === undefined, `multiple @Named decorators on class ${ctor.name}, but only one is allowed`);
|
1071
1068
|
metadata.name = name;
|
1072
1069
|
} else {
|
1073
1070
|
// The decorator has been applied to a method parameter
|
1074
1071
|
updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
|
1075
|
-
|
1072
|
+
check(dependency.name === undefined, ()=>{
|
1073
|
+
const location = getLocation(target, propertyKey, parameterIndex);
|
1074
|
+
return `multiple @Named decorators on ${location}, but only one is allowed`;
|
1075
|
+
});
|
1076
1076
|
dependency.name = name;
|
1077
|
+
checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
|
1077
1078
|
});
|
1078
1079
|
}
|
1079
1080
|
};
|
@@ -1095,6 +1096,7 @@ function OptionalAll(token) {
|
|
1095
1096
|
checkSingleDecorator(dependency, target, propertyKey, parameterIndex);
|
1096
1097
|
dependency.appliedBy = "OptionalAll";
|
1097
1098
|
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1099
|
+
checkNamedDecorator(dependency, target, propertyKey, parameterIndex);
|
1098
1100
|
});
|
1099
1101
|
};
|
1100
1102
|
}
|
@@ -1124,7 +1126,7 @@ function OptionalAll(token) {
|
|
1124
1126
|
return function(Class) {
|
1125
1127
|
const metadata = getMetadata(Class);
|
1126
1128
|
const currentScope = metadata.scope;
|
1127
|
-
|
1129
|
+
check(!currentScope || currentScope.value === scope, ()=>{
|
1128
1130
|
const { value, appliedBy } = currentScope;
|
1129
1131
|
const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
|
1130
1132
|
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.`;
|