@companix/utils-nodejs 0.0.1

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.
@@ -0,0 +1,21 @@
1
+ interface CacheOptions<T> {
2
+ cacheDir?: string;
3
+ source: string;
4
+ rotation?: number;
5
+ fetchData: () => Promise<T>;
6
+ }
7
+ export interface CacheFile<T> {
8
+ timestamp: number;
9
+ data: T;
10
+ }
11
+ export declare class LocalCache<T> {
12
+ private path;
13
+ private rotation;
14
+ private fetchData;
15
+ private getData;
16
+ constructor({ source, cacheDir, rotation, fetchData }: CacheOptions<T>);
17
+ get(): Promise<CacheFile<T>>;
18
+ getCacheValue(): CacheFile<T> | null;
19
+ reset(): Promise<CacheFile<T>>;
20
+ }
21
+ export {};
package/dist/cache.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalCache = void 0;
4
+ const fs_1 = require("fs");
5
+ const fs_2 = require("./fs");
6
+ const common_request_1 = require("./common-request");
7
+ class LocalCache {
8
+ path;
9
+ rotation;
10
+ fetchData;
11
+ getData = (0, common_request_1.commonQuery)(() => this.fetchData());
12
+ constructor({ source, cacheDir = 'caches', rotation, fetchData }) {
13
+ this.path = `${cacheDir}/${source}`;
14
+ this.rotation = rotation ?? null;
15
+ this.fetchData = fetchData;
16
+ }
17
+ async get() {
18
+ const value = this.getCacheValue();
19
+ if (value) {
20
+ return value;
21
+ }
22
+ return this.reset();
23
+ }
24
+ getCacheValue() {
25
+ if ((0, fs_1.existsSync)(this.path)) {
26
+ const cache = (0, fs_2.getFileData)(this.path);
27
+ if (cache) {
28
+ if (this.rotation && Date.now() > cache.timestamp + this.rotation) {
29
+ return null;
30
+ }
31
+ return cache;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ async reset() {
37
+ return this.getData().then((data) => {
38
+ const cache = {
39
+ timestamp: Date.now(),
40
+ data
41
+ };
42
+ (0, fs_1.writeFileSync)(this.path, JSON.stringify(cache));
43
+ return cache;
44
+ });
45
+ }
46
+ }
47
+ exports.LocalCache = LocalCache;
@@ -0,0 +1,9 @@
1
+ export declare const colors: {
2
+ bold: (text: string) => string;
3
+ green: (text: string) => string;
4
+ yellow: (text: string) => string;
5
+ red: (text: string) => string;
6
+ magentaBright: (text: string) => string;
7
+ cyanBright: (text: string) => string;
8
+ yellowBright: (text: string) => string;
9
+ };
package/dist/colors.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.colors = void 0;
4
+ exports.colors = {
5
+ bold: (text) => `\x1B[1m${text}\x1B[0m`,
6
+ green: (text) => `\x1B[32m${text}\x1B[39m`,
7
+ yellow: (text) => `\x1B[33m${text}\x1B[39m`,
8
+ red: (text) => `\x1B[31m${text}\x1B[39m`,
9
+ magentaBright: (text) => `\x1B[95m${text}\x1B[39m`,
10
+ cyanBright: (text) => `\x1B[96m${text}\x1B[39m`,
11
+ yellowBright: (text) => `\x1B[38;5;3m${text}\x1B[39m`
12
+ };
@@ -0,0 +1 @@
1
+ export declare const commonQuery: <T>(request: () => Promise<T>) => () => Promise<T>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.commonQuery = void 0;
4
+ const utils_js_1 = require("@companix/utils-js");
5
+ const commonQuery = (request) => {
6
+ const state = { isLoading: false };
7
+ const subscribers = new utils_js_1.EventBroadcaster();
8
+ return () => {
9
+ if (!state.isLoading) {
10
+ state.isLoading = true;
11
+ return request().then((value) => {
12
+ state.isLoading = false;
13
+ subscribers.emit(value);
14
+ return value;
15
+ });
16
+ }
17
+ return new Promise((resolve) => {
18
+ const unsubscribe = subscribers.subscribe((value) => {
19
+ unsubscribe();
20
+ resolve(value);
21
+ });
22
+ });
23
+ };
24
+ };
25
+ exports.commonQuery = commonQuery;
@@ -0,0 +1,4 @@
1
+ export declare const createContext: <T>() => {
2
+ provide: (value: T, fn: () => void) => void;
3
+ use: () => T;
4
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createContext = void 0;
4
+ const node_async_hooks_1 = require("node:async_hooks");
5
+ const createContext = () => {
6
+ const storage = new node_async_hooks_1.AsyncLocalStorage();
7
+ return {
8
+ provide: (value, fn) => {
9
+ return storage.run(value, fn);
10
+ },
11
+ use: () => {
12
+ const store = storage.getStore();
13
+ if (!store) {
14
+ throw new Error('useContext called outside of context');
15
+ }
16
+ return store;
17
+ }
18
+ };
19
+ };
20
+ exports.createContext = createContext;
@@ -0,0 +1,9 @@
1
+ export declare class FileDriver<T> {
2
+ private path;
3
+ private file;
4
+ constructor(path: string, defaultValue: T);
5
+ getData(): T;
6
+ change(mutate: (value: T) => void): void;
7
+ save(): void;
8
+ remove(): void;
9
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileDriver = void 0;
4
+ const fs_1 = require("fs");
5
+ const fs_2 = require("./fs");
6
+ class FileDriver {
7
+ path;
8
+ file;
9
+ constructor(path, defaultValue) {
10
+ this.path = path;
11
+ if (!(0, fs_1.existsSync)(path)) {
12
+ (0, fs_1.writeFileSync)(path, JSON.stringify(defaultValue));
13
+ }
14
+ this.file = (0, fs_2.getFileData)(path);
15
+ }
16
+ getData() {
17
+ return this.file;
18
+ }
19
+ change(mutate) {
20
+ mutate(this.file);
21
+ (0, fs_1.writeFileSync)(this.path, JSON.stringify(this.file));
22
+ }
23
+ save() {
24
+ (0, fs_1.writeFileSync)(this.path, JSON.stringify(this.file));
25
+ }
26
+ remove() {
27
+ (0, fs_1.rmSync)(this.path);
28
+ }
29
+ }
30
+ exports.FileDriver = FileDriver;
package/dist/fs.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare const getFilesList: (path: string) => string[];
2
+ export declare const getFileData: <T = any>(path: string) => T;
3
+ export declare const getFolderSize: (path: string) => Promise<string>;
package/dist/fs.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFolderSize = exports.getFileData = exports.getFilesList = void 0;
4
+ const fs_1 = require("fs");
5
+ const child_process_1 = require("child_process");
6
+ const getFilesList = (path) => {
7
+ return (0, fs_1.readdirSync)(path).filter((s) => {
8
+ return !s.startsWith('.');
9
+ });
10
+ };
11
+ exports.getFilesList = getFilesList;
12
+ const getFileData = (path) => {
13
+ return JSON.parse((0, fs_1.readFileSync)(path, { encoding: 'utf-8' }));
14
+ };
15
+ exports.getFileData = getFileData;
16
+ const getFolderSize = (path) => {
17
+ return new Promise((resolve, reject) => {
18
+ (0, child_process_1.exec)(`du -sk "${path}"`, (error, stdout, stderr) => {
19
+ if (error) {
20
+ return reject(`error: ${error.message}`);
21
+ }
22
+ if (stderr) {
23
+ return reject(`stderr: ${stderr}`);
24
+ }
25
+ resolve(stdout.split('\t')[0]);
26
+ });
27
+ });
28
+ };
29
+ exports.getFolderSize = getFolderSize;
@@ -0,0 +1,8 @@
1
+ export * from './logger';
2
+ export * from './perfomance';
3
+ export * from './limiter';
4
+ export * from './fs';
5
+ export * from './cache';
6
+ export * from './file-driver';
7
+ export * from './common-request';
8
+ export * from './context';
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./logger"), exports);
18
+ __exportStar(require("./perfomance"), exports);
19
+ __exportStar(require("./limiter"), exports);
20
+ __exportStar(require("./fs"), exports);
21
+ __exportStar(require("./cache"), exports);
22
+ __exportStar(require("./file-driver"), exports);
23
+ __exportStar(require("./common-request"), exports);
24
+ __exportStar(require("./context"), exports);
@@ -0,0 +1,26 @@
1
+ interface LimiterOptions {
2
+ name: string;
3
+ limit: number;
4
+ interval: number;
5
+ threshold?: number;
6
+ }
7
+ export declare class FrequencyLimiter {
8
+ private readonly logger;
9
+ private readonly limit;
10
+ private readonly interval;
11
+ private resetTimestamp;
12
+ private usedWeight;
13
+ private resolvedWeight;
14
+ private threshold;
15
+ private tasks;
16
+ constructor({ interval, threshold, name, limit }: LimiterOptions);
17
+ wrap<T>(weight: number, request: () => Promise<T>, id?: string): Promise<T>;
18
+ }
19
+ export declare class HeadersLimiter {
20
+ apiLimitTrackers: Record<string, number>;
21
+ apiLimitLastUpdated: number;
22
+ constructor(headers: Record<string, any>);
23
+ getRateLimitStates(): Record<string, number>;
24
+ updateApiLimitState(responseHeaders: Record<string, any>): void;
25
+ }
26
+ export {};
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeadersLimiter = exports.FrequencyLimiter = void 0;
4
+ const logger_1 = require("./logger");
5
+ const utils_js_1 = require("@companix/utils-js");
6
+ class FrequencyLimiter {
7
+ logger;
8
+ limit;
9
+ interval;
10
+ resetTimestamp = Date.now();
11
+ usedWeight = 0;
12
+ resolvedWeight = 0;
13
+ threshold;
14
+ tasks = new Set();
15
+ constructor({ interval, threshold, name, limit }) {
16
+ this.interval = interval;
17
+ this.limit = limit;
18
+ this.threshold = threshold ?? 1500;
19
+ this.logger = new logger_1.JSLogger(`LIMITTER${name ? `-${name.toUpperCase()}` : ''}`);
20
+ }
21
+ async wrap(weight, request, id = (0, utils_js_1.generateCode)(8)) {
22
+ this.tasks.add(id);
23
+ if (Date.now() > this.resetTimestamp) {
24
+ if (this.usedWeight >= this.limit) {
25
+ if (this.resolvedWeight !== this.usedWeight) {
26
+ this.logger.warn(`WAIT (${this.resolvedWeight}/${this.usedWeight}). [${this.tasks.size}]`, 'NOT RESOLVED');
27
+ await (0, utils_js_1.sleep)(1500);
28
+ return this.wrap(weight, request, id);
29
+ }
30
+ }
31
+ this.resetTimestamp = Date.now() + this.interval;
32
+ this.usedWeight = 0;
33
+ this.resolvedWeight = 0;
34
+ }
35
+ if (this.usedWeight + weight <= this.limit) {
36
+ this.usedWeight += weight;
37
+ return request().finally(() => {
38
+ this.tasks.delete(id);
39
+ this.resolvedWeight += weight;
40
+ });
41
+ }
42
+ const wait = this.resetTimestamp - Date.now() + this.threshold;
43
+ this.logger.warn(`WAIT: ${wait}. [${this.tasks.size}]`, 'VIOLATION');
44
+ await (0, utils_js_1.sleep)(wait);
45
+ return this.wrap(weight, request, id);
46
+ }
47
+ }
48
+ exports.FrequencyLimiter = FrequencyLimiter;
49
+ class HeadersLimiter {
50
+ apiLimitTrackers;
51
+ apiLimitLastUpdated;
52
+ constructor(headers) {
53
+ this.apiLimitTrackers = headers;
54
+ }
55
+ getRateLimitStates() {
56
+ return {
57
+ ...this.apiLimitTrackers,
58
+ lastUpdated: this.apiLimitLastUpdated
59
+ };
60
+ }
61
+ updateApiLimitState(responseHeaders) {
62
+ const delta = {};
63
+ for (const headerKey in this.apiLimitTrackers) {
64
+ const headerValue = responseHeaders[headerKey];
65
+ const value = parseInt(headerValue);
66
+ if (headerValue !== undefined && !isNaN(value)) {
67
+ this.apiLimitTrackers[headerKey] = value;
68
+ delta[headerKey] = {
69
+ updated: true,
70
+ valueParsed: value,
71
+ valueRaw: headerValue
72
+ };
73
+ }
74
+ else {
75
+ delta[headerKey] = {
76
+ updated: false,
77
+ valueParsed: value,
78
+ valueRaw: headerValue
79
+ };
80
+ }
81
+ }
82
+ this.apiLimitLastUpdated = new Date().getTime();
83
+ }
84
+ }
85
+ exports.HeadersLimiter = HeadersLimiter;
@@ -0,0 +1,21 @@
1
+ export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose' | 'fatal' | 'magneta';
2
+ interface Options {
3
+ bold?: boolean;
4
+ }
5
+ export declare class JSLogger {
6
+ private readonly service;
7
+ private write;
8
+ private pid;
9
+ constructor(service: string);
10
+ log(message: string, context: string): void;
11
+ error(message: string, context: string): void;
12
+ warn(message: string, context: string): void;
13
+ debug(message: string, context: string): void;
14
+ verbose(message: string, context: string, opts?: Options): void;
15
+ fatal(message: string, context: string): void;
16
+ console(message: string, level: LogLevel, opts?: Options): void;
17
+ private print;
18
+ private colorize;
19
+ private getColorByLogLevel;
20
+ }
21
+ export {};
package/dist/logger.js ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JSLogger = void 0;
4
+ const colors_1 = require("./colors");
5
+ class JSLogger {
6
+ service;
7
+ write;
8
+ pid = 8000;
9
+ constructor(service) {
10
+ this.service = service;
11
+ this.write = (buffer) => process.stdout.write(buffer);
12
+ this.pid = process.pid;
13
+ }
14
+ log(message, context) {
15
+ this.print(message, context, 'log');
16
+ }
17
+ error(message, context) {
18
+ this.print(message, context, 'error');
19
+ }
20
+ warn(message, context) {
21
+ this.print(message, context, 'warn');
22
+ }
23
+ debug(message, context) {
24
+ this.print(message, context, 'debug');
25
+ }
26
+ verbose(message, context, opts) {
27
+ this.print(message, context, 'verbose', opts);
28
+ }
29
+ fatal(message, context) {
30
+ this.print(message, context, 'fatal');
31
+ }
32
+ console(message, level, opts) {
33
+ this.write(this.colorize(`${this.service}: ${message}`, level, opts) + '\n');
34
+ }
35
+ print(message, context, logLevel, opts) {
36
+ const log = this.colorize(logLevel.toUpperCase().padStart(7, ' '), logLevel);
37
+ const ctx = colors_1.colors.yellowBright(`[${context}]`);
38
+ const text = this.colorize(message, logLevel, opts);
39
+ const service = this.colorize(`[${this.service}]`, logLevel);
40
+ const time = new Date().toLocaleTimeString('ru', { timeZone: 'UTC' });
41
+ this.write(`${time} - ${this.pid} ${service} - ${log} ${ctx} ${text}\n`);
42
+ }
43
+ colorize(message, logLevel, opts) {
44
+ return this.getColorByLogLevel(logLevel, opts)(message);
45
+ }
46
+ getColorByLogLevel(level, opts) {
47
+ switch (level) {
48
+ case 'debug':
49
+ return colors_1.colors.magentaBright;
50
+ case 'warn':
51
+ return colors_1.colors.yellow;
52
+ case 'error':
53
+ if (opts?.bold) {
54
+ return (text) => `\x1B[30m\x1B[41m${text}\x1B[0m`;
55
+ }
56
+ return colors_1.colors.red;
57
+ case 'verbose':
58
+ if (opts?.bold) {
59
+ return (text) => `\x1B[106m${text}\x1B[49m`;
60
+ }
61
+ return colors_1.colors.cyanBright;
62
+ case 'fatal':
63
+ return colors_1.colors.bold;
64
+ default:
65
+ return colors_1.colors.green;
66
+ }
67
+ }
68
+ }
69
+ exports.JSLogger = JSLogger;
@@ -0,0 +1,11 @@
1
+ export declare class InspectPerfomance {
2
+ private readonly name;
3
+ private moment;
4
+ private memory;
5
+ constructor(name: string);
6
+ start(): void;
7
+ done(): {
8
+ memory: string;
9
+ time: string;
10
+ };
11
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InspectPerfomance = void 0;
4
+ class InspectPerfomance {
5
+ name;
6
+ moment = 0;
7
+ memory = 0;
8
+ constructor(name) {
9
+ this.name = name;
10
+ }
11
+ start() {
12
+ this.moment = performance.now();
13
+ this.memory = process.memoryUsage().heapUsed;
14
+ }
15
+ done() {
16
+ const time = performance.now() - this.moment;
17
+ const memoryCost = process.memoryUsage().heapUsed - this.memory;
18
+ console.log(`${this.name}: Memory increase: ${memoryCost / 1024} KB`);
19
+ console.log(`${this.name}: Execution time: ${time} ms`);
20
+ return {
21
+ memory: `${memoryCost / 1024} KB`,
22
+ time: `${time} ms`
23
+ };
24
+ }
25
+ }
26
+ exports.InspectPerfomance = InspectPerfomance;
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@companix/utils-nodejs",
3
+ "version": "0.0.01",
4
+ "main": "dist/index.js",
5
+ "types": "./src/index.ts",
6
+ "author": "Pavel Victorov",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "rm -rf dist && tsc"
12
+ },
13
+ "dependencies": {
14
+ "chalk": "^5.4.1",
15
+ "@companix/utils-js": "file:../utils-js"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.13.9"
19
+ }
20
+ }