UNPKG

@knowark/injectarkjs

Version:

Dependency Injector for Javascript

151 lines (128 loc) 4.57 kB
import { Factory } from './factory.js' // eslint-disable-line export class Injectark { /** @param {{ factory: Factory, * strategy?: object, parent?: Injectark }} object */ constructor ({ factory, strategy = {}, parent = null } = {}) { this.strategy = strategy this.factory = factory this.parent = parent this.registry = {} this._load() } /** @return {object} */ get config () { return this.factory.config } /** @param {string | object} resource */ get (resource) { if (typeof resource !== 'string') resource = resource.name const allowed = this.factory.allowed if (allowed?.length && !allowed.some(pattern => resource.match(pattern))) { throw new Error(`Direct access to "${resource}" is not allowed.`) } return this.resolve(resource, true) } /** @param {string | object} resource @param {object} instance */ set (resource, instance) { if (typeof resource !== 'string') resource = resource.name this.registry[resource] = instance } /** @param {string | object} resource @param {boolean=} strict */ resolve (resource, strict = false) { if (typeof resource !== 'string') resource = resource.name const fetched = this._registryFetch(resource) if (fetched) { return fetched } const resourceStrategy = this.strategy[resource] || {} const persist = !(resourceStrategy.ephemeral) const instance = this._dependencyBuild(resource, persist) if (strict && !instance) { throw new Error(`The "${resource}" resource could not be resolved.`) } return instance } /** @param {{strategy?: Object<string, any>, factory?: Factory }} * Object */ forge ({ strategy = {}, factory = null } = {}) { return new Injectark( { parent: this, strategy, factory }) } _load () { if (!this.factory) return const lazy = this.factory.lazy if (Object.keys(this.strategy).length) { Object.keys(this.strategy).forEach( resource => !lazy.includes(resource) && this.resolve(resource)) return } const dependencies = this._getAllMethodNames(this.factory).filter( method => !['constructor', 'extract', ...lazy].includes(method)) for (const dependency of dependencies) { const normalized = dependency[0].toUpperCase() + dependency.slice(1) this.resolve(normalized) } } _getAllMethodNames (instance) { const methods = new Set() while ((instance = Reflect.getPrototypeOf(instance)) && instance.constructor !== Object) { const keys = Reflect.ownKeys(instance) keys.forEach((key) => methods.add(key)) } return Array.from(methods) } /** @param {string} resource */ _registryFetch (resource) { let fetched = false const rule = this.strategy[resource] || {} if (rule.unique) { return fetched } if (Object.keys(this.registry).includes(resource)) { fetched = this.registry[resource] } else { const parent = this.parent fetched = parent ? parent._registryFetch(resource) : false } return fetched } _dependencyBuild (resource, persistent) { let instance let persist = persistent const rule = this.strategy[resource] || { method: resource[0].toLowerCase() + resource.slice(1) } const extract = this.factory.extract.bind(this.factory) const builder = extract(rule.method) if (builder) { const dependencies = ( builder.dependencies || this._parseDependencies(builder)) const dependencyInstances = [] for (const dependency of dependencies) { const dependencyInstance = this.resolve(dependency) dependencyInstances.push(dependencyInstance) } instance = builder.bind(this.factory)(...dependencyInstances) } else { instance = (this.parent ? this.parent._dependencyBuild(resource, persist) : instance) const resourceStrategy = this.strategy[resource] || {} persist = (persist && resourceStrategy.unique) } if (persist) { this.registry[resource] = instance } return instance } _parseDependencies (builder) { const stripComments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg const argumentNames = /([^\s,]+)/g const functionString = builder.toString().replace(stripComments, '') const result = functionString.slice(functionString.indexOf( '(') + 1, functionString.indexOf(')')).match(argumentNames) || [] return result.map(dependency => dependency.charAt(0).toUpperCase() + dependency.slice(1)) } }