@wikicasa-dev/node-common 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Cache.d.ts +20 -0
- package/dist/Cache.js +114 -0
- package/dist/CacheES.d.ts +17 -0
- package/dist/CacheES.js +119 -0
- package/dist/CallPool.d.ts +32 -0
- package/dist/CallPool.js +226 -0
- package/dist/Console.d.ts +36 -0
- package/dist/Console.js +113 -0
- package/dist/Crawler.d.ts +29 -0
- package/dist/Crawler.js +85 -0
- package/dist/Pool.d.ts +13 -0
- package/dist/Pool.js +70 -0
- package/dist/common.d.ts +14 -0
- package/dist/common.js +150 -0
- package/dist/src/CallPool.js +20 -20
- package/dist/src/Crawler.js +8 -8
- package/dist/src/Pool.js +8 -8
- package/dist/src/common.js +5 -5
- package/dist/utils.d.ts +30 -0
- package/dist/utils.js +209 -0
- package/package.json +6 -1
package/dist/Cache.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RedisClientOptions, RedisClientType, RedisDefaultModules, RedisFunctions, RedisModules, RedisScripts } from "redis";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
export declare enum MODE {
|
|
4
|
+
STRING = 0,
|
|
5
|
+
JSON = 1,
|
|
6
|
+
HASH = 2,
|
|
7
|
+
STRING_BROTLI = 3
|
|
8
|
+
}
|
|
9
|
+
export declare class Cache {
|
|
10
|
+
init: Promise<any>;
|
|
11
|
+
client: RedisClientType<RedisDefaultModules & RedisModules, RedisFunctions, RedisScripts>;
|
|
12
|
+
constructor(options?: RedisClientOptions);
|
|
13
|
+
start(): Promise<void>;
|
|
14
|
+
get(key: string, mode?: MODE): Promise<unknown>;
|
|
15
|
+
set(key: string, value: Buffer | unknown, mode?: MODE): Promise<void>;
|
|
16
|
+
delete(key: string): Promise<void>;
|
|
17
|
+
exists(key: string): Promise<boolean | undefined>;
|
|
18
|
+
select(db: number): Promise<void>;
|
|
19
|
+
quit(): Promise<void>;
|
|
20
|
+
}
|
package/dist/Cache.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import redis from "redis";
|
|
2
|
+
import { brotliCompressSync, brotliDecompressSync } from "zlib";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
export var MODE;
|
|
5
|
+
(function (MODE) {
|
|
6
|
+
MODE[MODE["STRING"] = 0] = "STRING";
|
|
7
|
+
MODE[MODE["JSON"] = 1] = "JSON";
|
|
8
|
+
MODE[MODE["HASH"] = 2] = "HASH";
|
|
9
|
+
MODE[MODE["STRING_BROTLI"] = 3] = "STRING_BROTLI";
|
|
10
|
+
})(MODE || (MODE = {}));
|
|
11
|
+
export class Cache {
|
|
12
|
+
init;
|
|
13
|
+
client;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.client = redis.createClient(options);
|
|
16
|
+
}
|
|
17
|
+
async start() {
|
|
18
|
+
this.init = this.client.connect();
|
|
19
|
+
}
|
|
20
|
+
async get(key, mode = MODE.STRING_BROTLI) {
|
|
21
|
+
if (!this.init)
|
|
22
|
+
await this.start();
|
|
23
|
+
//if (!await this.client.exists(key))
|
|
24
|
+
// return null;
|
|
25
|
+
let data;
|
|
26
|
+
try {
|
|
27
|
+
switch (mode) {
|
|
28
|
+
case MODE.STRING:
|
|
29
|
+
data = await this.client.get(key);
|
|
30
|
+
break;
|
|
31
|
+
case MODE.STRING_BROTLI:
|
|
32
|
+
default: {
|
|
33
|
+
const value = await this.client.get(key);
|
|
34
|
+
if (value !== null) {
|
|
35
|
+
const dataRaw = brotliDecompressSync(Buffer.from(value, "binary")).toString();
|
|
36
|
+
data = JSON.parse(dataRaw);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
data = null;
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
data = null;
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
async set(key, value, mode = MODE.STRING_BROTLI) {
|
|
51
|
+
if (!this.init)
|
|
52
|
+
await this.start();
|
|
53
|
+
try {
|
|
54
|
+
switch (mode) {
|
|
55
|
+
case MODE.STRING: {
|
|
56
|
+
await this.client.set(key, value.toString("binary"));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case MODE.JSON:
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
await this.client.jSet(key, ".", value);
|
|
62
|
+
break;
|
|
63
|
+
case MODE.STRING_BROTLI:
|
|
64
|
+
default: {
|
|
65
|
+
const data = brotliCompressSync(JSON.stringify(value));
|
|
66
|
+
await this.client.set(key, data.toString("binary"));
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
console.error("Can't cache data");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async delete(key) {
|
|
76
|
+
if (!this.init)
|
|
77
|
+
await this.start();
|
|
78
|
+
try {
|
|
79
|
+
await this.client.del(key);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error("Can't delete data");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async exists(key) {
|
|
86
|
+
if (!this.init)
|
|
87
|
+
await this.start();
|
|
88
|
+
try {
|
|
89
|
+
return !!(await this.client.exists(key));
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.error("Can't check data exists");
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
async select(db) {
|
|
97
|
+
if (!this.init)
|
|
98
|
+
await this.start();
|
|
99
|
+
try {
|
|
100
|
+
await this.client.select(db);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
console.error("Can't select db");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async quit() {
|
|
107
|
+
try {
|
|
108
|
+
await this.client.quit();
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
console.error("Can't quit");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Client, ClientOptions } from "@elastic/elasticsearch";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
export declare enum MODE {
|
|
4
|
+
STRING = 0,
|
|
5
|
+
JSON = 1,
|
|
6
|
+
HASH = 2,
|
|
7
|
+
STRING_BROTLI = 3
|
|
8
|
+
}
|
|
9
|
+
export declare class CacheES {
|
|
10
|
+
client: Client;
|
|
11
|
+
constructor(options?: ClientOptions);
|
|
12
|
+
get(id: string, mode?: MODE): Promise<unknown>;
|
|
13
|
+
set(id: string, value: Buffer | unknown, mode?: MODE): Promise<void>;
|
|
14
|
+
delete(id: string): Promise<void>;
|
|
15
|
+
exists(id: string): Promise<boolean | undefined>;
|
|
16
|
+
quit(): Promise<void>;
|
|
17
|
+
}
|
package/dist/CacheES.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Client } from "@elastic/elasticsearch";
|
|
2
|
+
import { brotliCompressSync, brotliDecompressSync } from "zlib";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
const index = "redis_data";
|
|
5
|
+
export var MODE;
|
|
6
|
+
(function (MODE) {
|
|
7
|
+
MODE[MODE["STRING"] = 0] = "STRING";
|
|
8
|
+
MODE[MODE["JSON"] = 1] = "JSON";
|
|
9
|
+
MODE[MODE["HASH"] = 2] = "HASH";
|
|
10
|
+
MODE[MODE["STRING_BROTLI"] = 3] = "STRING_BROTLI";
|
|
11
|
+
})(MODE || (MODE = {}));
|
|
12
|
+
export class CacheES {
|
|
13
|
+
client;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.client = new Client({
|
|
16
|
+
...(options || {}),
|
|
17
|
+
tls: {
|
|
18
|
+
rejectUnauthorized: false
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async get(id, mode = MODE.STRING_BROTLI) {
|
|
23
|
+
let data;
|
|
24
|
+
try {
|
|
25
|
+
switch (mode) {
|
|
26
|
+
case MODE.STRING: {
|
|
27
|
+
const value = (await this.client.get({
|
|
28
|
+
index,
|
|
29
|
+
id
|
|
30
|
+
}))._source;
|
|
31
|
+
data = value.data;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case MODE.STRING_BROTLI:
|
|
35
|
+
default: {
|
|
36
|
+
const value = (await this.client.get({
|
|
37
|
+
index,
|
|
38
|
+
id
|
|
39
|
+
}))._source;
|
|
40
|
+
const dataRaw = brotliDecompressSync(Buffer.from(value.data, "binary")).toString();
|
|
41
|
+
data = JSON.parse(dataRaw);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
data = null;
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
async set(id, value, mode = MODE.STRING_BROTLI) {
|
|
52
|
+
try {
|
|
53
|
+
switch (mode) {
|
|
54
|
+
case MODE.STRING: {
|
|
55
|
+
await this.client.index({
|
|
56
|
+
index: "redis_data", // puoi cambiare il nome dell'indice
|
|
57
|
+
id,
|
|
58
|
+
document: {
|
|
59
|
+
id,
|
|
60
|
+
data: value.toString("binary")
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case MODE.JSON:
|
|
66
|
+
// @ts-ignore
|
|
67
|
+
await this.client.jSet(id, ".", value);
|
|
68
|
+
break;
|
|
69
|
+
case MODE.STRING_BROTLI:
|
|
70
|
+
default: {
|
|
71
|
+
const data = brotliCompressSync(JSON.stringify(value));
|
|
72
|
+
await this.client.index({
|
|
73
|
+
index: "redis_data", // puoi cambiare il nome dell'indice
|
|
74
|
+
id,
|
|
75
|
+
document: {
|
|
76
|
+
id,
|
|
77
|
+
data: data.toString("binary")
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
console.error(`Can't cache data: ${e}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async delete(id) {
|
|
89
|
+
try {
|
|
90
|
+
await this.client.delete({
|
|
91
|
+
index,
|
|
92
|
+
id
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
console.error(`Can't delete data: ${e}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async exists(id) {
|
|
100
|
+
try {
|
|
101
|
+
return !!(await this.client.exists({
|
|
102
|
+
index: "redis_data",
|
|
103
|
+
id
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
console.error(`Can't check data exists: ${e}`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
async quit() {
|
|
112
|
+
try {
|
|
113
|
+
await this.client.close();
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
console.error(`Can't quit connection: ${e}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
2
|
+
export declare enum PROXY {
|
|
3
|
+
NONE = 0,
|
|
4
|
+
STATIC = 1,
|
|
5
|
+
DYNAMIC = 2
|
|
6
|
+
}
|
|
7
|
+
export declare class CallPool {
|
|
8
|
+
private name;
|
|
9
|
+
private concurrency;
|
|
10
|
+
private readonly minConcurrency;
|
|
11
|
+
private readonly maxConcurrency;
|
|
12
|
+
private _print;
|
|
13
|
+
private readonly limiter;
|
|
14
|
+
private readonly proxy;
|
|
15
|
+
CLOCK_NUMBER: number;
|
|
16
|
+
WEIGHT: number;
|
|
17
|
+
mean: number;
|
|
18
|
+
targetMean: number;
|
|
19
|
+
clock: number;
|
|
20
|
+
private queue;
|
|
21
|
+
private activeCount;
|
|
22
|
+
private agent;
|
|
23
|
+
constructor(name: string, proxy: PROXY, minConcurrency: number, maxConcurrency: number, limitCall?: number, limitInterval?: number);
|
|
24
|
+
call(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse>;
|
|
25
|
+
private getRandomUserAgent;
|
|
26
|
+
enqueue(call: () => Promise<any>): Promise<any>;
|
|
27
|
+
private schedule;
|
|
28
|
+
print(): string;
|
|
29
|
+
updatePrint(): void;
|
|
30
|
+
private finish;
|
|
31
|
+
private fetchWithRetry;
|
|
32
|
+
}
|
package/dist/CallPool.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import throttledQueue from "throttled-queue";
|
|
2
|
+
import Console, { COLORS } from "./Console.js";
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import { sleep } from "./common.js";
|
|
6
|
+
import CacheableLookup from "cacheable-lookup";
|
|
7
|
+
import Agent from "agentkeepalive";
|
|
8
|
+
import * as RandomUserAgent from "random-useragent";
|
|
9
|
+
import axiosRetry from "axios-retry";
|
|
10
|
+
import _ from "lodash";
|
|
11
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
12
|
+
const cacheable = new CacheableLookup();
|
|
13
|
+
// Increase the limit
|
|
14
|
+
EventEmitter.defaultMaxListeners = 20;
|
|
15
|
+
//const a = new CallPool("test", 1, 10, 50, 1000, PROXY.DYNAMIC);
|
|
16
|
+
axiosRetry(axios, {
|
|
17
|
+
retries: 1,
|
|
18
|
+
retryDelay: (retryCount, error) => {
|
|
19
|
+
Console.log(`retry attempt: ${retryCount}. Error ${error}`);
|
|
20
|
+
return retryCount * 1000;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
export var PROXY;
|
|
24
|
+
(function (PROXY) {
|
|
25
|
+
PROXY[PROXY["NONE"] = 0] = "NONE";
|
|
26
|
+
PROXY[PROXY["STATIC"] = 1] = "STATIC";
|
|
27
|
+
PROXY[PROXY["DYNAMIC"] = 2] = "DYNAMIC";
|
|
28
|
+
})(PROXY || (PROXY = {}));
|
|
29
|
+
export class CallPool {
|
|
30
|
+
name;
|
|
31
|
+
concurrency;
|
|
32
|
+
minConcurrency = 1;
|
|
33
|
+
maxConcurrency;
|
|
34
|
+
_print = "";
|
|
35
|
+
limiter;
|
|
36
|
+
proxy;
|
|
37
|
+
CLOCK_NUMBER = 1;
|
|
38
|
+
WEIGHT = 100;
|
|
39
|
+
mean = 0;
|
|
40
|
+
targetMean = 0;
|
|
41
|
+
clock = this.CLOCK_NUMBER;
|
|
42
|
+
queue = [];
|
|
43
|
+
activeCount;
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
agent;
|
|
46
|
+
constructor(name = "default", proxy = PROXY.STATIC, minConcurrency = 1, maxConcurrency, limitCall = 1000, limitInterval = 1000) {
|
|
47
|
+
this.name = name;
|
|
48
|
+
this.concurrency = (minConcurrency + maxConcurrency) / 2;
|
|
49
|
+
this.minConcurrency = minConcurrency;
|
|
50
|
+
this.maxConcurrency = maxConcurrency;
|
|
51
|
+
this.queue = [];
|
|
52
|
+
this.activeCount = 0;
|
|
53
|
+
this.limiter = throttledQueue(limitCall, limitInterval, true);
|
|
54
|
+
this.proxy = proxy;
|
|
55
|
+
this.updatePrint();
|
|
56
|
+
this.agent = new Agent({
|
|
57
|
+
maxSockets: 100,
|
|
58
|
+
maxFreeSockets: 10,
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
lookup: cacheable.lookupAsync,
|
|
61
|
+
});
|
|
62
|
+
/*axios("https://api.ipify.org?format=json", {
|
|
63
|
+
method: "GET",
|
|
64
|
+
proxy: null,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
fetch("https://api.ipify.org?format=json", {
|
|
68
|
+
method: "GET",
|
|
69
|
+
agent: null,
|
|
70
|
+
headers: {
|
|
71
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
|
|
72
|
+
}
|
|
73
|
+
})*/
|
|
74
|
+
}
|
|
75
|
+
call(url, config) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
this.queue.push({
|
|
78
|
+
call: async () => {
|
|
79
|
+
/*const response = await this.fetchWithRetry(url, {
|
|
80
|
+
...config,
|
|
81
|
+
agent: this.agent,
|
|
82
|
+
headers: {
|
|
83
|
+
...config?.headers,
|
|
84
|
+
"User-Agent": this.proxy !== PROXY.NONE ? RandomUserAgent.getRandom() : undefined
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Andrea M: handle 400-500 errors
|
|
89
|
+
if (!response.ok)
|
|
90
|
+
throw response;*/
|
|
91
|
+
/*httpAgent: new Agent({
|
|
92
|
+
keepAlive: false,
|
|
93
|
+
maxSockets: 100,
|
|
94
|
+
maxFreeSockets: 10,
|
|
95
|
+
timeout: 60000,
|
|
96
|
+
lookup: cacheable.lookupAsync,
|
|
97
|
+
}),
|
|
98
|
+
httpsAgent: new Agent({
|
|
99
|
+
keepAlive: false,
|
|
100
|
+
maxSockets: 100,
|
|
101
|
+
maxFreeSockets: 10,
|
|
102
|
+
timeout: 60000,
|
|
103
|
+
lookup: cacheable.lookupAsync,
|
|
104
|
+
}),*/
|
|
105
|
+
return axios(url, _.merge({
|
|
106
|
+
method: "GET",
|
|
107
|
+
proxy: this.proxy === PROXY.DYNAMIC ? {
|
|
108
|
+
protocol: "http",
|
|
109
|
+
host: "api.zyte.com",
|
|
110
|
+
port: 8011,
|
|
111
|
+
auth: {
|
|
112
|
+
username: "cf3dd14c1b9c4daf9cf67007520d0c48",
|
|
113
|
+
password: ""
|
|
114
|
+
},
|
|
115
|
+
} : undefined,
|
|
116
|
+
httpsAgent: this.proxy === PROXY.STATIC ? new HttpsProxyAgent("http://wikicasa:VTt7KYb5fsqDhK6xXinS@162.19.104.35:3128") : undefined,
|
|
117
|
+
httpAgent: this.proxy === PROXY.STATIC ? new HttpsProxyAgent("http://wikicasa:VTt7KYb5fsqDhK6xXinS@162.19.104.35:3128") : undefined,
|
|
118
|
+
maxContentLength: Infinity,
|
|
119
|
+
maxBodyLength: Infinity,
|
|
120
|
+
timeout: 600000,
|
|
121
|
+
headers: {
|
|
122
|
+
...(this.proxy === PROXY.DYNAMIC && { "Zyte-Geolocation": "IT" }),
|
|
123
|
+
"User-Agent": this.proxy !== PROXY.NONE ? this.getRandomUserAgent() : undefined,
|
|
124
|
+
}
|
|
125
|
+
}, config));
|
|
126
|
+
},
|
|
127
|
+
resolve,
|
|
128
|
+
reject
|
|
129
|
+
});
|
|
130
|
+
this.schedule();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
getRandomUserAgent() {
|
|
134
|
+
const browsers = [
|
|
135
|
+
{ name: "Chrome", version: 109 },
|
|
136
|
+
{ name: "Firefox", version: 115 },
|
|
137
|
+
{ name: "Edge", version: 116 }
|
|
138
|
+
];
|
|
139
|
+
return RandomUserAgent.getRandom(function (ua) {
|
|
140
|
+
return browsers.some(b => b.name === ua.browserName && b.version <= parseInt(ua.browserVersion));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
enqueue(call) {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
this.queue.push({
|
|
146
|
+
call,
|
|
147
|
+
resolve,
|
|
148
|
+
reject,
|
|
149
|
+
});
|
|
150
|
+
this.schedule();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
schedule() {
|
|
154
|
+
while (this.activeCount < this.concurrency && this.queue.length > 0) {
|
|
155
|
+
const item = this.queue.shift();
|
|
156
|
+
if (!item)
|
|
157
|
+
return;
|
|
158
|
+
const { call, resolve, reject } = item;
|
|
159
|
+
this.activeCount++;
|
|
160
|
+
const time = performance.now();
|
|
161
|
+
this.limiter(() => {
|
|
162
|
+
call()
|
|
163
|
+
.then((result) => {
|
|
164
|
+
resolve(result);
|
|
165
|
+
})
|
|
166
|
+
.catch((error) => {
|
|
167
|
+
reject(error);
|
|
168
|
+
})
|
|
169
|
+
.finally(() => {
|
|
170
|
+
this.activeCount--;
|
|
171
|
+
this.clock--;
|
|
172
|
+
if (this.clock < 0) {
|
|
173
|
+
this.clock = this.CLOCK_NUMBER;
|
|
174
|
+
if (this.mean > this.targetMean && this.mean > 10)
|
|
175
|
+
this.concurrency = Math.max(this.concurrency - Math.floor(this.concurrency / 10), this.minConcurrency);
|
|
176
|
+
if (this.mean < this.targetMean || this.mean < 2)
|
|
177
|
+
this.concurrency = Math.min(this.concurrency + Math.ceil(this.concurrency / 20), this.maxConcurrency);
|
|
178
|
+
this.targetMean = this.mean;
|
|
179
|
+
}
|
|
180
|
+
const t = (performance.now() - time) / 1000;
|
|
181
|
+
this.mean = this.mean > 0 ? this.mean + (t - this.mean) / this.WEIGHT : t;
|
|
182
|
+
this.updatePrint();
|
|
183
|
+
//console.debug(this.name, t, this.mean, this.targetMean, `${this.activeCount}/${this.queue.length}`, this.concurrency, `${(60 / this.mean) * this.concurrency} avg/m`);
|
|
184
|
+
this.schedule();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
print() {
|
|
190
|
+
return this._print;
|
|
191
|
+
}
|
|
192
|
+
updatePrint() {
|
|
193
|
+
const textStart = `${Console.color(this.name.padEnd(16), COLORS.YELLOW)} [POOL][`;
|
|
194
|
+
// eslint-disable-next-line max-len
|
|
195
|
+
const textEnd = `][${this.activeCount + this.queue?.length}/${this.concurrency}][${this.mean.toFixed(2)}s, ${((60 / this.mean) * this.activeCount).toFixed(2)} avg/m]`;
|
|
196
|
+
let progress = "";
|
|
197
|
+
const max = process.stdout.columns - Math.max(textStart.length + textEnd.length, process.stdout.columns * 0.5);
|
|
198
|
+
const percent = this.activeCount / this.concurrency * max;
|
|
199
|
+
for (let j = 0; j < max; j++) {
|
|
200
|
+
const char = j < percent ? "=" : " ";
|
|
201
|
+
progress += Console.color(char, j < (max * 0.8) ? COLORS.GREEN : j < (max * 0.9) ? COLORS.YELLOW : COLORS.RED);
|
|
202
|
+
}
|
|
203
|
+
this._print = textStart + progress + textEnd;
|
|
204
|
+
}
|
|
205
|
+
async finish() {
|
|
206
|
+
while (this.activeCount) {
|
|
207
|
+
await this.finish();
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
async fetchWithRetry(url, options, retries = 3, sleepTime = 1000) {
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetch(url, options);
|
|
214
|
+
if (!response.ok)
|
|
215
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
216
|
+
return response;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (retries === 0)
|
|
220
|
+
throw new Error(`Failed to fetch ${url} after several retries`);
|
|
221
|
+
Console.log(`Retrying fetch for ${url}. Attempts remaining: ${retries - 1}`);
|
|
222
|
+
await sleep(sleepTime);
|
|
223
|
+
return this.fetchWithRetry(url, options, retries - 1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare enum COLORS {
|
|
2
|
+
BLACK = "\u001B[30m",
|
|
3
|
+
RED = "\u001B[31m",
|
|
4
|
+
GREEN = "\u001B[32m",
|
|
5
|
+
YELLOW = "\u001B[33m",
|
|
6
|
+
BLUE = "\u001B[34m",
|
|
7
|
+
MAGENTA = "\u001B[35m",
|
|
8
|
+
CYAN = "\u001B[36m",
|
|
9
|
+
WHITE = "\u001B[0m"
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @author Andrea Moraglia
|
|
13
|
+
*/
|
|
14
|
+
export default class Console {
|
|
15
|
+
private static prefix;
|
|
16
|
+
private static postfix;
|
|
17
|
+
private static loader;
|
|
18
|
+
private static first;
|
|
19
|
+
private static summaryLines;
|
|
20
|
+
static setPrefix(prefix: string, color?: COLORS): void;
|
|
21
|
+
static setPostfix(postfix: string, color?: COLORS): void;
|
|
22
|
+
static appendSummaryLine(fn: () => string, color?: COLORS): void;
|
|
23
|
+
static clearSummaryLine(): void;
|
|
24
|
+
private static processSummaryLines;
|
|
25
|
+
static clearPostfix(): void;
|
|
26
|
+
static clearPrefix(): void;
|
|
27
|
+
static color(str: string, color: COLORS): string;
|
|
28
|
+
static log(str?: string, spinner?: boolean, file?: string): void;
|
|
29
|
+
static error(str?: string, spinner?: boolean, file?: string): void;
|
|
30
|
+
static warn(str?: string, spinner?: boolean, file?: string): void;
|
|
31
|
+
static success(str?: string, spinner?: boolean, file?: string): void;
|
|
32
|
+
private static getPrefix;
|
|
33
|
+
private static getPostfix;
|
|
34
|
+
private static clearLines;
|
|
35
|
+
static write(str: string, color: COLORS, spinner: boolean, file?: string): void;
|
|
36
|
+
}
|
package/dist/Console.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import readline from "readline";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
const log_stdout = process.stdout;
|
|
4
|
+
export var COLORS;
|
|
5
|
+
(function (COLORS) {
|
|
6
|
+
COLORS["BLACK"] = "\u001B[30m";
|
|
7
|
+
COLORS["RED"] = "\u001B[31m";
|
|
8
|
+
COLORS["GREEN"] = "\u001B[32m";
|
|
9
|
+
COLORS["YELLOW"] = "\u001B[33m";
|
|
10
|
+
COLORS["BLUE"] = "\u001B[34m";
|
|
11
|
+
COLORS["MAGENTA"] = "\u001B[35m";
|
|
12
|
+
COLORS["CYAN"] = "\u001B[36m";
|
|
13
|
+
COLORS["WHITE"] = "\u001B[0m";
|
|
14
|
+
})(COLORS || (COLORS = {}));
|
|
15
|
+
/**
|
|
16
|
+
* @author Andrea Moraglia
|
|
17
|
+
*/
|
|
18
|
+
export default class Console {
|
|
19
|
+
static prefix = null;
|
|
20
|
+
static postfix = null;
|
|
21
|
+
static loader;
|
|
22
|
+
static first = true;
|
|
23
|
+
static summaryLines = [];
|
|
24
|
+
static setPrefix(prefix, color = COLORS.WHITE) {
|
|
25
|
+
this.prefix = this.color(prefix, color);
|
|
26
|
+
}
|
|
27
|
+
static setPostfix(postfix, color = COLORS.WHITE) {
|
|
28
|
+
this.postfix = this.color(postfix, color);
|
|
29
|
+
}
|
|
30
|
+
static appendSummaryLine(fn, color = COLORS.WHITE) {
|
|
31
|
+
if (process.env.NODE_ENV === "production")
|
|
32
|
+
return;
|
|
33
|
+
this.summaryLines.push({ fn, color });
|
|
34
|
+
}
|
|
35
|
+
static clearSummaryLine() {
|
|
36
|
+
this.summaryLines = [];
|
|
37
|
+
}
|
|
38
|
+
static processSummaryLines() {
|
|
39
|
+
return this.summaryLines.map(l => this.color(l.fn(), l.color)).join("\n");
|
|
40
|
+
}
|
|
41
|
+
static clearPostfix() {
|
|
42
|
+
this.postfix = null;
|
|
43
|
+
}
|
|
44
|
+
static clearPrefix() {
|
|
45
|
+
this.prefix = null;
|
|
46
|
+
}
|
|
47
|
+
static color(str, color) {
|
|
48
|
+
return `${color}${str}${COLORS.WHITE}`;
|
|
49
|
+
}
|
|
50
|
+
static log(str = "", spinner = false, file) {
|
|
51
|
+
this.write(str, COLORS.WHITE, spinner, file);
|
|
52
|
+
}
|
|
53
|
+
static error(str = "", spinner = false, file) {
|
|
54
|
+
this.write(str, COLORS.RED, spinner, file);
|
|
55
|
+
}
|
|
56
|
+
static warn(str = "", spinner = false, file) {
|
|
57
|
+
this.write(str, COLORS.YELLOW, spinner, file);
|
|
58
|
+
}
|
|
59
|
+
static success(str = "", spinner = false, file) {
|
|
60
|
+
this.write(str, COLORS.GREEN, spinner, file);
|
|
61
|
+
}
|
|
62
|
+
static getPrefix() {
|
|
63
|
+
return this.prefix ? this.prefix + " " : "";
|
|
64
|
+
}
|
|
65
|
+
static getPostfix() {
|
|
66
|
+
return this.postfix ? this.postfix + " " : "";
|
|
67
|
+
}
|
|
68
|
+
static clearLines(number) {
|
|
69
|
+
for (let i = 0; i < number; i++) {
|
|
70
|
+
const y = i === 0 ? null : -1;
|
|
71
|
+
readline.moveCursor(log_stdout, 0, y);
|
|
72
|
+
readline.clearLine(log_stdout, 0);
|
|
73
|
+
}
|
|
74
|
+
readline.cursorTo(log_stdout, 0);
|
|
75
|
+
}
|
|
76
|
+
static write(str = "", color, spinner, file = "") {
|
|
77
|
+
if (this.loader) {
|
|
78
|
+
clearInterval(this.loader);
|
|
79
|
+
this.loader = null;
|
|
80
|
+
}
|
|
81
|
+
if (spinner) {
|
|
82
|
+
const P = ["\\", "|", "/", "-"];
|
|
83
|
+
let x = 0;
|
|
84
|
+
readline.clearLine(log_stdout, 0);
|
|
85
|
+
log_stdout.write(`\r${this.getPrefix()}${P[x++]} ${this.color(str, color)}${this.getPostfix()}`);
|
|
86
|
+
if (this.summaryLines.length) {
|
|
87
|
+
readline.clearScreenDown(log_stdout);
|
|
88
|
+
log_stdout.write(`\n${this.processSummaryLines()}`);
|
|
89
|
+
readline.moveCursor(log_stdout, 0, -this.summaryLines.length);
|
|
90
|
+
}
|
|
91
|
+
x &= 3;
|
|
92
|
+
this.loader = setInterval(() => {
|
|
93
|
+
log_stdout.write(`\r${this.getPrefix()}${P[x++]} ${this.color(str, color)}${this.getPostfix()}`);
|
|
94
|
+
x &= 3;
|
|
95
|
+
}, 250);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
//this.clearLines(this.summaryLines.length + (this.loader ? 1 : 0));
|
|
99
|
+
//readline.moveCursor(log_stdout, 0, this.summaryLines.length);
|
|
100
|
+
//readline.cursorTo(log_stdout, 0);
|
|
101
|
+
readline.clearLine(log_stdout, 0);
|
|
102
|
+
log_stdout.write(`\r${this.getPrefix()}${this.color(str, color)}${this.getPostfix()}\n`);
|
|
103
|
+
if (file)
|
|
104
|
+
fs.appendFileSync(file, `\r${this.getPrefix()}${this.color(str, color)}${this.getPostfix()}\n`);
|
|
105
|
+
if (this.summaryLines.length && !this.first) {
|
|
106
|
+
readline.clearScreenDown(log_stdout);
|
|
107
|
+
log_stdout.write(`\r${this.processSummaryLines()}`);
|
|
108
|
+
readline.moveCursor(log_stdout, 0, -(this.summaryLines.length - 1));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.first = false;
|
|
112
|
+
}
|
|
113
|
+
}
|