@sigi/di
Version:
Dependencies injection library for sigi framework
291 lines (281 loc) • 9.71 kB
JavaScript
;
class InjectionToken {
constructor(desc) {
this.desc = desc;
}
toString() {
return this.desc;
}
}
class ReflectiveProvider {
constructor(provider) {
this.provider = provider;
if (typeof provider === 'function') {
this.name = provider.name;
this.token = provider;
}
else {
this.name = provider.provide instanceof InjectionToken ? provider.provide.toString() : provider.provide.name;
this.token = provider.provide;
}
}
toString() {
return this.name;
}
}
class InjectionProvider {
constructor() {
this.providersMap = new Map();
}
addProvider(provider) {
this.providersMap.set(provider.provide ?? provider, provider);
return provider;
}
findProviderByToken(token) {
if (!this.providersMap.has(token)) {
return null;
}
return this.providersMap.get(token);
}
}
class Injector {
constructor(parent = null) {
this.parent = parent;
this.provider = new InjectionProvider();
this.resolvedProviders = new Map();
this.providersCache = new Map();
}
addProvider(provider) {
return this.provider.addProvider(provider);
}
addProviders(providers) {
for (const provider of providers) {
this.provider.addProvider(provider);
}
return this;
}
getInstance(provider) {
return this.getInstanceInternal(provider, true);
}
resolveAndInstantiate(provider) {
return this.getInstanceInternal(provider, false);
}
createChild(providers) {
const childInjector = new Injector(this);
childInjector.addProviders(providers);
return childInjector;
}
resolveReflectiveProvider(provider) {
let reflectiveProvider = null;
if (this.provider.findProviderByToken(provider.provide ?? provider)) {
if (this.providersCache.has(provider)) {
return this.providersCache.get(provider);
}
reflectiveProvider = new ReflectiveProvider(provider);
this.providersCache.set(provider, reflectiveProvider);
}
return reflectiveProvider;
}
getInstanceInternal(provider, useCache) {
let injector = this;
let instance = null;
let reflectiveProvider = null;
const deps = this.findDeps(provider);
while (injector) {
reflectiveProvider = injector.resolveReflectiveProvider(provider);
if (!reflectiveProvider) {
injector = injector.parent;
continue;
}
if (injector.resolvedProviders.has(reflectiveProvider)) {
if (deps) {
if (useCache && (injector === this || this.checkDependenciesClean(injector, deps))) {
instance = injector.resolvedProviders.get(reflectiveProvider);
}
else {
instance = this.resolveByReflectiveProvider(reflectiveProvider, useCache, this);
if (useCache) {
this.provider.addProvider(provider);
this.providersCache.set(provider, reflectiveProvider);
this.resolvedProviders.set(reflectiveProvider, instance);
}
}
}
else {
instance = useCache
? injector.resolvedProviders.get(reflectiveProvider)
: this.resolveByReflectiveProvider(reflectiveProvider, false, this);
}
break;
}
instance = injector.resolveByReflectiveProvider(reflectiveProvider, useCache, this);
if (instance) {
if (useCache) {
this.provider.addProvider(provider);
this.providersCache.set(provider, reflectiveProvider);
this.resolvedProviders.set(reflectiveProvider, instance);
}
break;
}
injector = injector.parent;
}
if (!instance) {
reflectiveProvider = new ReflectiveProvider(provider);
throw new TypeError(`No provider for ${reflectiveProvider.name}!`);
}
return instance;
}
resolveByReflectiveProvider(reflectiveProvider, useCache = true, leaf = this) {
let instance = null;
const { provider, name } = reflectiveProvider;
if (typeof provider === 'function') {
const deps = Reflect.getMetadata('design:paramtypes', provider) ?? [];
const depsInstance = deps.map((dep) => leaf.getInstanceInternal(leaf.findExisting(dep), useCache));
instance = new provider(...depsInstance);
}
else if ('useValue' in provider) {
instance = provider.useValue;
}
else if ('useClass' in provider) {
instance = leaf.getInstanceInternal(provider.useClass, useCache);
}
else if ('useFactory' in provider) {
let deps = [];
if (provider.deps) {
deps = provider.deps.map((dep) => leaf.getInstanceInternal(leaf.findExisting(dep), useCache));
}
instance = provider.useFactory(...deps);
}
else if ('useExisting' in provider) {
instance = leaf.getInstanceInternal(this.findExisting(provider.useExisting), useCache);
}
if (!instance) {
throw new TypeError(`Can not resolve ${name}, it's not a valid provider`);
}
return instance;
}
findExisting(token) {
let provider = null;
let injector = this;
while (injector) {
provider = injector.provider.findProviderByToken(token);
if (provider) {
break;
}
injector = injector.parent;
}
if (!provider) {
throw new TypeError(`No provider for ${token.name ?? token.toString()}`);
}
return provider;
}
findDeps(provider) {
return typeof provider === 'function'
? Reflect.getMetadata('design:paramtypes', provider)
: provider.useClass
? Reflect.getMetadata('design:paramtypes', provider.useClass)
: provider.deps
? provider.deps
: null;
}
checkDependenciesClean(leaf, deps) {
return deps.every((dep) => {
const depInLeaf = leaf.findExisting(dep);
const depInRoot = this.findExisting(dep);
const isEqual = depInLeaf === depInRoot;
const deps = this.findDeps(depInLeaf);
if (deps) {
return this.checkDependenciesClean(leaf, deps) && isEqual;
}
return isEqual;
});
}
}
class RootInjector extends Injector {
constructor() {
super(...arguments);
this.provider = new InjectionProvider();
}
reset() {
this.provider = new InjectionProvider();
this.providersCache.clear();
this.resolvedProviders.clear();
}
}
const rootInjector = new RootInjector();
function Injectable(options) {
return function (target) {
rootInjector.addProvider({
useClass: target,
provide: target,
});
for (const provider of options?.providers ?? []) {
rootInjector.addProvider(provider);
}
return target;
};
}
function Inject(token) {
return function (target, _key, paramIndex) {
const deps = Reflect.getMetadata('design:paramtypes', target);
deps[paramIndex] = token;
};
}
class Test {
static createTestingModule(overrideConfig) {
return new Test(overrideConfig?.providers ? overrideConfig.providers : [], overrideConfig?.TestModule ? overrideConfig.TestModule : TestModule);
}
constructor(providers, TestModule) {
this.TestModule = TestModule;
this.providersMap = new Map();
for (const provider of providers) {
this.providersMap.set(provider.provide ?? provider, provider);
}
}
overrideProvider(token) {
return new MockProvider(this, token);
}
compile() {
const childInjector = rootInjector.createChild(Array.from(this.providersMap.values()));
return new this.TestModule(childInjector);
}
}
class MockProvider {
constructor(test, token) {
this.test = test;
this.token = token;
}
useClass(value) {
this.test.providersMap.set(this.token, { provide: this.token, useClass: value });
return this.test;
}
useValue(value) {
this.test.providersMap.set(this.token, { provide: this.token, useValue: value });
return this.test;
}
useFactory(value) {
this.test.providersMap.set(this.token, { provide: this.token, useFactory: value });
return this.test;
}
}
class AbstractTestModule {
}
class TestModule {
constructor(injector) {
this.injector = injector;
}
getInstance(token) {
return this.injector.getInstance(token);
}
}
exports.AbstractTestModule = AbstractTestModule;
exports.Inject = Inject;
exports.Injectable = Injectable;
exports.InjectionToken = InjectionToken;
exports.Injector = Injector;
exports.MockProvider = MockProvider;
exports.RootInjector = RootInjector;
exports.Test = Test;
exports.TestModule = TestModule;
exports.rootInjector = rootInjector;
//# sourceMappingURL=index.js.map