dioma
Version:
Elegant dependency injection container for vanilla JavaScript and TypeScript
216 lines (215 loc) • 7.22 kB
JavaScript
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
class DependencyCycleError extends Error {
constructor() {
super("Circular dependency detected");
}
}
class AsyncDependencyCycleError extends Error {
constructor() {
super("Circular dependency detected in async resolution");
}
}
class ArgumentsError extends Error {
constructor(scope, className) {
super(`Arguments are not supported for ${scope} of ${className}`);
}
}
class TokenNotRegisteredError extends Error {
constructor() {
super("Token is not registered in the container");
}
}
const _Scopes = class _Scopes {
static Singleton() {
return function SingletonScope(descriptor, args) {
if (args.length > 0) {
throw new ArgumentsError(SingletonScope.name, descriptor.class.name);
}
return globalContainer.$getInstance(descriptor);
};
}
static Transient() {
return function TransientScope(descriptor, args, container) {
return container.$getInstance(descriptor, args, false);
};
}
static Container() {
return function ContainerScope(descriptor, args, container) {
if (args.length > 0) {
throw new ArgumentsError(ContainerScope.name, descriptor.class.name);
}
return container.$getInstance(descriptor);
};
}
static Resolution() {
return function ResolutionScope(descriptor, args, _, resolutionContainer) {
return resolutionContainer.$getInstance(descriptor, args);
};
}
};
_Scopes.Scoped = _Scopes.Container;
let Scopes = _Scopes;
class Token {
constructor(name) {
this.name = name;
}
}
const MAX_LOOP_COUNT = 100;
class Container {
constructor(parentContainer = null, name) {
this.parentContainer = parentContainer;
this.name = name;
this.instances = /* @__PURE__ */ new WeakMap();
this.resolutionContainer = null;
this.resolutionSet = /* @__PURE__ */ new Set();
this.pendingPromiseMap = /* @__PURE__ */ new Map();
this.tokenDescriptorMap = /* @__PURE__ */ new Map();
this.resolutionsLoopCounter = 0;
this.childContainer = (name2) => {
return new Container(this, name2);
};
this.inject = (cls, ...args) => {
return this.injectImpl(cls, args, void 0);
};
this.injectAsync = (cls, ...args) => {
const resolutionContainer = this.resolutionContainer;
this.resolutionsLoopCounter += 1;
if (this.resolutionsLoopCounter > MAX_LOOP_COUNT) {
throw new AsyncDependencyCycleError();
}
if (this.pendingPromiseMap.has(cls)) {
return this.pendingPromiseMap.get(cls);
}
if (this.instances.has(cls)) {
return Promise.resolve(this.instances.get(cls));
}
const promise = Promise.resolve().then(() => {
try {
return this.injectImpl(cls, args, resolutionContainer);
} finally {
this.pendingPromiseMap.delete(cls);
}
});
this.pendingPromiseMap.set(cls, promise);
return promise;
};
this.waitAsync = async () => {
return new Promise((resolve) => setTimeout(resolve, 0));
};
this.unregister = (token) => {
const descriptor = this.getTokenDescriptor(token);
this.tokenDescriptorMap.delete(token);
this.instances.delete(token);
if (descriptor && "class" in descriptor) {
this.tokenDescriptorMap.delete(descriptor.class);
this.instances.delete(descriptor.class);
}
};
this.reset = () => {
this.instances = /* @__PURE__ */ new WeakMap();
this.resolutionSet.clear();
this.pendingPromiseMap.clear();
this.tokenDescriptorMap.clear();
this.resolutionContainer = null;
this.resolutionsLoopCounter = 0;
};
this.register = this.register.bind(this);
}
$getInstance(descriptor, args = [], cache = true) {
let instance = null;
let container = this;
const cls = descriptor.class;
while (!instance && container) {
instance = container.instances.get(cls);
container = container.parentContainer;
}
if (!instance) {
if (descriptor.beforeCreate) {
descriptor.beforeCreate(this, descriptor, args);
}
instance = new cls(...args);
if (cache) {
this.instances.set(cls, instance);
}
}
return instance;
}
getTokenDescriptor(clsOrToken) {
let tokenDescriptor;
let container = this;
while (!tokenDescriptor && container) {
tokenDescriptor = container.tokenDescriptorMap.get(clsOrToken);
container = container.parentContainer;
}
return tokenDescriptor;
}
injectImpl(clsOrToken, args, resolutionContainer = this.resolutionContainer) {
this.resolutionContainer = resolutionContainer || new Container();
try {
if (this.resolutionSet.has(clsOrToken)) {
throw new DependencyCycleError();
}
let cls = clsOrToken;
let scope;
let container = this;
let descriptor = this.getTokenDescriptor(clsOrToken);
this.resolutionSet.add(clsOrToken);
if (!descriptor) {
if (clsOrToken instanceof Token) {
throw new TokenNotRegisteredError();
}
cls = clsOrToken;
scope = cls.scope || Scopes.Transient();
container = this;
descriptor = { class: cls, container: this };
} else {
if (descriptor.beforeInject) {
descriptor.beforeInject(container, descriptor, args);
}
if ("class" in descriptor) {
cls = descriptor.class;
scope = descriptor.scope || cls.scope || Scopes.Transient();
container = descriptor.container;
} else if ("value" in descriptor) {
return descriptor.value;
} else if ("factory" in descriptor) {
return descriptor.factory(container, ...args);
} else {
throw new Error("Invalid descriptor");
}
}
return scope(descriptor, args, container, this.resolutionContainer);
} finally {
this.resolutionSet.delete(clsOrToken);
this.resolutionContainer = resolutionContainer;
if (!resolutionContainer) {
this.resolutionsLoopCounter = 0;
}
}
}
register(tokenDescriptor) {
const token = tokenDescriptor.token || tokenDescriptor.class;
const descriptorWithContainer = { ...tokenDescriptor, container: this };
this.tokenDescriptorMap.set(token, descriptorWithContainer);
if (tokenDescriptor.class) {
this.tokenDescriptorMap.set(token.class, descriptorWithContainer);
}
}
}
const globalContainer = new Container(null, "Global container");
const inject = globalContainer.inject;
const injectAsync = globalContainer.injectAsync;
const childContainer = globalContainer.childContainer;
exports.ArgumentsError = ArgumentsError;
exports.AsyncDependencyCycleError = AsyncDependencyCycleError;
exports.Container = Container;
exports.DependencyCycleError = DependencyCycleError;
exports.Scopes = Scopes;
exports.Token = Token;
exports.TokenNotRegisteredError = TokenNotRegisteredError;
exports.childContainer = childContainer;
exports.globalContainer = globalContainer;
exports.inject = inject;
exports.injectAsync = injectAsync;
//# sourceMappingURL=dioma.cjs.map