diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5ec8a6f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [angular-package] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: angularpackage # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/README.md b/README.md index 2ea1cd9..1c6d53e 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,11 @@ A lightweight TypeScript library for managing various queue and stack structures * [Installation](#installation) * [Api](#api) - * `ProcessingQueue` - * `Queue` - * `Stack` + * [`Elements`](#elements) + * [`ProcessingQueue`](#processingqueue) + * [`Processing`](#processing) + * [`Queue`](#queue) + * [`Stack`](#stack) * [Git](#git) * [Commit](#commit) * [Versioning](#versioning) @@ -40,12 +42,215 @@ npm install @typescript-package/queue ```typescript import { + // Class. + Elements, + // Abstract. ProcessingQueue, Queue, Stack } from '@typescript-package/queue'; ``` +### `Elements` + +```typescript +import { Elements } from '@typescript-package/queue'; + +let elements = new Elements( + [1, 2, 3, 4], // Array type elements. + 15 // Maximum size of the elements. +); + +// Appends the value at the end of an array state. +elements.append(5); +console.log(elements.state); // Output: [1, 2, 3, 4, 5] + +// Inserts the value at the specified index into the array state. +elements.insert(2, 10); +console.log(elements.state); // Output: [1, 2, 10, 3, 4, 5] + +// Adds the values at the beginning of array state. +elements.prepend(0); +console.log(elements.state); // Output: [0, 1, 2, 10, 3, 4, 5] + +// Updates the value at the index in the array state. +elements.update(0, 127); +console.log(elements.state); // Output: [127, 1, 2, 10, 3, 4, 5] +``` + +### `ProcessingQueue` + +```typescript +import { ProcessingQueue } from '@typescript-package/queue'; + +// Initialize the `ProcessingQueue`. +let processingQueue = new ProcessingQueue( + 2, // concurrency + 10, // size + 1, 2, 3 // items +); + +// The maximum number of elements that can be processed concurrently. +console.log(`concurrency, `, processingQueue.concurrency); // Output: 2 + +// A set containing all elements that have been successfully processed. +console.log(`processed, `, processingQueue.processed); // Output: Set(0) + +// Checks whether the queue is empty. +console.log(`isEmpty(), `, processingQueue.isEmpty()); // Output: false + +// Checks whether the queue is full. +console.log(`isFull(), `, processingQueue.isFull()); // Output: false + +// The maximum queue size. +console.log(`size, `, processingQueue.size); // Output: 10 + +// The actual queue Elements state - raw array state of the queue. +console.log(`state, `, processingQueue.state); // Output: [1, 2, 3] + +// The actual queue length. +console.log(`length, `, processingQueue.length); // Output: 3 + +// Adds a new element to the queue. +console.log(`enqueue(4), `, processingQueue.enqueue(4)); + +// The actual queue length. +console.log(`length, `, processingQueue.length); // Output: 4 + +// Returns the first element in the queue. +console.log(`peek(), `, processingQueue.peek()); // Output: 1 + +// Returns the first element in the queue. +console.log(`dequeue(), `, processingQueue.dequeue()); // Output: 1 + +// The actual queue length. +console.log(`length, `, processingQueue.length); // Output: 3 + +// Adds to the full. +processingQueue.enqueue(5).enqueue(6).enqueue(7).enqueue(8).enqueue(9).enqueue(10).enqueue(11); + +// The actual queue Elements state - raw array state of the queue. +console.log(`state, `, processingQueue.state); // Output: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + +// Waits for all elements in the queue to be processed and returns the set of processed elements. +processingQueue.isCompleted().then( + processed => console.log(`Completed`, processed), // Output: Completed Set(10) + reason => console.log(reason) +); + +// Starts processing elements in the queue using the provided callback function. +processingQueue.run(element => console.log(`Processed`, element)); // Output: Processed {element} + +// A set containing all elements that have been successfully processed. +console.log(`processed, `, processingQueue.processed); // Output: Set(10) +``` + +### `Processing` + +### `Queue` + +```typescript +import { Queue } from '@typescript-package/queue'; + +// Initialize the new `Queue`. +let queue = new Queue( + 10, // size + 1, 2, 3 // item +); + +// Adds a new element to the queue. +queue.enqueue(4); + +// The actual queue Elements state - raw array state of the queue. +console.log(`state,`, queue.state); // Output: [1, 2, 3, 4] + +// Returns the first element in the queue. +console.log(`peek(), `, queue.peek()); // Output: 1 + +// Checks if the queue is empty. +console.log(`isEmpty(),`, queue.isEmpty()); // Output: false + +// The maximum queue size. +console.log(`size,`, queue.size); // Output: 10 + +// The actual queue Elements state - raw array state of the queue. +console.log(`state,`, queue.state); // Output: [1, 2, 3, 4] + +// Adds to the full. +queue.enqueue(5).enqueue(6).enqueue(7).enqueue(8).enqueue(9).enqueue(10); + +// Checks whether the queue is full. +console.log(`isFull(), `, queue.isFull()); // Output: true + +try { + queue.enqueue(11); +} catch(error) { + console.log(error); // Error: Queue is full. +} + +// Clears the queue. +console.log(`clear(), `, queue.clear()); + +// Checks if the queue is empty. +console.log(`isEmpty(),`, queue.isEmpty()); // Output: true + +// The actual queue Elements state - raw array state of the queue. +console.log(`state,`, queue.state); // Output: [] + +``` + +### `Stack` + +```typescript +import { Stack } from '@typescript-package/queue'; + +// Initialize the `Stack`. +let stack = new Stack( + 10, // size + 1, 2, 3 // items +); + +// The actual stack length. +console.log(`length, `, stack.length); // Output: 3 + +// Adds a new element on the stack. +stack.push(4); + +// The maximum stack size. +console.log(`size, `, stack.size); // Output: 10 + +// The actual stack length. +console.log(`length, `, stack.length); // Output: 4 + +// Returns the top element on the stack. +console.log(`peek(), `, stack.peek()); // Output: 4 + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [1, 2, 3, 4] + +// Removes and returns the top element from the stack. +console.log(`pop(), `, stack.pop()); // Output: 4 + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [1, 2, 3] + +// Adds to the full. +stack.push(4).push(5).push(6).push(7).push(8).push(9).push(10); + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +// Checks if the stack is full. +console.log(`isFull(), `, stack.isFull()); // Output: true + +// Clears the queue. +stack.clear() + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [] +console.log(`isEmpty(), `, stack.isEmpty()); // Output: true +``` + ## GIT ### Commit diff --git a/package-lock.json b/package-lock.json index 1ae2769..c564924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "queue", - "version": "0.0.1", + "version": "1.0.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "queue", - "version": "0.0.1", + "version": "1.0.0-beta", "dependencies": { "tslib": "^2.3.0" }, diff --git a/package.json b/package.json index 8a1b992..52507bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-package/queue", - "version": "0.0.1", + "version": "1.0.0-beta", "author": "wwwdev.io ", "description": "A lightweight TypeScript library for managing various queue and stack structures.", "license": "MIT", diff --git a/src/lib/elements.class.ts b/src/lib/elements.class.ts new file mode 100644 index 0000000..55957a7 --- /dev/null +++ b/src/lib/elements.class.ts @@ -0,0 +1,111 @@ +// Abstract. +import { ArrayState } from "@typescript-package/state"; +/** + * @description Array state elements in data structures such as Stack and Queue. + * @export + * @class Elements + * @template Type + * @extends {ArrayState} + */ +export class Elements extends ArrayState{ + /** + * @description The maximum size of the `Elements`. + * @public + * @readonly + * @type {Size} + */ + public get size(): Size { + return this.#size; + } + + /** + * @description Privately stored maximum elements size. + * @type {Size} + */ + #size: Size; + + /** + * Creates an instance of `Elements`. + * @constructor + * @param {Type[]} elements + * @param {Size} [size=Infinity as Size] + */ + constructor(elements: Type[], size: Size = Infinity as Size) { + super(elements.length <= size ? elements : []); + // Sets the size. + this.#size = size; + // Throws an error if the elements exceeds the maximum size. + if (elements.length > size) { + throw new Error(`The \`elements\` size exceeds the maximum size ${size} by ${elements.length - size}.`); + } + } + + /** + * @inheritdoc + * @public + * @param {Type} element The element of `Type` to append. + * @returns {this} + */ + public override append(element: Type): this { + this.#checkFull(); + super.append(element); + return this; + } + + /** + * @inheritdoc + * @public + * @param {number} index The index under which the specified `element` is inserted. + * @param {Type} element The element of `Type` to insert at specified `index`. + * @returns {this} + */ + public override insert(index: number, element: Type): this { + this.#checkFull(); + super.insert(index, element); + return this; + } + + /** + * @description Checks whether the `Elements` state is full, equal to size. + * @public + * @returns {boolean} + */ + public isFull(): boolean { + return this.#size === this.length; + } + + /** + * @description Add the element at the beginning of `array` state. + * @public + * @param {Type} element The element of `Type` to prepend. + * @returns {this} + */ + public override prepend(element: Type): this { + this.#checkFull(); + super.prepend(element); + return this; + } + + /** + * @inheritdoc + * @public + * @param {number} index The index to update update element. + * @param {Type} element The element of `Type` to update under the specified `index`. + * @returns {this} + */ + public override update(index: number, element: Type): this { + super.update(index, element); + return this; + } + + /** + * @description Checks whether length of the array is equal to maximum size. + * @returns {this} + */ + #checkFull(): this { + if (this.isFull()) { + throw new Error(`Elements array state is full of size ${super.length}.`); + } + return this; + } +}; diff --git a/src/lib/index.ts b/src/lib/index.ts index 7e31f40..9b0b9e8 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,7 @@ +// Class. +export { Elements } from './elements.class'; +export { Processing } from './processing.class'; +// Abstract. export { ProcessingQueue } from './processing-queue.class'; export { Queue } from './queue.abstract'; export { Stack } from './stack.abstract'; \ No newline at end of file diff --git a/src/lib/processing-queue.class.ts b/src/lib/processing-queue.class.ts index e2dadce..edb440d 100644 --- a/src/lib/processing-queue.class.ts +++ b/src/lib/processing-queue.class.ts @@ -1,114 +1,109 @@ -import { Boolean as Processing } from "@typescript-package/state"; +// Abstract. import { Queue } from "./queue.abstract"; +// Class. +import { Processing } from "./processing.class"; +// Type. +import { ErrorCallback, ProcessCallback } from "../type"; /** - * @description A queue that processes items concurrently with a specified concurrency limit. + * @description A queue that processes elements concurrently with a specified concurrency limit. * @export * @class ProcessingQueue * @template Type * @extends {Queue} */ -export class ProcessingQueue extends Queue { +export class ProcessingQueue< + Type, + Concurrency extends number = number, + Size extends number = number +> extends Queue { /** - * @description The maximum number of items that can be processed concurrently. + * @description * @public * @readonly - * @type {number} + * @type {Processing} */ - public get concurrency() { - return this.#concurrency; + public get processing(): Processing { + return this.#processing; } /** - * @description A set containing all items that have been successfully processed. - * @public - * @readonly - * @type {Set} + * @description + * @type {Processing} */ - public get processed() { - return this.#processed; - } - - /** - * @description Privately stored current number of items being processed. - * @type {number} - */ - #activeCount: number = 0; - - /** - * @description Privately stored maximum concurrency level for processing. - * @type {number} - */ - #concurrency; - - /** - * @description Tracks whether the queue is processing items. - * @type {Processing} - */ - #processing = new Processing(false); - - /** - * @description Privately stored set of all processed items. - * @type {Set} - */ - #processed: Set = new Set(); - + #processing; + /** * Creates an instance of child class. * @constructor - * @param {number} [concurrency=1] - * @param {number} [size=Infinity] - * @param {...Type[]} items + * @param {Concurrency} [concurrency=1 as Concurrency] + * @param {Size} [size=Infinity as Size] + * @param {...Type[]} elements */ - constructor(concurrency: number = 1, size: number = Infinity, ...items: Type[]) { - super(size, ...items); - this.#concurrency = concurrency; + constructor( + concurrency: Concurrency = 1 as Concurrency, + size: Size = Infinity as Size, + ...elements: Type[] + ) { + super(size, ...elements); + this.#processing = new Processing(concurrency); } /** - * @description Waits for all items in the queue to be processed and returns the set of processed items. + * @description Checks whether the queue processing is completed. + * @public + * @returns {boolean} + */ + public isCompleted(): boolean { + return super.length === 0 && this.#processing.activeCount === 0 && this.#processing.isActive(false); + } + + //#region Public async + /** + * @description Waits for all elements in the queue to be processed and returns the set of processed elements. * @public * @async * @returns {Promise>} */ - public async isCompleted(): Promise> { + public async onCompleted(): Promise> { return new Promise>((resolve, reject) => { const interval = setInterval(() => - this.#processing.isTrue() - ? super.length === 0 && this.#processing.false() && resolve(this.#processed) + this.#processing.isActive() + ? super.length === 0 && resolve(this.#processing.processed) : clearInterval(interval) , 50); }); } - /** - * @description Starts processing items in the queue using the provided callback function. - * @public - * @param {(item: Type) => void} callbackFn A function to process each item in the queue. - * @returns {void) => void} - */ - public run(callbackFn: (item: Type) => void) { - this.#processing.true(); - while (this.#activeCount < this.#concurrency && super.length > 0) { - const item = this.dequeue(); - item && this.#process(item, callbackFn); + public async asyncRun( + callbackFn: ProcessCallback, + onError?: ErrorCallback, + ): Promise { + const activeProcesses: Promise[] = []; + const process = async (): Promise => { + while (this.#processing.activeCount < this.#processing.concurrency && super.length > 0) { + const element = this.dequeue(); + if (element) { + const task = this.#processing.asyncProcess(element, callbackFn, onError).finally(() => process()); + activeProcesses.push(task); + await activeProcesses; + } + } } + await process(); + await this.#processing.complete(); } + //#endregion Public async /** - * @description Processes a single item using the provided callback function. - * @param {Type} item The item to process. - * @param {(item: Type) => void} callbackFn A function to process the `item`. - * @returns {this} + * @description Starts processing elements in the queue using the provided callback function. + * @public + * @param {(element: Type) => void} callbackFn A function to process each element in the queue. + * @param {?(element: Type, error: unknown) => void} [onError] An optional function to handle the error. + * @returns {void) => void} */ - #process(item: Type, callbackFn: (item: Type) => void): this { - this.#activeCount++; - try { - callbackFn(item); - } finally { - this.#activeCount--; - this.#processed.add(item); - this.run(callbackFn); + public run(callbackFn: (element: Type) => void, onError?: (element: Type, error: unknown) => void) { + while (super.length > 0) { + this.#processing.process(this.dequeue(), callbackFn, onError); } - return this; } } diff --git a/src/lib/processing.class.ts b/src/lib/processing.class.ts new file mode 100644 index 0000000..b6b8d0c --- /dev/null +++ b/src/lib/processing.class.ts @@ -0,0 +1,297 @@ +// Class. +import { Boolean as Active, Boolean as Debug, State } from "@typescript-package/state"; +// Type. +import { ErrorCallback, ProcessCallback } from "../type"; +/** + * @description Class designed for asynchronous with concurrency control and synchronous processing the elements of `Type`. + * @export + * @class Processing + * @template Type + * @template {number} [Concurrency=number] - The type for concurrency, defaults to `number`. + * @extends {State>>} The state for active processing tasks, tracking the status of asynchronous operations. + */ +export class Processing extends State>> { + /** + * @description Tracks whether the queue is processing elements. + * @public + * @readonly + * @type {boolean} + */ + public get active(): boolean { + return this.#active.state; + } + + /** + * @description A current number of elements being processed. + * @public + * @readonly + * @type {number} + */ + public get activeCount(): number { + return this.#activeCount; + } + + /** + * @description The maximum number of elements that can be processed concurrently. + * @public + * @readonly + * @type {Concurrency} + */ + public get concurrency(): Concurrency { + return this.#concurrency; + } + + /** + * @description The set of processed elements. + * @public + * @readonly + * @type {Set} + */ + public get processed(): Set { + return this.#processed; + } + + /** + * @description Tracks whether the queue is processing elements. + * @type {Active} + */ + #active = new Active(false); + + /** + * @description Privately stored current number of elements being processed. + * @type {number} + */ + #activeCount = 0; + + /** + * @description + * @type {Concurrency} + */ + #concurrency: Concurrency; + + /** + * @description + * @type {Debug} + */ + #debug = new Debug(false); + + /** + * @description A set of processed elements. + * @type {Set} + */ + #processed: Set = new Set(); + + /** + * Creates a `Processing` object. + * @param {Concurrency} concurrency The maximum number of concurrent processes. + */ + constructor(concurrency: Concurrency) { + super(new Set()) + this.#concurrency = concurrency; + } + + /** + * @description Set the `Processing` to debug state. + * @public + */ + public debug(): this { + this.#debug.true(); + return this; + } + + /** + * @description Starts asynchronous processing elements with concurrency control. + * @public + * @async + * @param {Iterable} elements The elements to process. + * @param {ProcessCallback} callbackFn The function to process each element. + * @param {?ErrorCallback} [onError] An optional error handler. + * @param {('default' | 'race')} [method='default'] + * @returns {Promise} + */ + public async asyncRun( + elements: Iterable, + callbackFn: ProcessCallback, + onError?: ErrorCallback, + method: 'default' | 'race' = 'default' + ): Promise { + this.#consoleDebug("asyncRun started", { method, concurrency: this.#concurrency }); + switch(method) { + case 'race': + this.#consoleDebug("Using 'race' method"); + for (const element of elements) { + this.#consoleDebug("Processing element with 'race'", { element, activeCount: this.#activeCount }); + this.#activeCount >= this.#concurrency && await Promise.race(super.state); + this.asyncProcess(element, callbackFn, onError); + } + break + default: + this.#consoleDebug("Using 'default' method"); + const iterator = elements[Symbol.iterator](); + const process = async (): Promise => { + while (this.#activeCount < this.#concurrency) { + const { value: element, done } = iterator.next(); + if (done) break; + this.#consoleDebug("Processing element", { element, activeCount: this.#activeCount }); + this.asyncProcess(element, callbackFn, onError).finally(() => process()); + } + }; + await process(); + break; + } + this.#consoleDebug("asyncRun completed"); + await this.complete(); + } + + /** + * @description Returns `Promise` that waits for the processing completion. + * @public + * @async + * @returns {Promise} + */ + public async complete(): Promise { + await Promise.all(super.state); + } + + /** + * @description Runs the provided `callbackFn` synchronously on each element in the `elements` iterable. + * If an `onError` callback is provided, it will handle errors encountered during processing. + * @public + * @param {Iterable} elements An iterable collection of elements to be processed. + * @param {ProcessCallback} callbackFn A function that will process each element synchronously. + * @param {?ErrorCallback} [onError] Optional callback for handling errors that occur during processing. + */ + public run( + elements: Iterable, + callbackFn: ProcessCallback, + onError?: ErrorCallback + ) { + this.#consoleDebug("run started", { elements }); + for (const element of elements) { + this.#consoleDebug("Processing element synchronously", { element }); + this.process(element, callbackFn, onError); + } + this.#consoleDebug("run completed"); + } + + /** + * @description Runs asynchronous single processing task on the `element`. + * @public + * @async + * @param {Type} element The element to process. + * @param {ProcessCallback} callbackFn The callback function to process the element. + * @param {ErrorCallback} [onError] An optional error handler. + * @returns {Promise} + */ + public async asyncProcess( + element: Type, + callbackFn: ProcessCallback, + onError?: ErrorCallback + ): Promise { + this.#consoleDebug("asyncProcess started", { element, activeCount: this.#activeCount }); + + // Activate the processing state. + this.#active.isFalse() && this.#active.true(); + this.#consoleDebug("Processing state activated", { active: this.#active.state }); + + // Increase the active count. + this.#activeCount++; + this.#consoleDebug("Incremented activeCount", { activeCount: this.#activeCount }); + + // Create a task. + const task = (async () => { + try { + this.#consoleDebug("Processing element:", element); + await callbackFn(element); + } catch (error) { + this.#consoleDebug("Error occurred during processing:", { element, error }); + onError?.(element, error); + } finally { + this.#processed.add(element); + this.#consoleDebug("Element processed:", { element, processed: this.#processed.size }); + } + })(); + + // Add the task to the processing state. + super.state.add(task); + this.#consoleDebug("Task added to processing state", { taskCount: super.state.size }); + + try { + await task; + } finally { + // Remove the task from the processing state. + super.state.delete(task); + this.#consoleDebug("Task removed from processing state", { taskCount: super.state.size }); + + // Decrease the active count. + this.#activeCount--; + this.#consoleDebug("Decremented activeCount", { activeCount: this.#activeCount }); + + // If active count is equal to `0` then deactivate the active processing state. + this.#activeCount === 0 && (this.#active.false(), this.#consoleDebug("Processing state deactivated", { active: this.#active.state })); + } + } + + /** + * @description Checks whether the `Processing` is active. + * @public + * @param {?boolean} [expected] An optional `boolean` type value to check the active state. + * @returns {boolean} + */ + public isActive(expected?: boolean): boolean { + return typeof expected === 'boolean' ? this.#active.isTrue() === expected : this.#active.isTrue(); + } + + /** + * @description Runs a synchronous processing on the provided `element` using the `callbackFn`. + * If an `onError` callback is provided, it will handle any errors encountered during processing. + * @param {(Type | undefined)} element The element to be processed. + * @param {ProcessCallback} callbackFn A function that processes the element synchronously. + * @param {?ErrorCallback} [onError] An optional callback function to handle errors during processing. + * @returns {this} The current instance for method chaining. + */ + public process( + element: Type | undefined, + callbackFn: ProcessCallback, + onError?: ErrorCallback + ): this { + this.#consoleDebug("process started", { element }); + this.#active.isFalse() && this.#active.true(); + this.#consoleDebug("Processing state activated", { active: this.#active.state }); + if (element) { + try { + this.#consoleDebug("Processing element", { element }); + callbackFn(element); + } catch(error) { + this.#consoleDebug("Error during processing", { error, element }); + onError?.(element, error); + } finally { + this.#processed.add(element); + this.#consoleDebug("Element processed", { element, processedCount: this.#processed.size }); + this.#active.false(); + this.#consoleDebug("Processing state deactivated", { active: this.#active.state }); + } + } + return this; + } + + /** + * @description Unset the `Processing` from debug state. + * @public + */ + public unDebug(): this { + this.#debug.false(); + return this; + } + + /** + * @description + * @param {string} message + * @param {?*} [data] + * @returns {this} + */ + #consoleDebug(message: string, data?: any): this { + this.#debug.isTrue() && console.debug(message, data || ''); + return this; + } +} diff --git a/src/lib/queue.abstract.ts b/src/lib/queue.abstract.ts index ae1a355..54ba037 100644 --- a/src/lib/queue.abstract.ts +++ b/src/lib/queue.abstract.ts @@ -1,5 +1,5 @@ -import { ArrayState as AbstractArrayState } from "@typescript-package/state"; -export class ArrayState extends AbstractArrayState{}; +// Class. +import { Elements } from "./elements.class"; /** * @description A standard FIFO (First In, First Out) queue. * @export @@ -7,7 +7,17 @@ export class ArrayState extends AbstractArrayState{}; * @class Queue * @template Type */ -export abstract class Queue { +export abstract class Queue { + /** + * @description The `Elements` state holder. + * @public + * @readonly + * @type {Elements} + */ + public get elements(): Elements { + return this.#elements; + } + /** * @description The actual queue length. * @public @@ -15,50 +25,50 @@ export abstract class Queue { * @type {number} */ public get length(): number { - return this.#queue.length; + return this.#elements.length; } /** * @description The maximum queue size. * @public * @readonly - * @type {number} + * @type {Size} */ - public get size(): number { + public get size(): Size { return this.#size; } /** - * @description The `Array` queue state. + * @description The actual queue `Elements` state - raw `array` state of the queue. * @public * @readonly - * @type {ArrayState} + * @type {readonly Type[]} */ - public get queue() { - return this.#queue; + public get state(): readonly Type[] { + return this.#elements.state; } /** - * @description Privately stored maximum queue size. - * @type {number} + * @description Privately stored maximum queue size of generic type variable `Size`. + * @type {Size} */ - #size; + #size: Size; /** - * @description Privately stored `Array` queue state. - * @type {ArrayState} + * @description Privately stored `Array` queue elements state of `Elements`. + * @type {Elements} */ - #queue; + #elements: Elements; /** - * Creates an instance of `Queue`. + * Creates an instance of child class. * @constructor - * @param {number} [size=Infinity] - * @param {...Type[]} items + * @param {Size} [size=Infinity as Size] The maximum size of the `Queue`. + * @param {...Type[]} elements The arbitrary parameters of elements of `Type` to add. */ - constructor(size: number = Infinity, ...items: Type[]) { + constructor(size: Size = Infinity as Size, ...elements: Type[]) { this.#size = size; - this.#queue = new ArrayState(items); + this.#elements = new Elements(elements, size); } /** @@ -67,7 +77,7 @@ export abstract class Queue { * @returns {this} */ public clear(): this { - this.#queue.clear(); + this.#elements.clear(); return this; } @@ -77,22 +87,22 @@ export abstract class Queue { * @returns {(Type | undefined)} */ public dequeue(): Type | undefined { - const first = this.#queue.first(); - this.#queue.remove(0); + const first = this.#elements.first(); + this.#elements.remove(0); return first; } /** * @description Adds a new element to the queue. * @public - * @param {Type} item + * @param {Type} element The element of `Type` to add. * @returns {this} */ - public enqueue(item: Type): this { - if (this.length === this.size) { + public enqueue(element: Type): this { + if (this.isFull()) { throw new Error(`Queue is full.`); } - this.#queue.append(item); + this.#elements.append(element); return this; } @@ -111,7 +121,7 @@ export abstract class Queue { * @returns {boolean} */ public isFull(): boolean { - return this.length === this.#size; + return this.#elements.isFull(); } /** @@ -120,6 +130,6 @@ export abstract class Queue { * @returns {Type} */ public peek(): Type { - return this.#queue.first(); + return this.#elements.first(); } } diff --git a/src/lib/stack.abstract.ts b/src/lib/stack.abstract.ts index 0e55c54..83c59ee 100644 --- a/src/lib/stack.abstract.ts +++ b/src/lib/stack.abstract.ts @@ -1,3 +1,4 @@ +import { Elements } from "./elements.class"; import { Queue as AbstractQueue } from "./queue.abstract"; /** * @description A standard LIFO (Last In, First Out) queue. @@ -6,7 +7,17 @@ import { Queue as AbstractQueue } from "./queue.abstract"; * @class Stack * @template Type */ -export abstract class Stack { +export abstract class Stack { + /** + * @description The `Elements` of array state type. + * @public + * @readonly + * @type {Elements} + */ + public get elements(): Elements { + return this.#stack.elements; + } + /** * @description The actual stack length. * @public @@ -28,13 +39,13 @@ export abstract class Stack { } /** - * @description The `Array` stack state. + * @description The actual stack `Elements` state. * @public * @readonly - * @type {ArrayState} + * @type {readonly Type[]} */ - public get stack() { - return this.#stack.queue; + public get state(): readonly Type[] { + return this.#stack.elements.state; } /** @@ -53,11 +64,11 @@ export abstract class Stack { * Creates an instance of `Stack`. * @constructor * @param {number} [size=Infinity] - * @param {...Type[]} items + * @param {...Type[]} elements */ - constructor(size: number = Infinity, ...items: Type[]) { + constructor(size: Size = Infinity as Size, ...elements: Type[]) { this.#size = size; - this.#stack = new (class Stack extends AbstractQueue {})(size, ...items); + this.#stack = new (class Stack extends AbstractQueue {})(size, ...elements); } /** @@ -70,34 +81,52 @@ export abstract class Stack { return this; } + /** + * @description Checks whether the stack is empty. + * @public + * @returns {boolean} + */ + public isEmpty(): boolean { + return this.#stack.isEmpty(); + } + + /** + * @description Checks if the stack is full. + * @public + * @returns {boolean} + */ + public isFull(): boolean { + return this.#stack.isFull(); + } + /** * @description Returns the top element on the stack. * @public * @returns {(Type | undefined)} */ public peek(): Type | undefined { - return this.stack.last(); + return this.#stack.elements.last(); } /** * @description Removes and returns the top element from the stack. * @public - * @returns {Type} + * @returns {(Type | undefined)} */ - public pop(): Type { - const last = this.stack.last(); - this.#stack.length > 0 && this.stack.remove(this.#stack.length - 1); + public pop(): Type | undefined { + const last = this.peek(); + this.#stack.length > 0 && this.#stack.elements.remove(this.#stack.length - 1); return last; } /** * @description Adds a new element on the stack. * @public - * @param {Type} item + * @param {Type} element * @returns {this} */ - public push(item: Type): this { - this.stack.append(item); + public push(element: Type): this { + this.#stack.elements.append(element); return this; } } diff --git a/src/public-api.ts b/src/public-api.ts index 69860a7..f27e5cb 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -2,6 +2,10 @@ * Public API Surface of queue */ export { + // Class. + Elements, + Processing, + // Abstract. ProcessingQueue, Queue, Stack diff --git a/src/test/elements.spec.ts b/src/test/elements.spec.ts new file mode 100644 index 0000000..bdec6ac --- /dev/null +++ b/src/test/elements.spec.ts @@ -0,0 +1,30 @@ +import { Elements } from "../lib/elements.class"; + +let elements = new Elements( + [1, 2, 3, 4], // Array type elements. + 15 // Maximum size of the elements. +); + +// Appends the value at the end of an array state. +elements.append(5); +console.log(elements.state); // Output: [1, 2, 3, 4, 5] + +// Inserts the value at the specified index into the array state. +elements.insert(2, 10); +console.log(elements.state); // Output: [1, 2, 10, 3, 4, 5] + +// Adds the values at the beginning of array state. +elements.prepend(0); +console.log(elements.state); // Output: [0, 1, 2, 10, 3, 4, 5] + +// Updates the value at the index in the array state. +elements.update(0, 127); +console.log(elements.state); // Output: [127, 1, 2, 10, 3, 4, 5] + +try { + elements = new Elements([1, 2, 3, 4], 15); +} catch(error) { + console.log(error); +} + +console.log(elements); diff --git a/src/test/processing-queue.spec.ts b/src/test/processing-queue.spec.ts index 6c8ddd5..85561e9 100644 --- a/src/test/processing-queue.spec.ts +++ b/src/test/processing-queue.spec.ts @@ -2,16 +2,92 @@ import { ProcessingQueue as AbstractProcessingQueue } from "../lib"; export class ProcessingQueue extends AbstractProcessingQueue {} -let queue = new ProcessingQueue(2, Infinity, 1, 2, 3); +let processingQueue = new ProcessingQueue( + 3, // concurrency + 25, // size + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 // items +); -describe(`ReverseQueue`, () => { - beforeEach(() => queue = new ProcessingQueue(2, Infinity, 1, 2, 3)); - it(`enqueue()`, () => expect(queue.enqueue(4).length).toEqual(4)); +processingQueue.asyncRun((element) => console.log(`Processed, `, element, processingQueue.state)).then(() => { + console.log(`Completed`, processingQueue.state); +}); + + +console.group(`ProcessingQueue`); + +// Initialize the `ProcessingQueue`. +processingQueue = new ProcessingQueue( + 3, // concurrency + 10, // size + 1, 2, 3 // items +); + +// The maximum number of elements that can be processed concurrently. +console.log(`concurrency, `, processingQueue.processing.concurrency); // Output: 2 + +// A set containing all elements that have been successfully processed. +console.log(`processed, `, processingQueue.processing.processed); // Output: Set(0) + +// Checks whether the queue is empty. +console.log(`isEmpty(), `, processingQueue.isEmpty()); // Output: false + +// Checks whether the queue is full. +console.log(`isFull(), `, processingQueue.isFull()); // Output: false + +// The maximum queue size. +console.log(`size, `, processingQueue.size); // Output: 10 + +// The actual queue Elements state - raw array state of the queue. +console.log(`state, `, processingQueue.state); // Output: [1, 2, 3] + +// The actual queue length. +console.log(`length, `, processingQueue.length); // Output: 3 + +// Adds a new element to the queue. +console.log(`enqueue(4), `, processingQueue.enqueue(4)); + +// The actual queue length. +console.log(`length, `, processingQueue.length); // Output: 4 + +// Returns the first element in the queue. +console.log(`peek(), `, processingQueue.peek()); // Output: 1 + +// Returns the first element in the queue. +console.log(`dequeue(), `, processingQueue.dequeue()); // Output: 1 + +// The actual queue length. +console.log(`length, `, processingQueue.length); // Output: 3 + +// Adds to the full. +processingQueue.enqueue(5).enqueue(6).enqueue(7).enqueue(8).enqueue(9).enqueue(10).enqueue(11); + +// The actual queue Elements state - raw array state of the queue. +console.log(`state, `, processingQueue.state); // Output: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + +// Waits for all elements in the queue to be processed and returns the set of processed elements. +processingQueue.onCompleted().then( + processed => console.log(`Completed`, processed), // Output: Completed Set(10) + reason => console.log(reason) +); + +// Starts processing elements in the queue using the provided callback function. +// processingQueue.run(element => console.log(`Processed. `, element)); // Output: Processed {element} + +processingQueue.asyncRun(element => console.log(`Processed. `, element)).finally(() => console.log(`Async Run Completed.`)); // Output: Processed {element} + +// A set containing all elements that have been successfully processed. +console.log(`processed, `, processingQueue.processing.processed); // Output: Set(10) + +console.groupEnd(); + +describe(`ProcessingQueue`, () => { + beforeEach(() => processingQueue = new ProcessingQueue(2, 10, 1, 2, 3)); + it(`enqueue()`, () => expect(processingQueue.enqueue(4).length).toEqual(4)); it(`dequeue()`, () => { - expect(queue.enqueue(4).length).toEqual(4); - expect(queue.dequeue()).toEqual(1); - expect(queue.dequeue()).toEqual(2); + expect(processingQueue.enqueue(4).length).toEqual(4); + expect(processingQueue.dequeue()).toEqual(1); + expect(processingQueue.dequeue()).toEqual(2); }); - it(`peek()`, () => expect(queue.peek()).toEqual(1)); - it(`clear()`, () => expect(queue.clear().length).toEqual(0)); + it(`peek()`, () => expect(processingQueue.peek()).toEqual(1)); + it(`clear()`, () => expect(processingQueue.clear().length).toEqual(0)); }); diff --git a/src/test/processing.spec.ts b/src/test/processing.spec.ts new file mode 100644 index 0000000..7aa6571 --- /dev/null +++ b/src/test/processing.spec.ts @@ -0,0 +1,18 @@ +import { Processing } from "../lib/processing.class"; + +const processing = new Processing(3); + +processing.debug().asyncRun( + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], + element => console.log(`Processed element: `, element), + error => console.log(`Error`, error), + 'default' +); + +processing.unDebug().asyncRun( + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], + element => console.log(`Processed element: `, element), + error => console.log(`Error`, error), + 'default' +); + diff --git a/src/test/queue.spec.ts b/src/test/queue.spec.ts index 6fa7fc9..1c9ae34 100644 --- a/src/test/queue.spec.ts +++ b/src/test/queue.spec.ts @@ -1,11 +1,57 @@ import { Queue as AbstractQueue } from "../lib"; -export class Queue extends AbstractQueue {} +export class Queue extends AbstractQueue {} -let queue = new Queue(Infinity, 1, 2, 3); +console.group(`Queue`); + +let queue = new Queue( + 10, // size + 1, 2, 3 // item +); + +// Adds a new element to the queue. +queue.enqueue(4); + +// The actual queue Elements state - raw array state of the queue. +console.log(`state,`, queue.state); // Output: [1, 2, 3, 4] + +// Returns the first element in the queue. +console.log(`peek(), `, queue.peek()); // Output: 1 + +// Checks if the queue is empty. +console.log(`isEmpty(),`, queue.isEmpty()); // Output: false + +// The maximum queue size. +console.log(`size,`, queue.size); // Output: 10 + +// The actual queue Elements state - raw array state of the queue. +console.log(`state,`, queue.state); // Output: [1, 2, 3, 4] + +// Adds to the full. +queue.enqueue(5).enqueue(6).enqueue(7).enqueue(8).enqueue(9).enqueue(10); + +// Checks whether the queue is full. +console.log(`isFull(), `, queue.isFull()); // Output: true + +try { + queue.enqueue(11); +} catch(error) { + console.log(error); // Error: Queue is full. +} + +// Clears the queue. +console.log(`clear(), `, queue.clear()); + +// Checks if the queue is empty. +console.log(`isEmpty(),`, queue.isEmpty()); // Output: true + +// The actual queue Elements state - raw array state of the queue. +console.log(`state,`, queue.state); // Output: [] + +console.groupEnd(); describe(`Queue`, () => { - beforeEach(() => queue = new Queue(Infinity, 1, 2, 3)); + beforeEach(() => queue = new Queue(10, 1, 2, 3)); it(`enqueue()`, () => expect(queue.enqueue(4).length).toEqual(4)); it(`dequeue()`, () => { expect(queue.enqueue(4).length).toEqual(4); diff --git a/src/test/stack.spec.ts b/src/test/stack.spec.ts index b1ffc41..eb20bbf 100644 --- a/src/test/stack.spec.ts +++ b/src/test/stack.spec.ts @@ -1,16 +1,63 @@ import { Stack as AbstractStack } from "../lib"; -export class Stack extends AbstractStack {} +export class Stack extends AbstractStack {} -let queue = new Stack(Infinity, 1, 2, 3); +console.group(`Stack`); -describe(`ReverseQueue`, () => { - beforeEach(() => queue = new Stack(Infinity, 1, 2, 3)); - it(`enqueue()`, () => expect(queue.push(4).length).toEqual(4)); +let stack = new Stack( + 10, // size + 1, 2, 3 // items +); + +// The actual stack length. +console.log(`length, `, stack.length); // Output: 3 + +// Adds a new element on the stack. +stack.push(4); + +// The maximum stack size. +console.log(`size, `, stack.size); // Output: 10 + +// The actual stack length. +console.log(`length, `, stack.length); // Output: 4 + +// Returns the top element on the stack. +console.log(`peek(), `, stack.peek()); // Output: 4 + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [1, 2, 3, 4] + +// Removes and returns the top element from the stack. +console.log(`pop(), `, stack.pop()); // Output: 4 + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [1, 2, 3] + +// Adds to the full. +stack.push(4).push(5).push(6).push(7).push(8).push(9).push(10); + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +// Checks if the stack is full. +console.log(`isFull(), `, stack.isFull()); // Output: true + +// Clears the queue. +stack.clear() + +// The actual stack `Elements` state. +console.log(`state, `, stack.state); // Output: [] +console.log(`isEmpty(), `, stack.isEmpty()); // Output: true + +console.groupEnd(); + +describe(`Stack`, () => { + beforeEach(() => stack = new Stack(10, 1, 2, 3)); + it(`push()`, () => expect(stack.push(4).length).toEqual(4)); it(`pop()`, () => { - expect(queue.push(4).length).toEqual(4); - expect(queue.pop()).toEqual(4); + expect(stack.push(4).length).toEqual(4); + expect(stack.pop()).toEqual(4); }); - it(`peek()`, () => expect(queue.peek()).toEqual(3)); - it(`clear()`, () => expect(queue.clear().length).toEqual(0)); + it(`peek()`, () => expect(stack.peek()).toEqual(3)); + it(`clear()`, () => expect(stack.clear().length).toEqual(0)); }); diff --git a/src/type/error-callback.type.ts b/src/type/error-callback.type.ts new file mode 100644 index 0000000..cf58112 --- /dev/null +++ b/src/type/error-callback.type.ts @@ -0,0 +1 @@ +export type ErrorCallback = (element: Type, error: unknown) => void; diff --git a/src/type/index.ts b/src/type/index.ts new file mode 100644 index 0000000..a4406d3 --- /dev/null +++ b/src/type/index.ts @@ -0,0 +1,2 @@ +export type { ErrorCallback } from './error-callback.type'; +export type { ProcessCallback } from './process-callback.type'; diff --git a/src/type/process-callback.type.ts b/src/type/process-callback.type.ts new file mode 100644 index 0000000..3dce4f4 --- /dev/null +++ b/src/type/process-callback.type.ts @@ -0,0 +1 @@ +export type ProcessCallback = (element: Type) => void | Promise;