di-tory
Version:
Compose applications with dependency injection
87 lines (86 loc) • 3.38 kB
JavaScript
import { Stack } from './stack/Stack.js';
import { DependencyResolutionError as DRError, DependencyResolutionErrorCode as DRErrType, } from './DependencyResolutionError.js';
import { getStore } from './async-scope.js';
import { propertyKeys } from './objects.js';
import { normalizeScope, overrideScope } from './scope.js';
const singletonInstances = new Map();
export const createModule = (privateResolvers, publicResolvers, initializers, params = {}) => {
const resolvers = { ...privateResolvers, ...publicResolvers };
const resolutionStack = new Stack();
const moduleInstances = new Map();
const publicNames = new Set(propertyKeys(publicResolvers));
const self = new Proxy(Object.create(null), {
get(_, prop) {
return resolve(prop);
},
});
const ensureAsyncInstances = () => {
const store = getStore();
const asyncInstances = store.get(self);
if (asyncInstances != null)
return asyncInstances;
const newInstances = new Map();
store.set(self, newInstances);
return newInstances;
};
let transientInstances;
const getInstances = ({ scope }) => {
const normalizedScope = normalizeScope(scope);
return (
// prettier-ignore
normalizedScope === 'async' ? ensureAsyncInstances() :
normalizedScope === 'singleton' ? singletonInstances :
normalizedScope === 'transient' ? transientInstances :
moduleInstances);
};
const updateParentScope = (scope) => {
const parentItem = resolutionStack.peek();
if (parentItem == null)
return;
const parent = resolvers[parentItem];
const newParentScope = overrideScope(parent.scope, scope);
if (newParentScope != null)
parent.scope = newParentScope;
};
const resolve = (item) => {
if (resolutionStack.length === 0) {
transientInstances = new Map();
}
const resolver = resolvers[item];
if (resolver == null)
throw new DRError(DRErrType.ResolverIsNotDefined, resolutionStack.toStringArray(), String(item));
const instances = getInstances(resolver);
if (instances.has(item))
return instances.get(item);
const currentStack = resolutionStack.toStringArray();
try {
resolutionStack.push(item);
}
catch {
throw new DRError(DRErrType.CircularDependencyFailure, resolutionStack.toStringArray(), String(item));
}
let instance;
try {
instance = resolver(self, params);
}
catch (err) {
if (err instanceof DRError)
throw err;
throw new DRError(DRErrType.InstantiationFailure, currentStack, String(item), err);
}
resolutionStack.pop();
updateParentScope(resolver.scope);
getInstances(resolver).set(item, instance);
if (resolutionStack.length === 0) {
initializers?.[item]?.call(instance, self, params);
}
return instance;
};
return new Proxy(Object.create(null), {
get(_, prop) {
if (publicNames.has(prop))
return resolve(prop);
throw new DRError(DRErrType.PrivateMemberAccessFailure, [], String(prop));
},
});
};