@akala/core
Version:
169 lines • 7.21 kB
JavaScript
import "reflect-metadata";
import { SimpleInjector } from './simple-injector.js';
export const injectSymbol = Symbol('inject');
export const afterInjectSymbol = Symbol('after-inject');
export function inject(name) {
if (Array.isArray(name))
return function (target, propertyKey) {
name.forEach((name, i) => inject(name)(target, propertyKey, i));
};
return function (target, propertyKey, parameterIndex) {
if (typeof parameterIndex == 'number') {
if (!name)
throw new Error('name is required as parameter names are not available in reflection');
const injections = Reflect.getOwnMetadata(injectSymbol, target) || { [propertyKey]: [] };
if (!injections[propertyKey])
injections[propertyKey] = [];
injections[propertyKey].push(function (injector) {
const resolved = injector.resolve(name);
return { index: parameterIndex, value: resolved };
});
if (propertyKey)
Reflect.defineMetadata(injectSymbol, injections[propertyKey], target[propertyKey]);
Reflect.defineMetadata(injectSymbol, injections, target);
}
else {
const injections = Reflect.getOwnMetadata(injectSymbol, target) || { [propertyKey]: [] };
if (!injections[propertyKey])
injections[propertyKey] = [];
injections[propertyKey].push(function (injector) {
this[propertyKey] = injector.resolve(name || propertyKey);
});
Reflect.defineMetadata(injectSymbol, injections, target);
}
};
}
/**
* Processes dependency injection metadata for an object and its prototype chain.
*
* @param {SimpleInjector} injector - The injector to resolve dependencies.
* @param {object} obj - Target object to apply injections to.
* @param {object} [prototype] - Optional prototype to traverse (used internally).
*/
export function applyInjector(injector, obj, prototype) {
const injections = Reflect.getOwnMetadata(injectSymbol, prototype || obj);
// if (injections && injections.length)
// injections.forEach(f => f(injector));
if (prototype !== Object.prototype)
applyInjector(injector, obj, Reflect.getPrototypeOf(prototype || obj));
for (const property in injections) {
if (property?.length) {
let descriptor = Reflect.getOwnPropertyDescriptor(obj, property);
if (!descriptor && prototype) {
descriptor = Reflect.getOwnPropertyDescriptor(prototype, property);
if (descriptor && typeof descriptor.value !== 'function')
descriptor = null;
}
if (!descriptor) {
let valueSet = false;
let value;
Reflect.defineProperty(obj, property, {
get() {
if (valueSet)
return value;
injections[property].forEach(i => i.call(obj, injector));
return obj[property];
},
set(v) {
value = v;
valueSet = true;
}
});
}
else if (descriptor.value) {
const oldFunction = descriptor.value;
Object.defineProperty(obj, property, {
value: function injected(...args) {
const mergedArgs = SimpleInjector.mergeArrays(injections[property].map(p => p(injector)), ...args);
return oldFunction.apply(this, mergedArgs.args);
}
});
}
else {
injections[property].forEach(p => p.call(obj, injector));
}
}
}
}
/**
* Decorator to make a class injectable with dependency resolution.
*
* @template TInstance - Type of the class instance.
* @template TClass - Type of the class constructor.
* @param {TClass} ctor - Class constructor to wrap.
* @param {SimpleInjector} injector - Optional injector instance (default uses constructor argument).
* @returns {TClass} Injectable class with dependency injection setup.
*/
export function injectable(ctor, injector) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-expect-error
const result = class DynamicProxy extends ctor {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(...args) {
const injectionObj = Reflect.getOwnMetadata(injectSymbol, ctor);
if (injectionObj)
var injections = injectionObj['undefined'];
if (!injector) {
injector = args.shift();
if ((!injector || !(injector instanceof SimpleInjector)) && injections && injections.length)
throw new Error(`No injector was provided while it is required to construct ${ctor}`);
// if (injector)
// Reflect.defineMetadata(injectSymbol, injector, new.target);
}
let injected = injections && injections.map(f => f(injector)) || [];
injected = injected.filter(p => typeof p.index == 'number');
super(...SimpleInjector.mergeArrays(injected, injector, ...args).args);
// Reflect.deleteMetadata(injectSymbol, new.target);
// Object.setPrototypeOf(this, Object.create(ctor.prototype));
// if (new.target == result)
// {
applyInjector(injector, this);
if (typeof (this[afterInjectSymbol]) != 'undefined')
this[afterInjectSymbol]();
// }
}
};
// Object.setPrototypeOf(result, Object.create(ctor.prototype));
Object.assign(result, ctor);
return result;
}
/**
* Creates a class decorator to apply an injector to a class.
*
* @param {SimpleInjector} injector - Injector instance to bind to the class.
* @returns {Function} Class decorator that configures the class for dependency injection.
*/
export function useInjector(injector) {
return function classInjectorDecorator(ctor) {
return injectable(ctor, injector);
};
}
/**
* Extends a class with an injector for dependency resolution.
*
* @template TClass - Type of the class to extend.
* @param {SimpleInjector} injector - Injector instance to apply.
* @param {TClass} constructor - Class to extend.
* @returns {TClass} Extended class with injector configuration.
*/
export function extendInject(injector, constructor) {
return useInjector(injector)(constructor);
}
/**
* Reflection-based injector that resolves dependencies using metadata.
*
* @extends SimpleInjector
*/
export class ReflectionInjector extends SimpleInjector {
parent;
/**
* Creates a new ReflectionInjector instance.
*
* @param {SimpleInjector} [parent] - Optional parent injector to delegate unresolved dependencies to.
*/
constructor(parent) {
super(parent);
this.parent = parent;
}
}
//# sourceMappingURL=reflection-injector.js.map