UNPKG

di-tory

Version:

Compose applications with dependency injection

134 lines (133 loc) 5.02 kB
import { Stack } from '../stack/Stack.js'; import { DependencyResolutionError as DRError, DependencyResolutionErrorCode as DRErrType, } from '../DependencyResolutionError.js'; export const scope = { module: 'module', singleton: 'singleton', }; const createModule = (singletons, privateResolvers, publicResolvers, initializers, params) => { const resolvers = { ...privateResolvers, ...publicResolvers }; const resolutionStack = new Stack(); const moduleInstances = new Map(); const self = {}; const module = {}; for (const name of Object.keys(privateResolvers)) { Object.defineProperty(self, name, { get: () => resolve(name), }); Object.defineProperty(module, name, { get: () => { throw new DRError(DRErrType.PrivateMemberAccessFailure, resolutionStack.toStringArray(), String(name)); }, }); } for (const name of Object.keys(publicResolvers)) { Object.defineProperty(self, name, { get: () => resolve(name), }); Object.defineProperty(module, name, { get: () => resolve(name), }); } const resolve = (name) => { const resolver = resolvers[name]; const instances = resolver.scope === 'singleton' ? singletons : moduleInstances; if (instances.has(name)) return instances.get(name); const currentStack = resolutionStack.toStringArray(); try { resolutionStack.push(name); } catch { throw new DRError(DRErrType.CircularDependencyFailure, resolutionStack.toStringArray(), String(name)); } let instance; try { instance = resolver(self, params); } catch (err) { if (err instanceof DRError) throw err; throw new DRError(DRErrType.InstantiationFailure, currentStack, String(name), err); } resolutionStack.pop(); instances.set(name, instance); if (resolutionStack.length === 0) { initializers[name]?.call(instance, self, params); } return instance; }; return module; }; export const Module = () => { const privateResolvers = {}; const publicResolvers = {}; let initializers = null; let sealed = false; const singletons = new Map(); const self = { private(resolvers, scope) { if (sealed) { throw new Error('Cannot extend initialized module'); } for (const name of Object.keys(resolvers)) { const resolver = resolvers[name]; if (typeof resolver !== 'function') { throw new Error(`Expected ${String(name)} to be a function, but got ${typeof resolver}`); } resolver.scope ?? (resolver.scope = scope); privateResolvers[name] = resolver; } return self; }, privateImpl(implementation) { if (sealed) { throw new Error('Cannot extend initialized module'); } for (const name of Object.keys(implementation)) { const method = implementation[name]; const resolver = (moduleInstance) => (...args) => method(moduleInstance, ...args); resolver.scope = 'module'; privateResolvers[name] = resolver; } return self; }, public(resolvers, scope) { if (sealed) { throw new Error('Cannot extend initialized module'); } for (const name of Object.keys(resolvers)) { const resolver = resolvers[name]; if (typeof resolver !== 'function') { throw new Error(`Expected ${String(name)} to be a function, but got ${typeof resolver}`); } resolver.scope ?? (resolver.scope = scope); publicResolvers[name] = resolver; } return self; }, publicImpl(implementation) { if (sealed) { throw new Error('Cannot extend initialized module'); } for (const name of Object.keys(implementation)) { const method = implementation[name]; const resolver = (moduleInstance) => (...args) => method(moduleInstance, ...args); resolver.scope = 'module'; publicResolvers[name] = resolver; } return self; }, init(initializer) { initializers = initializer; sealed = true; return self; }, create(params = {}) { sealed = true; return createModule(singletons, privateResolvers, publicResolvers, initializers ?? {}, params); }, }; // eslint-disable-next-line @typescript-eslint/no-explicit-any return self; }; export default Module;