diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a183d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db + +*.ignore* +temp \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ea1cd9 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ + + + + + +## typescript-package/queue + +A lightweight TypeScript library for managing various queue and stack structures. + + +[![npm version][typescript-package-npm-badge-svg]][typescript-package-npm-badge] +[![GitHub issues][typescript-package-badge-issues]][typescript-package-issues] +[![GitHub license][typescript-package-badge-license]][typescript-package-license] + +
+ +## Table of contents + +* [Installation](#installation) +* [Api](#api) + * `ProcessingQueue` + * `Queue` + * `Stack` +* [Git](#git) + * [Commit](#commit) + * [Versioning](#versioning) +* [License](#license) + +## Installation + +```bash +npm install @typescript-package/queue +``` + +## Api + +```typescript +import { + ProcessingQueue, + Queue, + Stack +} from '@typescript-package/queue'; +``` + +## GIT + +### Commit + +* [AngularJS Git Commit Message Conventions][git-commit-angular] +* [Karma Git Commit Msg][git-commit-karma] +* [Conventional Commits][git-commit-conventional] + +### Versioning + +[Semantic Versioning 2.0.0][git-semver] + +**Given a version number MAJOR.MINOR.PATCH, increment the:** + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + +**FAQ** +How should I deal with revisions in the 0.y.z initial development phase? + +> The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release. + +How do I know when to release 1.0.0? + +> If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backwards compatibility, you should probably already be 1.0.0. + +## License + +MIT © typescript-package ([license][typescript-package-license]) + + + + [typescript-package-badge-issues]: https://img.shields.io/github/issues/typescript-package/queue + [isscript-package-badge-forks]: https://img.shields.io/github/forks/typescript-package/queue + [typescript-package-badge-stars]: https://img.shields.io/github/stars/typescript-package/queue + [typescript-package-badge-license]: https://img.shields.io/github/license/typescript-package/queue + + [typescript-package-issues]: https://github.com/typescript-package/queue/issues + [typescript-package-forks]: https://github.com/typescript-package/queue/network + [typescript-package-license]: https://github.com/typescript-package/queue/blob/master/LICENSE + [typescript-package-stars]: https://github.com/typescript-package/queue/stargazers + + + + + [typescript-package-npm-badge-svg]: https://badge.fury.io/js/@typescript-package%2Fqueue.svg + [typescript-package-npm-badge]: https://badge.fury.io/js/@typescript-package%2Fqueue + + +[git-semver]: http://semver.org/ + + +[git-commit-angular]: https://gist.github.com/stephenparish/9941e89d80e2bc58a153 +[git-commit-karma]: http://karma-runner.github.io/0.10/dev/git-commit-msg.html +[git-commit-conventional]: https://www.conventionalcommits.org/en/v1.0.0/ diff --git a/ng-package.json b/ng-package.json new file mode 100644 index 0000000..ee1647b --- /dev/null +++ b/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/queue", + "lib": { + "entryFile": "src/public-api.ts" + }, + "keepLifecycleScripts": true +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1ae2769 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,90 @@ +{ + "name": "queue", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "queue", + "version": "0.0.1", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@typescript-package/state": "^2.0.0" + } + }, + "node_modules/@angular/common": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.0.5.tgz", + "integrity": "sha512-fFK+euCj1AjBHBCpj9VnduMSeqoMRhZZHbhPYiND7tucRRJ8vwGU0sYK2KI/Ko+fsrNIXL/0O4F36jVPl09Smg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "19.0.5", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/core": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.0.5.tgz", + "integrity": "sha512-Ywc6sPO6G/Y1stfk3y/MallV/h0yzQ0vdOHRWueLrk5kD1DTdbolV4X03Cs3PuVvravgcSVE3nnuuHFuH32emQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + } + }, + "node_modules/@typescript-package/state": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@typescript-package/state/-/state-2.0.0.tgz", + "integrity": "sha512-IIPvddDv3BKI7gSwhfQoEJE7VCvokj9A3P+XrVaXrDMpSCqyNrUTvDHKiCL1wS7ZPWItFRpA5gh8m5ic1JrRkQ==", + "funding": [ + { + "type": "individual", + "url": "https://checkout.revolut.com/pay/048b10a3-0e10-42c8-a917-e3e9cb4c8e29" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/zone.js": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", + "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==", + "license": "MIT", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8a1b992 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "@typescript-package/queue", + "version": "0.0.1", + "author": "wwwdev.io ", + "description": "A lightweight TypeScript library for managing various queue and stack structures.", + "license": "MIT", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "devDependencies": {}, + "peerDependencies": { + "@typescript-package/state": "^2.0.0" + }, + "scripts": { + "prepublishOnly": "npm run pkg && npm run clean", + "pkg": "npm pkg delete dependencies", + "clean": "npm pkg delete scripts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/typescript-package/queue.git" + }, + "bugs": { + "url": "https://github.com/typescript-package/queue/issues" + }, + "keywords": [ + "@typescript-package", + "@typescript-package/queue" + ], + "funding": [ + { + "type": "individual", + "url": "https://checkout.revolut.com/pay/048b10a3-0e10-42c8-a917-e3e9cb4c8e29" + } + ], + "sideEffects": false +} diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..7e31f40 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..e2dadce --- /dev/null +++ b/src/lib/processing-queue.class.ts @@ -0,0 +1,114 @@ +import { Boolean as Processing } from "@typescript-package/state"; +import { Queue } from "./queue.abstract"; +/** + * @description A queue that processes items concurrently with a specified concurrency limit. + * @export + * @class ProcessingQueue + * @template Type + * @extends {Queue} + */ +export class ProcessingQueue extends Queue { + /** + * @description The maximum number of items that can be processed concurrently. + * @public + * @readonly + * @type {number} + */ + public get concurrency() { + return this.#concurrency; + } + + /** + * @description A set containing all items that have been successfully processed. + * @public + * @readonly + * @type {Set} + */ + 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(); + + /** + * Creates an instance of child class. + * @constructor + * @param {number} [concurrency=1] + * @param {number} [size=Infinity] + * @param {...Type[]} items + */ + constructor(concurrency: number = 1, size: number = Infinity, ...items: Type[]) { + super(size, ...items); + this.#concurrency = concurrency; + } + + /** + * @description Waits for all items in the queue to be processed and returns the set of processed items. + * @public + * @async + * @returns {Promise>} + */ + public async isCompleted(): Promise> { + return new Promise>((resolve, reject) => { + const interval = setInterval(() => + this.#processing.isTrue() + ? super.length === 0 && this.#processing.false() && resolve(this.#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); + } + } + + /** + * @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} + */ + #process(item: Type, callbackFn: (item: Type) => void): this { + this.#activeCount++; + try { + callbackFn(item); + } finally { + this.#activeCount--; + this.#processed.add(item); + this.run(callbackFn); + } + return this; + } +} diff --git a/src/lib/queue.abstract.ts b/src/lib/queue.abstract.ts new file mode 100644 index 0000000..0302442 --- /dev/null +++ b/src/lib/queue.abstract.ts @@ -0,0 +1,124 @@ +import { ArrayState as AbstractArrayState } from "@typescript-package/state"; +/** + * @description A standard FIFO (First In, First Out) queue. + * @export + * @abstract + * @class Queue + * @template Type + */ +export abstract class Queue { + /** + * @description The actual queue length. + * @public + * @readonly + * @type {number} + */ + public get length(): number { + return this.#queue.length; + } + + /** + * @description The maximum queue size. + * @public + * @readonly + * @type {number} + */ + public get size(): number { + return this.#size; + } + + /** + * @description The `Array` queue state. + * @public + * @readonly + * @type {ArrayState} + */ + public get queue() { + return this.#queue; + } + + /** + * @description Privately stored maximum queue size. + * @type {number} + */ + #size; + + /** + * @description Privately stored `Array` queue state. + * @type {ArrayState} + */ + #queue; + + /** + * Creates an instance of `Queue`. + * @constructor + * @param {number} [size=Infinity] + * @param {...Type[]} items + */ + constructor(size: number = Infinity, ...items: Type[]) { + this.#size = size; + this.#queue = new (class ArrayState extends AbstractArrayState{})(items); + } + + /** + * @description Clears the queue. + * @public + * @returns {this} + */ + public clear(): this { + this.#queue.clear(); + return this; + } + + /** + * @description Removes and returns the first (front) element from the queue. + * @public + * @returns {(Type | undefined)} + */ + public dequeue(): Type | undefined { + const first = this.#queue.first(); + this.#queue.remove(0); + return first; + } + + /** + * @description Adds a new element to the queue. + * @public + * @param {Type} item + * @returns {this} + */ + public enqueue(item: Type): this { + if (this.length === this.size) { + throw new Error(`Queue is full.`); + } + this.#queue.append(item); + return this; + } + + /** + * @description Checks if the queue is empty. + * @public + * @returns {boolean} + */ + public isEmpty(): boolean { + return this.length === 0; + } + + /** + * @description Checks if the queue is full. + * @public + * @returns {boolean} + */ + public isFull(): boolean { + return this.length === this.#size; + } + + /** + * @description Returns the first element in the queue. + * @public + * @returns {Type} + */ + public peek(): Type { + return this.#queue.first(); + } +} diff --git a/src/lib/stack.abstract.ts b/src/lib/stack.abstract.ts new file mode 100644 index 0000000..0e55c54 --- /dev/null +++ b/src/lib/stack.abstract.ts @@ -0,0 +1,103 @@ +import { Queue as AbstractQueue } from "./queue.abstract"; +/** + * @description A standard LIFO (Last In, First Out) queue. + * @export + * @abstract + * @class Stack + * @template Type + */ +export abstract class Stack { + /** + * @description The actual stack length. + * @public + * @readonly + * @type {number} + */ + public get length() { + return this.#stack.length; + } + + /** + * @description The maximum stack size. + * @public + * @readonly + * @type {number} + */ + public get size(): number { + return this.#size; + } + + /** + * @description The `Array` stack state. + * @public + * @readonly + * @type {ArrayState} + */ + public get stack() { + return this.#stack.queue; + } + + /** + * @description Privately stored maximum stack size. + * @type {number} + */ + #size; + + /** + * @description Privately stored `Array` stack state. + * @type {ArrayState} + */ + #stack; + + /** + * Creates an instance of `Stack`. + * @constructor + * @param {number} [size=Infinity] + * @param {...Type[]} items + */ + constructor(size: number = Infinity, ...items: Type[]) { + this.#size = size; + this.#stack = new (class Stack extends AbstractQueue {})(size, ...items); + } + + /** + * @description Clears the queue. + * @public + * @returns {this} + */ + public clear(): this { + this.#stack.clear(); + return this; + } + + /** + * @description Returns the top element on the stack. + * @public + * @returns {(Type | undefined)} + */ + public peek(): Type | undefined { + return this.stack.last(); + } + + /** + * @description Removes and returns the top element from the stack. + * @public + * @returns {Type} + */ + public pop(): Type { + const last = this.stack.last(); + this.#stack.length > 0 && this.stack.remove(this.#stack.length - 1); + return last; + } + + /** + * @description Adds a new element on the stack. + * @public + * @param {Type} item + * @returns {this} + */ + public push(item: Type): this { + this.stack.append(item); + return this; + } +} diff --git a/src/public-api.ts b/src/public-api.ts new file mode 100644 index 0000000..69860a7 --- /dev/null +++ b/src/public-api.ts @@ -0,0 +1,8 @@ +/* + * Public API Surface of queue + */ +export { + ProcessingQueue, + Queue, + Stack +} from './lib'; \ No newline at end of file diff --git a/src/test/processing-queue.spec.ts b/src/test/processing-queue.spec.ts new file mode 100644 index 0000000..6c8ddd5 --- /dev/null +++ b/src/test/processing-queue.spec.ts @@ -0,0 +1,17 @@ +import { ProcessingQueue as AbstractProcessingQueue } from "../lib"; + +export class ProcessingQueue extends AbstractProcessingQueue {} + +let queue = new ProcessingQueue(2, Infinity, 1, 2, 3); + +describe(`ReverseQueue`, () => { + beforeEach(() => queue = new ProcessingQueue(2, Infinity, 1, 2, 3)); + it(`enqueue()`, () => expect(queue.enqueue(4).length).toEqual(4)); + it(`dequeue()`, () => { + expect(queue.enqueue(4).length).toEqual(4); + expect(queue.dequeue()).toEqual(1); + expect(queue.dequeue()).toEqual(2); + }); + it(`peek()`, () => expect(queue.peek()).toEqual(1)); + it(`clear()`, () => expect(queue.clear().length).toEqual(0)); +}); diff --git a/src/test/queue.spec.ts b/src/test/queue.spec.ts new file mode 100644 index 0000000..6fa7fc9 --- /dev/null +++ b/src/test/queue.spec.ts @@ -0,0 +1,16 @@ +import { Queue as AbstractQueue } from "../lib"; + +export class Queue extends AbstractQueue {} + +let queue = new Queue(Infinity, 1, 2, 3); + +describe(`Queue`, () => { + beforeEach(() => queue = new Queue(Infinity, 1, 2, 3)); + it(`enqueue()`, () => expect(queue.enqueue(4).length).toEqual(4)); + it(`dequeue()`, () => { + expect(queue.enqueue(4).length).toEqual(4); + expect(queue.dequeue()).toEqual(1); + }); + it(`peek()`, () => expect(queue.peek()).toEqual(1)); + it(`clear()`, () => expect(queue.clear().length).toEqual(0)); +}); diff --git a/src/test/stack.spec.ts b/src/test/stack.spec.ts new file mode 100644 index 0000000..b1ffc41 --- /dev/null +++ b/src/test/stack.spec.ts @@ -0,0 +1,16 @@ +import { Stack as AbstractStack } from "../lib"; + +export class Stack extends AbstractStack {} + +let queue = new Stack(Infinity, 1, 2, 3); + +describe(`ReverseQueue`, () => { + beforeEach(() => queue = new Stack(Infinity, 1, 2, 3)); + it(`enqueue()`, () => expect(queue.push(4).length).toEqual(4)); + it(`pop()`, () => { + expect(queue.push(4).length).toEqual(4); + expect(queue.pop()).toEqual(4); + }); + it(`peek()`, () => expect(queue.peek()).toEqual(3)); + it(`clear()`, () => expect(queue.clear().length).toEqual(0)); +}); diff --git a/tsconfig.lib.json b/tsconfig.lib.json new file mode 100644 index 0000000..2359bf6 --- /dev/null +++ b/tsconfig.lib.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/lib", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/tsconfig.lib.prod.json b/tsconfig.lib.prod.json new file mode 100644 index 0000000..9215caa --- /dev/null +++ b/tsconfig.lib.prod.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..254686d --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +}