@rmk-labs/typescript-dependency-injector
Version:
Dependency injection framework for TypeScript
175 lines (170 loc) • 6.72 kB
TypeScript
declare const PROVIDER_SYMBOL: unique symbol;
interface Provider<T> {
provide: (...args: any[]) => T;
}
declare abstract class BaseProvider<T> implements Provider<T> {
private [PROVIDER_SYMBOL];
private _overridingProviders;
abstract provide(...args: any[]): T;
/**
* Returns a readonly copy of the overriding providers stack.
*/
get overrides(): readonly Provider<T>[];
/**
* Overrides the current provider with another provider.
* When the provider is called, it will delegate to the last overriding provider.
* @param provider - The provider to override with
* @throws Error if the argument is not a provider
*/
override(provider: Provider<T>): void;
/**
* Resets the last overriding provider and returns it.
* @returns The provider that was removed from the override stack
* @throws Error if there are no overriding providers
*/
resetLastOverriding(): Provider<T>;
/**
* Resets all overriding providers at once and returns them.
* @returns An array of all providers that were removed from the override stack
*/
resetOverride(): Provider<T>[];
/**
* Returns true if the provider is currently overridden, false otherwise.
*/
isOverridden(): boolean;
/**
* Returns a Delegate provider that wraps this provider.
* The Delegate's provide() method returns this provider instance.
*/
get provider(): Delegate<T>;
/**
* Protected method for providers to delegate to the overriding provider if one exists.
* Should be called at the beginning of each provider's provide() method.
*/
protected _delegateIfOverridden(...args: any[]): T | undefined;
}
/**
* Type representing a constructable that can create instances of T.
* Can be either a class constructor or a factory function.
*/
type FactoryConstructable<T> = (new (...args: any[]) => T) | ((...args: any[]) => T);
declare class Factory<T, ProvideArgs extends any[] = any[]> extends BaseProvider<T> {
private factory;
private injectedArgs;
private isConstructor;
constructor(factory: FactoryConstructable<T>, ...injectedArgs: any[]);
provide(...args: ProvideArgs): T;
/**
* Resolves an argument, handling Extend instances specially.
* If the argument is an Extend instance and context args are provided,
* merges the context with defaults (context takes priority).
*/
private _resolveArg;
}
declare class Singleton<T, ProvideArgs extends any[] = any[]> extends Factory<T, ProvideArgs> {
private instance;
provide(...args: ProvideArgs): T;
resetInstance(): void;
}
/**
* Delegate provider that wraps another provider.
* When provide() is called, it returns the wrapped provider instance itself.
* This is useful for dependency injection scenarios where you want to inject
* the provider rather than the provided value.
*/
declare class Delegate<T> extends BaseProvider<Provider<T>> {
private readonly delegatedProvider;
constructor(delegatedProvider: Provider<T>);
provide(): Provider<T>;
}
/**
* Base class for declarative dependency injection containers.
* Extend this class and define provider properties to create a container.
*
* @example
* ```ts
* class MyContainer extends DeclarativeContainer {
* databaseConfig = new Factory(DatabaseConfig, "localhost", 5432, "myapp");
* database = new Singleton(Database, this.databaseConfig);
* }
*
* const container = new MyContainer();
*
* // Access providers via instance properties
* const db = container.database.provide();
* const config = container.databaseConfig.provide();
* ```
*/
declare abstract class DeclarativeContainer {
resetProviderOverrides(): void;
resetSingletonInstances(): void;
}
type Constructor<T = object> = new (...args: any[]) => T;
type AnyFunction = Function;
type IsProviderLike<T> = T extends BaseProvider<any> ? true : T extends {
provide: (...args: any[]) => any;
} ? true : false;
type ProviderKeys<T> = {
[K in keyof T]-?: IsProviderLike<T[K]> extends true ? K : never;
}[keyof T];
type InjectableMarkers<TContainer extends DeclarativeContainer> = {
[K in ProviderKeys<TContainer>]: ParameterDecorator & {
provider: ParameterDecorator;
};
};
type InjectAPI<TContainer extends DeclarativeContainer> = InjectableMarkers<TContainer> & {
wire: (container: TContainer) => void;
unwire: (container: TContainer) => void;
Injectable: <T extends Constructor>(target: T) => T;
};
declare function createInject<TCtor extends Constructor<DeclarativeContainer>>(options: {
containerClass: TCtor;
}): InjectAPI<InstanceType<TCtor>>;
declare function getInjectedParamIds(fn: AnyFunction): ReadonlyMap<number, symbol> | undefined;
declare function getMarkerFor<TCtor extends Constructor<DeclarativeContainer>>(containerCtor: TCtor, key: ProviderKeys<InstanceType<TCtor>> & PropertyKey): symbol;
declare function InstanceOf<T>(): T;
declare function InstanceOf<T>(_type: T): T extends abstract new (...args: any[]) => any ? InstanceType<T> : T;
declare function ProviderOf<T>(): Provider<T>;
declare function ProviderOf<T>(_type: T): T extends abstract new (...args: any[]) => any ? Provider<InstanceType<T>> : Provider<T>;
/**
* Symbol to identify Extend instances
*/
declare const EXTEND_SYMBOL: unique symbol;
/**
* Wraps an object to indicate that it should be extended with context values
* when the provider's .provide() method is called.
*
* When a Factory is created with an Extend-wrapped object as an argument,
* calling .provide(contextObj) will merge contextObj with the wrapped defaults,
* with context values taking priority.
*
* @example
* ```ts
* interface ServiceDeps {
* logger: Logger;
* database: Database;
* requestId: string;
* }
*
* class AppContainer extends DeclarativeContainer {
* logger = new Singleton(Logger);
* database = new Singleton(Database, "localhost");
*
* // requestId will come from context at runtime
* service = new Factory(Service, Extend({
* logger: this.logger,
* database: this.database,
* }));
* }
*
* const container = new AppContainer();
* // Context values take priority and extend the defaults
* const instance = container.service.provide({ requestId: "req-123" });
* ```
*/
declare class Extend<T extends Record<string, any>> {
readonly defaults: T;
readonly [EXTEND_SYMBOL] = true;
constructor(defaults: T);
}
export { BaseProvider, DeclarativeContainer, Delegate, Extend, Factory, type FactoryConstructable, type InjectAPI, type InjectableMarkers, InstanceOf, type Provider, ProviderOf, Singleton, createInject, getInjectedParamIds, getMarkerFor };