{"version":3,"sources":["../../src/service/RequestQueue.ts"],"sourcesContent":["import type { Request } from './types/index.js';\n\ntype RequestQueueOptions = {\n    heartbeat?: number;\n    requestTimeout?: number; // Timeout for requests\n    onError?: 'throw' | 'return' | 'log' | 'ignore';\n}\n\ntype RequestQueueParameters = {\n    process_request: Function;\n    get_slave: Function;\n    options?: RequestQueueOptions;\n}\n\ntype addRequestParameters = {\n    method: string;\n    type: 'run' | 'exec';\n    parameters: any;\n    selector: string;\n}\n\nclass RequestQueue {\n    /* This class will keep track of all the requests that are made to the service,\n     * how long each request takes to be processed,\n     * how many requests are in the queue,\n     * when the requests are being processed, and\n     * request individually.\n     */\n\n    private queue: Request[] = [];\n    private process_request: Function;\n    private currentId: number = 0;\n    private get_slave: Function;\n    private isIntervalRunning: boolean = false;\n    private interval: NodeJS.Timeout;\n    private heartbeat;\n    private turnover_times: number[] = []; // Stores time taken for the last 500 requests\n    private MAX_TURNOVER_ENTRIES = 500; // Limit storage to last 500 requests\n    private requestTimeout: number; // Timeout for requests\n\n    constructor({ process_request, get_slave, options }: RequestQueueParameters) {\n        /*\n         * Set an interval to check if there are items in the queue.\n         * If there are, pop the first element and process it.\n         * If there are no elements, wait for the next element to be added.\n         */\n        // process wrapper for maintainig the request\n        this.process_request = async (slave: any, request: Request) => {\n            let result = await process_request(slave, request);\n            return { result, id: request.id }\n        }\n        this.get_slave = get_slave;\n        this.heartbeat = options?.heartbeat || 10;  // Check every 10ms if the request is completed\n        this.requestTimeout = options?.requestTimeout || 5 * 60 * 1000; // Default timeout is 5 minutes\n        if (!this.process_request) throw new Error('Process request cannot be null');\n        if (!this.get_slave) throw new Error('Get slave cannot be null');\n        // run interval\n        this.interval = setInterval( async () => {\n            // do not run another function if the previous one is still running\n            if (this.isIntervalRunning) return;\n            // if there are no items in the queue\n            if (this.queue.length === 0){\n                this.isIntervalRunning = false;\n                return;\n            }\n            // start the request function\n            this.isIntervalRunning = true;\n            // start to look at the queue\n            for (let request of this.queue) {\n                if(request.completed){\n                    // remove the request at the index\n                    this.queue = this.queue.filter((r) => r.id !== request.id);\n                    request.onComplete();\n                    this.isIntervalRunning = false;\n                    // get the turnover time\n                    this.addTurnOverTime(request.startTime);\n                    return;\n                }else if(request.isProcessing){\n                    // if check if the request has timed out\n                    if(Date.now() - request.startTime > this.requestTimeout){\n                        request.completed = true;\n                        request.result = {\n                            isError: true,\n                            error: new Error(`slavery-js: Request '${request.method}', timed out after: ${this.requestTimeout}.  You can change this behavior by setting the 'timeout', and 'onError' in the options at slavery({})`),\n                        }\n                    }\n                }else{ // if the is neither completed nor processing\n                    request.isProcessing = true;\n                    // get slave\n                    const slave = await this.get_slave(request.selector);\n                    this.isIntervalRunning = false;\n                    // process the request\n                    this.process_request(slave, request)\n                    .then(({ result, id }: { result: any, id: number }) => {\n                        let request = this.getRequest(id);\n                        if(request){ // complete the request\n                            request.completed = true;\n                            request.result = result;\n                        }\n                    })\n                    .catch((err : any) => {\n                        throw new Error('slavery-js [RequestQueue]: this Error should not be reachable, please report this issue', err);\n                    })\n                }\n            }\n            this.isIntervalRunning = false;\n        }, this.heartbeat);\n    }\n\n\n    public addRequest( { method, type, parameters, selector }: addRequestParameters ): Promise<any> {\n        // Add request to the queue and return a promise\n        // that will be resolved when the request is completed\n        return new Promise(async resolve => {\n            // is id has reached the max value, reset it to 0\n            if(this.currentId >= Number.MAX_SAFE_INTEGER) this.currentId = 0;\n            let request: Request = {\n                id: ++this.currentId,\n                method: method,\n                type: type,\n                parameters: parameters,\n                selector: selector || undefined,\n                completed: false,\n                isProcessing: false,\n                onComplete: () => { \n                    resolve(request.result)\n                },\n                startTime: Date.now(),\n                result: null,\n            };\n            // add the request to the queue\n            this.queue.push(request);\n        });\n    }\n\n    private getRequest(id: number): Request | null {\n        // Get request by id\n        const request = this.queue.find(r => r.id === id);\n        if (request) {\n            return request;\n        } else {\n            return null;\n        }\n        \n    }\n\n\n\n    private addTurnOverTime(startTime: number){\n        const timeTaken = Date.now() - startTime;\n        this.turnover_times.push(timeTaken);\n        // Keep only the last 500 entries\n        if (this.turnover_times.length > this.MAX_TURNOVER_ENTRIES)\n            this.turnover_times.shift(); // Remove the oldest entry\n    }\n\n\n    public queueSize() : number {\n        return this.queue.length;\n    }\n\n    public getTurnoverRatio(): number {\n        if (this.turnover_times.length === 0) return 0;\n        const sum = this.turnover_times.reduce((acc, time) => acc + time, 0);\n        return sum / this.turnover_times.length;\n    }\n\n    public getTurnoverTimes(): number[] {\n        return this.turnover_times;\n    }\n\n    public exit() {\n        this.queue = [];\n        clearInterval(this.interval);\n\n    }\n}\n\nexport default RequestQueue;\n"],"mappings":"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","names":["request"]}