@vbarbarosh/node-helpers 3.73.0 → 3.74.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.
package/README.md CHANGED
@@ -20,7 +20,7 @@ npm install @vbarbarosh/node-helpers
20
20
  A subset of `@vbarbarosh/node-helpers` is available for browser:
21
21
 
22
22
  ```html
23
- <script src="https://unpkg.com/@vbarbarosh/node-helpers@3.73.0/dist/browser.js"></script>
23
+ <script src="https://unpkg.com/@vbarbarosh/node-helpers@3.74.1/dist/browser.js"></script>
24
24
  ```
25
25
 
26
26
  ⚠️ All browser functions are exposed globally.
package/dist/browser.js CHANGED
@@ -80,7 +80,7 @@ function serializer(replacer, cycleReplacer) {
80
80
 
81
81
  var map = {
82
82
  "./array_chunk.js": "./src/array_chunk.js",
83
- "./array_chunk_jobs.js": "./src/array_chunk_jobs.js",
83
+ "./array_chunk_balanced.js": "./src/array_chunk_balanced.js",
84
84
  "./array_fps.js": "./src/array_fps.js",
85
85
  "./array_gcd.js": "./src/array_gcd.js",
86
86
  "./array_group.js": "./src/array_group.js",
@@ -205,39 +205,44 @@ module.exports = array_chunk;
205
205
 
206
206
  /***/ }),
207
207
 
208
- /***/ "./src/array_chunk_jobs.js":
209
- /*!*********************************!*\
210
- !*** ./src/array_chunk_jobs.js ***!
211
- \*********************************/
208
+ /***/ "./src/array_chunk_balanced.js":
209
+ /*!*************************************!*\
210
+ !*** ./src/array_chunk_balanced.js ***!
211
+ \*************************************/
212
212
  /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
213
213
 
214
214
  const clamp = __webpack_require__(/*! ./clamp */ "./src/clamp.js");
215
215
 
216
216
  /**
217
- * Split an array of jobs between up to `max_threads`, while trying to put at least
218
- * `min_jobs_per_thread` items into each chunk.
217
+ * Split an array into up to `max_chunks` balanced chunks, while ensuring that
218
+ * each chunk contains at least `min_items_per_chunk` items (when possible).
219
219
  *
220
- * - If an array is empty → returns [].
221
- * - If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk,
222
- * everything goes into a single chunk.
220
+ * The function distributes items as evenly as possible:
221
+ * - Chunk sizes differ by at most 1 item.
222
+ * - The number of chunks never exceeds `max_chunks`.
223
223
  *
224
- * Alternative names:
225
- * - array_chunk_balanced
224
+ * Originally designed for distributing workload across threads
225
+ * (e.g. `array_chunk_balanced(jobs, max_threads, min_jobs_per_thread)`).
226
226
  *
227
- * @param array
228
- * @param max_threads
229
- * @param min_jobs_per_thread
230
- * @returns {*[]}
227
+ * Edge cases:
228
+ * - If the input array is empty → returns [].
229
+ * - If there are too few items to satisfy `min_items_per_chunk`, even for a single
230
+ * chunk, all items are placed into one chunk.
231
+ *
232
+ * @param {Array} array
233
+ * @param {number} max_chunks
234
+ * @param {number} min_items_per_chunk
235
+ * @returns {Array<Array>}
231
236
  */
232
- function array_chunk_jobs(array = [], max_threads = 1, min_jobs_per_thread = 1)
237
+ function array_chunk_balanced(array = [], max_chunks = 1, min_items_per_chunk = 1)
233
238
  {
234
- const total_threads = clamp(1, max_threads, Math.floor(array.length / min_jobs_per_thread));
235
- const jobs_per_thread = Math.floor(array.length / total_threads);
236
- let extra = array.length % total_threads;
239
+ const total_chunks = clamp(1, max_chunks, Math.floor(array.length / min_items_per_chunk));
240
+ const items_per_chunk = Math.floor(array.length / total_chunks);
241
+ let extra = array.length % total_chunks;
237
242
 
238
243
  const out = [];
239
244
  for (let i = 0; i < array.length; ) {
240
- const size = jobs_per_thread + (extra ? 1 : 0);
245
+ const size = items_per_chunk + (extra ? 1 : 0);
241
246
  out.push(array.slice(i, i + size));
242
247
  i += size;
243
248
  if (extra) {
@@ -247,7 +252,7 @@ function array_chunk_jobs(array = [], max_threads = 1, min_jobs_per_thread = 1)
247
252
  return out;
248
253
  }
249
254
 
250
- module.exports = array_chunk_jobs;
255
+ module.exports = array_chunk_balanced;
251
256
 
252
257
 
253
258
  /***/ }),
@@ -2479,10 +2484,10 @@ var __webpack_exports__ = {};
2479
2484
  \******************************/
2480
2485
  const ns = new URL(document.currentScript.src).searchParams.get('var') ?? 'h';
2481
2486
  if (typeof window[ns] !== 'undefined') {
2482
- console.log(`❌ @vbarbarosh/node-helpers@${"3.73.0"} was not injected — window.${ns} is already in use`);
2487
+ console.log(`❌ @vbarbarosh/node-helpers@${"3.74.1"} was not injected — window.${ns} is already in use`);
2483
2488
  }
2484
2489
  else {
2485
- console.log(`🎉 @vbarbarosh/node-helpers@${"3.73.0"} successfully exposed as window.${ns}`);
2490
+ console.log(`🎉 @vbarbarosh/node-helpers@${"3.74.1"} successfully exposed as window.${ns}`);
2486
2491
  window[ns] = {};
2487
2492
  // https://github.com/webpack/webpack/issues/625
2488
2493
  // https://webpack.js.org/guides/dependency-management/#require-context
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
3
3
  "license": "MIT",
4
4
  "name": "@vbarbarosh/node-helpers",
5
- "version": "3.73.0",
5
+ "version": "3.74.1",
6
6
  "description": "A set of helpers for JavaScript/Node.js",
7
7
  "unpkg": "dist/browser.js",
8
8
  "jsdelivr": "dist/browser.js",
@@ -0,0 +1,42 @@
1
+ const clamp = require('./clamp');
2
+
3
+ /**
4
+ * Split an array into up to `max_chunks` balanced chunks, while ensuring that
5
+ * each chunk contains at least `min_items_per_chunk` items (when possible).
6
+ *
7
+ * The function distributes items as evenly as possible:
8
+ * - Chunk sizes differ by at most 1 item.
9
+ * - The number of chunks never exceeds `max_chunks`.
10
+ *
11
+ * Originally designed for distributing workload across threads
12
+ * (e.g. `array_chunk_balanced(jobs, max_threads, min_jobs_per_thread)`).
13
+ *
14
+ * Edge cases:
15
+ * - If the input array is empty → returns [].
16
+ * - If there are too few items to satisfy `min_items_per_chunk`, even for a single
17
+ * chunk, all items are placed into one chunk.
18
+ *
19
+ * @param {Array} array
20
+ * @param {number} max_chunks
21
+ * @param {number} min_items_per_chunk
22
+ * @returns {Array<Array>}
23
+ */
24
+ function array_chunk_balanced(array = [], max_chunks = 1, min_items_per_chunk = 1)
25
+ {
26
+ const total_chunks = clamp(1, max_chunks, Math.floor(array.length / min_items_per_chunk));
27
+ const items_per_chunk = Math.floor(array.length / total_chunks);
28
+ let extra = array.length % total_chunks;
29
+
30
+ const out = [];
31
+ for (let i = 0; i < array.length; ) {
32
+ const size = items_per_chunk + (extra ? 1 : 0);
33
+ out.push(array.slice(i, i + size));
34
+ i += size;
35
+ if (extra) {
36
+ extra--;
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+
42
+ module.exports = array_chunk_balanced;
@@ -0,0 +1,37 @@
1
+ const array_chunk_balanced = require('./array_chunk_balanced');
2
+ const assert = require('assert');
3
+
4
+ describe('array_chunk_balanced', function () {
5
+ it('should handle basic input', function () {
6
+ assert.deepStrictEqual(array_chunk_balanced([1]), [[1]]);
7
+ assert.deepStrictEqual(array_chunk_balanced([1,2]), [[1,2]]);
8
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3]), [[1,2,3]]);
9
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5], 3, 2), [[1,2,3],[4,5]]);
10
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6], 3, 2), [[1,2],[3,4],[5,6]]);
11
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6,7], 3, 2), [[1,2,3],[4,5],[6,7]]);
12
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5], 3, 3), [[1,2,3,4,5]]);
13
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6], 3, 3), [[1,2,3],[4,5,6]]);
14
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6,7], 3, 3), [[1,2,3,4],[5,6,7]]);
15
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6,7,8], 3, 3), [[1,2,3,4],[5,6,7,8]]);
16
+ });
17
+ it('edge: If an array is empty → returns []', function () {
18
+ assert.deepStrictEqual(array_chunk_balanced([]), []);
19
+ });
20
+ it('edge: If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk, everything goes into a single chunk', function () {
21
+ assert.deepStrictEqual(array_chunk_balanced([1,2,3], 5, 5), [[1,2,3]]);
22
+ });
23
+ it('should never produce more chunks than max_threads', function () {
24
+ // 12 jobs, max 5 threads, min 2 jobs per thread
25
+ // Correct result MUST have exactly 5 chunks
26
+ assert.deepStrictEqual(
27
+ array_chunk_balanced([1,2,3,4,5,6,7,8,9,10,11,12], 5, 2),
28
+ [
29
+ [1,2,3],
30
+ [4,5,6],
31
+ [7,8],
32
+ [9,10],
33
+ [11,12]
34
+ ]
35
+ );
36
+ });
37
+ });
@@ -7,5 +7,66 @@ describe('array_fps', function () {
7
7
  assert.deepStrictEqual(array_fps(1, 2), [0, 0.99999]);
8
8
  assert.deepStrictEqual(array_fps(1, 3), [0, 0.49999, 0.99999]);
9
9
  assert.deepStrictEqual(array_fps(1, 4), [0, 0.33333, 0.66666, 0.99999]);
10
+ assert.deepStrictEqual(array_fps(5, 20), [
11
+ 0, 0.0505, 0.10101, 0.15151, 0.20202, 0.25252, 0.30303, 0.35353, 0.40404, 0.45454,
12
+ 0.50505, 0.55555, 0.60606, 0.65656, 0.70707, 0.75757, 0.80808, 0.85858, 0.90909, 0.95959,
13
+ 1.0101, 1.0606, 1.11111, 1.16161, 1.21212, 1.26262, 1.31313, 1.36363, 1.41414, 1.46464,
14
+ 1.51515, 1.56565, 1.61616, 1.66666, 1.71717, 1.76767, 1.81818, 1.86868, 1.91919, 1.96969,
15
+ 2.0202, 2.0707, 2.12121, 2.17171, 2.22222, 2.27272, 2.32323, 2.37373, 2.42424, 2.47474,
16
+ 2.52525, 2.57575, 2.62626, 2.67676, 2.72727, 2.77777, 2.82828, 2.87878, 2.92929, 2.97979,
17
+ 3.0303, 3.0808, 3.13131, 3.18181, 3.23232, 3.28282, 3.33333, 3.38383, 3.43434, 3.48484,
18
+ 3.53535, 3.58585, 3.63636, 3.68686, 3.73737, 3.78787, 3.83838, 3.88888, 3.93939, 3.98989,
19
+ 4.0404, 4.0909, 4.14141, 4.19191, 4.24242, 4.29292, 4.34343, 4.39393, 4.44444, 4.49494,
20
+ 4.54545, 4.59595, 4.64646, 4.69696, 4.74747, 4.79797, 4.84848, 4.89898, 4.94949, 4.99999,
21
+ ]);
22
+ assert.deepStrictEqual(array_fps(5, 30), [
23
+ 0, 0.03356, 0.06711, 0.10067, 0.13423, 0.16778, 0.20134, 0.2349, 0.26846, 0.30201,
24
+ 0.33557, 0.36913, 0.40268, 0.43624, 0.4698, 0.50335, 0.53691, 0.57047, 0.60403, 0.63758,
25
+ 0.67114, 0.7047, 0.73825, 0.77181, 0.80537, 0.83892, 0.87248, 0.90604, 0.9396, 0.97315,
26
+ 1.00671, 1.04027, 1.07382, 1.10738, 1.14094, 1.17449, 1.20805, 1.24161, 1.27517, 1.30872,
27
+ 1.34228, 1.37584, 1.40939, 1.44295, 1.47651, 1.51006, 1.54362, 1.57718, 1.61074, 1.64429,
28
+ 1.67785, 1.71141, 1.74496, 1.77852, 1.81208, 1.84563, 1.87919, 1.91275, 1.9463, 1.97986,
29
+ 2.01342, 2.04698, 2.08053, 2.11409, 2.14765, 2.1812, 2.21476, 2.24832, 2.28187, 2.31543,
30
+ 2.34899, 2.38255, 2.4161, 2.44966, 2.48322, 2.51677, 2.55033, 2.58389, 2.61744, 2.651,
31
+ 2.68456, 2.71812, 2.75167, 2.78523, 2.81879, 2.85234, 2.8859, 2.91946, 2.95301, 2.98657,
32
+ 3.02013, 3.05369, 3.08724, 3.1208, 3.15436, 3.18791, 3.22147, 3.25503, 3.28858, 3.32214,
33
+ 3.3557, 3.38925, 3.42281, 3.45637, 3.48993, 3.52348, 3.55704, 3.5906, 3.62415, 3.65771,
34
+ 3.69127, 3.72482, 3.75838, 3.79194, 3.8255, 3.85905, 3.89261, 3.92617, 3.95972, 3.99328,
35
+ 4.02684, 4.06039, 4.09395, 4.12751, 4.16107, 4.19462, 4.22818, 4.26174, 4.29529, 4.32885,
36
+ 4.36241, 4.39596, 4.42952, 4.46308, 4.49664, 4.53019, 4.56375, 4.59731, 4.63086, 4.66442,
37
+ 4.69798, 4.73153, 4.76509, 4.79865, 4.83221, 4.86576, 4.89932, 4.93288, 4.96643, 4.99999,
38
+ ]);
39
+ assert.deepStrictEqual(array_fps(5, 60), [
40
+ 0, 0.01672, 0.03344, 0.05017, 0.06689, 0.08361, 0.10033, 0.11706, 0.13378, 0.1505,
41
+ 0.16722, 0.18395, 0.20067, 0.21739, 0.23411, 0.25084, 0.26756, 0.28428, 0.301, 0.31773,
42
+ 0.33445, 0.35117, 0.36789, 0.38461, 0.40134, 0.41806, 0.43478, 0.4515, 0.46823, 0.48495,
43
+ 0.50167, 0.51839, 0.53512, 0.55184, 0.56856, 0.58528, 0.60201, 0.61873, 0.63545, 0.65217,
44
+ 0.66889, 0.68562, 0.70234, 0.71906, 0.73578, 0.75251, 0.76923, 0.78595, 0.80267, 0.8194,
45
+ 0.83612, 0.85284, 0.86956, 0.88629, 0.90301, 0.91973, 0.93645, 0.95318, 0.9699, 0.98662,
46
+ 1.00334, 1.02006, 1.03679, 1.05351, 1.07023, 1.08695, 1.10368, 1.1204, 1.13712, 1.15384,
47
+ 1.17057, 1.18729, 1.20401, 1.22073, 1.23746, 1.25418, 1.2709, 1.28762, 1.30435, 1.32107,
48
+ 1.33779, 1.35451, 1.37123, 1.38796, 1.40468, 1.4214, 1.43812, 1.45485, 1.47157, 1.48829,
49
+ 1.50501, 1.52174, 1.53846, 1.55518, 1.5719, 1.58863, 1.60535, 1.62207, 1.63879, 1.65552,
50
+ 1.67224, 1.68896, 1.70568, 1.7224, 1.73913, 1.75585, 1.77257, 1.78929, 1.80602, 1.82274,
51
+ 1.83946, 1.85618, 1.87291, 1.88963, 1.90635, 1.92307, 1.9398, 1.95652, 1.97324, 1.98996,
52
+ 2.00668, 2.02341, 2.04013, 2.05685, 2.07357, 2.0903, 2.10702, 2.12374, 2.14046, 2.15719,
53
+ 2.17391, 2.19063, 2.20735, 2.22408, 2.2408, 2.25752, 2.27424, 2.29097, 2.30769, 2.32441,
54
+ 2.34113, 2.35785, 2.37458, 2.3913, 2.40802, 2.42474, 2.44147, 2.45819, 2.47491, 2.49163,
55
+ 2.50836, 2.52508, 2.5418, 2.55852, 2.57525, 2.59197, 2.60869, 2.62541, 2.64214, 2.65886,
56
+ 2.67558, 2.6923, 2.70902, 2.72575, 2.74247, 2.75919, 2.77591, 2.79264, 2.80936, 2.82608,
57
+ 2.8428, 2.85953, 2.87625, 2.89297, 2.90969, 2.92642, 2.94314, 2.95986, 2.97658, 2.99331,
58
+ 3.01003, 3.02675, 3.04347, 3.06019, 3.07692, 3.09364, 3.11036, 3.12708, 3.14381, 3.16053,
59
+ 3.17725, 3.19397, 3.2107, 3.22742, 3.24414, 3.26086, 3.27759, 3.29431, 3.31103, 3.32775,
60
+ 3.34447, 3.3612, 3.37792, 3.39464, 3.41136, 3.42809, 3.44481, 3.46153, 3.47825, 3.49498,
61
+ 3.5117, 3.52842, 3.54514, 3.56187, 3.57859, 3.59531, 3.61203, 3.62876, 3.64548, 3.6622,
62
+ 3.67892, 3.69564, 3.71237, 3.72909, 3.74581, 3.76253, 3.77926, 3.79598, 3.8127, 3.82942,
63
+ 3.84615, 3.86287, 3.87959, 3.89631, 3.91304, 3.92976, 3.94648, 3.9632, 3.97993, 3.99665,
64
+ 4.01337, 4.03009, 4.04681, 4.06354, 4.08026, 4.09698, 4.1137, 4.13043, 4.14715, 4.16387,
65
+ 4.18059, 4.19732, 4.21404, 4.23076, 4.24748, 4.26421, 4.28093, 4.29765, 4.31437, 4.3311,
66
+ 4.34782, 4.36454, 4.38126, 4.39798, 4.41471, 4.43143, 4.44815, 4.46487, 4.4816, 4.49832,
67
+ 4.51504, 4.53176, 4.54849, 4.56521, 4.58193, 4.59865, 4.61538, 4.6321, 4.64882, 4.66554,
68
+ 4.68226, 4.69899, 4.71571, 4.73243, 4.74915, 4.76588, 4.7826, 4.79932, 4.81604, 4.83277,
69
+ 4.84949, 4.86621, 4.88293, 4.89966, 4.91638, 4.9331, 4.94982, 4.96655, 4.98327, 4.99999,
70
+ ]);
10
71
  });
11
72
  });
package/src/cache.js ADDED
@@ -0,0 +1,15 @@
1
+ // read, write, refresh
2
+ async function cache({get, set, refresh})
3
+ {
4
+ try {
5
+ return await get();
6
+ }
7
+ catch {
8
+ }
9
+
10
+ const value = await refresh();
11
+ await set(value);
12
+ return value;
13
+ }
14
+
15
+ module.exports = cache;
package/src/cache.md ADDED
@@ -0,0 +1,9 @@
1
+ Probably the most basic cache engine ever.
2
+
3
+ ```javascript
4
+ await cache({
5
+ get: () => fs_read_json('data.json'),
6
+ set: v => fs_write_json('data.json', v),
7
+ refresh: () => compute(),
8
+ });
9
+ ```
@@ -32,4 +32,9 @@ describe('urlmod', function () {
32
32
  assert.deepEqual(urlmod('/#ex', {b: 2}), '/?b=2#ex');
33
33
  assert.deepEqual(urlmod('http://www.example.com/some/path?a=1#ex', {b: 2}), 'http://www.example.com/some/path?a=1&b=2#ex');
34
34
  });
35
+ it('should keep port', function () {
36
+ const actual = urlmod('http://localhost:3000/auth/sign-in', {return: '/auth/profile'});
37
+ const expected = 'http://localhost:3000/auth/sign-in?return=%2Fauth%2Fprofile';
38
+ assert.strictEqual(actual, expected);
39
+ })
35
40
  });
@@ -0,0 +1,20 @@
1
+ function urlparts(url)
2
+ {
3
+ const tmp = url instanceof URL
4
+ ? url
5
+ : new URL((typeof url === 'string' ? url : ''), 'fake://fake/');
6
+ return {
7
+ href: tmp.href,
8
+ protocol: tmp.protocol,
9
+ hostname: tmp.hostname,
10
+ username: tmp.username,
11
+ password: tmp.password,
12
+ host: tmp.host,
13
+ port: tmp.port,
14
+ path: tmp.pathname,
15
+ search: tmp.search,
16
+ hash: tmp.hash,
17
+ };
18
+ }
19
+
20
+ module.exports = urlparts;
@@ -0,0 +1,294 @@
1
+ const assert = require('assert');
2
+ const edge_values = require('@vbarbarosh/type-helpers/src/edge_values');
3
+ const urlparts = require('./urlparts');
4
+
5
+ const extra_edge_values = [
6
+ // whitespace strings
7
+ {label: '" "', value: ' '},
8
+ {label: '"\\t"', value: '\t'},
9
+ {label: '"\\n"', value: '\n'},
10
+ {label: '"\\r\\n"', value: '\r\n'},
11
+
12
+ // numeric strings
13
+ {label: '"0"', value: '0'},
14
+ {label: '"1"', value: '1'},
15
+ {label: '"NaN"', value: 'NaN'},
16
+ {label: '"Infinity"', value: 'Infinity'},
17
+
18
+ // weird unicode
19
+ {label: '"💩"', value: '💩'},
20
+ {label: '"😀"', value: '😀'},
21
+ {label: '"привет"', value: 'привет'},
22
+ {label: '"こんにちは"', value: 'こんにちは'},
23
+
24
+ // dangerous chars
25
+ {label: '"<script>"', value: '<script>'},
26
+ {label: '"../"', value: '../'},
27
+ {label: '"%20"', value: '%20'},
28
+ {label: '"a?b=c"', value: 'a?b=c'},
29
+ {label: '"a#b"', value: 'a#b'},
30
+
31
+ // arrays
32
+ {label: '[1,2,3]', value: [1, 2, 3]},
33
+ {label: '[null]', value: [null]},
34
+ {label: '[undefined]', value: [undefined]},
35
+ {label: 'new Array(5)', value: new Array(5)},
36
+
37
+ // typed arrays
38
+ {label: 'Uint8Array', value: new Uint8Array([1, 2, 3])},
39
+ {label: 'Buffer', value: Buffer.from('abc')},
40
+
41
+ // objects
42
+ {label: '{a:1}', value: {a: 1}},
43
+ {label: 'Object.create(null)', value: Object.create(null)},
44
+
45
+ // custom coercion
46
+ {label: 'toString() => x', value: {toString() {return 'x';}}},
47
+ {label: 'valueOf() => 123', value: {valueOf() {return 123;}}},
48
+
49
+ // dates
50
+ {label: 'new Date()', value: new Date()},
51
+ {label: 'Invalid Date', value: new Date('invalid')},
52
+
53
+ // regexp
54
+ {label: '/foo/g', value: /foo/g},
55
+
56
+ // maps/sets
57
+ {label: 'Map', value: new Map()},
58
+ {label: 'Set', value: new Set()},
59
+ {label: 'WeakMap', value: new WeakMap()},
60
+ {label: 'WeakSet', value: new WeakSet()},
61
+
62
+ // promises
63
+ {label: 'Promise.resolve(1)', value: Promise.resolve(1)},
64
+
65
+ // errors
66
+ {label: 'Error', value: new Error('boom')},
67
+ {label: 'TypeError', value: new TypeError('boom')},
68
+
69
+ // wrappers
70
+ {label: 'new String()', value: new String('abc')},
71
+ {label: 'new Number()', value: new Number(123)},
72
+ {label: 'new Boolean()', value: new Boolean(false)},
73
+
74
+ // url-like
75
+ {label: 'new URL("https://example.com")', value: new URL('https://example.com')},
76
+
77
+ // bigint
78
+ {label: '1n', value: 1n},
79
+ {label: '-1n', value: -1n},
80
+
81
+ // special numbers
82
+ {label: 'Number.EPSILON', value: Number.EPSILON},
83
+
84
+ // proxy
85
+ {label: 'Proxy', value: new Proxy({}, {})},
86
+ ];
87
+
88
+ const happy_paths = [
89
+ {
90
+ label: 'parses full https url',
91
+ input: 'https://john:secret@example.com:8080/users/15?page=2#top',
92
+ expected: {
93
+ href: 'https://john:secret@example.com:8080/users/15?page=2#top',
94
+ protocol: 'https:',
95
+ hostname: 'example.com',
96
+ username: 'john',
97
+ password: 'secret',
98
+ host: 'example.com:8080',
99
+ port: '8080',
100
+ path: '/users/15',
101
+ search: '?page=2',
102
+ hash: '#top',
103
+ },
104
+ },
105
+ {
106
+ label: 'parses https url without auth',
107
+ input: 'https://example.com/users',
108
+ expected: {
109
+ href: 'https://example.com/users',
110
+ protocol: 'https:',
111
+ hostname: 'example.com',
112
+ username: '',
113
+ password: '',
114
+ host: 'example.com',
115
+ port: '',
116
+ path: '/users',
117
+ search: '',
118
+ hash: '',
119
+ },
120
+ },
121
+ {
122
+ label: 'parses root url',
123
+ input: 'https://example.com',
124
+ expected: {
125
+ href: 'https://example.com/',
126
+ protocol: 'https:',
127
+ hostname: 'example.com',
128
+ username: '',
129
+ password: '',
130
+ host: 'example.com',
131
+ port: '',
132
+ path: '/',
133
+ search: '',
134
+ hash: '',
135
+ },
136
+ },
137
+ {
138
+ label: 'parses relative path',
139
+ input: '/users?page=5',
140
+ expected: {
141
+ href: 'fake://fake/users?page=5',
142
+ protocol: 'fake:',
143
+ hostname: 'fake',
144
+ username: '',
145
+ password: '',
146
+ host: 'fake',
147
+ port: '',
148
+ path: '/users',
149
+ search: '?page=5',
150
+ hash: '',
151
+ },
152
+ },
153
+ {
154
+ label: 'parses hash',
155
+ input: 'https://example.com/docs#intro',
156
+ expected: {
157
+ href: 'https://example.com/docs#intro',
158
+ protocol: 'https:',
159
+ hostname: 'example.com',
160
+ username: '',
161
+ password: '',
162
+ host: 'example.com',
163
+ port: '',
164
+ path: '/docs',
165
+ search: '',
166
+ hash: '#intro',
167
+ },
168
+ },
169
+ {
170
+ label: 'parses query string',
171
+ input: 'https://example.com?a=1&b=2',
172
+ expected: {
173
+ href: 'https://example.com/?a=1&b=2',
174
+ protocol: 'https:',
175
+ hostname: 'example.com',
176
+ username: '',
177
+ password: '',
178
+ host: 'example.com',
179
+ port: '',
180
+ path: '/',
181
+ search: '?a=1&b=2',
182
+ hash: '',
183
+ },
184
+ },
185
+ {
186
+ label: 'parses localhost with port',
187
+ input: 'http://localhost:3000/foo',
188
+ expected: {
189
+ href: 'http://localhost:3000/foo',
190
+ protocol: 'http:',
191
+ hostname: 'localhost',
192
+ username: '',
193
+ password: '',
194
+ host: 'localhost:3000',
195
+ port: '3000',
196
+ path: '/foo',
197
+ search: '',
198
+ hash: '',
199
+ },
200
+ },
201
+ {
202
+ label: 'parses ftp url',
203
+ input: 'ftp://example.com/file.txt',
204
+ expected: {
205
+ href: 'ftp://example.com/file.txt',
206
+ protocol: 'ftp:',
207
+ hostname: 'example.com',
208
+ username: '',
209
+ password: '',
210
+ host: 'example.com',
211
+ port: '',
212
+ path: '/file.txt',
213
+ search: '',
214
+ hash: '',
215
+ },
216
+ },
217
+ ];
218
+
219
+ describe('urlparts', function () {
220
+ it('no args', function () {
221
+ assert.deepStrictEqual(urlparts(), {
222
+ href: 'fake://fake/',
223
+ protocol: 'fake:',
224
+ hostname: 'fake',
225
+ username: '',
226
+ password: '',
227
+ host: 'fake',
228
+ port: '',
229
+ path: '/',
230
+ search: '',
231
+ hash: '',
232
+ });
233
+ });
234
+
235
+ describe('happy paths', function () {
236
+ for (const item of happy_paths) {
237
+ it(item.label, function () {
238
+ const actual = urlparts(item.input);
239
+ assert.deepStrictEqual(actual, item.expected);
240
+ });
241
+ }
242
+ });
243
+
244
+ describe('edge values', function () {
245
+ for (const item of [...edge_values, ...extra_edge_values]) {
246
+ it(item.label, function () {
247
+ if (typeof item.value === 'string') {
248
+ const tmp = new URL(item.value, 'fake://fake/');
249
+ assert.deepStrictEqual(urlparts(item.value), {
250
+ href: tmp.href,
251
+ protocol: tmp.protocol,
252
+ hostname: tmp.hostname,
253
+ username: tmp.username,
254
+ password: tmp.password,
255
+ host: tmp.host,
256
+ port: tmp.port,
257
+ path: tmp.pathname,
258
+ search: tmp.search,
259
+ hash: tmp.hash,
260
+ });
261
+ }
262
+ else if (item.value instanceof URL) {
263
+ const tmp = item.value;
264
+ assert.deepStrictEqual(urlparts(item.value), {
265
+ href: tmp.href,
266
+ protocol: tmp.protocol,
267
+ hostname: tmp.hostname,
268
+ username: tmp.username,
269
+ password: tmp.password,
270
+ host: tmp.host,
271
+ port: tmp.port,
272
+ path: tmp.pathname,
273
+ search: tmp.search,
274
+ hash: tmp.hash,
275
+ });
276
+ }
277
+ else {
278
+ assert.deepStrictEqual(urlparts(item.value), {
279
+ href: 'fake://fake/',
280
+ protocol: 'fake:',
281
+ hostname: 'fake',
282
+ username: '',
283
+ password: '',
284
+ host: 'fake',
285
+ port: '',
286
+ path: '/',
287
+ search: '',
288
+ hash: '',
289
+ });
290
+ }
291
+ });
292
+ }
293
+ });
294
+ });
@@ -1,37 +0,0 @@
1
- const clamp = require('./clamp');
2
-
3
- /**
4
- * Split an array of jobs between up to `max_threads`, while trying to put at least
5
- * `min_jobs_per_thread` items into each chunk.
6
- *
7
- * - If an array is empty → returns [].
8
- * - If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk,
9
- * everything goes into a single chunk.
10
- *
11
- * Alternative names:
12
- * - array_chunk_balanced
13
- *
14
- * @param array
15
- * @param max_threads
16
- * @param min_jobs_per_thread
17
- * @returns {*[]}
18
- */
19
- function array_chunk_jobs(array = [], max_threads = 1, min_jobs_per_thread = 1)
20
- {
21
- const total_threads = clamp(1, max_threads, Math.floor(array.length / min_jobs_per_thread));
22
- const jobs_per_thread = Math.floor(array.length / total_threads);
23
- let extra = array.length % total_threads;
24
-
25
- const out = [];
26
- for (let i = 0; i < array.length; ) {
27
- const size = jobs_per_thread + (extra ? 1 : 0);
28
- out.push(array.slice(i, i + size));
29
- i += size;
30
- if (extra) {
31
- extra--;
32
- }
33
- }
34
- return out;
35
- }
36
-
37
- module.exports = array_chunk_jobs;
@@ -1,37 +0,0 @@
1
- const array_chunk_jobs = require('./array_chunk_jobs');
2
- const assert = require('assert');
3
-
4
- describe('array_chunk_jobs', function () {
5
- it('should handle basic input', function () {
6
- assert.deepStrictEqual(array_chunk_jobs([1]), [[1]]);
7
- assert.deepStrictEqual(array_chunk_jobs([1,2]), [[1,2]]);
8
- assert.deepStrictEqual(array_chunk_jobs([1,2,3]), [[1,2,3]]);
9
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5], 3, 2), [[1,2,3],[4,5]]);
10
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6], 3, 2), [[1,2],[3,4],[5,6]]);
11
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6,7], 3, 2), [[1,2,3],[4,5],[6,7]]);
12
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5], 3, 3), [[1,2,3,4,5]]);
13
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6], 3, 3), [[1,2,3],[4,5,6]]);
14
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6,7], 3, 3), [[1,2,3,4],[5,6,7]]);
15
- assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6,7,8], 3, 3), [[1,2,3,4],[5,6,7,8]]);
16
- });
17
- it('edge: If an array is empty → returns []', function () {
18
- assert.deepStrictEqual(array_chunk_jobs([]), []);
19
- });
20
- it('edge: If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk, everything goes into a single chunk', function () {
21
- assert.deepStrictEqual(array_chunk_jobs([1,2,3], 5, 5), [[1,2,3]]);
22
- });
23
- it('should never produce more chunks than max_threads', function () {
24
- // 12 jobs, max 5 threads, min 2 jobs per thread
25
- // Correct result MUST have exactly 5 chunks
26
- assert.deepStrictEqual(
27
- array_chunk_jobs([1,2,3,4,5,6,7,8,9,10,11,12], 5, 2),
28
- [
29
- [1,2,3],
30
- [4,5,6],
31
- [7,8],
32
- [9,10],
33
- [11,12]
34
- ]
35
- );
36
- });
37
- });