import { AsyncEach } from "@fizzwiz/fluent";
/**
* Abstract base class for asynchronous iterable collections.<br>
* The `AsyncCollection` class mirrors the synchronous {@link Collection} interface,<br>
* providing async CRUD-style operations and utilities for sorted or unsorted data structures.<br><br>
*
* <b>Abstract Methods</b>:<br>
* Subclasses must implement the following:<br>
* - {@link n} — number of items in the collection<br>
* - {@link has} — check item membership<br>
* - {@link add} — add a new item<br>
* - {@link remove} — remove an existing item<br>
* - {@link clear} — clear all items<br>
* - {@link get} — retrieve items matching a query<br>
* - {@link [Symbol.asyncIterator]} — iterate over the collection<br><br>
*
* <b>Concrete Methods</b>:<br>
* Built on top of the abstract primitives:<br>
* - CRUD operations: {@link create}, {@link read}, {@link update}, {@link delete}<br>
* - Query helpers: {@link query}, {@link readAll}<br>
* - Utility methods: {@link isEmpty}, {@link let}, {@link addAll}, {@link removeAll}, {@link deleteAll}<br>
*/
export class AsyncCollection extends AsyncEach {
constructor() {
super();
}
// ------------------ Abstract primitives ------------------
/**
* Returns the number of items in the collection.
* @abstract
* @returns {Promise<number>}
*/
async n() {
throw new Error("Abstract method: n()");
}
/**
* Checks whether the collection contains the given item.
* @abstract
* @param {any} item - The item to test for membership.
* @returns {Promise<boolean>} True if the item is present.
*/
async has(item) {
throw new Error("Abstract method: has(item)");
}
/**
* Adds the given item to the collection.
* Semantics (allowing or rejecting duplicates) depend on the concrete subclass.
* @abstract
* @param {any} item - The item to add.
* @returns {Promise<boolean>} True if the collection was modified.
*/
async add(item) {
throw new Error("Abstract method: add(item)");
}
/**
* Removes the given item from the collection.
* @abstract
* @param {any} item - The item to remove.
* @returns {Promise<boolean>} True if the collection was modified.
*/
async remove(item) {
throw new Error("Abstract method: remove(item)");
}
/**
* Removes all items from the collection.
* @abstract
* @returns {Promise<Boolean>}
*/
async clear() {
throw new Error("Abstract method: clear()");
}
/**
* Retrieves items matching a given query.
* In the simplest case, the query is an item, and all equivalent items
* are returned. Subclasses may support richer query semantics.
* @abstract
* @param {any} query - The query object or value.
* @returns {AsyncEach<any>} A fluent AsyncEAch of all matching items.
*/
get(query) {
throw new Error("Abstract method: get(query)");
}
/**
* Returns an async iterator over all items in the collection.
* @abstract
* @returns {AsyncIterator<any>}
*/
[Symbol.asyncIterator]() {
throw new Error("Abstract method: Symbol.asyncIterator()");
}
// ------------------ CRUD operations ------------------
/**
* **Create (C in CRUD)**
* Adds a new item if it is not already present.
* @param {any} item - The item to create.
* @returns {Promise<boolean>} True if the collection was modified.
*/
async create(item) {
if (await this.has(item)) return false;
return this.add(item);
}
/**
* **Read (R in CRUD)**
* Returns a single matching item, or `undefined` if none exist.
* Retrieves the "first" match from {@link get}.
* @param {any} item - The item or query to read.
* @returns {Promise<any|undefined>} The matching item or `undefined`.
*/
async read(item) {
if (await this.has(item)) {
for await (const val of this.get(item)) return val;
}
return undefined;
}
/**
* **Read All (extended CRUD)**
* Returns all items that match the given query.
* @param {any} query - The query to evaluate.
* @returns {AsyncIterable<any>} All matching items.
*/
readAll(query) {
return this.get(query);
}
/**
* **Update (U in CRUD)**
* Replaces an existing item with a new one.
* Inserts the new item if `upsert` is true and old item is not found.
* @param {any} oldItem - The item to be replaced.
* @param {any} newItem - The new item to insert.
* @param {boolean} [upsert=false] - Whether to insert if `oldItem` is not found.
* @returns {Promise<boolean>} True if updated or inserted, false otherwise.
*/
async update(oldItem, newItem, upsert = false) {
if (await this.has(oldItem)) {
return (await this.remove(oldItem)) && (await this.add(newItem));
} else if (upsert) {
return this.add(newItem);
}
return false;
}
/**
* **Delete (D in CRUD)**
* Removes an item from the collection.
* @param {any} item - The item to delete.
* @returns {Promise<boolean>} True if the collection was modified.
*/
async delete(item) {
return this.remove(item);
}
/**
* **Delete Many (extended CRUD)**
* Removes all given items from the collection.
* Alias of {@link removeAll}.
* @param {Iterable<any> | AsyncIterable<any>} items - Items to delete.
* @returns {Promise<any[]>} List of actually removed items.
*/
async deleteAll(items) {
return this.removeAll(items);
}
// ------------------ Query helpers ------------------
/**
* Alias for {@link get}, for database-style terminology.
* @param {any} query - The query to evaluate.
* @returns {AsyncEach<any>} All matching items.
*/
query(query) {
return this.get(query);
}
// ------------------ Utility methods ------------------
/**
* Checks whether the collection is empty.
* @returns {Promise<boolean>} True if the collection has no items.
*/
async isEmpty() {
return (await this.n()) === 0;
}
/**
* Adds all items from an iterable.
* @param {Iterable<any> | AsyncIterable<any>} items - Items to add.
* @returns {Promise<any[]>} List of actually added items.
*/
async addAll(items) {
const added = [];
for await (const item of items) {
if (await this.add(item)) added.push(item);
}
return added;
}
/**
* Removes all given items from the collection.
* @param {Iterable<any> | AsyncIterable<any>} items - Items to remove.
* @returns {Promise<any[]>} List of actually removed items.
*/
async removeAll(items) {
const removed = [];
for await (const item of items) {
if (await this.remove(item)) removed.push(item);
}
return removed;
}
}