@mikrokit/di
Version:
A lightweight TypeScript dependency injection container that uses only strip-tipes compliant methodologies and does not rely on reflect-metadata
297 lines (294 loc) • 9.26 kB
JavaScript
// src/utils.ts
var mergeDefaults = (defaults, overrides) => {
if (!overrides) {
return defaults;
}
return { ...defaults, ...overrides };
};
// src/module.ts
var _Module = class _Module {
constructor(moduleName, providers) {
this.moduleName = moduleName;
this.providers = providers ?? /* @__PURE__ */ new Map();
}
provide(token, factory, options) {
const normalizedOptions = mergeDefaults(
_Module.DEFAULT_PROVIDE_OPTIONS,
options
);
const normalizedToken = "token" in token ? token.token : token;
const normalizedFactory = typeof token === "function" && !factory ? token : factory;
if (normalizedToken._.group) {
this.provideGroupToken(
normalizedToken,
normalizedFactory
);
} else {
this.provideSingleToken(
normalizedToken,
normalizedFactory,
normalizedOptions
);
}
return this;
}
import(module) {
for (const token of module.providers.keys()) {
const ownProvider = this.providers.get(token);
const importedModuleProvider = module.providers.get(token);
if (!ownProvider) {
this.providers.set(token, importedModuleProvider);
continue;
}
if (ownProvider.group && importedModuleProvider.group) {
ownProvider.factories.push(...importedModuleProvider.factories);
continue;
}
if (ownProvider.group && !importedModuleProvider.group) {
throw new Error(
`Trying to re-provide group-type provider ${token.toString()} that was already provided as single-type provider`
);
}
throw new Error(
"Trying to re-provide single-type provider for " + token.toString()
);
}
return this;
}
provideGroupToken(token, factory) {
const existingProviderDefinition = this.providers.get(token);
if (existingProviderDefinition && existingProviderDefinition.group !== token._.group) {
throw new Error(
`Trying to re-provide group-type provider ${token.toString} that was already provided as single-type provider`
);
}
if (existingProviderDefinition) {
existingProviderDefinition.factories.push(factory);
return;
}
const providerDefinition = {
group: true,
factories: [factory]
};
this.providers.set(token, providerDefinition);
}
provideSingleToken(token, factory, options) {
if (this.providers.has(token)) {
const existingProvider = this.providers.get(token);
if (existingProvider.group !== token._.group) {
throw new Error(
`Trying to re-provide single-type provider ${token.toString()} as group-type provider`
);
}
if (!options.override) {
throw new Error(
"Trying to re-provide single-type provider for " + token.toString()
);
}
}
this.providers.set(token, {
group: false,
factory
});
}
};
_Module.DEFAULT_PROVIDE_OPTIONS = {
override: false
};
var Module = _Module;
var createModule = () => {
return new Module();
};
// src/container.ts
var ProvideScope = /* @__PURE__ */ ((ProvideScope2) => {
ProvideScope2["TRANSIENT"] = "transient";
ProvideScope2["SINGLETON"] = "singleton";
return ProvideScope2;
})(ProvideScope || {});
var Container = class _Container extends Module {
constructor(parentContainer, injectionItem, injectionStack) {
super(parentContainer?.moduleName, parentContainer?.providers);
this.lazyInjectionsQueue = [];
this.instantiatedSingleSingletonProviders = parentContainer?.instantiatedSingleSingletonProviders ?? /* @__PURE__ */ new Map();
this.instantiatedGroupSingletonProviders = parentContainer?.instantiatedGroupSingletonProviders ?? /* @__PURE__ */ new Map();
this.injectionStack = injectionItem ? [
...injectionStack ?? parentContainer?.injectionStack ?? [],
injectionItem
] : [];
}
static createEmpty() {
return new _Container();
}
async inject(token, scope = "singleton" /* SINGLETON */) {
const normalizedToken = "token" in token ? token.token : token;
if (this.injectionStack.includes(normalizedToken)) {
throw new Error(
`Provider ${normalizedToken.toString()} is already being instantiated. This error can be caused by either a circular dependency or not awaiting the inject calls`
);
}
if (normalizedToken._.group) {
const result2 = await this.injectGroup(
normalizedToken,
scope
);
return result2;
}
const result = await this.injectSingle(
normalizedToken,
scope
);
return result;
}
injectLazy(token, scope) {
const normalizedToken = "token" in token ? token.token : token;
let value;
this.enqueueLazyInjection(
normalizedToken,
scope ?? "singleton" /* SINGLETON */
).then((resolvedProvider) => value = resolvedProvider);
return {
get value() {
if (!value) {
throw new Error(
"Lazy provider is not yet resolved. Do not use lazy-injected providers before the provider construction ends."
);
}
return value;
}
};
}
async injectSingle(token, scope, injectionStack) {
if (scope === "singleton" /* SINGLETON */) {
const instantiatedProvider = this.instantiatedSingleSingletonProviders.get(token);
if (instantiatedProvider) {
return instantiatedProvider;
}
}
const definition = this.providers.get(token);
if (!definition) {
throw new Error(
"No factory available for the specified token " + token.toString()
);
}
if (token._.group !== definition.group) {
throw new Error(
`Trying to inject group-type provider ${token.toString()} as single-type provider`
);
}
const container = new _Container(this, token, injectionStack);
const result = await definition.factory(container);
if (scope === "singleton" /* SINGLETON */) {
this.instantiatedSingleSingletonProviders.set(token, result);
}
await container.dequeueLazyInjections();
return result;
}
async injectGroup(token, scope) {
if (scope === "singleton" /* SINGLETON */) {
const instantiatedProviders = this.instantiatedGroupSingletonProviders.get(token);
if (instantiatedProviders) {
return instantiatedProviders;
}
}
const definition = this.providers.get(token);
if (!definition) {
return [];
}
if (token._.group !== definition.group) {
throw new Error(
`Trying to inject single-type provider ${token.toString()} as group-type provider`
);
}
const result = [];
for (const factory of definition.factories) {
const container = new _Container(this, token);
result.push(await factory(container));
await container.dequeueLazyInjections();
}
if (scope === "singleton" /* SINGLETON */) {
this.instantiatedGroupSingletonProviders.set(token, result);
}
return result;
}
enqueueLazyInjection(token, scope) {
return new Promise((resolve) => {
const resolveLazy = (value) => {
resolve(value);
};
this.lazyInjectionsQueue.push({
token,
scope,
resolve: resolveLazy
});
});
}
async dequeueLazyInjections() {
for (const lazyInjection of this.lazyInjectionsQueue) {
try {
const resolvedValue = await this.injectSingle(
lazyInjection.token,
lazyInjection.scope,
// Exclude the latest token from the stack as it is already resolved
[]
// this.injectionStack.slice(0, -1)
);
lazyInjection.resolve(resolvedValue);
} catch (error) {
throw new Error(
`Failed to resolve lazy injection for token ${lazyInjection.token.toString()}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
}
};
var createContainer = () => {
return Container.createEmpty();
};
// src/helpers.ts
var createProviderToken = (_factory, name) => {
const symbol = Symbol(name);
const metadata = {
provided: void 0,
group: false
};
return Object.assign(symbol, { _: metadata });
};
var createGroupProviderToken = (name) => {
const symbol = Symbol(name);
const metadata = {
provided: void 0,
group: true
};
return Object.assign(symbol, { _: metadata });
};
var defineProviderFactory = (factory) => factory;
var defineStaticProviderFactory = (staticValue) => () => staticValue;
var attachProviderToken = (factory, token) => {
return Object.assign(factory, {
token
});
};
function defineStaticProvider(staticValue, token) {
return defineProvider(defineStaticProviderFactory(staticValue), token);
}
function defineProvider(factory, tokenOrName) {
const token = typeof tokenOrName === "string" || !tokenOrName ? createProviderToken(factory, tokenOrName) : tokenOrName;
return Object.assign(factory, {
token
});
}
export {
Container,
Module,
ProvideScope,
attachProviderToken,
createContainer,
createGroupProviderToken,
createModule,
createProviderToken,
defineProvider,
defineProviderFactory,
defineStaticProvider,
defineStaticProviderFactory
};
//# sourceMappingURL=index.js.map