UNPKG

@akala/core

Version:
181 lines 6.98 kB
import { EventEmitter } from '../events/event-emitter.js'; import "reflect-metadata"; import { Injector, LocalInjector, injectorLog } from './shared.js'; import { LogLevels } from '../logging/shared.js'; /** * A simple dependency injection container that provides basic resolution capabilities. * This class extends LocalInjector and manages injectables through a straightforward key-value store. */ export class SimpleInjector extends LocalInjector { /** * Creates an instance of SimpleInjector. * @param {Injector | null} [parent=null] - The parent injector to fallback to when resolving dependencies. */ constructor(parent) { super(parent === null ? null : parent || defaultInjector); this.register('$injector', this); this.notifier = new EventEmitter(); } notifier; /** * Sets the injectables. * @param {{ [key: string]: unknown }} value - The injectables to set. */ setInjectables(value) { this.injectables = value; } /** * Returns the keys of the injectables. * @returns {string[]} The keys of the injectables. */ keys() { return Object.keys(this.injectables); } /** * Merges another SimpleInjector into this one. * @param {SimpleInjector} i - The SimpleInjector to merge. */ merge(i) { Object.getOwnPropertyNames(i.injectables).forEach((property) => { if (property != '$injector') this.registerDescriptor(property, Object.getOwnPropertyDescriptor(i.injectables, property)); }); } /** * Notifies listeners of a change. * @param {string} name - The name of the property that changed. * @param {PropertyDescriptor} [value] - The new value of the property. */ notify(name, value) { if (this.notifier == null) return; if (typeof value == 'undefined') value = Object.getOwnPropertyDescriptor(this.injectables, name); if (this.notifier.hasListener(name)) this.notifier.emit(name, value); if (this.parent && this.parent instanceof SimpleInjector) this.parent.notify(name, value); } onResolve(name, handler) { if (!handler) return new Promise((resolve) => { this.onResolve(name, resolve); }); const value = this.resolve(name); if (value !== undefined && value !== null) { handler(value); return; } if (!this.parent) this.notifier.once(name, (prop) => { if (prop.get) handler(prop.get()); else handler(prop.value); }); else this.parent.onResolve(name, handler); } resolveKeys(keys) { return Injector.resolveKeys(this.injectables, keys, keys => this.parent?.resolve(keys)); } /** * Resolves a value. * @param {Resolvable} param - The parameter to resolve. * @returns {T} The resolved value. */ resolve(param) { injectorLog.silly('resolving %O', param); if (typeof param == 'object') { if (Array.isArray(param)) return this.resolveKeys(param); const x = Injector.collectMap(param); return Injector.applyCollectedMap(param, Object.fromEntries(x.map(x => [x, this.resolve(x)]))); } if (this.injectables && param in this.injectables) { if (injectorLog.isEnabled(LogLevels.verbose)) { const obj = this.injectables[param]; if (typeof obj === 'object' && 'name' in obj) injectorLog.verbose(`resolved ${param.toString()} to ${obj} with name ${obj.name}`); else injectorLog.verbose(`resolved ${param.toString()} to %O`, obj); } else injectorLog.debug(`resolved ${param.toString()}`); return this.injectables[param]; } const indexOfDot = typeof param !== 'string' ? -1 : param.indexOf('.'); if (~indexOfDot) { return this.resolveKeys(param.split('.')); } if (this.parent) { injectorLog.silly('trying parent injector'); return this.parent.resolve(param); } return null; } inspecting = false; /** * Inspects the injectables. */ inspect() { if (this.inspecting) return; this.inspecting = true; console.error(this.injectables); this.inspecting = false; } browsingForJSON = false; /** * Converts the injectables to JSON. * @returns {object} The injectables as JSON. */ toJSON() { const wasBrowsingForJSON = this.browsingForJSON; this.browsingForJSON = true; if (!wasBrowsingForJSON) return this.injectables; this.browsingForJSON = wasBrowsingForJSON; return undefined; } injectables; /** * Unregisters an injectable. * @param {string} name - The name of the injectable to unregister. */ unregister(name) { const registration = Object.getOwnPropertyDescriptor(this.injectables, name); if (registration) delete this.injectables[name]; } /** * Registers a descriptor for an injectable. * @param {string | symbol} name - The name of the injectable. * @param {PropertyDescriptor} value - The descriptor of the injectable. * @param {boolean} [override] - Whether to override an existing injectable. */ registerDescriptor(name, value, override) { this.injectables ??= {}; if (typeof name == 'string') { const indexOfDot = name.indexOf('.'); if (~indexOfDot) { let nested = this.resolve(name.substring(0, indexOfDot)); if (typeof nested == 'undefined' || nested === null) this.registerDescriptor(name.substring(0, indexOfDot), { configurable: false, value: nested = new SimpleInjector() }); if (nested instanceof LocalInjector) nested.registerDescriptor(name.substring(indexOfDot + 1), value, override); else throw new Error('compound keys like ' + name + ' can be used only with injector-like as intermediaries'); } } injectorLog.debug('registering ' + name.toString()); if (!override && typeof (this.injectables[name]) != 'undefined') throw new Error('There is already a registered item for ' + name.toString()); if (typeof (this.injectables[name]) !== 'undefined') this.unregister(name.toString()); Object.defineProperty(this.injectables, name, value); this.notify(name.toString(), value); } } export const defaultInjector = new SimpleInjector(null); //# sourceMappingURL=simple-injector.js.map