@akala/core
Version:
181 lines • 6.98 kB
JavaScript
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