/** * Factory function type that creates a service instance. * @template T - The type of service being created * @template TContainer - The type of the container being injected */ export type ServiceFactory = (container: TContainer) => T; /** * Extender function type that modifies an existing service. * @template T - The type of service being extended * @template TContainer - The type of the container being injected */ export type ServiceExtender = (original: T, container: TContainer) => T; /** * Interface for service providers that can register services with a container. * @template TMap - The service map type extending ServiceMap */ export interface ServiceProvider { /** * Registers services with the provided container. * @param container - The container to register services with */ register(container: JimpleWithProxy): void; } /** * Interface for async service providers that can register services with a container. * @template TMap - The service map type extending ServiceMap */ export interface AsyncServiceProvider { /** * Registers services asynchronously with the provided container. * @param container - The container to register services with */ registerAsync(container: JimpleWithProxy): Promise; } /** * Base interface for service mapping. Extend this interface to define your service types. * @example * ```typescript * interface MyServiceMap extends ServiceMap { * userService: UserService; * logger: Logger; * } * ``` */ export interface ServiceMap { } /** * Utility type to extract the service type from a service map. * @template TMap - The service map * @template TKey - The key in the service map */ export type ServiceType = TMap[TKey]; /** * Type for initial service registration, allowing either concrete instances or factory functions. * @template TMap - The service map * @template TContainer - The container type */ export type InitialServiceMap = { [TKey in keyof TMap]: ServiceType | ServiceFactory, TContainer>; }; /** * Proxy-enhanced Jimple container with direct property access to services. * @template TMap - The service map extending ServiceMap */ export type JimpleWithProxy = Jimple & { readonly [TKey in keyof TMap]: ServiceType; }; /** * The Jimple Container class with TypeScript support. * A dependency injection container that manages services and parameters. * * @template TMap - The service map extending ServiceMap * * @example * ```typescript * interface MyServices extends ServiceMap { * logger: Logger; * userService: UserService; * } * * const container = Jimple.create(); * container.set('logger', () => new ConsoleLogger()); * container.set('userService', (c) => new UserService(c.logger)); * * const userService = container.userService; // Typed as UserService * ``` */ export default class Jimple { /** Internal storage for service definitions and parameters */ private readonly _items; /** Cache for instantiated services */ private readonly _instances; /** Set of functions marked as factories (always return new instances) */ private readonly _factories; /** Set of functions marked as protected (returned as-is, not called) */ private readonly _protected; /** Proxy-enhanced version of this container for property access */ private readonly _bind; /** * Creates a service provider object. * @template TMap - The service map * @param register - Function that registers services with a container * @returns A service provider object * * @example * ```typescript * const myProvider = Jimple.provider((container) => { * container.set('logger', () => new Logger()); * }); * ``` */ static provider(register: ServiceProvider["register"]): ServiceProvider; /** * Creates an asynchronous service provider object. * @template TMap - The service map * @param registerAsync - Function that registers services with a container asynchronously * @returns A service provider object * * @example * ```typescript * const myProvider = Jimple.providerAsync(async (container) => { * container.set('config', await initLogger()); * }); * ``` */ static providerAsync(registerAsync: AsyncServiceProvider["registerAsync"]): AsyncServiceProvider; /** * Creates a new Jimple container instance with proxy support. * @template TMap - The service map * @param values - Initial services and parameters to register * @returns A proxy-enhanced container * * @example * ```typescript * const container = Jimple.create({ * logger: () => new ConsoleLogger(), * apiUrl: 'https://api.example.com' * }); * ``` */ static create(values?: InitialServiceMap>): JimpleWithProxy; /** * Create a Jimple Container. * @param values - Initial services and parameters to register * * @example * ```typescript * const container = new Jimple({ * config: { apiUrl: 'https://api.example.com' }, * logger: (c) => new Logger(c.config) * }); * ``` */ constructor(values?: Partial>>); /** * Return the specified parameter or service with correct typing. * Services defined as functions are instantiated when first accessed (singleton pattern). * Services marked as factories are instantiated on every access. * Services marked as protected are returned as-is without being called. * * @template TKey - The service key type * @param key - The service key to retrieve * @returns The service instance or parameter value * @throws {Error} When the service is not defined * * @example * ```typescript * const logger = container.get('logger'); * const apiUrl = container.get('apiUrl'); * ``` */ get(key: TKey): ServiceType; /** * Defines a new parameter or service. * Functions are treated as service factories unless marked as protected. * * @template TKey - The service key type * @param key - The service key * @param value - The service value, instance, or factory function * @throws {Error} When trying to redefine an already instantiated service * * @example * ```typescript * // Parameter * container.set('apiUrl', 'https://api.example.com'); * * // Service factory * container.set('logger', (c) => new Logger(c.apiUrl)); * * // Service instance * container.set('cache', new MemoryCache()); * ``` */ set(key: TKey, value: ServiceFactory, JimpleWithProxy>): void; set(key: TKey, value: ServiceType): void; /** * Unsets a parameter or service, removing it from the container. * Also clears any cached instances and metadata for the service. * * @template TKey - The service key type * @param key - The service key to remove * * @example * ```typescript * container.unset('logger'); * console.log(container.has('logger')); // false * ``` */ unset(key: TKey): void; /** * Returns if a service or parameter is defined in the container. * * @template TKey - The service key type * @param key - The service key to check * @returns True if the service is defined * * @example * ```typescript * if (container.has('logger')) { * const logger = container.get('logger'); * } * ``` */ has(key: TKey): boolean; /** * Defines a service as a factory that creates new instances on every access. * Unlike regular services (which are singletons), factories always call the function. * * @template TKey - The service key type * @template T - The factory function type * @param fn - The factory function to mark * @returns The same function (for chaining) * @throws {Error} When the value is not a function * * @example * ```typescript * container.set('requestId', container.factory(() => Math.random().toString())); * * const id1 = container.get('requestId'); // Different value each time * const id2 = container.get('requestId'); // Different value each time * ``` */ factory, JimpleWithProxy>>(fn: T): T; /** * Defines a function as a parameter (protected from being called as a service factory). * The function will be returned as-is when accessed, not executed. * * @template T - The function type * @param fn - The function to protect * @returns The same function (for chaining) * @throws {Error} When the value is not a function * * @example * ```typescript * const callback = (data: any) => console.log(data); * container.set('onComplete', container.protect(callback)); * * const fn = container.get('onComplete'); // Returns the function itself * fn('Hello'); // Can be called later * ``` */ protect>(fn: T extends (...args: any[]) => any ? T : never): T; /** * Return all the keys registered in the container. * * @returns Array of all service keys * * @example * ```typescript * const keys = container.keys(); * console.log('Registered services:', keys); * ``` */ keys(): (keyof TMap)[]; /** * Extends a service already registered in the container. * Allows decorating or modifying an existing service definition. * * @template TKey - The service key type * @template TResult - The extended service type * @param key - The service key to extend * @param fn - Function that receives the original service and returns the extended version * @throws {Error} When the service is not defined, not a function, protected, or already instantiated * * @example * ```typescript * container.set('logger', () => new Logger()); * * container.extend('logger', (logger, c) => { * return new LoggerDecorator(logger, c.config); * }); * ``` */ extend(key: TKey, fn: ServiceExtender, JimpleWithProxy>): void; /** * Uses a provider to extend the service container. * Providers are objects with a register method that can add multiple services. * * @template K - The subset of service keys the provider manages * @param provider - The service provider to register * * @example * ```typescript * const databaseProvider = Jimple.provider((container) => { * container.set('db', () => new Database(container.config)); * container.set('userRepo', (c) => new UserRepository(c.db)); * }); * * container.register(databaseProvider); * ``` */ register(provider: ServiceProvider>): void; /** * Uses an async provider to extend the service container. * Providers are objects with a register method that can add multiple services and returns a Promise. * * @template K - The subset of service keys the provider manages * @param provider - The asynchronous service provider to register * * @example * ```typescript * const databaseProvider = Jimple.providerAsync(async (container) => { * container.set("config", await initDb()); * container.set('db', () => new Database(container.config)); * container.set('userRepo', (c) => new UserRepository(c.db)); * }); * * container.registerAsync(databaseProvider); * ``` */ registerAsync(provider: AsyncServiceProvider>): Promise; /** * Returns the raw value of a service or parameter without instantiation. * For services defined as functions, returns the function itself rather than calling it. * * @template TKey - The service key type * @param key - The service key * @returns The raw service definition or parameter value * @throws {Error} When the service is not defined * * @example * ```typescript * const factory = container.raw('logger'); // Returns the factory function * const logger = factory(container); // Manually instantiate * ``` */ raw(key: TKey): ServiceType | ServiceFactory, JimpleWithProxy>; }