UNPKG

@hazae41/piscine

Version:

Create async pools with automatic retry

436 lines (432 loc) 12 kB
'use strict'; var tslib_es6 = require('../../../node_modules/tslib/tslib.es6.cjs'); var arrays = require('@hazae41/arrays'); var box = require('@hazae41/box'); var plume = require('@hazae41/plume'); var result = require('@hazae41/result'); var _a, _b; class EmptyPoolError extends Error { #class = _a; name = this.#class.name; constructor() { super(`Empty pool`); } } _a = EmptyPoolError; class EmptySlotError extends Error { #class = _b; name = this.#class.name; constructor() { super(`Empty slot`); } } _b = EmptySlotError; class Indexed { index; value; constructor(index, value) { this.index = index; this.value = value; } [Symbol.dispose]() { this.value[Symbol.dispose](); } get() { return this.value; } } class PoolItem extends box.Box { pool; value; clean; constructor(pool, value, clean) { super(value); this.pool = pool; this.value = value; this.clean = clean; } [Symbol.dispose]() { if (this.dropped) return; this.clean[Symbol.dispose](); super[Symbol.dispose](); this.pool.delete(this.value.index); } moveOrNull() { const box = super.moveOrNull(); if (box == null) return; this.clean[Symbol.dispose](); this.pool.delete(this.value.index); return box; } moveOrThrow() { const box = super.moveOrThrow(); this.clean[Symbol.dispose](); this.pool.delete(this.value.index); return box; } unwrapOrNull() { const value = super.unwrapOrNull(); if (value == null) return; this.clean[Symbol.dispose](); this.pool.delete(this.value.index); return value; } unwrapOrThrow() { const value = super.unwrapOrThrow(); this.clean[Symbol.dispose](); this.pool.delete(this.value.index); return value; } returnOrThrow() { super.returnOrThrow(); if (!this.owned) return; this.pool.update(this.value.index); } } class Pool { events = new plume.SuperEventTarget(); /** * Sparse entries by index */ #entries = new Array(); /** * A pool of disposable items */ constructor() { } [Symbol.iterator]() { return this.#entries.values(); } [Symbol.dispose]() { for (const entry of this.#entries) { const env_1 = { stack: [], error: void 0, hasError: false }; try { if (entry == null) continue; if (entry.isErr()) continue; const _ = tslib_es6.__addDisposableResource(env_1, entry.get(), false); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { tslib_es6.__disposeResources(env_1); } } this.#entries.length = 0; } #delete(index) { const env_2 = { stack: [], error: void 0, hasError: false }; try { const previous = this.#entries.at(index); if (previous == null) return; delete this.#entries[index]; if (previous.isErr()) return previous; const _ = tslib_es6.__addDisposableResource(env_2, previous.get(), false); return previous; } catch (e_2) { env_2.error = e_2; env_2.hasError = true; } finally { tslib_es6.__disposeResources(env_2); } } /** * Set the entry at the given index and return it * @param index * @param result * @returns */ #set(index, result$1) { if (result$1.isOk()) { const { value, clean } = result$1.get(); const indexed = new Indexed(index, value); const item = new PoolItem(this, indexed, clean); const entry = new result.Ok(item); this.#delete(index); this.#entries[index] = entry; this.events.emit("update", index).catch(console.error); return entry; } else { const value = result$1.getErr(); const entry = new result.Err(value); this.#delete(index); this.#entries[index] = entry; this.events.emit("update", index).catch(console.error); return entry; } } /** * Set the entry at the given index and return it * @param index * @param result * @returns */ set(index, result) { this.#set(index, result); } /** * Delete the entry at the given index and return it * @param index * @returns */ delete(index) { this.#delete(index); } update(index) { const entry = this.#entries.at(index); if (entry == null) return; this.events.emit("update", index).catch(console.error); } /** * Get the entry at the given index or null if empty * @param index * @returns */ getAnyOrNull(index) { return this.#entries.at(index); } /** * Get the entry at the given index or throw if empty * @param index * @returns */ getAnyOrThrow(index) { const entry = this.#entries.at(index); if (entry == null) throw new EmptySlotError(); return entry; } /** * Get the item at the given index or null if empty or errored * @param index * @returns */ getOrNull(index) { const entry = this.#entries.at(index); if (entry == null) return; if (entry.isErr()) return; return entry.get(); } /** * Get the item at the given index or throw if empty or errored * @param index * @returns */ getOrThrow(index) { const entry = this.#entries.at(index); if (entry == null) throw new EmptySlotError(); return entry.getOrThrow(); } /** * Get a random item or null if none available * @returns */ getRandomOrNull(filter) { return arrays.Arrays.random(this.#entries.map(filter).filter(x => x != null)); } /** * Get a random item or throw if none available * @returns */ getRandomOrThrow(filter) { const value = arrays.Arrays.random(this.#entries.map(filter).filter(x => x != null)); if (value == null) throw new EmptyPoolError(); return value; } /** * Get a crypto-random item or null if none available * @returns */ getCryptoRandomOrNull(filter) { return arrays.Arrays.cryptoRandom(this.#entries.map(filter).filter(x => x != null)); } /** * Get a crypto-random item or throw if none available * @returns */ getCryptoRandomOrThrow(filter) { const value = arrays.Arrays.cryptoRandom(this.#entries.map(filter).filter(x => x != null)); if (value == null) throw new EmptyPoolError(); return value; } async waitOrThrow(index, filter, signal = new AbortController().signal) { while (!signal.aborted) { const entry = this.#entries.at(index); const value = filter(entry); if (value != null) return value; await plume.Plume.waitOrThrow(this.events, "update", (f, i) => { if (i !== index) return; f.resolve(); }, signal); } throw signal.reason; } async waitRandomOrThrow(filter, signal = new AbortController().signal) { while (!signal.aborted) { const entry = arrays.Arrays.random(this.#entries); const value = filter(entry); if (value != null) return value; await plume.Plume.waitOrThrow(this.events, "update", (f) => { f.resolve(); }, signal); } throw signal.reason; } async waitCryptoRandomOrThrow(filter, signal = new AbortController().signal) { while (!signal.aborted) { const entry = arrays.Arrays.cryptoRandom(this.#entries); const value = filter(entry); if (value != null) return value; await plume.Plume.waitOrThrow(this.events, "update", (f) => { f.resolve(); }, signal); } throw signal.reason; } } class StartPool extends Pool { /** * Sparse aborters by index */ #aborters = new Array(); /** * A pool of startable items */ constructor() { super(); } [Symbol.dispose]() { super[Symbol.dispose](); for (const aborter of this.#aborters) aborter?.abort(); this.#aborters.length = 0; } async #create(index, creator, signal) { try { const env_3 = { stack: [], error: void 0, hasError: false }; try { const disposer = await creator(index, signal); const stack = tslib_es6.__addDisposableResource(env_3, new box.Stack(), false); stack.push(disposer); stack.push(disposer.get()); if (signal.aborted) return; delete this.#aborters[index]; stack.array.length = 0; super.set(index, new result.Ok(disposer)); } catch (e_3) { env_3.error = e_3; env_3.hasError = true; } finally { tslib_es6.__disposeResources(env_3); } } catch (e) { if (signal.aborted) return; delete this.#aborters[index]; const value = result.Catched.wrap(e); super.set(index, new result.Err(value)); } } #start(index, creator) { this.#abort(index); const aborter = new AbortController(); this.#aborters[index] = aborter; const { signal } = aborter; this.#create(index, creator, signal); } #abort(index) { const aborter = this.#aborters.at(index); if (aborter != null) aborter.abort(); delete this.#aborters[index]; } /** * Start the given index * @param index * @returns */ start(index, creator) { this.#start(index, creator); } /** * Abort the given index * @param index * @returns */ abort(index) { this.#abort(index); } } class AutoPool extends StartPool { creator; capacity; #state = "started"; /** * An automatic pool or startable items * @param creator * @param capacity * @returns */ constructor(creator, capacity) { super(); this.creator = creator; this.capacity = capacity; for (let i = 0; i < capacity; i++) this.delete(i); return this; } [Symbol.dispose]() { this.#state = "stopped"; super[Symbol.dispose](); } set(index, result) { throw new Error("Disallowed"); } start(index, creator) { throw new Error("Disallowed"); } abort(index) { throw new Error("Disallowed"); } delete(index) { if (index >= this.capacity) return; super.set(index, new result.Err(new EmptySlotError())); if (this.#state === "stopped") return; super.start(index, this.creator); } } exports.AutoPool = AutoPool; exports.EmptyPoolError = EmptyPoolError; exports.EmptySlotError = EmptySlotError; exports.Indexed = Indexed; exports.Pool = Pool; exports.PoolItem = PoolItem; exports.StartPool = StartPool; //# sourceMappingURL=index.cjs.map