UNPKG

@stone-js/service-container

Version:

Javascript/Typescript IoC Service Container with proxy resolver and destructuring injection

529 lines (519 loc) 19.9 kB
/** * Class representing a Proxiable. * * This class allows instances to be wrapped in a Proxy, enabling custom behaviors for property access, assignment, etc. * * @author Mr. Stone <evensstone@gmail.com> */ declare abstract class Proxiable { /** * Creates a Proxiable instance wrapped in a Proxy. * * @param handler - A trap object for the proxy, which defines custom behavior for fundamental operations (e.g., property lookup, assignment, etc.). * @returns A new proxy object for this instance. */ constructor(handler: ProxyHandler<Proxiable>); } /** * A resolver function that takes a container and returns a value of type V. * * @template V - The type of value that the resolver returns. * @param container - The container used to resolve dependencies. * @returns The resolved value of type V. * * @example * ```typescript * const myResolver: Resolver<number> = (container: IContainer) => { * // Use the container to resolve dependencies and return a number. * return 42; * }; * ``` */ type Resolver<V> = (container: IContainer) => V; /** * A union type representing the possible keys that can be used to bind values in the container. * * Binding keys can be of various types, such as numbers, booleans, strings, functions, objects, or symbols. * These types are used because they provide a broad range of ways to uniquely identify a binding. * * - `number`, `boolean`, `string`: These are basic types that are easy to use and uniquely identify a binding. * - `Function`: Useful for identifying bindings by constructor or other functions. * - `object`: Allows more complex key types, like instances of classes. * - `symbol`: Guarantees a unique identifier, which can prevent conflicts. * * @example * ```typescript * const key1: BindingKey = 42; // Using a number as a key * const key2: BindingKey = 'serviceName'; // Using a string as a key * const key3: BindingKey = Symbol('uniqueKey'); // Using a symbol for uniqueness * const key4: BindingKey = MyServiceClass; // Using a function (constructor) as a key * const key5: BindingKey = { custom: 'objectKey' }; // Using an object as a key * ``` */ type BindingKey = number | boolean | string | Function | object | symbol; /** * A union type representing the possible values that can be bound in the container. * * Binding values can be of various types, including numbers, booleans, strings, functions, objects, or symbols. * Unlike `BindingKey`, `BindingValue` represents the actual data or instance being bound, while `BindingKey` represents the identifier used to access that data. */ type BindingValue = number | boolean | string | Function | object | symbol; /** * Interface representing a Binding. * * This interface defines the contract for all types of bindings in the service container. * Bindings are used to manage dependencies and control how objects are instantiated within the container. * * @template V - The type of value that this binding holds. * @author Mr. Stone <evensstone@gmail.com> */ interface IBinding<V extends BindingValue> { /** * Resolve and return the value of the binding. * * @param container - The container to resolve dependencies from. * @returns The resolved value of the binding. */ resolve: (container: IContainer) => V | undefined; } /** * Interface representing a Container. * * This interface defines the public contract for dependency injection containers, * allowing for better testability and preventing circular dependencies. * * @author Mr. Stone <evensstone@gmail.com> */ interface IContainer { /** * Retrieve the value of the bindings property. */ getBindings: () => Map<BindingKey, IBinding<BindingValue>>; /** * Retrieve the value of the aliases property. */ getAliases: () => Map<string, BindingKey>; /** * Set a binding as alias. */ alias: (key: BindingKey, aliases: string | string[]) => this; /** * Check if an alias exists in the container. */ isAlias: (alias: BindingKey) => boolean; /** * Get a binding key by its alias. */ getAliasKey: (alias: BindingKey) => BindingKey | undefined; /** * Bind a single instance or value into the container under the provided key. */ instance: (key: BindingKey, value: BindingValue) => this; /** * Bind a single instance or value into the container under the provided key if not already bound. */ instanceIf: (key: BindingKey, value: BindingValue) => this; /** * Bind a resolver function into the container under the provided key as a singleton. */ singleton: <V extends BindingValue>(key: BindingKey, resolver: Resolver<V>) => this; /** * Bind a resolver function into the container under the provided key as a singleton if not already bound. */ singletonIf: <V extends BindingValue>(key: BindingKey, resolver: Resolver<V>) => this; /** * Bind a resolver function into the container under the provided key, returning a new instance each time. */ binding: <V extends BindingValue>(key: BindingKey, resolver: Resolver<V>) => this; /** * Bind a resolver function into the container under the provided key, returning a new instance each time if not already bound. */ bindingIf: <V extends BindingValue>(key: BindingKey, resolver: Resolver<V>) => this; /** * Resolve a registered value from the container by its key. */ make: <V extends BindingValue>(key: BindingKey) => V; /** * Resolve a value from the container by its key, binding it if necessary. */ resolve: <V extends BindingValue>(key: BindingKey, singleton?: boolean) => V; /** * Resolve a value from the container by its key and return it in a factory function. */ factory: <V extends BindingValue>(key: BindingKey) => () => V; /** * Check if a value is already bound in the container by its key. */ bound: (key: BindingKey) => boolean; /** * Check if a value is already bound in the container by its key. */ has: (key: BindingKey) => boolean; /** * Reset the container so that all bindings are removed. */ clear: () => this; /** * AutoBind value to the service container. */ autoBinding: <V extends BindingValue>(name: BindingKey, item?: V, singleton?: boolean, alias?: string | string[]) => this; } /** * Abstract class representing a Binding. * * This abstract class serves as the base class for all types of bindings in the service container. It holds a value and provides an abstract method * to resolve and return that value, allowing different subclasses to implement their own resolution logic. Bindings are used to manage dependencies * and control how objects are instantiated within the container. * * @template V - The type of value that this binding holds. * @author Mr. Stone <evensstone@gmail.com> */ declare abstract class Binding<V extends BindingValue> implements IBinding<V> { /** * The value held by the binding. * * This value is resolved at runtime, either directly or through a resolver function. */ protected value?: V; /** * Create a new instance of Binding. * * @param value - The value to be held by the binding. */ constructor(value?: V); /** * Check if the value has been resolved. * * @returns A boolean indicating whether the value has been resolved. */ protected isResolved(): boolean; /** * Resolve and return the value of the binding. * * This abstract method must be implemented by subclasses to provide specific resolution logic. * * @param container - The container to resolve dependencies from. * @returns The resolved value of the binding. */ abstract resolve(container: IContainer): V | undefined; } /** * Class representing a Container. * * The Container class acts as a dependency injection container, managing bindings and resolving instances. * It supports different types of bindings, such as singletons, factories, and instances, and allows the use of aliases for bindings. * This makes it easier to manage and resolve complex dependency trees in an application. * * @author Mr. Stone <evensstone@gmail.com> */ declare class Container extends Proxiable implements IContainer { private readonly aliases; private readonly resolvingKeys; private readonly bindings; /** * Create a Container. * * @returns A new Container instance. */ static create(): Container; /** * Create a ProxyHandler for the container. * * @returns A new ProxyHandler instance. */ private static Proxyhandler; /** * Create a container. * * Initializes the container with empty alias and binding maps. */ protected constructor(); /** * Retrieve the value of the bindings property. * * @returns A map of all bindings registered in the container. */ getBindings(): Map<BindingKey, Binding<BindingValue>>; /** * Retrieve the value of the aliases property. * * @returns A map of all aliases registered in the container. */ getAliases(): Map<string, BindingKey>; /** * Set a binding as alias. * * Adds one or more aliases for a given binding key. * * @param key - The binding value. * @param aliases - One or more strings representing the aliases. * @returns The container instance. */ alias(key: BindingKey, aliases: string | string[]): this; /** * Check if an alias exists in the container. * * @param alias - The alias to check. * @returns True if the alias exists, false otherwise. */ isAlias(alias: BindingKey): boolean; /** * Get a binding key by its alias. * * @param alias - The alias name. * @returns The binding key associated with the alias, or undefined if not found. */ getAliasKey(alias: BindingKey): BindingKey | undefined; /** * Bind a single instance or value into the container under the provided key. * * @param key - The key to associate with the value. * @param value - The value to be bound. * @returns The container instance. */ instance(key: BindingKey, value: BindingValue): this; /** * Bind a single instance or value into the container under the provided key if not already bound. * * @param key - The key to associate with the value. * @param value - The value to be bound. * @returns The container instance. */ instanceIf(key: BindingKey, value: BindingValue): this; /** * Bind a resolver function into the container under the provided key as a singleton. * * The resolver function will be called once, and the resulting value will be cached for future use. * * @param key - The key to associate with the singleton value. * @param resolver - The resolver function to provide the value. * @returns The container instance. */ singleton<V extends BindingValue>(key: BindingKey, resolver: Resolver<V>): this; /** * Bind a resolver function into the container under the provided key as a singleton if not already bound. * * @param key - The key to associate with the singleton value. * @param resolver - The resolver function to provide the value. * @returns The container instance. */ singletonIf<V extends BindingValue>(key: BindingKey, resolver: Resolver<V>): this; /** * Bind a resolver function into the container under the provided key, returning a new instance each time. * * @param key - The key to associate with the value. * @param resolver - The resolver function to provide the value. * @returns The container instance. */ binding<V extends BindingValue>(key: BindingKey, resolver: Resolver<V>): this; /** * Bind a resolver function into the container under the provided key, returning a new instance each time if not already bound. * * @param key - The key to associate with the value. * @param resolver - The resolver function to provide the value. * @returns The container instance. */ bindingIf<V extends BindingValue>(key: BindingKey, resolver: Resolver<V>): this; /** * Resolve a registered value from the container by its key. * * @param key - The key to resolve. * @returns The resolved value. * @throws ContainerError if the key cannot be resolved. */ make<V extends BindingValue>(key: BindingKey): V; /** * Resolve a value from the container by its key, binding it if necessary. * * @param key - The key to resolve. * @param singleton - Whether to bind as a singleton if not already bound. * @returns The resolved value. */ resolve<V extends BindingValue>(key: BindingKey, singleton?: boolean): V; /** * Resolve a value from the container by its key and return it in a factory function. * * @param key - The key to resolve. * @returns A factory function that returns the resolved value. */ factory<V extends BindingValue>(key: BindingKey): () => V; /** * Check if a value is already bound in the container by its key. * * @param key - The key to check. * @returns True if the key is bound, false otherwise. */ bound(key: BindingKey): boolean; /** * Check if a value is already bound in the container by its key. * * @param key - The key to check. * @returns True if the key is bound, false otherwise. */ has(key: BindingKey): boolean; /** * Reset the container so that all bindings are removed. * * @returns The container instance. */ clear(): this; /** * AutoBind value to the service container. * * @param name - A key to make the binding. Can be anything. * @param item - The item to bind. * @param singleton - Bind as singleton when true. * @param alias - Key binding aliases. * @returns The container instance. */ autoBinding<V extends BindingValue>(name: BindingKey, item?: V, singleton?: boolean, alias?: string | string[]): this; } /** * Class representing a ContainerError. * * @author Mr. Stone <evensstone@gmail.com> */ declare class ContainerError extends Error { /** * Error type indicating an alias conflict. */ static readonly ALIAS_TYPE = "alias"; /** * Error type indicating that the resolver is not a function. */ static readonly RESOLVER_TYPE = "resolver"; /** * Error type indicating a resolution failure. */ static readonly RESOLUTION_TYPE = "resolution"; /** * Error type indicating an attempt to alias an unbound value. */ static readonly ALIAS_UNBOUND_TYPE = "alias_unbound"; /** * Error type indicating that a value is not a service. */ static readonly NOT_A_SERVICE_TYPE = "not_a_service"; /** * Error type indicating an error thrown by the resolver function. */ static readonly CANNOT_RESOLVE_TYPE = "cannot_resolve"; /** * Error type indicating a circular dependency. */ static readonly CIRCULAR_DEPENDENCY_TYPE = "circular_dependency"; /** * The type of the error. */ private readonly type; /** * Create a ContainerError. * * @param type - The type of the error. * @param message - The error message or key related to the error. */ constructor(type: string, message: BindingKey); /** * Retrieve the error message based on the type and provided message. * * @param type - The type of the error. * @param message - The error message or key related to the error. * @returns The formatted error message. */ private getMessage; /** * Retrieve the resolution message based on the key. * * @param key - The key for which the resolution failed. * @returns The formatted resolution error message. */ private getResolutionMessage; } /** * Class representing a ResolverBinding. * * This class extends the Binding class, using a resolver function to lazily resolve the value when needed. * * @template V - The type of value that this binding holds. * @author Mr. Stone <evensstone@gmail.com> */ declare abstract class ResolverBinding<V extends BindingValue> extends Binding<V> { /** * The resolver function used to provide the binding value. * * This function will be called when the value is needed, allowing for lazy instantiation * and dependency resolution. It should return an instance of type `V`. */ protected readonly resolver: Resolver<V>; /** * Create a new instance of ResolverBinding. * * @param resolver - The resolver function to provide the binding value. * @throws ContainerError if the resolver is not a function. */ constructor(resolver: Resolver<V>); } /** * Class representing a Factory. * * The Factory class extends the ResolverBinding class, providing a mechanism to resolve a new instance each time the binding is resolved. * This ensures that a fresh instance is created with each call to the `resolve` method. * * @template V - The type of value that this binding holds. * @author Mr. Stone <evensstone@gmail.com> */ declare class Factory<V extends BindingValue> extends ResolverBinding<V> { /** * Resolve and return the value of the binding. * * Each time this method is called, a new value is resolved using the resolver function. * This is intended for cases where a fresh instance is required for each resolution, such as factories or transient dependencies. * * @param container - The container to resolve dependencies from. * @returns The resolved value of the binding. * @throws ContainerError if the value cannot be resolved. */ resolve(container: IContainer): V; } /** * Class representing an Instance. * * This class extends the Binding class and directly holds an instance value. * It provides a straightforward resolution mechanism that simply returns the stored value. * * @template V - The type of value that this binding holds. * @author Mr. Stone <evensstone@gmail.com> */ declare class Instance<V extends BindingValue> extends Binding<V> { /** * Resolve and return the value of the binding. * * @param _container - Container to resolve dependencies (not used in this implementation). * @returns The resolved value of the binding. */ resolve(_container: IContainer): V | undefined; } /** * Class representing a Singleton. * * The Singleton class extends the ResolverBinding class, ensuring that the value is only resolved once. * Subsequent calls to the `resolve` method will return the previously resolved value, making it behave as a singleton. * * @template V - The type of value that this binding holds. * @author Mr. Stone <evensstone@gmail.com> */ declare class Singleton<V extends BindingValue> extends ResolverBinding<V> { /** * Resolve and return the value of the binding. * * If the value has already been resolved, return the cached value. Otherwise, use the resolver function * to resolve the value, store it, and return it. * * @param container - The container to resolve dependencies from. * @returns The resolved value of the binding. * @throws ContainerError if the value cannot be resolved. */ resolve(container: IContainer): V | undefined; } export { Binding, Container, ContainerError, Factory, Instance, Proxiable, ResolverBinding, Singleton }; export type { BindingKey, BindingValue, IBinding, IContainer, Resolver };