UNPKG

@elsikora/cladi

Version:

Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.

105 lines (101 loc) 4.1 kB
'use strict'; var error_class = require('../../base/error.class.js'); class DisposalManager { activeAsyncResolutions; DISPOSAL_WAITERS; DISPOSE_INTERNAL; GET_ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS; GET_DISPOSE_PROMISE; IS_DISPOSED; IS_DISPOSING; SET_DISPOSE_PROMISE; SET_IS_DISPOSING; constructor(options) { this.DISPOSE_INTERNAL = options.disposeInternal; this.GET_ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS = options.getAsyncResolutionDrainTimeoutMs; this.GET_DISPOSE_PROMISE = options.getDisposePromise; this.IS_DISPOSED = options.isDisposed; this.IS_DISPOSING = options.isDisposing; this.SET_DISPOSE_PROMISE = options.setDisposePromise; this.SET_IS_DISPOSING = options.setIsDisposing; this.activeAsyncResolutions = 0; this.DISPOSAL_WAITERS = []; } async dispose() { if (this.IS_DISPOSED()) { return; } if (this.IS_DISPOSING()) { await this.GET_DISPOSE_PROMISE(); return; } this.SET_IS_DISPOSING(true); const disposePromise = this.DISPOSE_INTERNAL(); this.SET_DISPOSE_PROMISE(disposePromise); await disposePromise; } onAsyncResolutionEnd() { this.activeAsyncResolutions = Math.max(0, this.activeAsyncResolutions - 1); this.releaseDisposalWaitersIfIdle(); } onAsyncResolutionStart() { this.activeAsyncResolutions += 1; } async waitForInFlightAsyncResolutions() { const asyncResolutionDrainTimeoutMs = this.GET_ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS(); const hasTimeout = typeof asyncResolutionDrainTimeoutMs === "number" && Number.isFinite(asyncResolutionDrainTimeoutMs) && asyncResolutionDrainTimeoutMs >= 0; const normalizedTimeoutMs = hasTimeout ? asyncResolutionDrainTimeoutMs : undefined; const deadlineAtMs = normalizedTimeoutMs === undefined ? undefined : Date.now() + normalizedTimeoutMs; while (this.activeAsyncResolutions > 0) { await this.waitForIdleSignal(deadlineAtMs, normalizedTimeoutMs); } } createAsyncResolutionDrainTimeoutError(asyncResolutionDrainTimeoutMs) { return new error_class.BaseError("Timed out waiting for in-flight async resolutions during disposal", { code: "SCOPE_DISPOSE_ASYNC_DRAIN_TIMEOUT", context: { activeAsyncResolutions: this.activeAsyncResolutions, timeoutMs: asyncResolutionDrainTimeoutMs, }, source: "DIContainer", }); } releaseDisposalWaitersIfIdle() { if (this.activeAsyncResolutions > 0 || this.DISPOSAL_WAITERS.length === 0) { return; } const pendingWaiters = [...this.DISPOSAL_WAITERS]; this.DISPOSAL_WAITERS.length = 0; for (const waiter of pendingWaiters) { waiter(); } } async waitForIdleSignal(deadlineAtMs, configuredTimeoutMs) { if (deadlineAtMs === undefined) { await new Promise((resolve) => { this.DISPOSAL_WAITERS.push(resolve); }); return; } const remainingTimeoutMs = deadlineAtMs - Date.now(); if (remainingTimeoutMs <= 0) { throw this.createAsyncResolutionDrainTimeoutError(configuredTimeoutMs ?? 0); } await new Promise((resolve, reject) => { const waiter = () => { clearTimeout(timeoutHandle); resolve(); }; const timeoutHandle = setTimeout(() => { const waiterIndex = this.DISPOSAL_WAITERS.indexOf(waiter); if (waiterIndex !== -1) { this.DISPOSAL_WAITERS.splice(waiterIndex, 1); } reject(this.createAsyncResolutionDrainTimeoutError(configuredTimeoutMs ?? remainingTimeoutMs)); }, remainingTimeoutMs); this.DISPOSAL_WAITERS.push(waiter); }); } } exports.DisposalManager = DisposalManager; //# sourceMappingURL=manager.class.js.map