/**
* @description Type representing either a class constructor or an instance.
* @summary Defines an Injectable type that can be either a class constructor or an instance of a class.
* @template T The type of the injectable object
* @typedef {function(any): T | T} Injectable
* @memberOf module:injectable-decorators
*/
export type Injectable<T> = { new (...args: any[]): T } | T;
/**
* @description Contract for a registry that manages injectable objects.
* @summary Interface for an injectable registry that provides methods for retrieving, registering, and building injectable objects.
* @template T Type parameter used in the interface methods
* @interface InjectablesRegistry
* @memberOf module:injectable-decorators
*/
export interface InjectablesRegistry {
/**
* @description Fetches an injectable instance by its registered name.
* @summary Retrieves an {@link Injectable} from the registry by name, optionally passing constructor arguments.
* @template T Type of the injectable object to retrieve
* @param {string} name The registered name of the injectable to retrieve
* @param {any[]} args Constructor arguments to pass when instantiating the injectable
* @return {Injectable<T> | undefined} The injectable instance or undefined if not found
* @memberOf module:injectable-decorators
*/
get<T>(name: string, ...args: any[]): Injectable<T> | undefined;
/**
* @description Adds a class or object to the injectable registry.
* @summary Registers an injectable constructor or instance with the registry, making it available for injection.
* @template T Type of the injectable object to register
* @param {Injectable<T>} constructor The class constructor or object instance to register
* @param {any[]} args Additional arguments for registration (category, singleton flag, etc.)
* @return {void}
* @memberOf module:injectable-decorators
*/
register<T>(constructor: Injectable<T>, ...args: any[]): void;
/**
* @description Creates a new instance of an injectable class.
* @summary Instantiates an injectable class using its constructor and the provided arguments.
* @template T Type of the object to build
* @param {Record<string, any>} obj Object containing the name of the injectable to build
* @param {any[]} args Constructor arguments to pass when instantiating the injectable
* @return {T} The newly created instance
* @memberOf module:injectable-decorators
*/
build<T>(obj: Record<string, any>, ...args: any[]): T;
}
/**
* @description Default implementation of the InjectablesRegistry interface.
* @summary Holds the various {@link Injectable}s in a cache and provides methods to register, retrieve, and build them.
* @template T Type parameter used in the class methods
*
* @class InjectableRegistryImp
* @implements InjectablesRegistry
*
* @memberOf module:injectable-decorators
*
* @example
* // Create a new registry
* const registry = new InjectableRegistryImp();
*
* // Register a class
* class MyService {
* doSomething() {
* return 'Hello World';
* }
* }
* registry.register(MyService, 'MyService', true);
*
* // Get the instance
* const service = registry.get('MyService');
* service.doSomething(); // 'Hello World'
*
* @mermaid
* sequenceDiagram
* participant Client
* participant Registry
*
* Client->>Registry: register(MyService)
* Registry->>Registry: Store in cache
*
* Client->>Registry: get("MyService")
* alt Instance exists and is singleton
* Registry-->>Client: Return cached instance
* else No instance or not singleton
* Registry->>Registry: build(name)
* Registry-->>Client: Return new instance
* end
*/
export class InjectableRegistryImp implements InjectablesRegistry {
private cache: { [indexer: string]: any } = {};
/**
* @inheritDoc
*/
get<T>(name: string, ...args: any[]): T | undefined {
try {
const innerCache = this.cache[name];
const buildDef = { name: name };
if (!innerCache.singleton && !innerCache.instance)
return this.build<T>(buildDef, ...args);
return innerCache.instance || this.build<T>(buildDef, ...args);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
return undefined;
}
}
/**
* @inheritDoc
*/
register<T>(
obj: Injectable<T>,
category: string | undefined = undefined,
isSingleton: boolean = true,
force: boolean = false
): void {
const castObj: Record<string, any> = obj as Record<string, any>;
const constructor = !castObj.name && castObj.constructor;
if (typeof castObj !== "function" && !constructor)
throw new Error(
`Injectable registering failed. Missing Class name or constructor`
);
const name =
category ||
(constructor && constructor.name && constructor.name !== "Function"
? (constructor as { [indexer: string]: any }).name
: castObj.name);
if (!this.cache[name] || force)
this.cache[name] = {
instance: constructor ? obj : undefined,
constructor: !constructor ? obj : undefined,
singleton: isSingleton,
};
}
/**
* @inheritDoc
*/
build<T>(defs: { name: string }, ...args: any[]): T {
const { constructor, singleton } = this.cache[defs.name];
const instance = new constructor(...args);
this.cache[defs.name] = {
instance: instance,
constructor: constructor,
singleton: singleton,
};
return instance;
}
}
Source