@worker-tools/deno-kv-storage
Version:
An implementation of the StorageArea (1,2,3) interface for Deno with an extensible system for supporting various database backends.
137 lines (121 loc) • 3.73 kB
text/typescript
import { Deferred, deferred } from "../deps.js";
export class DeferredStack<T> {
#array: Array<T>;
#creator?: () => Promise<T>;
#max_size: number;
#queue: Array<Deferred<T>>;
#size: number;
constructor(
max?: number,
ls?: Iterable<T>,
creator?: () => Promise<T>,
) {
this.#array = ls ? [...ls] : [];
this.#creator = creator;
this.#max_size = max || 10;
this.#queue = [];
this.#size = this.#array.length;
}
get available(): number {
return this.#array.length;
}
async pop(): Promise<T> {
if (this.#array.length > 0) {
return this.#array.pop()!;
} else if (this.#size < this.#max_size && this.#creator) {
this.#size++;
return await this.#creator();
}
const d = deferred<T>();
this.#queue.push(d);
await d;
return this.#array.pop()!;
}
push(value: T): void {
this.#array.push(value);
if (this.#queue.length > 0) {
const d = this.#queue.shift()!;
d.resolve();
}
}
get size(): number {
return this.#size;
}
}
/**
* The DeferredAccessStack provides access to a series of elements provided on the stack creation,
* but with the caveat that they require an initialization of sorts before they can be used
*
* Instead of providing a `creator` function as you would with the `DeferredStack`, you provide
* an initialization callback to execute for each element that is retrieved from the stack and a check
* callback to determine if the element requires initialization and return a count of the initialized
* elements
*/
export class DeferredAccessStack<T> {
#elements: Array<T>;
#initializeElement: (element: T) => Promise<void>;
#checkElementInitialization: (element: T) => Promise<boolean> | boolean;
#queue: Array<Deferred<undefined>>;
#size: number;
get available(): number {
return this.#elements.length;
}
/**
* The max number of elements that can be contained in the stack a time
*/
get size(): number {
return this.#size;
}
/**
* @param initialize This function will execute for each element that hasn't been initialized when requested from the stack
*/
constructor(
elements: T[],
initCallback: (element: T) => Promise<void>,
checkInitCallback: (element: T) => Promise<boolean> | boolean,
) {
this.#checkElementInitialization = checkInitCallback;
this.#elements = elements;
this.#initializeElement = initCallback;
this.#queue = [];
this.#size = elements.length;
}
/**
* Will execute the check for initialization on each element of the stack
* and then return the number of initialized elements that pass the check
*/
async initialized(): Promise<number> {
const initialized = await Promise.all(
this.#elements.map((e) => this.#checkElementInitialization(e)),
);
return initialized
.filter((initialized) => initialized === true)
.length;
}
async pop(): Promise<T> {
let element: T;
if (this.available > 0) {
element = this.#elements.pop()!;
} else {
// If there are not elements left in the stack, it will await the call until
// at least one is restored and then return it
const d = deferred<undefined>();
this.#queue.push(d);
await d;
element = this.#elements.pop()!;
}
if (!await this.#checkElementInitialization(element)) {
await this.#initializeElement(element);
}
return element;
}
push(value: T): void {
this.#elements.push(value);
// If an element has been requested while the stack was empty, indicate
// that an element has been restored
if (this.#queue.length > 0) {
const d = this.#queue.shift()!;
d.resolve();
}
}
}