import { Logger } from "../log.client"; import * as DTypes from '../../types/db.types'; import { IInstance, IInstanceClient, InstanceInfo } from "../../types/instance"; import { InstanceErrors } from '../../types/err.types'; import { CordXError } from "../error.client"; export class InstanceClient implements IInstanceClient { private static instances: Map = new Map(); private errors: typeof InstanceErrors = InstanceErrors; private idleTimeoutDuration: number = 1000 * 60 * 20; private warningInterval: number = 1000 * 60 * 5; public instance: IInstance; private constructor(instance: any, name: string) { this.instance = { ...instance, id: InstanceClient.generateId(), name: name, createdAt: new Date(), isActive: true, isIdle: false, lastUsed: new Date().toLocaleTimeString(), idleTimeout: null, idleStart: null, logs: Logger.getInstance(name, false), create: this.createInstance, destroy: this.destroyInstance } } private static generateId(): string { let id: string; do { id = Math.floor(Math.random() * 9 + 1).toString(); for (let i = 0; i < 17; i++) { id += Math.floor(Math.random() * 10).toString(); } } while (InstanceClient.instances.has(id)); return id; } /** * @function setIdleState * @description Sets the instance to idle state. * @returns {void} * @memberof InstanceClient * @private internal use only * @memberof InstanceClient */ private async setIdleState(): Promise { this.instance.isActive = false; this.instance.isIdle = true; this.instance.idleStart = Date.now(); await this.monitorState(this.instance.name); return this.instance.logs.trace(this.errors['INSTANCE_IDLE']({ message: `Instance: ${this.instance.name}(${this.instance.id}) is now circulating in the idle pool.`, details: [ `- This instance will be destroyed in ${this.idleTimeoutDuration / 1000 / 60} minutes.`, `- If you wish to keep this instance alive, please perform an operation on it to reset its active state.` ] })) } /** * @function setActiveState * @description Sets the instance to active state. * @returns {void} * @memberof InstanceClient * @private internal use only * @memberof InstanceClient */ private setActiveState(): void { this.instance.isActive = true; this.instance.idleStart = null; this.instance.lastUsed = new Date(); await this.monitorState(this.instance.name); if (this.instance.idleTimeout) { clearTimeout(this.instance.idleTimeout); this.instance.idleTimeout = null; this.instance.isIdle = false; this.instance.idleStart = null; } } private monitorState(): void { const instance = this.instance if (!instance) return; if (instance.idleTimeout) { clearInterval(instance.idleTimeout); instance.idleTimeout = null; instance.isIdle = false; instance.idleStart = null; } let count = 0; const max = instance.idleTimeoutDuration / instance.warningInterval; const logMessage = (idleDuration: number, fatal?: boolean = false) => { const method = fatal ? instance.logs.fatal : instance.logs.trace; method.call(instance.logs, this.errors['INSTANCE_MONITOR']({ message: `Instance: ${instance.name}(${instance.id}) has been idle for ${idleDuration / 1000 / 60} minutes.`, details: fatal ? [ `- This instance has been idle for way too long and will now be destroyed to prevent memory leaks/overloads.` ] : [ `- This instance will be destroyed in ${instance.idleTimeoutDuration / 1000 / 60} minutes.`, `- If you wish to keep this instance alive, please perform an operation on it to reset its active state.` ] })) } const warnAndClose = () => { if (instance.lastUsed) { const currentTime = new Date().getTime(); const lastUsedTime = instance.lastUsed ? instance.lastUsed.getTime() : currentTime; const idleDuration = currentTime - lastUsedTime; if (idleDuration < instance.idleTimeoutDuration) { logMessage(idleDuration); count++; if (count >= max) { logMessage(idleDuration, true); instance.destroy(); } } else { logMessage(idleDuration, true); instance.destroy(); } } } instance.idleTimeout = setInterval(warnAndClose, instance.warningInterval); } public createInstance(instance: InstanceClient, name: string): InstanceClient | CordXError { let exists = InstanceClient.instances.get(name); if (exists) return exists.instance.getInstance().catch((err: Error) => { let error = instance.errors['INSTANCE_CREATION_FAILED']({}); let { status, message = err.stack } = error; throw this.instance.logs.fatal({ status, message }); }) exists = new InstanceClient(instance, name); InstanceClient.instances.set(name, exists); return exists; } public destroyInstance(): boolean { const instance = this.instance; if (instance) { try { if (instance.idleTimeout) { clearInterval(instance.idleTimeout); instance.idleTimeout = null; } InstanceClient.instances.delete(instance.name); instance.logs.fatal(this.errors['INSTANCE_DESTROYED']({ message: `Instance: ${instance.name}(${instance.id}) has been destroyed.`, })) return true; } catch (error: Error) { if (error instanceof CordXError) { instance.logs.trace(this.errors['INSTANCE_DESTROY_FAILED']({ message: `Failed to destroy instance: ${instance.name}(${instance.id}).`, details: [ `- ${error.stack}` ] })); return false } else { instance.logs.trace(this.errors['INSTANCE_DESTROY_FAILED']({ message: `Failed to destroy instance: ${instance.name}(${instance.id}).`, details: [ `- ${error.stack}` ] })) return false; } } } this.errors['INSTANCE_NOT_FOUND']({ message: `That instance does not exist, or has already been destroyed.`, }) return false; } public static findInstance(name: string): any { const instance = InstanceClient.instances.get(name); if (!instance) return { success: false, message: `Instance: ${name} does not exist.` } return { success: true, instance: { id: instance.id, name: instance.name, created: instance.createdAt, active: instance.isActive, idle: instance.isIdle, } } } public static viewInstance(name: string): any { const instance = InstanceClient.instances.get(name); if (!instance) return { success: false, message: `Instance: ${name} does not exist.` } return { success: true, instance: { id: instance.id, name: instance.name, created: instance.createdAt, active: instance.isActive, idle: instance.isIdle, } } } /** * @function getInstance * @description Returns an instance of the specified name. If the instance does not exist, it will be created. * @param {InstanceClient} instance The instance to get/create. (usually a client or class). * @param {string} name The name of the instance to get/create (should be short and sweet). * @returns {InstanceClient | CordXError} Returns the instance of a client/class or errors out. * @memberof InstanceClient * @example * const instance = InstanceClient.getInstance(MongoClient, 'MongoDB'); * if (instance instanceof CordXError) return console.error(instance.message); * console.log(instance); */ private getInstance(): InstanceClient | CordXError { let exists = InstanceClient.instances.get(this.instance.name); if (!exists) exists = this.createInstance(this.instance, this.instance.name); if (!exists) return this.errors['INSTANCE_CREATION_FAILED']({ details: [ `- Failed to create instance: ${name}.`, `- Please check the logs for more information.`, `- If this issue persists, please contact Toxic Dev.` ] }) return exists; } public static getAllInstances(): InstanceInfo[] { return Array.from(InstanceClient.instances.values()).map(i => ({ id: i.instance.id, name: i.instance.name, created: i.instance.createdAt, active: i.instance.isActive, idle: i.instance.isIdle, })) } public static destroyAllInstances(): void { InstanceClient.instances.forEach((instance: InstanceClient) => { InstanceClient.destroyInstance(instance.name); }) } public static getInstanceCount(): number { return InstanceClient.instances.size; } public performOperation(operation: string): void { const instance = InstanceClient.instances.get(this.instance.name); if (!instance) return this.errors. if(this.instance) { instance.lastUsed = Date.now(); InstanceClient.monitorState(name); instance.logs.info(`Instance: ${instance.name}(${instance.id}) is performing operation: ${operation}`) if (instance.isIdle) { InstanceClient.setActiveState(name); } else { InstanceClient.setIdleState(name); } } } }