melperjs 16.1.0 → 17.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/node.mjs ADDED
@@ -0,0 +1,142 @@
1
+ import { CONSTANTS, checkEmpty } from "./general.mjs";
2
+ import fs, { promises } from "fs";
3
+ import path from "path";
4
+ import crypto from "crypto";
5
+ import { networkInterfaces } from "os";
6
+ import { exec, execFileSync } from "child_process";
7
+ import { promisify } from "util";
8
+ import bcrypt from "bcryptjs";
9
+ //#region src/node.js
10
+ const execAsync = promisify(exec);
11
+ function secureRandomBoolean() {
12
+ return secureRandomInteger(2) === 1;
13
+ }
14
+ function secureRandomString(length, useNumbers = true, useUppercase = false) {
15
+ let characters = CONSTANTS.LOWER_CASE;
16
+ if (useUppercase) characters += CONSTANTS.UPPER_CASE;
17
+ if (useNumbers) characters += CONSTANTS.NUMBERS;
18
+ let result = "";
19
+ for (let i = 0; i < length; i++) result += characters[secureRandomInteger(0, characters.length)];
20
+ return result;
21
+ }
22
+ function secureRandomHex(length) {
23
+ return crypto.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
24
+ }
25
+ function secureRandomInteger(min, max = void 0) {
26
+ return crypto.randomInt(min, max);
27
+ }
28
+ function secureRandomUuid(useDashes = true) {
29
+ const uuid = crypto.randomUUID();
30
+ return useDashes ? uuid : uuid.replaceAll("-", "");
31
+ }
32
+ function secureRandomWeighted(object) {
33
+ if (checkEmpty(object)) return void 0;
34
+ const elements = Object.keys(object);
35
+ const weights = Object.values(object);
36
+ const randomNum = secureRandomInteger(0, weights.reduce((sum, weight) => sum + weight, 0));
37
+ let weightSum = 0;
38
+ for (let i = 0; i < elements.length; i++) {
39
+ weightSum += weights[i];
40
+ if (randomNum < weightSum) return elements[i];
41
+ }
42
+ }
43
+ function secureRandomElement(object) {
44
+ if (checkEmpty(object)) return void 0;
45
+ const values = Array.isArray(object) ? object : Object.values(object);
46
+ if (values.length === 0) return void 0;
47
+ return values[secureRandomInteger(0, values.length)];
48
+ }
49
+ function uuidFromSeed(seed, useDashes = true) {
50
+ const hash = crypto.createHash("md5").update(seed).digest();
51
+ hash[6] = hash[6] & 15 | 48;
52
+ hash[8] = hash[8] & 63 | 128;
53
+ const hex = hash.toString("hex");
54
+ if (!useDashes) return hex;
55
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
56
+ }
57
+ function hash(algorithm, data) {
58
+ return crypto.createHash(algorithm).update(data).digest("hex");
59
+ }
60
+ function md5(data) {
61
+ return hash("md5", data);
62
+ }
63
+ function sha256(data) {
64
+ return hash("sha256", data);
65
+ }
66
+ function base64Encode(data) {
67
+ return Buffer.from(data).toString("base64");
68
+ }
69
+ function base64Decode(data, encoding = "utf8") {
70
+ return Buffer.from(data, "base64").toString(encoding);
71
+ }
72
+ function bcryptHash(plainText, { key = "", strength = 12, preHash = true } = {}) {
73
+ let input = plainText + key;
74
+ if (preHash) input = sha256(input);
75
+ return bcrypt.hashSync(input, strength);
76
+ }
77
+ function bcryptVerify(plainText, hash, { key = "", preHash = true } = {}) {
78
+ let input = plainText + key;
79
+ if (preHash) input = sha256(input);
80
+ return bcrypt.compareSync(input, hash);
81
+ }
82
+ async function readJsonFile(filePath, defaultValue = {}) {
83
+ try {
84
+ const data = await promises.readFile(filePath, "utf8");
85
+ return JSON.parse(data);
86
+ } catch {
87
+ return defaultValue;
88
+ }
89
+ }
90
+ function readJsonFileSync(filePath, defaultValue = {}) {
91
+ try {
92
+ const data = fs.readFileSync(filePath, "utf8");
93
+ return JSON.parse(data);
94
+ } catch {
95
+ return defaultValue;
96
+ }
97
+ }
98
+ function writeJsonFile(filePath, data) {
99
+ const jsonData = JSON.stringify(data);
100
+ return promises.writeFile(filePath, jsonData, "utf8");
101
+ }
102
+ function writeJsonFileSync(filePath, data) {
103
+ const jsonData = JSON.stringify(data);
104
+ return fs.writeFileSync(filePath, jsonData, "utf8");
105
+ }
106
+ async function clearDirectory(directoryPath, keepDir = true) {
107
+ await promises.rm(directoryPath, {
108
+ recursive: true,
109
+ force: true
110
+ });
111
+ if (keepDir) await promises.mkdir(directoryPath, { recursive: true });
112
+ }
113
+ function createNumberedDirs(mainDirectory, start = 0, end = 9) {
114
+ fs.mkdirSync(mainDirectory, { recursive: true });
115
+ for (let i = start; i <= end; i++) fs.mkdirSync(path.join(mainDirectory, `${i}`), { recursive: true });
116
+ }
117
+ async function executeCommand(command) {
118
+ const { stdout } = await execAsync(command);
119
+ return stdout.trim();
120
+ }
121
+ function hostIp() {
122
+ for (const list of Object.values(networkInterfaces())) for (const alias of list) if (alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.address.startsWith("192.168.") && !alias.internal) return alias.address;
123
+ return "127.0.0.1";
124
+ }
125
+ function gitVersion() {
126
+ try {
127
+ const raw = execFileSync("git", [
128
+ "show",
129
+ "-s",
130
+ "--format=%ct",
131
+ "HEAD"
132
+ ], { encoding: "utf8" }).trim();
133
+ const timestamp = parseInt(raw, 10);
134
+ if (isNaN(timestamp)) return "1.0";
135
+ const iso = (/* @__PURE__ */ new Date(timestamp * 1e3)).toISOString();
136
+ return `${iso.slice(2, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}.${iso.slice(11, 13)}${iso.slice(14, 16)}`;
137
+ } catch {
138
+ return "1.0";
139
+ }
140
+ }
141
+ //#endregion
142
+ export { base64Decode, base64Encode, bcryptHash, bcryptVerify, clearDirectory, createNumberedDirs, executeCommand, gitVersion, hash, hostIp, md5, readJsonFile, readJsonFileSync, secureRandomBoolean, secureRandomElement, secureRandomHex, secureRandomInteger, secureRandomString, secureRandomUuid, secureRandomWeighted, sha256, uuidFromSeed, writeJsonFile, writeJsonFileSync };
@@ -1,10 +1,9 @@
1
1
  # Documentation
2
2
 
3
- `melperjs` is a small utility library split into two entry points:
3
+ `melperjs` is a lightweight utility library split into two entry points:
4
4
 
5
- - **Core module** (`melperjs`) — browser-safe helpers. No Node-only APIs, no `crypto`, no `fs`. Safe to import from any
6
- JavaScript environment.
7
- - **Node module** (`melperjs/node`) — Node.js-specific helpers built on `crypto`, `fs`, `child_process`, and `os`.
5
+ - **Core module** (`melperjs`) — browser-safe helpers. Safe to import from any JavaScript environment.
6
+ - **Node module** (`melperjs/node`) — Node.js-specific helpers built on `crypto`, `fs`, `child_process`, `os`, and more.
8
7
  Importing this in a browser will fail.
9
8
 
10
9
  ## Usage
@@ -23,5 +22,5 @@ Both forms are supported via dual ESM/CJS builds.
23
22
 
24
23
  ## Sections
25
24
 
26
- - [General Functions](index.md) — browser-safe helpers (`melperjs`)
25
+ - [General Functions](general.md) — browser-safe helpers (`melperjs`)
27
26
  - [Node.js Functions](./node.md) — Node-only helpers (`melperjs/node`)
@@ -87,7 +87,7 @@ Calls `task` up to `maxAttempts` times, returning the first successful result. O
87
87
  - **Parameters:**
88
88
  - `task` (Function): Async function to attempt.
89
89
  - `maxAttempts` (Number): Total attempt count (1 = no retries). Default `1`.
90
- - `onError` (Function): Called as `(attempt, error)` after each failed attempt.
90
+ - `onError` (Function): Called as `(error, attempt)` after each failed attempt.
91
91
  - `options.delayMs` (Number): Base delay between retries in ms. `0` disables delay.
92
92
  - `options.backoffFactor` (Number): Multiplier applied per attempt. `1` keeps delay constant; `2` doubles each retry.
93
93
  - **Returns:** The first non-throwing result of `task`.
@@ -288,17 +288,17 @@ Depth-first search through a nested object for the first node that owns `key`. I
288
288
  - `pair` (Any): Optional value constraint. `null` means "match any value".
289
289
  - **Returns:** The matching node, or `null` if not found.
290
290
 
291
- ### waitForProperty(object, property, timeout = 5000, interval = 100)
291
+ ### waitForProperty(object, property, timeoutMs, interval = 100)
292
292
 
293
293
  Polls `object` until it owns `property`, then resolves with the property's value. Rejects after `timeout` milliseconds.
294
294
 
295
295
  - **Parameters:**
296
296
  - `object` (Object): Object to watch.
297
297
  - `property` (String): Property name to wait for.
298
- - `timeout` (Number): Maximum wait time in milliseconds.
298
+ - `timeoutMs` (Number): Maximum wait time in milliseconds.
299
299
  - `interval` (Number): Poll interval in milliseconds.
300
300
  - **Returns:** `Promise` resolving to the property's value.
301
- - **Throws:** When the property does not appear within `timeout`.
301
+ - **Throws:** When the property does not appear within `timeoutMs`.
302
302
 
303
303
  ### shuffleObject(object)
304
304
 
@@ -361,3 +361,51 @@ Extracts a short error description from an HTTP error-like object. Prefers `erro
361
361
  - `error` (Error): Error from an HTTP client.
362
362
  - `limit` (Number): Maximum length of the returned string.
363
363
  - **Returns:** Trimmed error description.
364
+
365
+ ## Proxy Helpers
366
+
367
+ ### normalizeProxy(proxy, protocol = "http")
368
+
369
+ Normalizes a wide range of proxy formats into a canonical `protocol://[user:pass@]host:port` URL. Supports:
370
+
371
+ - `host:port`
372
+ - `host:port:user:pass` (auth appended)
373
+ - `user:pass:host:port` (auth prepended; auto-detected via numeric port pattern)
374
+ - `host:portStart:portEnd:user:pass` (random port in range, inclusive)
375
+ - `user:pass:host:portStart:portEnd` (auth prepended, random port in range)
376
+ - `user:pass@host:port`
377
+ - `user:pass@host:portStart:portEnd` (random port in range)
378
+ - Any of the above prefixed with `scheme://` (`http`, `https`, `socks5`, `socks5h`, …)
379
+
380
+ Returns `null` for empty or non-string input. Does not crash on unparseable input — returns it as-is wrapped with `protocol://`.
381
+
382
+ - **Parameters:**
383
+ - `proxy` (String): Source proxy string.
384
+ - `protocol` (String): Default protocol when none is present in the input.
385
+ - **Returns:** Canonical proxy URL, or `null` when input is empty/missing.
386
+
387
+ ### parseProxy(proxy, protocol = "http")
388
+
389
+ Normalizes `proxy` via `normalizeProxy`, then decomposes it into structured fields. Returns `null` when normalization fails (empty input).
390
+
391
+ - **Parameters:**
392
+ - `proxy` (String): Source proxy string.
393
+ - `protocol` (String): Default protocol when none is present in the input.
394
+ - **Returns:** `{protocol, host, port, auth?: {username, password}}` or `null`.
395
+
396
+ ### proxyValue(rawProxy, replacements = {})
397
+
398
+ Picks a random proxy from a newline-separated list, normalizes it, and applies placeholder substitution.
399
+
400
+ `{SESSION}` is a built-in placeholder:
401
+
402
+ - If `SESSION` is not provided, it is autofilled with a non-secure `randomHex(8)`.
403
+ - If `SESSION` is a string, it is treated as a seed and replaced via `seedHex(seed, 8)` (deterministic).
404
+ - If `SESSION` is a function, the function is called per invocation.
405
+
406
+ Any other key in `replacements` is also substituted (`{KEY}` → value). For non-SESSION entries: functions are called, strings are used literally.
407
+
408
+ - **Parameters:**
409
+ - `rawProxy` (String): Newline-separated proxy list.
410
+ - `replacements` (Object): Placeholder values keyed by placeholder name.
411
+ - **Returns:** Final proxy URL string, or `null` if the list is empty.
package/docs/node.md CHANGED
@@ -7,7 +7,7 @@ and will not run in a browser.
7
7
 
8
8
  All `secureRandom*` helpers use Node's `crypto` module (`crypto.randomInt`, `crypto.randomBytes`, `crypto.randomUUID`).
9
9
  Use these for session tokens, API keys, nonces, and anything security-sensitive. For non-secure / faster equivalents,
10
- see the `random*` family in [General Functions](index.md).
10
+ see the `random*` family in [General Functions](general.md).
11
11
 
12
12
  ### secureRandomBoolean()
13
13
 
@@ -145,57 +145,6 @@ Verifies that `plainText` (with the same `key`) matches a previously generated b
145
145
  - `options.preHash` (Boolean): Must match the `preHash` used for `bcryptHash`. Default `true`.
146
146
  - **Returns:** `Boolean` indicating match.
147
147
 
148
- ## Proxy Helpers
149
-
150
- ### normalizeProxy(proxy, protocol = "http")
151
-
152
- Normalizes a wide range of proxy formats into a canonical `protocol://[user:pass@]host:port` URL. Supports:
153
-
154
- - `host:port`
155
- - `host:port:user:pass` (auth appended)
156
- - `user:pass:host:port` (auth prepended; auto-detected via numeric port pattern)
157
- - `host:portStart:portEnd:user:pass` (random port in range, inclusive)
158
- - `user:pass:host:portStart:portEnd` (auth prepended, random port in range)
159
- - `user:pass@host:port`
160
- - `user:pass@host:portStart:portEnd` (random port in range)
161
- - Any of the above prefixed with `scheme://` (`http`, `https`, `socks5`, `socks5h`, …)
162
-
163
- Returns `null` for empty or non-string input. Does not crash on unparseable input — returns it as-is wrapped with
164
- `protocol://`.
165
-
166
- - **Parameters:**
167
- - `proxy` (String): Source proxy string.
168
- - `protocol` (String): Default protocol when none is present in the input.
169
- - **Returns:** Canonical proxy URL, or `null` when input is empty/missing.
170
-
171
- ### parseProxy(proxy, protocol = "http")
172
-
173
- Normalizes `proxy` via `normalizeProxy`, then decomposes it into structured fields. Returns `null` when normalization
174
- fails (empty input).
175
-
176
- - **Parameters:**
177
- - `proxy` (String): Source proxy string.
178
- - `protocol` (String): Default protocol when none is present in the input.
179
- - **Returns:** `{protocol, host, port, auth?: {username, password}}` or `null`.
180
-
181
- ### proxyValue(rawProxy, replacements = {})
182
-
183
- Picks a random proxy from a newline-separated list, normalizes it, and applies placeholder substitution.
184
-
185
- `{SESSION}` is a built-in placeholder:
186
-
187
- - If `SESSION` is not provided, it is autofilled with a non-secure `randomHex(8)`.
188
- - If `SESSION` is a string, it is treated as a seed and replaced via `seedHex(seed, 8)` (deterministic).
189
- - If `SESSION` is a function, the function is called per invocation.
190
-
191
- Any other key in `replacements` is also substituted (`{KEY}` → value). For non-SESSION entries: functions are called,
192
- strings are used literally.
193
-
194
- - **Parameters:**
195
- - `rawProxy` (String): Newline-separated proxy list.
196
- - `replacements` (Object): Placeholder values keyed by placeholder name.
197
- - **Returns:** Final proxy URL string, or `null` if the list is empty.
198
-
199
148
  ## File I/O (JSON)
200
149
 
201
150
  ### readJsonFile(filePath, defaultValue = {})
package/package.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "melperjs",
3
+ "version": "17.1.0",
3
4
  "description": "A compact JavaScript utility library for strings, objects, tokens, hashing, cookies, proxies, file I/O, shell, and more.",
4
5
  "keywords": [
5
6
  "melperjs",
@@ -24,37 +25,40 @@
24
25
  "file-system",
25
26
  "shell"
26
27
  ],
28
+ "homepage": "https://github.com/mahelbir/melperjs#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/mahelbir/melperjs/issues"
31
+ },
27
32
  "repository": {
28
33
  "type": "git",
29
34
  "url": "git+https://github.com/mahelbir/melperjs.git"
30
35
  },
31
- "author": "Mahmuthan Elbir",
32
36
  "license": "MIT",
37
+ "author": "Mahmuthan Elbir",
33
38
  "type": "module",
34
- "files": [
35
- "lib/**/*",
36
- "docs/**/*"
37
- ],
38
- "private": false,
39
- "publishConfig": {
40
- "access": "public"
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
41
  },
42
+ "sideEffects": false,
42
43
  "exports": {
43
44
  ".": {
44
- "import": "./lib/esm/index.mjs",
45
- "require": "./lib/cjs/index.cjs"
45
+ "import": "./dist/general.mjs",
46
+ "require": "./dist/general.cjs"
46
47
  },
47
48
  "./node": {
48
- "import": "./lib/esm/node.mjs",
49
- "require": "./lib/cjs/node.cjs"
49
+ "import": "./dist/node.mjs",
50
+ "require": "./dist/node.cjs"
50
51
  }
51
52
  },
53
+ "files": [
54
+ "dist/**/*",
55
+ "docs/**/*"
56
+ ],
52
57
  "scripts": {
53
- "build:esm": "npx cross-env BABEL_ENV=esm babel ./src --out-dir ./lib/esm --out-file-extension .mjs",
54
- "build:cjs": "npx cross-env BABEL_ENV=cjs babel ./src --out-dir ./lib/cjs --out-file-extension .cjs",
55
- "build": "npm run build:esm && npm run build:cjs",
56
- "test": "node --test 'tests/**/*.test.js'",
57
- "prepublishOnly": "npm run build && npm test"
58
+ "build": "tsdown",
59
+ "test": "node --test",
60
+ "prepublishOnly": "npm run build && npm test",
61
+ "relock": "rm -rf node_modules package-lock.json && NODE_ENV= npm install --include=dev --include=optional"
58
62
  },
59
63
  "dependencies": {
60
64
  "bcryptjs": "^3.0.3",
@@ -63,12 +67,9 @@
63
67
  "xss": "^1.0.15"
64
68
  },
65
69
  "devDependencies": {
66
- "@babel/cli": "^7.28.6",
67
- "@babel/core": "^7.29.0",
68
- "@babel/preset-env": "^7.29.5",
69
- "babel-plugin-replace-import-extension": "^1.1.5",
70
- "babel-plugin-transform-import-meta": "^2.3.3",
71
- "cross-env": "^10.1.0"
70
+ "tsdown": "^0.22.0"
72
71
  },
73
- "version": "16.1.0"
72
+ "publishConfig": {
73
+ "access": "public"
74
+ }
74
75
  }