/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. */ /** * A resource that can be disposed of either synchronously or asynchronously. */ export type DisposableLike = Disposable | AsyncDisposable export type ResourceConstructor = new (key: K) => R export type ResourceFactory = (key: K) => R | Promise export type ResourceFactoryLike = | ResourceConstructor | ResourceFactory export type InferResource = F extends ResourceConstructor ? R : F extends ResourceFactory ? R : never export type OpenResourceResult = F extends ResourceConstructor ? R : F extends ResourceFactory ? ReturnType : never /** * Type-guard to determine if a function is a constructor. */ function isConstructor(value: unknown): value is T { if (typeof value !== "function") return false return Boolean(value.prototype && value.prototype.constructor === value) } /** * Type-guard to determine if a value is a promise-like object. */ export function isPromiseLike(value: unknown): value is PromiseLike { if (typeof value !== "object" || value === null) return false return typeof (value as PromiseLike).then === "function" } /** * A map-like object that caches disposable resources, creating them on demand. */ export class ResourceMapCache< K extends PropertyKey = PropertyKey, R extends DisposableLike = DisposableLike, F extends ResourceFactoryLike = ResourceConstructor, > extends Map implements AsyncDisposable { /** * The human-readable name of the resource. */ public displayName = "ResourceMapCache" protected readonly factoryLike: F constructor(ResourceConstructor: ResourceConstructor) constructor(factory: ResourceFactory) constructor(factoryLike: F) { super() this.factoryLike = factoryLike } /** * Gets a resource from the cache, creating if it doesn't exist. */ public open(key: K): OpenResourceResult { const existingResource = super.get(key) if (existingResource) return existingResource as OpenResourceResult if (isConstructor>(this.factoryLike)) { const resource = new this.factoryLike(key) super.set(key, resource) return resource as OpenResourceResult } const factoryResult = this.factoryLike(key) if (isPromiseLike(factoryResult)) { return factoryResult.then((resolvedResource) => { super.set(key, resolvedResource) return resolvedResource }) as OpenResourceResult } super.set(key, factoryResult) return factoryResult as OpenResourceResult } /** * Closes a resource and removes it from the cache. */ public close(key: K): void { const resource = super.get(key) if (resource && Symbol.dispose in resource) { resource[Symbol.dispose]() } super.delete(key) } public async [Symbol.asyncDispose]() { const resourceKeys = Array.from(super.keys()).toReversed() for (const resourceKey of resourceKeys) { this.close(resourceKey) } super.clear() } }