melperjs 16.0.0 → 16.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/README.md CHANGED
@@ -13,10 +13,10 @@ npm i melperjs
13
13
 
14
14
  ## Documentation
15
15
 
16
- Full API reference lives in the [docs folder](docs/index.md):
16
+ Full API reference lives in the [docs folder](docs/docs.md):
17
17
 
18
- - [General Functions](docs/general.md) — `melperjs` (browser-safe)
19
- - [Node.js Functions](docs/node.md) — `melperjs/node`
18
+ - [General Functions](docs/index.md) — browser-safe helpers (`melperjs`)
19
+ - [Node.js Functions](docs/node.md) — Node-only helpers (`melperjs/node`)
20
20
 
21
21
  ## License
22
22
 
package/docs/docs.md ADDED
@@ -0,0 +1,27 @@
1
+ # Documentation
2
+
3
+ `melperjs` is a small utility library split into two entry points:
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`.
8
+ Importing this in a browser will fail.
9
+
10
+ ## Usage
11
+
12
+ ```javascript
13
+ // ES Module
14
+ import * as helper from "melperjs";
15
+ import * as nodeHelper from "melperjs/node";
16
+
17
+ // CommonJS
18
+ const helper = require("melperjs");
19
+ const nodeHelper = require("melperjs/node");
20
+ ```
21
+
22
+ Both forms are supported via dual ESM/CJS builds.
23
+
24
+ ## Sections
25
+
26
+ - [General Functions](index.md) — browser-safe helpers (`melperjs`)
27
+ - [Node.js Functions](./node.md) — Node-only helpers (`melperjs/node`)
package/docs/index.md CHANGED
@@ -1,27 +1,363 @@
1
- # Documentation
1
+ # General Functions
2
2
 
3
- `melperjs` is a small utility library split into two entry points:
3
+ Browser-safe utilities exported from `melperjs`. No Node.js APIs are used here every function runs in the browser and in Node.js without polyfills.
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`.
8
- Importing this in a browser will fail.
5
+ ## Constants
9
6
 
10
- ## Usage
7
+ `CONSTANTS` bundles a few character sets and integer bounds that other functions in this module rely on. Useful when you need the same alphabets in your own code.
11
8
 
12
- ```javascript
13
- // ES Module
14
- import * as helper from "melperjs";
15
- import * as nodeHelper from "melperjs/node";
9
+ - `CONSTANTS.LOWER_CASE` — lowercase ASCII letters (`a-z`).
10
+ - `CONSTANTS.UPPER_CASE` — uppercase ASCII letters (`A-Z`).
11
+ - `CONSTANTS.HEXADECIMAL` hex digits (`0-9a-f`).
12
+ - `CONSTANTS.NUMBERS` decimal digits (`0-9`).
13
+ - `CONSTANTS.INT32_MIN` — `-2147483648`.
14
+ - `CONSTANTS.INT32_MAX` — `2147483647`.
16
15
 
17
- // CommonJS
18
- const helper = require("melperjs");
19
- const nodeHelper = require("melperjs/node");
20
- ```
16
+ ## Errors
21
17
 
22
- Both forms are supported via dual ESM/CJS builds.
18
+ ### Exception(message, response = {}, name = null)
23
19
 
24
- ## Sections
20
+ Builds a standard `Error` with two extra attached fields (`response`, custom `name`) so error handlers can carry HTTP-style context without subclassing. A null/empty `response` is normalized to `{}`.
25
21
 
26
- - [General Functions](./general.md) — browser-safe helpers (`melperjs`)
27
- - [Node.js Functions](./node.md) — Node-only helpers (`melperjs/node`)
22
+ - **Parameters:**
23
+ - `message` (String): Human-readable error message.
24
+ - `response` (Object): Arbitrary payload attached as `error.response`.
25
+ - `name` (String|null): Overrides `error.name`. Defaults to `"Exception"`.
26
+ - **Returns:** `Error` with `.response` and `.name` populated.
27
+
28
+ ## Time & Async
29
+
30
+ ### time()
31
+
32
+ Current Unix timestamp in seconds (integer).
33
+
34
+ - **Returns:** Seconds since the epoch.
35
+
36
+ ### sleepMs(milliseconds)
37
+
38
+ Promise that resolves after a delay, measured in milliseconds.
39
+
40
+ - **Parameters:**
41
+ - `milliseconds` (Number): Delay in ms.
42
+ - **Returns:** `Promise<void>`.
43
+
44
+ ### sleep(seconds)
45
+
46
+ Same as `sleepMs` but the delay is given in seconds.
47
+
48
+ - **Parameters:**
49
+ - `seconds` (Number): Delay in seconds.
50
+ - **Returns:** `Promise<void>`.
51
+
52
+ ### promiseTimeout(milliseconds, promise)
53
+
54
+ Races a promise against a timer. If the promise doesn't settle in time the result rejects; either way the timer is cleared.
55
+
56
+ - **Parameters:**
57
+ - `milliseconds` (Number): Maximum wait time before rejecting.
58
+ - `promise` (Promise): The work to await.
59
+ - **Returns:** Settles with the inner promise's value, or rejects with `Error("Promise timed out after Xms")`.
60
+
61
+ ### promiseSilent(promise)
62
+
63
+ Awaits a promise but swallows both the resolved value and any rejection. Handy for fire-and-forget work where you only care about the side effects.
64
+
65
+ - **Parameters:**
66
+ - `promise` (Promise): The promise to consume silently.
67
+ - **Returns:** `Promise<undefined>` that always resolves.
68
+
69
+ ### forever(delayMs, task, onError = null, onFinally = null)
70
+
71
+ Runs `task` in an infinite loop with a delay between iterations. Errors are routed to `onError`; `onFinally` runs after every iteration regardless of outcome. Any of the three callbacks can return a new positive number to update `delayMs` on the fly (useful for adaptive polling).
72
+
73
+ Errors thrown from `task` are caught and routed to `onError`; the loop keeps running. Errors thrown from `onError` propagate out and stop the loop — useful for soft shutdown by throwing on a stop signal. Errors thrown from `onFinally` are caught and ignored so that observability/cleanup failures cannot kill the worker.
74
+
75
+ - **Parameters:**
76
+ - `delayMs` (Number): Initial delay in milliseconds between iterations. Must be a positive finite number.
77
+ - `task` (Function): Async function to invoke each iteration. Exceptions are caught and forwarded to `onError`.
78
+ - `onError` (Function): Called with the caught error when `task` throws. Throwing from here aborts the loop.
79
+ - `onFinally` (Function): Called after every iteration (success or failure). Errors thrown here are swallowed.
80
+ - **Returns:** Promise that never resolves on its own; it rejects when `delayMs` validation fails or when `onError` throws.
81
+ - **Throws:** When `delayMs` is not a positive finite number.
82
+
83
+ ### retry(task, maxAttempts = 1, onError = null, {delayMs = 0, backoffFactor = 1} = {})
84
+
85
+ Calls `task` up to `maxAttempts` times, returning the first successful result. Optionally waits between retries with an exponential backoff (delay grows by `delayMs * backoffFactor^(attempt-1)`).
86
+
87
+ - **Parameters:**
88
+ - `task` (Function): Async function to attempt.
89
+ - `maxAttempts` (Number): Total attempt count (1 = no retries). Default `1`.
90
+ - `onError` (Function): Called as `(attempt, error)` after each failed attempt.
91
+ - `options.delayMs` (Number): Base delay between retries in ms. `0` disables delay.
92
+ - `options.backoffFactor` (Number): Multiplier applied per attempt. `1` keeps delay constant; `2` doubles each retry.
93
+ - **Returns:** The first non-throwing result of `task`.
94
+ - **Throws:** The last error after `maxAttempts` failures.
95
+
96
+ ## Strings
97
+
98
+ ### isValidURL(url)
99
+
100
+ Tests whether the input parses as a valid URL via the `URL` constructor.
101
+
102
+ - **Parameters:**
103
+ - `url` (String): Candidate URL.
104
+ - **Returns:** `Boolean`.
105
+
106
+ ### splitTrim(string, separator = null)
107
+
108
+ Splits a string, trims each piece, and drops empty results. Default separator is `\r?\n` (any newline).
109
+
110
+ - **Parameters:**
111
+ - `string` (String): Source text.
112
+ - `separator` (String|RegExp|null): Custom delimiter; `null` falls back to newlines.
113
+ - **Returns:** Array of non-empty trimmed strings.
114
+
115
+ ### pascalCase(string)
116
+
117
+ Converts arbitrary text to `PascalCase` (uses lodash internally).
118
+
119
+ - **Parameters:**
120
+ - `string` (String): Input text.
121
+ - **Returns:** PascalCase string.
122
+
123
+ ### titleCase(string, separator = " ")
124
+
125
+ Capitalizes the first letter of each word delimited by `separator`. Other characters are preserved as-is.
126
+
127
+ - **Parameters:**
128
+ - `string` (String): Input text.
129
+ - `separator` (String): Word boundary. Defaults to a single space.
130
+ - **Returns:** Title-cased string.
131
+
132
+ ### limitString(string, limit = 35, omission = "...")
133
+
134
+ Truncates a string if it exceeds `limit` characters and appends `omission`. Strings shorter than the limit are returned unchanged.
135
+
136
+ - **Parameters:**
137
+ - `string` (String): Input text.
138
+ - `limit` (Number): Maximum length of the result (including `omission`).
139
+ - `omission` (String): Suffix used when truncation happens.
140
+ - **Returns:** Possibly truncated string.
141
+
142
+ ### safeString(string)
143
+
144
+ Strips HTML tags via the `xss` library and additionally removes the body of dangerous block tags (`<script>`, `<style>`, `<iframe>`, `<object>`, `<embed>`, `<form>`) so leftover CSS/markup cannot leak as text. CSS attribute sanitization is also disabled (no `<style>` attribute support). Intended for rendering untrusted text safely; not a substitute for a full HTML sanitizer like DOMPurify.
145
+
146
+ - **Parameters:**
147
+ - `string` (String): Untrusted text.
148
+ - **Returns:** Sanitized string with no allowed tags.
149
+
150
+ ### shuffleString(string)
151
+
152
+ Randomly reorders the characters in a string using lodash's `shuffle` (Fisher-Yates).
153
+
154
+ - **Parameters:**
155
+ - `string` (String): Source string.
156
+ - **Returns:** Shuffled string of the same length.
157
+
158
+ ## Random (non-cryptographic, Math.random)
159
+
160
+ ### randomBoolean()
161
+
162
+ Returns a random `true` or `false` with uniform probability.
163
+
164
+ - **Returns:** `Boolean`.
165
+
166
+ ### randomString(length, useNumbers = true, useUppercase = false)
167
+
168
+ Generates a random string from a configurable character set.
169
+
170
+ - **Parameters:**
171
+ - `length` (Number): Output length.
172
+ - `useNumbers` (Boolean): Include digits `0-9`.
173
+ - `useUppercase` (Boolean): Include uppercase letters.
174
+ - **Returns:** Random string.
175
+
176
+ ### randomHex(length)
177
+
178
+ Generates a random hexadecimal string.
179
+
180
+ - **Parameters:**
181
+ - `length` (Number): Output length.
182
+ - **Returns:** Hex string of the requested length.
183
+
184
+ ### randomInteger(min, max)
185
+
186
+ Returns a random integer in `[min, max)`. If called with a single argument it is treated as `max` with `min = 0` (e.g., `randomInteger(10)` returns `0..9`).
187
+
188
+ - **Parameters:**
189
+ - `min` (Number): Inclusive lower bound, or `max` when called with one argument.
190
+ - `max` (Number): Exclusive upper bound.
191
+ - **Returns:** Integer in `[min, max)`.
192
+ - **Throws:** When inputs are not numbers, or when `max <= min`.
193
+
194
+ ### randomUuid(useDashes = true)
195
+
196
+ Generates a random UUID v4-shaped string. Not suitable for security tokens; use `secureRandomUuid` instead.
197
+
198
+ - **Parameters:**
199
+ - `useDashes` (Boolean): When `false`, dashes are stripped.
200
+ - **Returns:** UUID string.
201
+
202
+ ### randomWeighted(object)
203
+
204
+ Picks a random key from `object` with probability proportional to its weight. Returns `undefined` for empty or nullish input.
205
+
206
+ - **Parameters:**
207
+ - `object` (Object): Map of key → positive weight.
208
+ - **Returns:** Selected key, or `undefined`.
209
+
210
+ ### randomElement(object)
211
+
212
+ Picks a random value from an array or from an object's own enumerable values. Returns `undefined` for empty or nullish input.
213
+
214
+ - **Parameters:**
215
+ - `object` (Array|Object): Source collection.
216
+ - **Returns:** A random value, or `undefined`.
217
+
218
+ ## Deterministic Random (seeded)
219
+
220
+ ### mulberry32(seed)
221
+
222
+ Returns a deterministic PRNG (Mulberry32) seeded by a 32-bit integer or by a string (hashed internally to 32 bits). Each call to the returned function produces a `[0, 1)` float. Very fast, but only 32 bits of state — not for cryptographic use.
223
+
224
+ - **Parameters:**
225
+ - `seed` (Number|String): Seed value.
226
+ - **Returns:** Function that returns a `Number` in `[0, 1)` per call.
227
+
228
+ ### seedHex(seed, length)
229
+
230
+ Builds a deterministic hex string of the requested length from a seed via `mulberry32`. Same seed always yields the same output. Useful for short, repeatable identifiers (e.g., proxy session stickiness).
231
+
232
+ - **Parameters:**
233
+ - `seed` (Any): Seed value (coerced to string).
234
+ - `length` (Number): Output length in hex characters. Required.
235
+ - **Returns:** Hex string.
236
+
237
+ ## Predicates
238
+
239
+ ### checkEmpty(value)
240
+
241
+ Like lodash's `isEmpty` but additionally treats `0` (numeric zero) as empty.
242
+
243
+ - **Parameters:**
244
+ - `value` (Any): Value to test.
245
+ - **Returns:** `Boolean`.
246
+
247
+ ### isInt32(value)
248
+
249
+ Tests whether `value` is an integer within the signed 32-bit range.
250
+
251
+ - **Parameters:**
252
+ - `value` (Any): Value to test.
253
+ - **Returns:** `Boolean`.
254
+
255
+ ### isPositiveNumber(value)
256
+
257
+ Tests whether `value` is a finite positive number (excludes `NaN`, `Infinity`, non-numbers, `0`, and negatives).
258
+
259
+ - **Parameters:**
260
+ - `value` (Any): Value to test.
261
+ - **Returns:** `Boolean`.
262
+
263
+ ## Objects
264
+
265
+ ### coerceObjectNumbers(object)
266
+
267
+ Walks an object's own enumerable keys and converts string values that match a numeric pattern (e.g., `"1.5"`, `"-3"`, `"1e3"`) to `Number`. Non-string values and non-strict numeric strings (e.g., `"12abc"`, `"1,000"`) are left untouched. Mutates the input.
268
+
269
+ - **Parameters:**
270
+ - `object` (Object): Object to coerce in place.
271
+ - **Returns:** The same `object`.
272
+
273
+ ### coerceObjectIntegers(object)
274
+
275
+ Same as `coerceObjectNumbers` but only converts whole integer strings via `parseInt` (e.g., `"002"` → `2`, `"-7"` → `-7`). Mutates the input.
276
+
277
+ - **Parameters:**
278
+ - `object` (Object): Object to coerce in place.
279
+ - **Returns:** The same `object`.
280
+
281
+ ### findNodeByKey(key, node, pair = null)
282
+
283
+ Depth-first search through a nested object for the first node that owns `key`. If `pair` is provided, the node's value at `key` must equal `pair` (strict equality, supports falsy values like `false` / `0` / `""`).
284
+
285
+ - **Parameters:**
286
+ - `key` (String): Property name to find.
287
+ - `node` (Object): Tree to search.
288
+ - `pair` (Any): Optional value constraint. `null` means "match any value".
289
+ - **Returns:** The matching node, or `null` if not found.
290
+
291
+ ### waitForProperty(object, property, timeout = 5000, interval = 100)
292
+
293
+ Polls `object` until it owns `property`, then resolves with the property's value. Rejects after `timeout` milliseconds.
294
+
295
+ - **Parameters:**
296
+ - `object` (Object): Object to watch.
297
+ - `property` (String): Property name to wait for.
298
+ - `timeout` (Number): Maximum wait time in milliseconds.
299
+ - `interval` (Number): Poll interval in milliseconds.
300
+ - **Returns:** `Promise` resolving to the property's value.
301
+ - **Throws:** When the property does not appear within `timeout`.
302
+
303
+ ### shuffleObject(object)
304
+
305
+ Returns a new object whose entries are in random order (the underlying iteration order is the only thing being shuffled).
306
+
307
+ - **Parameters:**
308
+ - `object` (Object): Source object.
309
+ - **Returns:** New object with the same keys/values in shuffled order.
310
+
311
+ ### objectStringify(object)
312
+
313
+ Recursively walks an object and converts every leaf value to `String(value)`. Nested objects and arrays are descended into; mutates the input.
314
+
315
+ - **Parameters:**
316
+ - `object` (Object): Object to mutate.
317
+ - **Returns:** The same `object`.
318
+
319
+ ## Cookies
320
+
321
+ ### cookiesFromResponse(response, decodeValues = false)
322
+
323
+ Parses the `Set-Cookie` headers off a response-like object (compatible with Node `http`, fetch responses, or anything with `headers["set-cookie"]`) and returns a flat `{name: value}` map via `set-cookie-parser`.
324
+
325
+ - **Parameters:**
326
+ - `response` (Object): Response-like with parsed headers.
327
+ - `decodeValues` (Boolean): Whether to URL-decode cookie values.
328
+ - **Returns:** Map of cookie name → value.
329
+
330
+ ### cookiesToHeader(cookies)
331
+
332
+ Serializes a `{name: value}` map into a `Cookie:` header string (`name=value` pairs joined with `"; "`). Null/undefined values are dropped.
333
+
334
+ - **Parameters:**
335
+ - `cookies` (Object): Map of cookie name → value.
336
+ - **Returns:** Cookie header string, or `""` for empty/missing input.
337
+
338
+ ### cookiesFromHeader(header)
339
+
340
+ Parses a single `Cookie:` header string into a `{name: value}` map. Pieces without `=` are skipped; multiple `=` inside a value are preserved.
341
+
342
+ - **Parameters:**
343
+ - `header` (String): Cookie header value.
344
+ - **Returns:** Map of cookie name → value. Empty input returns `{}`.
345
+
346
+ ## HTTP Helpers
347
+
348
+ ### isTransientHttpCode(httpCode)
349
+
350
+ Flags HTTP status codes that are typically transient or worth retrying (missing/`NaN`, `100`, `402`, `407`, `460-469`, anything `≥ 500`).
351
+
352
+ - **Parameters:**
353
+ - `httpCode` (Number|null|undefined): Status code to inspect.
354
+ - **Returns:** `Boolean`.
355
+
356
+ ### getResponseError(error, limit = 200)
357
+
358
+ Extracts a short error description from an HTTP error-like object. Prefers `error.response.status|error.response.data`, then `error.response.data`, then `error.message`. Truncates the result to `limit` characters via `limitString`.
359
+
360
+ - **Parameters:**
361
+ - `error` (Error): Error from an HTTP client.
362
+ - `limit` (Number): Maximum length of the returned string.
363
+ - **Returns:** Trimmed error description.
package/docs/node.md CHANGED
@@ -7,11 +7,11 @@ 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](./general.md).
10
+ see the `random*` family in [General Functions](index.md).
11
11
 
12
12
  ### secureRandomBoolean()
13
13
 
14
- Returns `true` or `false` with cryptographically uniform probability.
14
+ Returns a cryptographically random `true` or `false` with uniform probability.
15
15
 
16
16
  - **Returns:** `Boolean`.
17
17
 
@@ -35,16 +35,17 @@ Generates a cryptographically random hexadecimal string.
35
35
 
36
36
  ### secureRandomInteger(min, max)
37
37
 
38
- Returns a cryptographically random integer in `[min, max)`. Thin wrapper over `crypto.randomInt`.
38
+ Returns a cryptographically random integer in `[min, max)`. If called with a single argument it is treated as `max` with `min = 0` (e.g., `secureRandomInteger(10)` returns `0..9`).
39
39
 
40
40
  - **Parameters:**
41
- - `min` (Number): Inclusive lower bound.
41
+ - `min` (Number): Inclusive lower bound, or `max` when called with one argument.
42
42
  - `max` (Number): Exclusive upper bound.
43
43
  - **Returns:** Integer in `[min, max)`.
44
+ - **Throws:** When inputs are not numbers, or when `max <= min`.
44
45
 
45
46
  ### secureRandomUuid(useDashes = true)
46
47
 
47
- Generates a cryptographically random UUID v4 via `crypto.randomUUID()`. Suitable for security tokens.
48
+ Generates a cryptographically random UUID v4 string. Suitable for security tokens.
48
49
 
49
50
  - **Parameters:**
50
51
  - `useDashes` (Boolean): When `false`, dashes are stripped.
@@ -52,17 +53,15 @@ Generates a cryptographically random UUID v4 via `crypto.randomUUID()`. Suitable
52
53
 
53
54
  ### secureRandomWeighted(object)
54
55
 
55
- Picks a key from `object` with probability proportional to its weight, using `crypto.randomInt` for selection. Weights
56
- must be positive integers.
56
+ Picks a cryptographically random key from `object` with probability proportional to its weight. Returns `undefined` for empty or nullish input.
57
57
 
58
58
  - **Parameters:**
59
59
  - `object` (Object): Map of key → positive integer weight.
60
- - **Returns:** Selected key.
60
+ - **Returns:** Selected key, or `undefined`.
61
61
 
62
62
  ### secureRandomElement(object)
63
63
 
64
- Picks a cryptographically random value from an array or from an object's own enumerable values. Returns `undefined` for
65
- empty or nullish input.
64
+ Picks a cryptographically random value from an array or from an object's own enumerable values. Returns `undefined` for empty or nullish input.
66
65
 
67
66
  - **Parameters:**
68
67
  - `object` (Array|Object): Source collection.
package/lib/cjs/index.cjs CHANGED
@@ -89,33 +89,33 @@ function promiseSilent(promise) {
89
89
  }
90
90
  async function forever(delayMs, task, onError = null, onFinally = null) {
91
91
  if (!isPositiveNumber(delayMs)) throw new Error("delayMs must be a positive number");
92
- const maybeUpdate = value => {
92
+ const update = value => {
93
93
  if (isPositiveNumber(value)) delayMs = value;
94
94
  };
95
95
  while (true) {
96
96
  try {
97
- maybeUpdate(await task());
97
+ update(await task());
98
98
  } catch (error) {
99
- if (onError) maybeUpdate(await onError(error));
99
+ if (onError) update(await onError(error));
100
100
  } finally {
101
101
  if (onFinally) {
102
102
  try {
103
- maybeUpdate(await onFinally());
103
+ update(await onFinally());
104
104
  } catch {}
105
105
  }
106
106
  await sleepMs(delayMs);
107
107
  }
108
108
  }
109
109
  }
110
- async function retry(callFn, maxAttempts, errorFn = null, {
110
+ async function retry(task, maxAttempts = 1, onError = null, {
111
111
  delayMs = 0,
112
112
  backoffFactor = 1
113
113
  } = {}) {
114
114
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
115
115
  try {
116
- return await callFn();
116
+ return await task();
117
117
  } catch (error) {
118
- if (errorFn) await errorFn(attempt, error);
118
+ if (onError) await onError(attempt, error);
119
119
  if (attempt >= maxAttempts) throw error;
120
120
  if (delayMs > 0) await sleepMs(delayMs * backoffFactor ** (attempt - 1));
121
121
  }
@@ -146,7 +146,7 @@ function isInt32(value) {
146
146
  return Number.isInteger(value) && value >= CONSTANTS.INT32_MIN && value <= CONSTANTS.INT32_MAX;
147
147
  }
148
148
  function isPositiveNumber(value) {
149
- return typeof value === 'number' && Number.isFinite(value) && value > 0;
149
+ return Number.isFinite(value) && value > 0;
150
150
  }
151
151
  function coerceObjectNumbers(object) {
152
152
  for (const key of Object.keys(object)) {
@@ -191,7 +191,7 @@ function waitForProperty(object, property, timeout = 5000, interval = 100) {
191
191
  resolve(object[property]);
192
192
  } else if (Date.now() - startTime >= timeout) {
193
193
  clearInterval(checkProperty);
194
- reject(new Error(`Property "${property}" did not appear within ${timeout} milliseconds`));
194
+ reject(new Error(`Property "${property}" did not appear within ${timeout}ms`));
195
195
  }
196
196
  }, interval);
197
197
  });
@@ -246,7 +246,7 @@ function randomHex(length) {
246
246
  }
247
247
  return result;
248
248
  }
249
- function randomInteger(min, max) {
249
+ function randomInteger(min, max = undefined) {
250
250
  if (typeof max === 'undefined') {
251
251
  max = min;
252
252
  min = 0;
@@ -267,6 +267,7 @@ function randomUuid(useDashes = true) {
267
267
  return useDashes ? uuid : uuid.replaceAll("-", "");
268
268
  }
269
269
  function randomWeighted(object) {
270
+ if (checkEmpty(object)) return undefined;
270
271
  const elements = Object.keys(object);
271
272
  const weights = Object.values(object);
272
273
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
@@ -280,7 +281,7 @@ function randomWeighted(object) {
280
281
  }
281
282
  }
282
283
  function randomElement(object) {
283
- if (!object) return undefined;
284
+ if (checkEmpty(object)) return undefined;
284
285
  const values = Array.isArray(object) ? object : Object.values(object);
285
286
  if (values.length === 0) return undefined;
286
287
  return values[Math.floor(Math.random() * values.length)];
@@ -310,14 +311,14 @@ function seedHex(seed, length) {
310
311
  return result.slice(0, length);
311
312
  }
312
313
  function cookiesFromResponse(response, decodeValues = false) {
313
- const dict = {};
314
+ const obj = {};
314
315
  const cookies = _setCookieParser.default.parse(response, {
315
316
  decodeValues
316
317
  });
317
318
  for (const cookie of cookies) {
318
- dict[cookie.name] = cookie.value;
319
+ obj[cookie.name] = cookie.value;
319
320
  }
320
- return dict;
321
+ return obj;
321
322
  }
322
323
  function cookiesToHeader(cookies) {
323
324
  if (!cookies) return "";
package/lib/cjs/node.cjs CHANGED
@@ -42,7 +42,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
42
42
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
43
43
  const execAsync = (0, _util.promisify)(_child_process.exec);
44
44
  function secureRandomBoolean() {
45
- return _crypto.default.randomInt(2) === 0;
45
+ return secureRandomInteger(2) === 1;
46
46
  }
47
47
  function secureRandomString(length, useNumbers = true, useUppercase = false) {
48
48
  let characters = _index.CONSTANTS.LOWER_CASE;
@@ -50,14 +50,14 @@ function secureRandomString(length, useNumbers = true, useUppercase = false) {
50
50
  if (useNumbers) characters += _index.CONSTANTS.NUMBERS;
51
51
  let result = '';
52
52
  for (let i = 0; i < length; i++) {
53
- result += characters[_crypto.default.randomInt(0, characters.length)];
53
+ result += characters[secureRandomInteger(0, characters.length)];
54
54
  }
55
55
  return result;
56
56
  }
57
57
  function secureRandomHex(length) {
58
58
  return _crypto.default.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length);
59
59
  }
60
- function secureRandomInteger(min, max) {
60
+ function secureRandomInteger(min, max = undefined) {
61
61
  return _crypto.default.randomInt(min, max);
62
62
  }
63
63
  function secureRandomUuid(useDashes = true) {
@@ -65,6 +65,7 @@ function secureRandomUuid(useDashes = true) {
65
65
  return useDashes ? uuid : uuid.replaceAll("-", "");
66
66
  }
67
67
  function secureRandomWeighted(object) {
68
+ if ((0, _index.checkEmpty)(object)) return undefined;
68
69
  const elements = Object.keys(object);
69
70
  const weights = Object.values(object);
70
71
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
@@ -78,10 +79,10 @@ function secureRandomWeighted(object) {
78
79
  }
79
80
  }
80
81
  function secureRandomElement(object) {
81
- if (!object) return undefined;
82
+ if ((0, _index.checkEmpty)(object)) return undefined;
82
83
  const values = Array.isArray(object) ? object : Object.values(object);
83
84
  if (values.length === 0) return undefined;
84
- return values[_crypto.default.randomInt(0, values.length)];
85
+ return values[secureRandomInteger(0, values.length)];
85
86
  }
86
87
  function uuidFromSeed(seed, useDashes = true) {
87
88
  const hash = _crypto.default.createHash('md5').update(seed).digest();
@@ -174,7 +175,7 @@ function normalizeProxy(proxy, protocol = "http") {
174
175
  const start = Number(parts[1]);
175
176
  const end = Number(parts[2]);
176
177
  if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
177
- body = `${parts[0]}:${_crypto.default.randomInt(start, end + 1)}`;
178
+ body = `${parts[0]}:${(0, _index.randomInteger)(start, end + 1)}`;
178
179
  }
179
180
  }
180
181
  return `${protocol}://${auth}${body}`;
package/lib/esm/index.mjs CHANGED
@@ -44,33 +44,33 @@ export function promiseSilent(promise) {
44
44
  }
45
45
  export async function forever(delayMs, task, onError = null, onFinally = null) {
46
46
  if (!isPositiveNumber(delayMs)) throw new Error("delayMs must be a positive number");
47
- const maybeUpdate = value => {
47
+ const update = value => {
48
48
  if (isPositiveNumber(value)) delayMs = value;
49
49
  };
50
50
  while (true) {
51
51
  try {
52
- maybeUpdate(await task());
52
+ update(await task());
53
53
  } catch (error) {
54
- if (onError) maybeUpdate(await onError(error));
54
+ if (onError) update(await onError(error));
55
55
  } finally {
56
56
  if (onFinally) {
57
57
  try {
58
- maybeUpdate(await onFinally());
58
+ update(await onFinally());
59
59
  } catch {}
60
60
  }
61
61
  await sleepMs(delayMs);
62
62
  }
63
63
  }
64
64
  }
65
- export async function retry(callFn, maxAttempts, errorFn = null, {
65
+ export async function retry(task, maxAttempts = 1, onError = null, {
66
66
  delayMs = 0,
67
67
  backoffFactor = 1
68
68
  } = {}) {
69
69
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
70
70
  try {
71
- return await callFn();
71
+ return await task();
72
72
  } catch (error) {
73
- if (errorFn) await errorFn(attempt, error);
73
+ if (onError) await onError(attempt, error);
74
74
  if (attempt >= maxAttempts) throw error;
75
75
  if (delayMs > 0) await sleepMs(delayMs * backoffFactor ** (attempt - 1));
76
76
  }
@@ -101,7 +101,7 @@ export function isInt32(value) {
101
101
  return Number.isInteger(value) && value >= CONSTANTS.INT32_MIN && value <= CONSTANTS.INT32_MAX;
102
102
  }
103
103
  export function isPositiveNumber(value) {
104
- return typeof value === 'number' && Number.isFinite(value) && value > 0;
104
+ return Number.isFinite(value) && value > 0;
105
105
  }
106
106
  export function coerceObjectNumbers(object) {
107
107
  for (const key of Object.keys(object)) {
@@ -146,7 +146,7 @@ export function waitForProperty(object, property, timeout = 5000, interval = 100
146
146
  resolve(object[property]);
147
147
  } else if (Date.now() - startTime >= timeout) {
148
148
  clearInterval(checkProperty);
149
- reject(new Error(`Property "${property}" did not appear within ${timeout} milliseconds`));
149
+ reject(new Error(`Property "${property}" did not appear within ${timeout}ms`));
150
150
  }
151
151
  }, interval);
152
152
  });
@@ -201,7 +201,7 @@ export function randomHex(length) {
201
201
  }
202
202
  return result;
203
203
  }
204
- export function randomInteger(min, max) {
204
+ export function randomInteger(min, max = undefined) {
205
205
  if (typeof max === 'undefined') {
206
206
  max = min;
207
207
  min = 0;
@@ -222,6 +222,7 @@ export function randomUuid(useDashes = true) {
222
222
  return useDashes ? uuid : uuid.replaceAll("-", "");
223
223
  }
224
224
  export function randomWeighted(object) {
225
+ if (checkEmpty(object)) return undefined;
225
226
  const elements = Object.keys(object);
226
227
  const weights = Object.values(object);
227
228
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
@@ -235,7 +236,7 @@ export function randomWeighted(object) {
235
236
  }
236
237
  }
237
238
  export function randomElement(object) {
238
- if (!object) return undefined;
239
+ if (checkEmpty(object)) return undefined;
239
240
  const values = Array.isArray(object) ? object : Object.values(object);
240
241
  if (values.length === 0) return undefined;
241
242
  return values[Math.floor(Math.random() * values.length)];
@@ -265,14 +266,14 @@ export function seedHex(seed, length) {
265
266
  return result.slice(0, length);
266
267
  }
267
268
  export function cookiesFromResponse(response, decodeValues = false) {
268
- const dict = {};
269
+ const obj = {};
269
270
  const cookies = setCookieParser.parse(response, {
270
271
  decodeValues
271
272
  });
272
273
  for (const cookie of cookies) {
273
- dict[cookie.name] = cookie.value;
274
+ obj[cookie.name] = cookie.value;
274
275
  }
275
- return dict;
276
+ return obj;
276
277
  }
277
278
  export function cookiesToHeader(cookies) {
278
279
  if (!cookies) return "";
package/lib/esm/node.mjs CHANGED
@@ -6,10 +6,10 @@ import { networkInterfaces } from "os";
6
6
  import { exec, execFileSync } from "child_process";
7
7
  import { promisify } from "util";
8
8
  import bcrypt from "bcryptjs";
9
- import { CONSTANTS, splitTrim, randomInteger, randomHex, seedHex } from "./index.mjs";
9
+ import { CONSTANTS, splitTrim, randomInteger, randomHex, seedHex, checkEmpty } from "./index.mjs";
10
10
  const execAsync = promisify(exec);
11
11
  export function secureRandomBoolean() {
12
- return crypto.randomInt(2) === 0;
12
+ return secureRandomInteger(2) === 1;
13
13
  }
14
14
  export function secureRandomString(length, useNumbers = true, useUppercase = false) {
15
15
  let characters = CONSTANTS.LOWER_CASE;
@@ -17,14 +17,14 @@ export function secureRandomString(length, useNumbers = true, useUppercase = fal
17
17
  if (useNumbers) characters += CONSTANTS.NUMBERS;
18
18
  let result = '';
19
19
  for (let i = 0; i < length; i++) {
20
- result += characters[crypto.randomInt(0, characters.length)];
20
+ result += characters[secureRandomInteger(0, characters.length)];
21
21
  }
22
22
  return result;
23
23
  }
24
24
  export function secureRandomHex(length) {
25
25
  return crypto.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length);
26
26
  }
27
- export function secureRandomInteger(min, max) {
27
+ export function secureRandomInteger(min, max = undefined) {
28
28
  return crypto.randomInt(min, max);
29
29
  }
30
30
  export function secureRandomUuid(useDashes = true) {
@@ -32,6 +32,7 @@ export function secureRandomUuid(useDashes = true) {
32
32
  return useDashes ? uuid : uuid.replaceAll("-", "");
33
33
  }
34
34
  export function secureRandomWeighted(object) {
35
+ if (checkEmpty(object)) return undefined;
35
36
  const elements = Object.keys(object);
36
37
  const weights = Object.values(object);
37
38
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
@@ -45,10 +46,10 @@ export function secureRandomWeighted(object) {
45
46
  }
46
47
  }
47
48
  export function secureRandomElement(object) {
48
- if (!object) return undefined;
49
+ if (checkEmpty(object)) return undefined;
49
50
  const values = Array.isArray(object) ? object : Object.values(object);
50
51
  if (values.length === 0) return undefined;
51
- return values[crypto.randomInt(0, values.length)];
52
+ return values[secureRandomInteger(0, values.length)];
52
53
  }
53
54
  export function uuidFromSeed(seed, useDashes = true) {
54
55
  const hash = crypto.createHash('md5').update(seed).digest();
@@ -141,7 +142,7 @@ export function normalizeProxy(proxy, protocol = "http") {
141
142
  const start = Number(parts[1]);
142
143
  const end = Number(parts[2]);
143
144
  if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
144
- body = `${parts[0]}:${crypto.randomInt(start, end + 1)}`;
145
+ body = `${parts[0]}:${randomInteger(start, end + 1)}`;
145
146
  }
146
147
  }
147
148
  return `${protocol}://${auth}${body}`;
package/package.json CHANGED
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "https://github.com/mahelbir/melperjs.git"
29
+ "url": "git+https://github.com/mahelbir/melperjs.git"
30
30
  },
31
31
  "author": "Mahmuthan Elbir",
32
32
  "license": "MIT",
@@ -70,5 +70,5 @@
70
70
  "babel-plugin-transform-import-meta": "^2.3.3",
71
71
  "cross-env": "^10.1.0"
72
72
  },
73
- "version": "16.0.0"
73
+ "version": "16.1.0"
74
74
  }
package/docs/general.md DELETED
@@ -1,363 +0,0 @@
1
- # General Functions
2
-
3
- Browser-safe utilities exported from `melperjs`. No Node.js APIs are used here — every function runs in the browser and in Node.js without polyfills.
4
-
5
- ## Constants
6
-
7
- `CONSTANTS` bundles a few character sets and integer bounds that other functions in this module rely on. Useful when you need the same alphabets in your own code.
8
-
9
- - `CONSTANTS.LOWER_CASE` — lowercase ASCII letters (`a-z`).
10
- - `CONSTANTS.UPPER_CASE` — uppercase ASCII letters (`A-Z`).
11
- - `CONSTANTS.HEXADECIMAL` — hex digits (`0-9a-f`).
12
- - `CONSTANTS.NUMBERS` — decimal digits (`0-9`).
13
- - `CONSTANTS.INT32_MIN` — `-2147483648`.
14
- - `CONSTANTS.INT32_MAX` — `2147483647`.
15
-
16
- ## Errors
17
-
18
- ### Exception(message, response = {}, name = null)
19
-
20
- Builds a standard `Error` with two extra attached fields (`response`, custom `name`) so error handlers can carry HTTP-style context without subclassing. A null/empty `response` is normalized to `{}`.
21
-
22
- - **Parameters:**
23
- - `message` (String): Human-readable error message.
24
- - `response` (Object): Arbitrary payload attached as `error.response`.
25
- - `name` (String|null): Overrides `error.name`. Defaults to `"Exception"`.
26
- - **Returns:** `Error` with `.response` and `.name` populated.
27
-
28
- ## Time & Async
29
-
30
- ### time()
31
-
32
- Current Unix timestamp in seconds (integer).
33
-
34
- - **Returns:** Seconds since the epoch.
35
-
36
- ### sleepMs(milliseconds)
37
-
38
- Promise that resolves after a delay, measured in milliseconds.
39
-
40
- - **Parameters:**
41
- - `milliseconds` (Number): Delay in ms.
42
- - **Returns:** `Promise<void>`.
43
-
44
- ### sleep(seconds)
45
-
46
- Same as `sleepMs` but the delay is given in seconds.
47
-
48
- - **Parameters:**
49
- - `seconds` (Number): Delay in seconds.
50
- - **Returns:** `Promise<void>`.
51
-
52
- ### promiseTimeout(milliseconds, promise)
53
-
54
- Races a promise against a timer. If the promise doesn't settle in time the result rejects; either way the timer is cleared.
55
-
56
- - **Parameters:**
57
- - `milliseconds` (Number): Maximum wait time before rejecting.
58
- - `promise` (Promise): The work to await.
59
- - **Returns:** Settles with the inner promise's value, or rejects with `Error("Promise timed out after Xms")`.
60
-
61
- ### promiseSilent(promise)
62
-
63
- Awaits a promise but swallows both the resolved value and any rejection. Handy for fire-and-forget work where you only care about the side effects.
64
-
65
- - **Parameters:**
66
- - `promise` (Promise): The promise to consume silently.
67
- - **Returns:** `Promise<undefined>` that always resolves.
68
-
69
- ### forever(delayMs, task, onError = null, onFinally = null)
70
-
71
- Runs `task` in an infinite loop with a delay between iterations. Errors are routed to `onError`; `onFinally` runs after every iteration regardless of outcome. Any of the three callbacks can return a new positive number to update `delayMs` on the fly (useful for adaptive polling).
72
-
73
- Errors thrown from `task` are caught and routed to `onError`; the loop keeps running. Errors thrown from `onError` propagate out and stop the loop — useful for soft shutdown by throwing on a stop signal. Errors thrown from `onFinally` are caught and ignored so that observability/cleanup failures cannot kill the worker.
74
-
75
- - **Parameters:**
76
- - `delayMs` (Number): Initial delay in milliseconds between iterations. Must be a positive finite number.
77
- - `task` (Function): Async function to invoke each iteration. Exceptions are caught and forwarded to `onError`.
78
- - `onError` (Function): Called with the caught error when `task` throws. Throwing from here aborts the loop.
79
- - `onFinally` (Function): Called after every iteration (success or failure). Errors thrown here are swallowed.
80
- - **Returns:** Promise that never resolves on its own; it rejects when `delayMs` validation fails or when `onError` throws.
81
- - **Throws:** When `delayMs` is not a positive finite number.
82
-
83
- ### retry(callFn, maxAttempts, errorFn = null, {delayMs = 0, backoffFactor = 1} = {})
84
-
85
- Calls `callFn` up to `maxAttempts` times, returning the first successful result. Optionally waits between retries with an exponential backoff (delay grows by `delayMs * backoffFactor^(attempt-1)`).
86
-
87
- - **Parameters:**
88
- - `callFn` (Function): Async function to attempt.
89
- - `maxAttempts` (Number): Total attempt count (1 = no retries).
90
- - `errorFn` (Function): Called as `(attempt, error)` after each failed attempt.
91
- - `options.delayMs` (Number): Base delay between retries in ms. `0` disables delay.
92
- - `options.backoffFactor` (Number): Multiplier applied per attempt. `1` keeps delay constant; `2` doubles each retry.
93
- - **Returns:** The first non-throwing result of `callFn`.
94
- - **Throws:** The last error after `maxAttempts` failures.
95
-
96
- ## Strings
97
-
98
- ### isValidURL(url)
99
-
100
- Tests whether the input parses as a valid URL via the `URL` constructor.
101
-
102
- - **Parameters:**
103
- - `url` (String): Candidate URL.
104
- - **Returns:** `Boolean`.
105
-
106
- ### splitTrim(string, separator = null)
107
-
108
- Splits a string, trims each piece, and drops empty results. Default separator is `\r?\n` (any newline).
109
-
110
- - **Parameters:**
111
- - `string` (String): Source text.
112
- - `separator` (String|RegExp|null): Custom delimiter; `null` falls back to newlines.
113
- - **Returns:** Array of non-empty trimmed strings.
114
-
115
- ### pascalCase(string)
116
-
117
- Converts arbitrary text to `PascalCase` (uses lodash internally).
118
-
119
- - **Parameters:**
120
- - `string` (String): Input text.
121
- - **Returns:** PascalCase string.
122
-
123
- ### titleCase(string, separator = " ")
124
-
125
- Capitalizes the first letter of each word delimited by `separator`. Other characters are preserved as-is.
126
-
127
- - **Parameters:**
128
- - `string` (String): Input text.
129
- - `separator` (String): Word boundary. Defaults to a single space.
130
- - **Returns:** Title-cased string.
131
-
132
- ### limitString(string, limit = 35, omission = "...")
133
-
134
- Truncates a string if it exceeds `limit` characters and appends `omission`. Strings shorter than the limit are returned unchanged.
135
-
136
- - **Parameters:**
137
- - `string` (String): Input text.
138
- - `limit` (Number): Maximum length of the result (including `omission`).
139
- - `omission` (String): Suffix used when truncation happens.
140
- - **Returns:** Possibly truncated string.
141
-
142
- ### safeString(string)
143
-
144
- Strips HTML tags via the `xss` library and additionally removes the body of dangerous block tags (`<script>`, `<style>`, `<iframe>`, `<object>`, `<embed>`, `<form>`) so leftover CSS/markup cannot leak as text. CSS attribute sanitization is also disabled (no `<style>` attribute support). Intended for rendering untrusted text safely; not a substitute for a full HTML sanitizer like DOMPurify.
145
-
146
- - **Parameters:**
147
- - `string` (String): Untrusted text.
148
- - **Returns:** Sanitized string with no allowed tags.
149
-
150
- ### shuffleString(string)
151
-
152
- Randomly reorders the characters in a string using lodash's `shuffle` (Fisher-Yates).
153
-
154
- - **Parameters:**
155
- - `string` (String): Source string.
156
- - **Returns:** Shuffled string of the same length.
157
-
158
- ## Random (non-cryptographic, Math.random)
159
-
160
- ### randomBoolean()
161
-
162
- Returns `true` or `false` with roughly equal probability.
163
-
164
- - **Returns:** `Boolean`.
165
-
166
- ### randomString(length, useNumbers = true, useUppercase = false)
167
-
168
- Generates a non-secure random string from configurable character sets.
169
-
170
- - **Parameters:**
171
- - `length` (Number): Output length.
172
- - `useNumbers` (Boolean): Include digits `0-9`.
173
- - `useUppercase` (Boolean): Include uppercase letters.
174
- - **Returns:** Random string.
175
-
176
- ### randomHex(length)
177
-
178
- Generates a non-secure random hexadecimal string.
179
-
180
- - **Parameters:**
181
- - `length` (Number): Output length.
182
- - **Returns:** Hex string of the requested length.
183
-
184
- ### randomInteger(min, max)
185
-
186
- Returns an integer in `[min, max)`. If called with a single argument it is treated as `max` with `min = 0` (e.g., `randomInteger(10)` returns `0..9`).
187
-
188
- - **Parameters:**
189
- - `min` (Number): Inclusive lower bound, or `max` when called with one argument.
190
- - `max` (Number): Exclusive upper bound.
191
- - **Returns:** Integer in `[min, max)`.
192
- - **Throws:** When inputs are not numbers, or when `max <= min`.
193
-
194
- ### randomUuid(useDashes = true)
195
-
196
- Generates a non-secure UUID v4-shaped string. Sufficient for client-side keys; do not use for security tokens.
197
-
198
- - **Parameters:**
199
- - `useDashes` (Boolean): When `false`, dashes are stripped.
200
- - **Returns:** UUID string.
201
-
202
- ### randomWeighted(object)
203
-
204
- Picks a key from `object` with probability proportional to its numeric value.
205
-
206
- - **Parameters:**
207
- - `object` (Object): Map of key → positive weight.
208
- - **Returns:** Selected key.
209
-
210
- ### randomElement(object)
211
-
212
- Picks a random value from an array or from an object's own enumerable values. Returns `undefined` for empty or nullish input.
213
-
214
- - **Parameters:**
215
- - `object` (Array|Object): Source collection.
216
- - **Returns:** A random value, or `undefined`.
217
-
218
- ## Deterministic Random (seeded)
219
-
220
- ### mulberry32(seed)
221
-
222
- Returns a deterministic PRNG (Mulberry32) seeded by a 32-bit integer or by a string (hashed internally to 32 bits). Each call to the returned function produces a `[0, 1)` float. Very fast, but only 32 bits of state — not for cryptographic use.
223
-
224
- - **Parameters:**
225
- - `seed` (Number|String): Seed value.
226
- - **Returns:** Function that returns a `Number` in `[0, 1)` per call.
227
-
228
- ### seedHex(seed, length)
229
-
230
- Builds a deterministic hex string of the requested length from a seed via `mulberry32`. Same seed always yields the same output. Useful for short, repeatable identifiers (e.g., proxy session stickiness).
231
-
232
- - **Parameters:**
233
- - `seed` (Any): Seed value (coerced to string).
234
- - `length` (Number): Output length in hex characters. Required.
235
- - **Returns:** Hex string.
236
-
237
- ## Predicates
238
-
239
- ### checkEmpty(value)
240
-
241
- Like lodash's `isEmpty` but additionally treats `0` (numeric zero) as empty.
242
-
243
- - **Parameters:**
244
- - `value` (Any): Value to test.
245
- - **Returns:** `Boolean`.
246
-
247
- ### isInt32(value)
248
-
249
- Tests whether `value` is an integer within the signed 32-bit range.
250
-
251
- - **Parameters:**
252
- - `value` (Any): Value to test.
253
- - **Returns:** `Boolean`.
254
-
255
- ### isPositiveNumber(value)
256
-
257
- Tests whether `value` is a finite positive number (excludes `NaN`, `Infinity`, non-numbers, `0`, and negatives).
258
-
259
- - **Parameters:**
260
- - `value` (Any): Value to test.
261
- - **Returns:** `Boolean`.
262
-
263
- ## Objects
264
-
265
- ### coerceObjectNumbers(object)
266
-
267
- Walks an object's own enumerable keys and converts string values that match a numeric pattern (e.g., `"1.5"`, `"-3"`, `"1e3"`) to `Number`. Non-string values and non-strict numeric strings (e.g., `"12abc"`, `"1,000"`) are left untouched. Mutates the input.
268
-
269
- - **Parameters:**
270
- - `object` (Object): Object to coerce in place.
271
- - **Returns:** The same `object`.
272
-
273
- ### coerceObjectIntegers(object)
274
-
275
- Same as `coerceObjectNumbers` but only converts whole integer strings via `parseInt` (e.g., `"002"` → `2`, `"-7"` → `-7`). Mutates the input.
276
-
277
- - **Parameters:**
278
- - `object` (Object): Object to coerce in place.
279
- - **Returns:** The same `object`.
280
-
281
- ### findNodeByKey(key, node, pair = null)
282
-
283
- Depth-first search through a nested object for the first node that owns `key`. If `pair` is provided, the node's value at `key` must equal `pair` (strict equality, supports falsy values like `false` / `0` / `""`).
284
-
285
- - **Parameters:**
286
- - `key` (String): Property name to find.
287
- - `node` (Object): Tree to search.
288
- - `pair` (Any): Optional value constraint. `null` means "match any value".
289
- - **Returns:** The matching node, or `null` if not found.
290
-
291
- ### waitForProperty(object, property, timeout = 5000, interval = 100)
292
-
293
- Polls `object` until it owns `property`, then resolves with the property's value. Rejects after `timeout` milliseconds.
294
-
295
- - **Parameters:**
296
- - `object` (Object): Object to watch.
297
- - `property` (String): Property name to wait for.
298
- - `timeout` (Number): Maximum wait time in milliseconds.
299
- - `interval` (Number): Poll interval in milliseconds.
300
- - **Returns:** `Promise` resolving to the property's value.
301
- - **Throws:** When the property does not appear within `timeout`.
302
-
303
- ### shuffleObject(object)
304
-
305
- Returns a new object whose entries are in random order (the underlying iteration order is the only thing being shuffled).
306
-
307
- - **Parameters:**
308
- - `object` (Object): Source object.
309
- - **Returns:** New object with the same keys/values in shuffled order.
310
-
311
- ### objectStringify(object)
312
-
313
- Recursively walks an object and converts every leaf value to `String(value)`. Nested objects and arrays are descended into; mutates the input.
314
-
315
- - **Parameters:**
316
- - `object` (Object): Object to mutate.
317
- - **Returns:** The same `object`.
318
-
319
- ## Cookies
320
-
321
- ### cookiesFromResponse(response, decodeValues = false)
322
-
323
- Parses the `Set-Cookie` headers off a response-like object (compatible with Node `http`, fetch responses, or anything with `headers["set-cookie"]`) and returns a flat `{name: value}` map via `set-cookie-parser`.
324
-
325
- - **Parameters:**
326
- - `response` (Object): Response-like with parsed headers.
327
- - `decodeValues` (Boolean): Whether to URL-decode cookie values.
328
- - **Returns:** Map of cookie name → value.
329
-
330
- ### cookiesToHeader(cookies)
331
-
332
- Serializes a `{name: value}` map into a `Cookie:` header string (`name=value` pairs joined with `"; "`). Null/undefined values are dropped.
333
-
334
- - **Parameters:**
335
- - `cookies` (Object): Map of cookie name → value.
336
- - **Returns:** Cookie header string, or `""` for empty/missing input.
337
-
338
- ### cookiesFromHeader(header)
339
-
340
- Parses a single `Cookie:` header string into a `{name: value}` map. Pieces without `=` are skipped; multiple `=` inside a value are preserved.
341
-
342
- - **Parameters:**
343
- - `header` (String): Cookie header value.
344
- - **Returns:** Map of cookie name → value. Empty input returns `{}`.
345
-
346
- ## HTTP Helpers
347
-
348
- ### isTransientHttpCode(httpCode)
349
-
350
- Flags HTTP status codes that are typically transient or worth retrying (missing/`NaN`, `100`, `402`, `407`, `460-469`, anything `≥ 500`).
351
-
352
- - **Parameters:**
353
- - `httpCode` (Number|null|undefined): Status code to inspect.
354
- - **Returns:** `Boolean`.
355
-
356
- ### getResponseError(error, limit = 200)
357
-
358
- Extracts a short error description from an HTTP error-like object. Prefers `error.response.status|error.response.data`, then `error.response.data`, then `error.message`. Truncates the result to `limit` characters via `limitString`.
359
-
360
- - **Parameters:**
361
- - `error` (Error): Error from an HTTP client.
362
- - `limit` (Number): Maximum length of the returned string.
363
- - **Returns:** Trimmed error description.