gistfile1.ts
· 11 KiB · TypeScript
Исходник
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<string, InstanceClient> = 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<void> {
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);
}
}
}
}
1 | import { Logger } from "../log.client"; |
2 | import * as DTypes from '../../types/db.types'; |
3 | import { IInstance, IInstanceClient, InstanceInfo } from "../../types/instance"; |
4 | import { InstanceErrors } from '../../types/err.types'; |
5 | import { CordXError } from "../error.client"; |
6 | |
7 | export class InstanceClient implements IInstanceClient { |
8 | private static instances: Map<string, InstanceClient> = new Map(); |
9 | private errors: typeof InstanceErrors = InstanceErrors; |
10 | private idleTimeoutDuration: number = 1000 * 60 * 20; |
11 | private warningInterval: number = 1000 * 60 * 5; |
12 | public instance: IInstance; |
13 | |
14 | private constructor(instance: any, name: string) { |
15 | this.instance = { |
16 | ...instance, |
17 | id: InstanceClient.generateId(), |
18 | name: name, |
19 | createdAt: new Date(), |
20 | isActive: true, |
21 | isIdle: false, |
22 | lastUsed: new Date().toLocaleTimeString(), |
23 | idleTimeout: null, |
24 | idleStart: null, |
25 | logs: Logger.getInstance(name, false), |
26 | create: this.createInstance, |
27 | destroy: this.destroyInstance |
28 | } |
29 | } |
30 | |
31 | private static generateId(): string { |
32 | let id: string; |
33 | do { |
34 | id = Math.floor(Math.random() * 9 + 1).toString(); |
35 | for (let i = 0; i < 17; i++) { |
36 | id += Math.floor(Math.random() * 10).toString(); |
37 | } |
38 | } while (InstanceClient.instances.has(id)); |
39 | return id; |
40 | } |
41 | |
42 | /** |
43 | * @function setIdleState |
44 | * @description Sets the instance to idle state. |
45 | * @returns {void} |
46 | * @memberof InstanceClient |
47 | * @private internal use only |
48 | * @memberof InstanceClient |
49 | */ |
50 | private async setIdleState(): Promise<void> { |
51 | this.instance.isActive = false; |
52 | this.instance.isIdle = true; |
53 | this.instance.idleStart = Date.now(); |
54 | await this.monitorState(this.instance.name); |
55 | |
56 | return this.instance.logs.trace(this.errors['INSTANCE_IDLE']({ |
57 | message: `Instance: ${this.instance.name}(${this.instance.id}) is now circulating in the idle pool.`, |
58 | details: [ |
59 | `- This instance will be destroyed in ${this.idleTimeoutDuration / 1000 / 60} minutes.`, |
60 | `- If you wish to keep this instance alive, please perform an operation on it to reset its active state.` |
61 | ] |
62 | })) |
63 | } |
64 | |
65 | /** |
66 | * @function setActiveState |
67 | * @description Sets the instance to active state. |
68 | * @returns {void} |
69 | * @memberof InstanceClient |
70 | * @private internal use only |
71 | * @memberof InstanceClient |
72 | */ |
73 | private setActiveState(): void { |
74 | this.instance.isActive = true; |
75 | this.instance.idleStart = null; |
76 | this.instance.lastUsed = new Date(); |
77 | await this.monitorState(this.instance.name); |
78 | |
79 | if (this.instance.idleTimeout) { |
80 | clearTimeout(this.instance.idleTimeout); |
81 | this.instance.idleTimeout = null; |
82 | this.instance.isIdle = false; |
83 | this.instance.idleStart = null; |
84 | } |
85 | } |
86 | |
87 | private monitorState(): void { |
88 | |
89 | const instance = this.instance |
90 | |
91 | if (!instance) return; |
92 | |
93 | if (instance.idleTimeout) { |
94 | clearInterval(instance.idleTimeout); |
95 | instance.idleTimeout = null; |
96 | instance.isIdle = false; |
97 | instance.idleStart = null; |
98 | } |
99 | |
100 | let count = 0; |
101 | const max = instance.idleTimeoutDuration / instance.warningInterval; |
102 | |
103 | const logMessage = (idleDuration: number, fatal?: boolean = false) => { |
104 | const method = fatal ? instance.logs.fatal : instance.logs.trace; |
105 | method.call(instance.logs, this.errors['INSTANCE_MONITOR']({ |
106 | message: `Instance: ${instance.name}(${instance.id}) has been idle for ${idleDuration / 1000 / 60} minutes.`, |
107 | details: fatal ? [ |
108 | `- This instance has been idle for way too long and will now be destroyed to prevent memory leaks/overloads.` |
109 | ] : [ |
110 | `- This instance will be destroyed in ${instance.idleTimeoutDuration / 1000 / 60} minutes.`, |
111 | `- If you wish to keep this instance alive, please perform an operation on it to reset its active state.` |
112 | ] |
113 | })) |
114 | } |
115 | |
116 | const warnAndClose = () => { |
117 | if (instance.lastUsed) { |
118 | const currentTime = new Date().getTime(); |
119 | const lastUsedTime = instance.lastUsed ? instance.lastUsed.getTime() : currentTime; |
120 | const idleDuration = currentTime - lastUsedTime; |
121 | if (idleDuration < instance.idleTimeoutDuration) { |
122 | logMessage(idleDuration); |
123 | count++; |
124 | if (count >= max) { |
125 | logMessage(idleDuration, true); |
126 | instance.destroy(); |
127 | } |
128 | } else { |
129 | logMessage(idleDuration, true); |
130 | instance.destroy(); |
131 | } |
132 | } |
133 | } |
134 | |
135 | instance.idleTimeout = setInterval(warnAndClose, instance.warningInterval); |
136 | } |
137 | |
138 | |
139 | public createInstance(instance: InstanceClient, name: string): InstanceClient | CordXError { |
140 | let exists = InstanceClient.instances.get(name); |
141 | |
142 | if (exists) return exists.instance.getInstance().catch((err: Error) => { |
143 | let error = instance.errors['INSTANCE_CREATION_FAILED']({}); |
144 | let { status, message = err.stack } = error; |
145 | |
146 | throw this.instance.logs.fatal({ status, message }); |
147 | }) |
148 | |
149 | exists = new InstanceClient(instance, name); |
150 | InstanceClient.instances.set(name, exists); |
151 | |
152 | return exists; |
153 | } |
154 | |
155 | public destroyInstance(): boolean { |
156 | const instance = this.instance; |
157 | |
158 | if (instance) { |
159 | try { |
160 | if (instance.idleTimeout) { |
161 | clearInterval(instance.idleTimeout); |
162 | instance.idleTimeout = null; |
163 | } |
164 | InstanceClient.instances.delete(instance.name); |
165 | instance.logs.fatal(this.errors['INSTANCE_DESTROYED']({ |
166 | message: `Instance: ${instance.name}(${instance.id}) has been destroyed.`, |
167 | })) |
168 | return true; |
169 | } catch (error: Error) { |
170 | if (error instanceof CordXError) { |
171 | instance.logs.trace(this.errors['INSTANCE_DESTROY_FAILED']({ |
172 | message: `Failed to destroy instance: ${instance.name}(${instance.id}).`, |
173 | details: [ |
174 | `- ${error.stack}` |
175 | ] |
176 | |
177 | })); |
178 | return false |
179 | } else { |
180 | instance.logs.trace(this.errors['INSTANCE_DESTROY_FAILED']({ |
181 | message: `Failed to destroy instance: ${instance.name}(${instance.id}).`, |
182 | details: [ |
183 | `- ${error.stack}` |
184 | ] |
185 | })) |
186 | return false; |
187 | } |
188 | } |
189 | } |
190 | |
191 | this.errors['INSTANCE_NOT_FOUND']({ |
192 | message: `That instance does not exist, or has already been destroyed.`, |
193 | }) |
194 | |
195 | return false; |
196 | } |
197 | |
198 | public static findInstance(name: string): any { |
199 | const instance = InstanceClient.instances.get(name); |
200 | |
201 | if (!instance) return { |
202 | success: false, |
203 | message: `Instance: ${name} does not exist.` |
204 | } |
205 | |
206 | return { |
207 | success: true, |
208 | instance: { |
209 | id: instance.id, |
210 | name: instance.name, |
211 | created: instance.createdAt, |
212 | active: instance.isActive, |
213 | idle: instance.isIdle, |
214 | } |
215 | } |
216 | } |
217 | |
218 | public static viewInstance(name: string): any { |
219 | const instance = InstanceClient.instances.get(name); |
220 | |
221 | if (!instance) return { |
222 | success: false, |
223 | message: `Instance: ${name} does not exist.` |
224 | } |
225 | |
226 | return { |
227 | success: true, |
228 | instance: { |
229 | id: instance.id, |
230 | name: instance.name, |
231 | created: instance.createdAt, |
232 | active: instance.isActive, |
233 | idle: instance.isIdle, |
234 | } |
235 | } |
236 | } |
237 | |
238 | /** |
239 | * @function getInstance |
240 | * @description Returns an instance of the specified name. If the instance does not exist, it will be created. |
241 | * @param {InstanceClient} instance The instance to get/create. (usually a client or class). |
242 | * @param {string} name The name of the instance to get/create (should be short and sweet). |
243 | * @returns {InstanceClient | CordXError} Returns the instance of a client/class or errors out. |
244 | * @memberof InstanceClient |
245 | * @example |
246 | * const instance = InstanceClient.getInstance(MongoClient, 'MongoDB'); |
247 | * if (instance instanceof CordXError) return console.error(instance.message); |
248 | * console.log(instance); |
249 | */ |
250 | private getInstance(): InstanceClient | CordXError { |
251 | let exists = InstanceClient.instances.get(this.instance.name); |
252 | |
253 | if (!exists) exists = this.createInstance(this.instance, this.instance.name); |
254 | if (!exists) return this.errors['INSTANCE_CREATION_FAILED']({ |
255 | details: [ |
256 | `- Failed to create instance: ${name}.`, |
257 | `- Please check the logs for more information.`, |
258 | `- If this issue persists, please contact Toxic Dev.` |
259 | ] |
260 | }) |
261 | |
262 | return exists; |
263 | } |
264 | |
265 | public static getAllInstances(): InstanceInfo[] { |
266 | return Array.from(InstanceClient.instances.values()).map(i => ({ |
267 | id: i.instance.id, |
268 | name: i.instance.name, |
269 | created: i.instance.createdAt, |
270 | active: i.instance.isActive, |
271 | idle: i.instance.isIdle, |
272 | })) |
273 | } |
274 | |
275 | public static destroyAllInstances(): void { |
276 | InstanceClient.instances.forEach((instance: InstanceClient) => { |
277 | InstanceClient.destroyInstance(instance.name); |
278 | }) |
279 | } |
280 | |
281 | public static getInstanceCount(): number { |
282 | return InstanceClient.instances.size; |
283 | } |
284 | |
285 | public performOperation(operation: string): void { |
286 | const instance = InstanceClient.instances.get(this.instance.name); |
287 | |
288 | if (!instance) return this.errors. |
289 | |
290 | if(this.instance) { |
291 | instance.lastUsed = Date.now(); |
292 | InstanceClient.monitorState(name); |
293 | instance.logs.info(`Instance: ${instance.name}(${instance.id}) is performing operation: ${operation}`) |
294 | if (instance.isIdle) { |
295 | InstanceClient.setActiveState(name); |
296 | } else { |
297 | InstanceClient.setIdleState(name); |
298 | } |
299 | } |
300 | } |
301 | } |