type Provider = (manager: DependencyManager) => Promise | T; // deno-lint-ignore no-explicit-any type HasConstructor = T extends new (...args: any[]) => unknown ? T : never; // deno-lint-ignore no-explicit-any type ConstructorInstance = T extends new (...args: any[]) => infer I ? I : never; // deno-lint-ignore no-explicit-any type ModuleIdentifier = any; type Modules = Map; // deno-lint-ignore no-explicit-any instance?: any; }>; export class DependencyManager { private modules: Modules = new Map(); private resolving: Set = 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(identifier: ModuleIdentifier, provider: Provider): void { if (this.modules.has(identifier)) { throw new Error(`Module ${identifier} is already registered.`); } this.modules.set(identifier, { provider }); } /** * 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( identifier: HasConstructor, ): Promise>; async resolve(identifier: ModuleIdentifier): Promise; async resolve(identifier: ModuleIdentifier) { const module = this.modules.get(identifier); if (!module) { throw new Error(`Module ${identifier} is not registered.`); } if (module.instance) { return module.instance; } if (this.resolving.has(identifier)) { throw new Error(`Circular dependency detected for module ${identifier}.`); } this.resolving.add(identifier); try { module.instance = await module.provider(this); return module.instance; } finally { this.resolving.delete(identifier); } } }