provider is key
This commit is contained in:
@@ -1,75 +1,58 @@
|
||||
type Provider<T> = (manager: DependencyManager) => Promise<T> | T;
|
||||
export type Provider<T = unknown> = (manager: DependencyManager) => T;
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
type HasConstructor<T> = T extends new (...args: any[]) => unknown ? T : never;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
type ConstructorInstance<T> = T extends new (...args: any[]) => infer I ? I
|
||||
: never;
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
type ModuleIdentifier = any;
|
||||
|
||||
type Modules = Map<ModuleIdentifier, {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
provider: Provider<any>;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
instance?: any;
|
||||
}>;
|
||||
export type ProviderGroup = {
|
||||
[key: string]: Provider | ProviderGroup;
|
||||
};
|
||||
|
||||
export class DependencyManager {
|
||||
private modules: Modules = new Map();
|
||||
private resolving: Set<ModuleIdentifier> = new Set();
|
||||
private declarations = new Map<unknown, Provider>();
|
||||
private instances = new Map();
|
||||
private resolving = new Set();
|
||||
|
||||
/**
|
||||
* Set the given provider to be used to resolve the dependency identified by the given identifier.
|
||||
*
|
||||
* Throw an error if the identifier is already used.
|
||||
*
|
||||
* @param identifier the identifier that will be used to ask the manager for the resolved dependency.
|
||||
* @param provider a function that receive the dependency manager as param and return the dependency. Can be asynchronous.
|
||||
*/
|
||||
register<T>(identifier: ModuleIdentifier, provider: Provider<T>): void {
|
||||
if (this.modules.has(identifier)) {
|
||||
throw new Error(`Module ${identifier} is already registered.`);
|
||||
}
|
||||
this.modules.set(identifier, { provider });
|
||||
}
|
||||
register(dependency: Provider): void;
|
||||
register(module: ProviderGroup): void;
|
||||
register(dependencyOrModule: Provider | ProviderGroup): void;
|
||||
register(dependencyOrModule: Provider | ProviderGroup) {
|
||||
if (this.declarations.has(dependencyOrModule)) {
|
||||
throw new Error('This dependency has already been registred');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dependency matching the given identifier.
|
||||
* If the dependency has not been resolved yet, resolve it first.
|
||||
*
|
||||
* Throw an error if:
|
||||
* - no provider has been registred with that identifier
|
||||
* - a circular dependency is detected
|
||||
*
|
||||
* @param identifier the identifier used to register the provider
|
||||
*/
|
||||
async resolve<T>(
|
||||
identifier: HasConstructor<T>,
|
||||
): Promise<ConstructorInstance<T>>;
|
||||
async resolve<T>(identifier: ModuleIdentifier): Promise<T>;
|
||||
async resolve(identifier: ModuleIdentifier) {
|
||||
const module = this.modules.get(identifier);
|
||||
if (!module) {
|
||||
throw new Error(`Module ${identifier} is not registered.`);
|
||||
}
|
||||
if (typeof dependencyOrModule === 'function') {
|
||||
this.declarations.set(dependencyOrModule, dependencyOrModule);
|
||||
return;
|
||||
}
|
||||
|
||||
if (module.instance) {
|
||||
return module.instance;
|
||||
}
|
||||
const module = dependencyOrModule;
|
||||
for (const key in module) {
|
||||
this.register(module[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.resolving.has(identifier)) {
|
||||
throw new Error(`Circular dependency detected for module ${identifier}.`);
|
||||
}
|
||||
resolve<T>(dependency: Provider<T>): T {
|
||||
if (!this.declarations.has(dependency)) {
|
||||
throw new Error('This dependency has not been registred');
|
||||
}
|
||||
|
||||
this.resolving.add(identifier);
|
||||
if (this.instances.has(dependency)) {
|
||||
return this.instances.get(dependency) as T;
|
||||
}
|
||||
|
||||
try {
|
||||
module.instance = await module.provider(this);
|
||||
return module.instance;
|
||||
} finally {
|
||||
this.resolving.delete(identifier);
|
||||
}
|
||||
}
|
||||
if (this.resolving.has(dependency)) {
|
||||
throw new Error('Circular dependency detected');
|
||||
}
|
||||
|
||||
this.resolving.add(dependency);
|
||||
const instance = this.instanciate(dependency);
|
||||
this.resolving.delete(dependency);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private instanciate<T>(dependency: Provider<T>): T {
|
||||
const declaration = this.declarations.get(dependency) as Provider<T>;
|
||||
const instance = declaration(this);
|
||||
this.instances.set(dependency, instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user