From 8bd6f2debc4b973d67e6c7b764bf4e945d215e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 07:41:00 +0000 Subject: [PATCH 01/27] feat(Elements): add the class as the array state elements for Queue and Stack. --- src/lib/elements.class.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/lib/elements.class.ts diff --git a/src/lib/elements.class.ts b/src/lib/elements.class.ts new file mode 100644 index 0000000..29738f0 --- /dev/null +++ b/src/lib/elements.class.ts @@ -0,0 +1,9 @@ +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{}; From f55149026db456ffd80e27449a12fccd1da8ef17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 07:41:19 +0000 Subject: [PATCH 02/27] refactor(ProcessingQueue): modify the item to element. --- src/lib/processing-queue.class.ts | 44 ++++++++++++++++--------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/lib/processing-queue.class.ts b/src/lib/processing-queue.class.ts index e2dadce..fc502d6 100644 --- a/src/lib/processing-queue.class.ts +++ b/src/lib/processing-queue.class.ts @@ -1,7 +1,9 @@ +// Class. import { Boolean as Processing } from "@typescript-package/state"; +// Abstract. import { Queue } from "./queue.abstract"; /** - * @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 @@ -9,7 +11,7 @@ import { Queue } from "./queue.abstract"; */ export class ProcessingQueue extends Queue { /** - * @description The maximum number of items that can be processed concurrently. + * @description The maximum number of elements that can be processed concurrently. * @public * @readonly * @type {number} @@ -19,7 +21,7 @@ export class ProcessingQueue extends Queue { } /** - * @description A set containing all items that have been successfully processed. + * @description A set containing all elements that have been successfully processed. * @public * @readonly * @type {Set} @@ -29,7 +31,7 @@ export class ProcessingQueue extends Queue { } /** - * @description Privately stored current number of items being processed. + * @description Privately stored current number of elements being processed. * @type {number} */ #activeCount: number = 0; @@ -41,13 +43,13 @@ export class ProcessingQueue extends Queue { #concurrency; /** - * @description Tracks whether the queue is processing items. + * @description Tracks whether the queue is processing elements. * @type {Processing} */ #processing = new Processing(false); /** - * @description Privately stored set of all processed items. + * @description Privately stored set of all processed elements. * @type {Set} */ #processed: Set = new Set(); @@ -57,15 +59,15 @@ export class ProcessingQueue extends Queue { * @constructor * @param {number} [concurrency=1] * @param {number} [size=Infinity] - * @param {...Type[]} items + * @param {...Type[]} elements */ - constructor(concurrency: number = 1, size: number = Infinity, ...items: Type[]) { - super(size, ...items); + constructor(concurrency: number = 1, size: number = Infinity, ...elements: Type[]) { + super(size, ...elements); this.#concurrency = concurrency; } /** - * @description Waits for all items in the queue to be processed and returns the set of processed items. + * @description Waits for all elements in the queue to be processed and returns the set of processed elements. * @public * @async * @returns {Promise>} @@ -81,32 +83,32 @@ export class ProcessingQueue extends Queue { } /** - * @description Starts processing items in the queue using the provided callback function. + * @description Starts processing elements in the queue using the provided callback function. * @public - * @param {(item: Type) => void} callbackFn A function to process each item in the queue. + * @param {(element: Type) => void} callbackFn A function to process each element in the queue. * @returns {void) => void} */ - public run(callbackFn: (item: Type) => void) { + public run(callbackFn: (element: Type) => void) { this.#processing.true(); while (this.#activeCount < this.#concurrency && super.length > 0) { - const item = this.dequeue(); - item && this.#process(item, callbackFn); + const element = this.dequeue(); + element && this.#process(element, callbackFn); } } /** - * @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`. + * @description Processes a single element using the provided callback function. + * @param {Type} element The element to process. + * @param {(element: Type) => void} callbackFn A function to process the `element`. * @returns {this} */ - #process(item: Type, callbackFn: (item: Type) => void): this { + #process(element: Type, callbackFn: (element: Type) => void): this { this.#activeCount++; try { - callbackFn(item); + callbackFn(element); } finally { this.#activeCount--; - this.#processed.add(item); + this.#processed.add(element); this.run(callbackFn); } return this; From 2da7163f01180e6d1bd4b7819fe642ae0148f97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 07:41:36 +0000 Subject: [PATCH 03/27] refactor(Queue): modify the item to element. --- src/lib/queue.abstract.ts | 48 +++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/lib/queue.abstract.ts b/src/lib/queue.abstract.ts index ae1a355..c42b445 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 @@ -8,6 +8,16 @@ export class ArrayState extends AbstractArrayState{}; * @template Type */ export abstract class Queue { + /** + * @description + * @public + * @readonly + * @type {readonly Type[]} + */ + public get elements(): readonly Type[] { + return this.#elements.state; + } + /** * @description The actual queue length. * @public @@ -15,7 +25,7 @@ export abstract class Queue { * @type {number} */ public get length(): number { - return this.#queue.length; + return this.#elements.length; } /** @@ -32,10 +42,10 @@ export abstract class Queue { * @description The `Array` queue state. * @public * @readonly - * @type {ArrayState} + * @type {Elements} */ - public get queue() { - return this.#queue; + public get state() { + return this.#elements; } /** @@ -45,20 +55,20 @@ export abstract class Queue { #size; /** - * @description Privately stored `Array` queue state. - * @type {ArrayState} + * @description Privately stored `Array` queue elements state. + * @type {Elements} */ - #queue; + #elements; /** * Creates an instance of `Queue`. * @constructor * @param {number} [size=Infinity] - * @param {...Type[]} items + * @param {...Type[]} elements */ - constructor(size: number = Infinity, ...items: Type[]) { + constructor(size: number = Infinity, ...elements: Type[]) { this.#size = size; - this.#queue = new ArrayState(items); + this.#elements = new Elements(elements); } /** @@ -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 * @returns {this} */ - public enqueue(item: Type): this { + public enqueue(element: Type): this { if (this.length === this.size) { throw new Error(`Queue is full.`); } - this.#queue.append(item); + this.#elements.append(element); return this; } @@ -120,6 +130,6 @@ export abstract class Queue { * @returns {Type} */ public peek(): Type { - return this.#queue.first(); + return this.#elements.first(); } } From 32886ebb88401f44ed8a628ddb709d01ff837875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 08:57:20 +0000 Subject: [PATCH 04/27] refactor(Elements): add functionality specific to the elements. --- src/lib/elements.class.ts | 104 +++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/src/lib/elements.class.ts b/src/lib/elements.class.ts index 29738f0..55957a7 100644 --- a/src/lib/elements.class.ts +++ b/src/lib/elements.class.ts @@ -1,3 +1,4 @@ +// Abstract. import { ArrayState } from "@typescript-package/state"; /** * @description Array state elements in data structures such as Stack and Queue. @@ -6,4 +7,105 @@ import { ArrayState } from "@typescript-package/state"; * @template Type * @extends {ArrayState} */ -export class Elements 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; + } +}; From b762baad0f94ce486f60f627275262a5ff805744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 08:57:29 +0000 Subject: [PATCH 05/27] chore(index): add `Elements`. --- src/lib/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/index.ts b/src/lib/index.ts index 7e31f40..66f2de5 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,6 @@ +// Class. +export { Elements } from './elements.class'; +// Abstract. export { ProcessingQueue } from './processing-queue.class'; export { Queue } from './queue.abstract'; export { Stack } from './stack.abstract'; \ No newline at end of file From b4b86918df96cdf9e4c88e6bd86ca67a51237d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 08:58:46 +0000 Subject: [PATCH 06/27] refactor(Queue): use `elements` as `Elements` and `state` as raw `array` state from `Elements`. Set `size` to use generic type variable `Size`. --- src/lib/queue.abstract.ts | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/lib/queue.abstract.ts b/src/lib/queue.abstract.ts index c42b445..54ba037 100644 --- a/src/lib/queue.abstract.ts +++ b/src/lib/queue.abstract.ts @@ -7,15 +7,15 @@ import { Elements } from "./elements.class"; * @class Queue * @template Type */ -export abstract class Queue { +export abstract class Queue { /** - * @description + * @description The `Elements` state holder. * @public * @readonly - * @type {readonly Type[]} + * @type {Elements} */ - public get elements(): readonly Type[] { - return this.#elements.state; + public get elements(): Elements { + return this.#elements; } /** @@ -32,43 +32,43 @@ export abstract class Queue { * @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 {Elements} + * @type {readonly Type[]} */ - public get state() { - return this.#elements; + 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 elements state. + * @description Privately stored `Array` queue elements state of `Elements`. * @type {Elements} */ - #elements; + #elements: Elements; /** - * Creates an instance of `Queue`. + * Creates an instance of child class. * @constructor - * @param {number} [size=Infinity] - * @param {...Type[]} elements + * @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, ...elements: Type[]) { + constructor(size: Size = Infinity as Size, ...elements: Type[]) { this.#size = size; - this.#elements = new Elements(elements); + this.#elements = new Elements(elements, size); } /** @@ -95,11 +95,11 @@ export abstract class Queue { /** * @description Adds a new element to the queue. * @public - * @param {Type} element + * @param {Type} element The element of `Type` to add. * @returns {this} */ public enqueue(element: Type): this { - if (this.length === this.size) { + if (this.isFull()) { throw new Error(`Queue is full.`); } this.#elements.append(element); @@ -121,7 +121,7 @@ export abstract class Queue { * @returns {boolean} */ public isFull(): boolean { - return this.length === this.#size; + return this.#elements.isFull(); } /** From 37a75c78f0bcdb1182fed7fd4d529c216273de9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 09:00:12 +0000 Subject: [PATCH 07/27] refactor(Stack): add `Size` generic type variable, and use properly getters `state` and `elements`. --- src/lib/stack.abstract.ts | 43 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/lib/stack.abstract.ts b/src/lib/stack.abstract.ts index 0e55c54..55f0c90 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); } /** @@ -76,28 +87,28 @@ export abstract class Stack { * @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; } } From ec4e874315f07d1d59d232a4509790fcfb25a2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 09:00:22 +0000 Subject: [PATCH 08/27] chore(API): update. --- src/public-api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/public-api.ts b/src/public-api.ts index 69860a7..61b82a2 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -2,6 +2,9 @@ * Public API Surface of queue */ export { + // Class. + Elements, + // Abstract. ProcessingQueue, Queue, Stack From 129fd567f520f568b83cb0d6d4f58f7d1c74d489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 09:43:08 +0000 Subject: [PATCH 09/27] test(Queue): update. --- src/test/queue.spec.ts | 52 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) 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); From 2c39e08c9170476d83afadfe134462c29ac6a43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 09:43:26 +0000 Subject: [PATCH 10/27] test(Elements): update. --- src/test/elements.spec.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/test/elements.spec.ts 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); From 288c969b309eedea0eef1f9b92665288ec0e6bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 09:56:14 +0000 Subject: [PATCH 11/27] test(Stack): update. --- src/test/stack.spec.ts | 65 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) 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)); }); From 47cbbbe0e39c28a2a1f229042b163652f78d488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 09:56:35 +0000 Subject: [PATCH 12/27] refactor(Stack): add the `isFull()` and `isEmpty()` methods. --- src/lib/stack.abstract.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/stack.abstract.ts b/src/lib/stack.abstract.ts index 55f0c90..83c59ee 100644 --- a/src/lib/stack.abstract.ts +++ b/src/lib/stack.abstract.ts @@ -81,6 +81,24 @@ 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 From 2abd66b9b1558af6681833c8fd47ef9395c806f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 10:17:29 +0000 Subject: [PATCH 13/27] refactor(ProcessingQueue): add `Concurrency` and `Size` generic type variables. --- src/lib/processing-queue.class.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lib/processing-queue.class.ts b/src/lib/processing-queue.class.ts index fc502d6..687b133 100644 --- a/src/lib/processing-queue.class.ts +++ b/src/lib/processing-queue.class.ts @@ -9,7 +9,11 @@ import { Queue } from "./queue.abstract"; * @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 elements that can be processed concurrently. * @public @@ -53,15 +57,19 @@ export class ProcessingQueue extends Queue { * @type {Set} */ #processed: Set = new Set(); - + /** * Creates an instance of child class. * @constructor - * @param {number} [concurrency=1] - * @param {number} [size=Infinity] - * @param {...Type[]} elements + * @param {Concurrency} [concurrency=1 as Concurrency] + * @param {Size} [size=Infinity as Size] + * @param {...Type[]} elements */ - constructor(concurrency: number = 1, size: number = Infinity, ...elements: Type[]) { + constructor( + concurrency: Concurrency = 1 as Concurrency, + size: Size = Infinity as Size, + ...elements: Type[] + ) { super(size, ...elements); this.#concurrency = concurrency; } From 7dfa3fd87234e11d6a823056b9fa53bd7b0b14e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 10:17:38 +0000 Subject: [PATCH 14/27] test(ProcessingQueue): update. --- src/test/processing-queue.spec.ts | 79 +++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/src/test/processing-queue.spec.ts b/src/test/processing-queue.spec.ts index 6c8ddd5..a682379 100644 --- a/src/test/processing-queue.spec.ts +++ b/src/test/processing-queue.spec.ts @@ -2,16 +2,79 @@ import { ProcessingQueue as AbstractProcessingQueue } from "../lib"; export class ProcessingQueue extends AbstractProcessingQueue {} -let queue = new ProcessingQueue(2, Infinity, 1, 2, 3); +console.group(`ProcessingQueue`); + +// 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) + +console.groupEnd(); describe(`ReverseQueue`, () => { - beforeEach(() => queue = new ProcessingQueue(2, Infinity, 1, 2, 3)); - it(`enqueue()`, () => expect(queue.enqueue(4).length).toEqual(4)); + 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)); }); From d0e56fd0033b50c3cb4bb653b3eae39dbb2641a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 10:17:45 +0000 Subject: [PATCH 15/27] docs(README.md): update. --- README.md | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2ea1cd9..692aa8d 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,10 @@ A lightweight TypeScript library for managing various queue and stack structures * [Installation](#installation) * [Api](#api) - * `ProcessingQueue` - * `Queue` - * `Stack` + * [`Elements`](#elements) + * [`ProcessingQueue`](#processingqueue) + * [`Queue`](#queue) + * [`Stack`](#stack) * [Git](#git) * [Commit](#commit) * [Versioning](#versioning) @@ -40,12 +41,213 @@ 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) +``` + +### `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 From 6624cfc33eea24296657b3ada49ad60927746c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 18:24:44 +0000 Subject: [PATCH 16/27] feat(Processing): add. --- src/lib/processing.class.ts | 189 ++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/lib/processing.class.ts diff --git a/src/lib/processing.class.ts b/src/lib/processing.class.ts new file mode 100644 index 0000000..84b221f --- /dev/null +++ b/src/lib/processing.class.ts @@ -0,0 +1,189 @@ +// Class. +import { Boolean as Active } from "@typescript-package/state"; +export type ProcessCallback = (element: Type) => void | Promise; +export type ErrorCallback = (element: Type, error: unknown) => void; +/** + * @description + * @export + * @class Processing + * @template Type + */ +export class Processing { + /** + * @description Whether processing is active. + * @public + * @readonly + * @type {boolean} + */ + public get active(): boolean { + return this.#active.state; + } + + /** + * @description The set of processed elements. + * @public + * @readonly + * @type {Set} + */ + public get processed(): Set { + return this.#processed; + } + + /** + * @description + * @type {Active} + */ + #active = new Active(false); + + /** + * @description + * @type {number} + */ + #activeCount = 0; + + /** + * @description + * @type {Concurrency} + */ + #concurrency: Concurrency; + + /** + * @description A set of processed elements. + */ + #processed: Set = new Set(); + + /** + * @description + * @readonly + * @type {*} + */ + readonly #processing = new Set>(); + + /** + * Creates a `Processing` object. + * @param {Concurrency} concurrency The maximum number of concurrent processes. + */ + constructor(concurrency: Concurrency) { + this.#concurrency = concurrency; + } + + /** + * @description Starts processing elements. + * @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.#active.true(); + const processing = new Set>(); + switch(method) { + case 'race': + for (const element of elements) { + this.#activeCount >= this.#concurrency && await Promise.race(this.#processing); + const task = this.#asyncProcess(element, callbackFn, onError).finally(() => this.#processing.delete(task)); + this.#processing.add(task); + } + break + default: + const iterator = elements[Symbol.iterator](); + const process = async (): Promise => { + while (this.#activeCount < this.#concurrency) { + const { value: element, done } = iterator.next(); + if (done) break; + const task = this.#asyncProcess(element, callbackFn, onError).finally(() => (process(), processing.delete(task))); + processing.add(task); + } + }; + await process(); + break; + } + await this.complete(); + this.#active.false(); + } + + /** + * @description Returns `Promise` that waits for the processing completion. + * @public + * @async + * @returns {Promise} + */ + public async complete(): Promise { + await Promise.all(this.#processing); + } + + /** + * @description + * @public + * @param {Iterable} elements + * @param {ProcessCallback} callbackFn + * @param {?ErrorCallback} [onError] + */ + public run( + elements: Iterable, + callbackFn: ProcessCallback, + onError?: ErrorCallback + ) { + this.#active.true(); + for (const element of elements) { + this.#process(element, callbackFn, onError); + } + this.#active.false(); + } + + /** + * @description Runs asynchronous single processing task. + * @private + * @param {Type} element The element to process. + * @param {ProcessCallback} callbackFn The function to process the element. + * @param {ErrorCallback} [onError] An optional error handler. + * @returns {Promise} + */ + async #asyncProcess( + element: Type, + callbackFn: ProcessCallback, + onError?: ErrorCallback + ): Promise { + this.#activeCount++; + try { + await callbackFn(element); + } catch (error) { + onError?.(element, error); + } finally { + this.#activeCount--; + this.#processed.add(element); + } + } + + /** + * @description Runs synchronous single processing task. + * @param {(Type | undefined)} element + * @param {ProcessCallback} callbackFn + * @param {?ErrorCallback} [onError] + * @returns {this} + */ + #process( + element: Type | undefined, + callbackFn: ProcessCallback, + onError?: ErrorCallback + ): this { + if (element) { + try { + callbackFn(element); + } catch(error) { + onError?.(element, error); + } finally { + this.#processed.add(element); + } + } + return this; + } +} From 5f12b3881e35311b4fea2d7535f41adb302f166d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Mon, 6 Jan 2025 18:25:05 +0000 Subject: [PATCH 17/27] test(Processing): add. --- src/test/processing.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/test/processing.spec.ts diff --git a/src/test/processing.spec.ts b/src/test/processing.spec.ts new file mode 100644 index 0000000..c25b1f3 --- /dev/null +++ b/src/test/processing.spec.ts @@ -0,0 +1,11 @@ +import { Processing } from "../lib/processing.class"; + +const processing = new Processing(3); + +processing.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' +); + From 7336a3e5b7b429b003d1f0f8ed64f3e49fded737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Tue, 7 Jan 2025 08:51:09 +0000 Subject: [PATCH 18/27] refactor(Processing): use the `State` of `Set` type to handle async processing. Make`asyncProcess()` and `process()` methods as `public`. General update the processing functionality. --- src/lib/processing.class.ts | 129 +++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 47 deletions(-) diff --git a/src/lib/processing.class.ts b/src/lib/processing.class.ts index 84b221f..2824f7c 100644 --- a/src/lib/processing.class.ts +++ b/src/lib/processing.class.ts @@ -1,16 +1,18 @@ // Class. -import { Boolean as Active } from "@typescript-package/state"; -export type ProcessCallback = (element: Type) => void | Promise; -export type ErrorCallback = (element: Type, error: unknown) => void; +import { Boolean as Active, State } from "@typescript-package/state"; +// Type. +import { ErrorCallback, ProcessCallback } from "../type"; /** - * @description + * @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 { +export class Processing extends State>> { /** - * @description Whether processing is active. + * @description Tracks whether the queue is processing elements. * @public * @readonly * @type {boolean} @@ -19,6 +21,26 @@ export class Processing { 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 @@ -30,13 +52,13 @@ export class Processing { } /** - * @description + * @description Tracks whether the queue is processing elements. * @type {Active} */ #active = new Active(false); /** - * @description + * @description Privately stored current number of elements being processed. * @type {number} */ #activeCount = 0; @@ -46,29 +68,24 @@ export class Processing { * @type {Concurrency} */ #concurrency: Concurrency; - + /** * @description A set of processed elements. + * @type {Set} */ #processed: Set = new Set(); - /** - * @description - * @readonly - * @type {*} - */ - readonly #processing = 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 Starts processing elements. + * @description Starts asynchronous processing elements with concurrency control. * @public * @async * @param {Iterable} elements The elements to process. @@ -83,14 +100,11 @@ export class Processing { onError?: ErrorCallback, method: 'default' | 'race' = 'default' ): Promise { - this.#active.true(); - const processing = new Set>(); switch(method) { case 'race': for (const element of elements) { - this.#activeCount >= this.#concurrency && await Promise.race(this.#processing); - const task = this.#asyncProcess(element, callbackFn, onError).finally(() => this.#processing.delete(task)); - this.#processing.add(task); + this.#activeCount >= this.#concurrency && await Promise.race(super.state); + this.asyncProcess(element, callbackFn, onError); } break default: @@ -99,15 +113,13 @@ export class Processing { while (this.#activeCount < this.#concurrency) { const { value: element, done } = iterator.next(); if (done) break; - const task = this.#asyncProcess(element, callbackFn, onError).finally(() => (process(), processing.delete(task))); - processing.add(task); + this.asyncProcess(element, callbackFn, onError).finally(() => process()); } }; await process(); break; } await this.complete(); - this.#active.false(); } /** @@ -117,64 +129,86 @@ export class Processing { * @returns {Promise} */ public async complete(): Promise { - await Promise.all(this.#processing); + await Promise.all(super.state); } /** - * @description + * @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 - * @param {ProcessCallback} callbackFn - * @param {?ErrorCallback} [onError] + * @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.#active.true(); for (const element of elements) { - this.#process(element, callbackFn, onError); + this.process(element, callbackFn, onError); } - this.#active.false(); } /** - * @description Runs asynchronous single processing task. - * @private + * @description Runs asynchronous single processing task on the `element`. + * @public + * @async * @param {Type} element The element to process. - * @param {ProcessCallback} callbackFn The function to process the element. + * @param {ProcessCallback} callbackFn The callback function to process the element. * @param {ErrorCallback} [onError] An optional error handler. * @returns {Promise} */ - async #asyncProcess( + public async asyncProcess( element: Type, callbackFn: ProcessCallback, onError?: ErrorCallback ): Promise { + // Activate the processing state. + this.#active.isFalse() && this.#active.true(); + // Increase the active count. this.#activeCount++; + + // Create a task. + const task = (async () => { + try { + await callbackFn(element); + } catch (error) { + onError?.(element, error); + } finally { + this.#processed.add(element); + } + })(); + + // Add the task to the processing state. + super.state.add(task); + try { - await callbackFn(element); - } catch (error) { - onError?.(element, error); + await task; } finally { + // Remove the task from the processing state. + super.state.delete(task); + // Decrease the active count. this.#activeCount--; - this.#processed.add(element); + // If active count is equal to `0` then deactivate the active processing state. + this.#activeCount === 0 && this.#active.false(); } } /** - * @description Runs synchronous single processing task. - * @param {(Type | undefined)} element - * @param {ProcessCallback} callbackFn - * @param {?ErrorCallback} [onError] - * @returns {this} + * @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. */ - #process( + public process( element: Type | undefined, callbackFn: ProcessCallback, onError?: ErrorCallback ): this { + this.#active.isFalse() && this.#active.true(); if (element) { try { callbackFn(element); @@ -182,6 +216,7 @@ export class Processing { onError?.(element, error); } finally { this.#processed.add(element); + this.#active.false(); } } return this; From 00c5b1d47c5fcdee760dbeee00011586f4b8245f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:31:14 +0000 Subject: [PATCH 19/27] chore: add funding. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml 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'] From 22dd140f6ba1debf96357f2da30957d9e98d0459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:31:28 +0000 Subject: [PATCH 20/27] feat(type): add. --- src/type/error-callback.type.ts | 1 + src/type/index.ts | 2 ++ src/type/process-callback.type.ts | 1 + 3 files changed, 4 insertions(+) create mode 100644 src/type/error-callback.type.ts create mode 100644 src/type/index.ts create mode 100644 src/type/process-callback.type.ts 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; From 782ca34bdbb524731a2311972ff387a78655b716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:32:56 +0000 Subject: [PATCH 21/27] refactor(Processing): add the ability to debug in the console. --- src/lib/processing.class.ts | 81 +++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/src/lib/processing.class.ts b/src/lib/processing.class.ts index 2824f7c..b6b8d0c 100644 --- a/src/lib/processing.class.ts +++ b/src/lib/processing.class.ts @@ -1,5 +1,5 @@ // Class. -import { Boolean as Active, State } from "@typescript-package/state"; +import { Boolean as Active, Boolean as Debug, State } from "@typescript-package/state"; // Type. import { ErrorCallback, ProcessCallback } from "../type"; /** @@ -68,7 +68,13 @@ export class Processing extends State * @type {Concurrency} */ #concurrency: Concurrency; - + + /** + * @description + * @type {Debug} + */ + #debug = new Debug(false); + /** * @description A set of processed elements. * @type {Set} @@ -84,6 +90,15 @@ export class Processing extends State 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 @@ -100,25 +115,31 @@ export class Processing extends State 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 => { + 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(); } @@ -145,9 +166,12 @@ export class Processing extends State 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"); } /** @@ -164,37 +188,60 @@ export class Processing extends State 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.#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. @@ -208,17 +255,43 @@ export class Processing extends State 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; + } } From 94a8ea3460eee8421278afb37a2b88f316b1c4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:33:26 +0000 Subject: [PATCH 22/27] refactor(ProcessingQueue): use types and `Processing` class. --- src/lib/processing-queue.class.ts | 113 +++++++++++++----------------- 1 file changed, 49 insertions(+), 64 deletions(-) diff --git a/src/lib/processing-queue.class.ts b/src/lib/processing-queue.class.ts index 687b133..edb440d 100644 --- a/src/lib/processing-queue.class.ts +++ b/src/lib/processing-queue.class.ts @@ -1,7 +1,9 @@ -// Class. -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 elements concurrently with a specified concurrency limit. * @export @@ -15,48 +17,20 @@ export class ProcessingQueue< Size extends number = number > extends Queue { /** - * @description The maximum number of elements that can be processed concurrently. - * @public - * @readonly - * @type {number} - */ - public get concurrency() { - return this.#concurrency; - } - - /** - * @description A set containing all elements that have been successfully processed. + * @description * @public * @readonly - * @type {Set} + * @type {Processing} */ - public get processed() { - return this.#processed; + public get processing(): Processing { + return this.#processing; } /** - * @description Privately stored current number of elements being processed. - * @type {number} + * @description + * @type {Processing} */ - #activeCount: number = 0; - - /** - * @description Privately stored maximum concurrency level for processing. - * @type {number} - */ - #concurrency; - - /** - * @description Tracks whether the queue is processing elements. - * @type {Processing} - */ - #processing = new Processing(false); - - /** - * @description Privately stored set of all processed elements. - * @type {Set} - */ - #processed: Set = new Set(); + #processing; /** * Creates an instance of child class. @@ -71,54 +45,65 @@ export class ProcessingQueue< ...elements: Type[] ) { super(size, ...elements); - this.#concurrency = concurrency; + this.#processing = new Processing(concurrency); } + /** + * @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); }); } + 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 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} */ - public run(callbackFn: (element: Type) => void) { - this.#processing.true(); - while (this.#activeCount < this.#concurrency && super.length > 0) { - const element = this.dequeue(); - element && this.#process(element, callbackFn); - } - } - - /** - * @description Processes a single element using the provided callback function. - * @param {Type} element The element to process. - * @param {(element: Type) => void} callbackFn A function to process the `element`. - * @returns {this} - */ - #process(element: Type, callbackFn: (element: Type) => void): this { - this.#activeCount++; - try { - callbackFn(element); - } finally { - this.#activeCount--; - this.#processed.add(element); - 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; } } From 71e3b5e6ed3328e92381789898e1671f1b819b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:33:36 +0000 Subject: [PATCH 23/27] chore(lib/index): update. --- src/lib/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/index.ts b/src/lib/index.ts index 66f2de5..9b0b9e8 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,5 +1,6 @@ // Class. export { Elements } from './elements.class'; +export { Processing } from './processing.class'; // Abstract. export { ProcessingQueue } from './processing-queue.class'; export { Queue } from './queue.abstract'; From f23be6171c44ab5347d1b02e00b9928ccd0d386f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:33:52 +0000 Subject: [PATCH 24/27] test: update. --- src/test/processing-queue.spec.ts | 29 +++++++++++++++++++++-------- src/test/processing.spec.ts | 9 ++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/test/processing-queue.spec.ts b/src/test/processing-queue.spec.ts index a682379..85561e9 100644 --- a/src/test/processing-queue.spec.ts +++ b/src/test/processing-queue.spec.ts @@ -2,20 +2,31 @@ import { ProcessingQueue as AbstractProcessingQueue } from "../lib"; export class ProcessingQueue extends AbstractProcessingQueue {} +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 +); + +processingQueue.asyncRun((element) => console.log(`Processed, `, element, processingQueue.state)).then(() => { + console.log(`Completed`, processingQueue.state); +}); + + console.group(`ProcessingQueue`); // Initialize the `ProcessingQueue`. -let processingQueue = new ProcessingQueue( - 2, // concurrency +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.concurrency); // Output: 2 +console.log(`concurrency, `, processingQueue.processing.concurrency); // Output: 2 // A set containing all elements that have been successfully processed. -console.log(`processed, `, processingQueue.processed); // Output: Set(0) +console.log(`processed, `, processingQueue.processing.processed); // Output: Set(0) // Checks whether the queue is empty. console.log(`isEmpty(), `, processingQueue.isEmpty()); // Output: false @@ -54,20 +65,22 @@ processingQueue.enqueue(5).enqueue(6).enqueue(7).enqueue(8).enqueue(9).enqueue(1 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( +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.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.processed); // Output: Set(10) +console.log(`processed, `, processingQueue.processing.processed); // Output: Set(10) console.groupEnd(); -describe(`ReverseQueue`, () => { +describe(`ProcessingQueue`, () => { beforeEach(() => processingQueue = new ProcessingQueue(2, 10, 1, 2, 3)); it(`enqueue()`, () => expect(processingQueue.enqueue(4).length).toEqual(4)); it(`dequeue()`, () => { diff --git a/src/test/processing.spec.ts b/src/test/processing.spec.ts index c25b1f3..7aa6571 100644 --- a/src/test/processing.spec.ts +++ b/src/test/processing.spec.ts @@ -2,7 +2,14 @@ import { Processing } from "../lib/processing.class"; const processing = new Processing(3); -processing.asyncRun( +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), From c10a98073e00c3f7fa6b2d3930a5ea1afc208ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:33:58 +0000 Subject: [PATCH 25/27] chore(API): update. --- src/public-api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public-api.ts b/src/public-api.ts index 61b82a2..f27e5cb 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -4,6 +4,7 @@ export { // Class. Elements, + Processing, // Abstract. ProcessingQueue, Queue, From 1eb5fa5a632e38d1d353bc3f0d046a2b0d324d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:34:19 +0000 Subject: [PATCH 26/27] docs(README.md): update. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 692aa8d..1c6d53e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ A lightweight TypeScript library for managing various queue and stack structures * [Api](#api) * [`Elements`](#elements) * [`ProcessingQueue`](#processingqueue) + * [`Processing`](#processing) * [`Queue`](#queue) * [`Stack`](#stack) * [Git](#git) @@ -144,6 +145,8 @@ processingQueue.run(element => console.log(`Processed`, element)); // Output: Pr console.log(`processed, `, processingQueue.processed); // Output: Set(10) ``` +### `Processing` + ### `Queue` ```typescript From 51401219351dfe0e6057df8efeec651fed5d6456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Acibor=20Rudnicki?= Date: Fri, 10 Jan 2025 10:34:49 +0000 Subject: [PATCH 27/27] 1.0.0-beta --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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",