function e(e) { '@hwc/retry' === globalThis?.process?.env.DEBUG && console.debug(e); } class RetryTask { id = Math.random().toString(36).slice(2); fn; error; timestamp = Date.now(); lastAttempt = this.timestamp; resolve; reject; signal; constructor(e, t, r, i, s) { (this.fn = e), (this.error = t), (this.timestamp = Date.now()), (this.lastAttempt = Date.now()), (this.resolve = r), (this.reject = i), (this.signal = s); } get age() { return Date.now() - this.timestamp; } } class Retrier { #e = []; #t = []; #r = 0; #i; #s; #n; #o; #c; constructor( e, { timeout: t = 6e4, maxDelay: r = 100, concurrency: i = 1e3 } = {} ) { if ('function' != typeof e) throw new Error('Missing function to check errors'); (this.#o = e), (this.#i = t), (this.#s = r), (this.#c = i); } get retrying() { return this.#e.length; } get pending() { return this.#t.length; } get working() { return this.#r; } #a(t, { signal: r, promise: i, resolve: s, reject: n }) { let o; try { o = t(); } catch (e) { return n(new Error(`Synchronous error: ${e.message}`, { cause: e })), i; } return o && 'function' == typeof o.then ? (this.#r++, i .finally(() => { this.#r--, this.#h(); }) .catch(() => {}), Promise.resolve(o) .then((t) => { e('Function called successfully without retry.'), s(t); }) .catch((i) => { if (!this.#o(i)) return void n(i); const o = new RetryTask(t, i, s, n, r); e(`Function failed, queuing for retry with task ${o.id}.`), this.#e.push(o), r?.addEventListener('abort', () => { e(`Task ${o.id} was aborted due to AbortSignal.`), n(r.reason); }), this.#g(); }), i) : (n(new Error('Result is not a promise.')), i); } retry(e, { signal: t } = {}) { t?.throwIfAborted(); const { promise: r, resolve: i, reject: s, } = (function () { if (Promise.withResolvers) return Promise.withResolvers(); let e, t; const r = new Promise((r, i) => { (e = r), (t = i); }); if (void 0 === e || void 0 === t) throw new Error( 'Promise executor did not initialize resolve or reject.' ); return { promise: r, resolve: e, reject: t }; })(); return ( this.#t.push(() => this.#a(e, { signal: t, promise: r, resolve: i, reject: s }) ), this.#h(), r ); } #u() { this.pending && this.#h(), this.retrying && this.#g(); } #h() { e( `Processing pending tasks: ${this.pending} pending, ${this.working} working.` ); const t = this.#c - this.working; if (t <= 0) return; const r = Math.min(this.pending, t); for (let e = 0; e < r; e++) { const e = this.#t.shift(); e?.(); } e( `Processed pending tasks: ${this.pending} pending, ${this.working} working.` ); } #g() { clearTimeout(this.#n), (this.#n = void 0), e( `Processing retry queue: ${this.retrying} retrying, ${this.working} working.` ); const t = () => { this.#n = setTimeout(() => this.#u(), 0); }, r = this.#e.shift(); return ( r ? ( (function (e, t) { return e.age > t; })(r, this.#i) ) ? (e(`Task ${r.id} was abandoned due to timeout.`), r.reject(r.error), void t()) : ( (function (e, t) { const r = Date.now() - e.lastAttempt, i = Math.max(e.lastAttempt - e.timestamp, 1); return r >= Math.min(1.2 * i, t); })(r, this.#s) ) ? ((r.lastAttempt = Date.now()), void Promise.resolve(r.fn()) .then((t) => { e(`Task ${r.id} succeeded after ${r.age}ms.`), r.resolve(t); }) .catch((t) => { if (!this.#o(t)) return ( e( `Task ${r.id} failed with non-retryable error: ${t.message}.` ), void r.reject(t) ); (r.lastAttempt = Date.now()), this.#e.push(r), e(`Task ${r.id} failed, requeueing to try again.`); }) .finally(() => { this.#u(); })) : (e(`Task ${r.id} is not ready to retry, skipping.`), this.#e.push(r), void t()) : (e('Queue is empty, exiting.'), void (this.pending && t())) ); } } export { Retrier };