UNPKG

@akala/core

Version:
169 lines 7.21 kB
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