@hazae41/piscine
Version:
Create async pools with automatic retry
436 lines (432 loc) • 12 kB
JavaScript
'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