initial commit

This commit is contained in:
Pierre
2026-01-11 17:26:49 +01:00
commit 888790fd11
240 changed files with 48691 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Pierre-Henri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+96
View File
@@ -0,0 +1,96 @@
# Async Utils
A TypeScript library providing utility classes and functions to simplify working with asynchronous operations and promises.
## Installation
```bash
yarn add github:worph/async-utils#main
```
## Features
- **Lazy<T>**: A utility type for lazy initialization of asynchronous operations.
- **ListenerCleaner**: Manages cleanup functions, allowing for easy resource management.
- **PromiseQueue**: A queue system for promises, allowing for sequential execution and cancellation.
- **MultiQueue**: Manages multiple `PromiseQueue` instances, distributing tasks to optimize concurrency.
- **Id Generation**: Utility function for generating random IDs.
## Usage
### Lazy Initialization
```typescript
import { Lazy } from "@worph/async-utils";
const lazyValue: Lazy<number> = async () => {
// some asynchronous operation
return 42;
};
// Usage
lazyValue().then(value => console.log(value));
```
### Listener Cleaner
```typescript
import { ListenerCleaner } from "worph/async-utils";
const cleaner = new ListenerCleaner();
cleaner.add(() => console.log("Cleanup action"));
// Trigger all cleanup actions
cleaner.cleanUp();
```
### Promise Queue
```typescript
import { PromiseQueue } from "@worph/async-utils";
const queue = new PromiseQueue<number>();
queue.add(async () => {
// some asynchronous task
return 1;
});
// Wait for the queue to be empty
queue.awaitQueueEmpty().then(() => console.log("Queue empty"));
```
### Multi Queue
```typescript
import { MultiQueue } from "@worph/async-utils";
const multiQueue = new MultiQueue<number>(2); // 2 concurrent tasks
multiQueue.add(async () => {
// some asynchronous task
return 1;
});
// Add more tasks...
// Wait for all queues to be empty
multiQueue.awaitQueueEmpty().then(() => console.log("All queues empty"));
```
### Generating an ID
```typescript
import { makeid } from "@worph/async-utils";
const id = makeid(10); // Generates a random 10 character string
console.log(id);
```
## Contributing
Contributions are welcome! Please submit a pull request or open an issue for discussion.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@worph/async-utils",
"version": "1.0.0",
"description": "",
"repository": {
"type": "git",
"url": "git+https://github.com/ApteroSAS/async-utils.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/ApteroSAS/async-utils/issues"
},
"homepage": "https://github.com/ApteroSAS/async-utils#readme",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"scripts": {
"build": "tsup src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
},
"devDependencies": {
"@types/jest": "^29.5.1",
"jest": "^29.5.0",
"tsup": "^6.7.0",
"typescript": "^5.0.4"
}
}
+10
View File
@@ -0,0 +1,10 @@
export function makeid(length) {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for ( let i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
}
return result;
}
+2
View File
@@ -0,0 +1,2 @@
export type Lazy<T> = () => Promise<T>;
@@ -0,0 +1,23 @@
export class ListenerCleaner {
cleaners: (() => void)[] = [];
add(cleanerCallback: () => void) {
this.cleaners.push(cleanerCallback);
}
cleaner(): () => void {
return () => {
this.cleanUp();
}
}
/**
* Call all the cleaner callbacks and reset this cleaner to be reused.
*/
cleanUp() {
for (const cleaner of this.cleaners) {
cleaner();
}
this.cleaners = [];
}
}
+66
View File
@@ -0,0 +1,66 @@
import {PromiseQueue} from "./PromiseQueue";
export class MultiQueue<T> {
private readonly queues:PromiseQueue<T>[];
/**
*
* @param concurrentTask
*/
constructor(
concurrentTask:number=1,
) {
if (concurrentTask<1){
throw new Error("concurrentTask must be >=1");
}
this.queues = [];
for(let i=0;i<concurrentTask;i++){
this.queues.push(new PromiseQueue<T>());
}
}
public getQueueSize(): number {
let ret = 0;
for(let queue of this.queues){
ret+=queue.getQueueSize();
}
return ret;
}
public async awaitQueueEmpty(){
for(let queue of this.queues){
await queue.awaitQueueEmpty();
}
}
/**
* Add a task to the queue. The queue with the smallest task size is used
* @param task
* @param id
**/
public add(task: () => Promise<T>, id: string = null): { id: string, promise: Promise<T> } {
let queue = this.queues[0];
for(let i=1;i<this.queues.length;i++){
if(this.queues[i].getQueueSize()<queue.getQueueSize()){
queue = this.queues[i];
}
}
return queue.add(task,id);
}
public cancel(id: string) {
for(let queue of this.queues){
queue.cancel(id);
}
}
/**
* The last task in progress is not canceled
*/
public async cancelAll(){
for(let queue of this.queues){
await queue.cancelAll();
}
}
}
+80
View File
@@ -0,0 +1,80 @@
import {makeid} from "./Id";
export class PromiseQueue<T> {
private nextPromise: Promise<T> = Promise.resolve(null);
private canceled = {};
private queueSize = 0;
private queueID = 0;
/**
*
* @param onlyLast if true all task are cancel and only the last one is keep alive (a task started still finishes)
*/
constructor(
private onlyLast: boolean = false
) {
this.cancelAll();
}
getQueueSize(): number {
return this.queueSize;
}
async awaitQueueEmpty(){
await this.nextPromise;//await the current promise
while (this.queueSize!=0){//await all the fowllofing promise
await this.nextPromise;
}
}
add(task: () => Promise<T>, id: string = null): { id: string, promise: Promise<T> } {
if (!id) {
id = "promise-" + makeid(8);
}
let lastPromise = this.nextPromise;
this.queueSize++;
let currentID = this.queueID;
let currentPromise=null;
currentPromise = (async () => {
this.queueSize--;
try {
await lastPromise;
}catch (e) {
/* ignore error of previous promise*/
}
if(currentID!==this.queueID){
throw new Error("canceled caused by new queue");
}
if (this.canceled[id]) {
delete this.canceled[id];
throw new Error("canceled by user");
}
if(this.onlyLast && currentPromise!=this.nextPromise){
throw new Error("canceled caused by not last");
}
//execute task and return result
return task();
})();
currentPromise.catch(e=>{
/* we catch the exception this way to avoid an uncaught exception in the console*/
});
this.nextPromise = currentPromise
return {id, promise: this.nextPromise};
}
cancel(id: string) {
this.canceled[id] = true;
}
/**
* The last task in progress is not canceled
*/
async cancelAll(){
this.queueID++;//create a new queue will cancel all previous task except the one in progress
await this.awaitQueueEmpty();
this.nextPromise = Promise.resolve(null);
this.canceled = {};
this.queueSize = 0;
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from "./ListenerCleaner";
export * from "./PromiseQueue";
export * from "./MultiQueue";
export * from "./Id";
export * from "./Lazy";
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"experimentalDecorators": true,//needed for inversify
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"noImplicitAny": false,
"noLib": false,
"skipLibCheck": true,
"removeComments": true,
"sourceMap": true,
"outDir": "./dist"
},
"include": [
"./src"
],
"exclude": [
"node_modules",
"test/*",
"examples/*"
]
}
+11
View File
@@ -0,0 +1,11 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
splitting: false,
sourcemap: true,
clean: true,
dts:true,
minify:true,
format: ['cjs', 'esm'],
})