UNPKG

@noxfly/noxus

Version:

Simulate lightweight HTTP-like requests between renderer and main process in Electron applications with MessagePort, with structured and modular design.

163 lines (138 loc) 6.18 kB
/** * @copyright 2025 NoxFly * @license MIT * @author NoxFly */ import 'reflect-metadata'; import { INJECT_METADATA_KEY } from 'src/decorators/inject.decorator'; import { InternalServerException } from 'src/exceptions'; import { ForwardReference } from 'src/utils/forward-ref'; import { Type } from 'src/utils/types'; /** * Represents a lifetime of a binding in the dependency injection system. * It can be one of the following: * - 'singleton': The instance is created once and shared across the application. * - 'scope': The instance is created once per scope (e.g., per request). * - 'transient': A new instance is created every time it is requested. */ export type Lifetime = 'singleton' | 'scope' | 'transient'; /** * Represents a binding in the dependency injection system. * It contains the lifetime of the binding, the implementation type, and optionally an instance. */ export interface IBinding { lifetime: Lifetime; implementation: Type<unknown>; instance?: InstanceType<Type<unknown>>; } /** * AppInjector is the root dependency injection container. * It is used to register and resolve dependencies in the application. * It supports different lifetimes for dependencies: * This should not be manually instantiated, outside of the framework. * Use the `RootInjector` instance instead. */ export class AppInjector { public bindings = new Map<Type<unknown>, IBinding>(); public singletons = new Map<Type<unknown>, InstanceType<Type<unknown>>>(); public scoped = new Map<Type<unknown>, InstanceType<Type<unknown>>>(); constructor( public readonly name: string | null = null, ) {} /** * Typically used to create a dependency injection scope * at the "scope" level (i.e., per-request lifetime). * * SHOULD NOT BE USED by anything else than the framework itself. */ public createScope(): AppInjector { const scope = new AppInjector(); scope.bindings = this.bindings; // pass injectable declarations scope.singletons = this.singletons; // share parent's singletons to avoid recreating them // do not keep parent's scoped instances return scope; } /** * Called when resolving a dependency, * i.e., retrieving the instance of a given class. */ public resolve<T>(target: Type<T> | ForwardReference<T>): T { if (target instanceof ForwardReference) { return new Proxy({}, { get: (obj, prop, receiver) => { const realType = target.forwardRefFn(); const instance = this.resolve(realType) as any; const value = Reflect.get(instance, prop, receiver); return typeof value === 'function' ? value.bind(instance) : value; }, set: (obj, prop, value, receiver) => { const realType = target.forwardRefFn(); const instance = this.resolve(realType) as any; return Reflect.set(instance, prop, value, receiver); }, getPrototypeOf: () => { const realType = target.forwardRefFn(); return (realType as any).prototype; } }) as T; } const binding = this.bindings.get(target); if(!binding) { if(target === undefined) { throw new InternalServerException( "Failed to resolve a dependency injection : Undefined target type.\n" + "This might be caused by a circular dependency." ); } const name = target.name || "unknown"; throw new InternalServerException( `Failed to resolve a dependency injection : No binding for type ${name}.\n` + `Did you forget to use @Injectable() decorator ?` ); } switch(binding.lifetime) { case 'transient': return this.instantiate(binding.implementation) as T; case 'scope': { if(this.scoped.has(target)) { return this.scoped.get(target) as T; } const instance = this.instantiate(binding.implementation); this.scoped.set(target, instance); return instance as T; } case 'singleton': { if(binding.instance === undefined && this.name === 'root') { binding.instance = this.instantiate(binding.implementation); this.singletons.set(target, binding.instance); } return binding.instance as T; } } } /** * Instantiates a class, resolving its dependencies. */ private instantiate<T extends Type<unknown>>(target: T): InstanceType<T> { const paramTypes = Reflect.getMetadata('design:paramtypes', target) || []; const injectParams = Reflect.getMetadata(INJECT_METADATA_KEY, target) || []; const params = paramTypes.map((paramType: any, index: number) => { const overrideToken = injectParams[index]; const actualToken = overrideToken !== undefined ? overrideToken : paramType; return this.resolve(actualToken); }); return new target(...params) as InstanceType<T>; } } /** * Injects a type from the dependency injection system. * This function is used to retrieve an instance of a type that has been registered in the dependency injection system. * It is typically used in the constructor of a class to inject dependencies. * @param t - The type to inject. * @returns An instance of the type. * @throws If the type is not registered in the dependency injection system. */ export function inject<T>(t: Type<T> | ForwardReference<T>): T { return RootInjector.resolve(t); } export const RootInjector = new AppInjector('root');