@vbarbarosh/node-helpers 3.66.1 → 3.68.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 +2 -2
- package/dist/browser.js +603 -141
- package/package.json +3 -1
- package/src/HeartbeatServer.js +99 -0
- package/src/_finally.js +8 -0
- package/src/_finally.md +19 -0
- package/src/_perf.js +18 -0
- package/src/_perf.md +13 -0
- package/src/_restore.js +16 -0
- package/src/_restore.md +21 -0
- package/src/_try.js +11 -0
- package/src/_try.md +5 -0
- package/src/array_group.js +11 -7
- package/src/array_index.js +3 -1
- package/src/array_max.js +8 -14
- package/src/array_max.md +1 -0
- package/src/array_max.test.js +8 -0
- package/src/array_min.js +8 -14
- package/src/array_min.md +1 -0
- package/src/array_min.test.js +8 -0
- package/src/array_shuffle.md +5 -0
- package/src/array_sort.js +21 -12
- package/src/array_sort.test.js +78 -3
- package/src/array_sort_other.js +2 -2
- package/src/array_unique.js +9 -3
- package/src/array_unique.md +1 -0
- package/src/array_unique_last.js +7 -14
- package/src/array_unique_last.md +1 -0
- package/src/browser/index.js +15 -7
- package/src/countdown.js +1 -1
- package/src/fastdl.md +8 -0
- package/src/fcmp_default.js +12 -0
- package/src/fcmp_default_desc.js +12 -0
- package/src/fcmp_from_spec.js +2 -0
- package/src/fcmp_numbers.js +1 -1
- package/src/fcmp_tuples.js +15 -0
- package/src/fcmp_utf8_bin.js +13 -0
- package/src/fcmp_utf8_ci.js +7 -0
- package/src/fcmp_utf8_cs.js +7 -0
- package/src/fcmp_utf8_natural_ci.js +7 -0
- package/src/fcmp_utf8_natural_cs.js +7 -0
- package/src/fcmpx.js +120 -0
- package/src/fcmpx.md +17 -0
- package/src/fcmpx.test.js +173 -0
- package/src/filter1_from_spec.md +7 -0
- package/src/format_date_fs.js +13 -0
- package/src/format_date_human.js +13 -0
- package/src/format_hrtime.md +1 -0
- package/src/format_progress_bytes.js +6 -6
- package/src/format_progress_kilo.js +6 -6
- package/src/fs_fopen_update.md +5 -0
- package/src/fs_list.js +16 -0
- package/src/fs_list_deep.js +20 -0
- package/src/fs_ls.js +3 -0
- package/src/fs_path_extname.js +14 -0
- package/src/fs_path_extname.test.js +22 -0
- package/src/fs_size_enoent.md +3 -0
- package/src/fs_tempdir.md +6 -0
- package/src/now_atom.js +1 -1
- package/src/now_atom.md +1 -0
- package/src/now_fs.js +8 -0
- package/src/now_human.js +8 -0
- package/src/parallel.js +3 -3
- package/src/parallel.md +5 -0
- package/src/pid_exists.js +12 -0
- package/src/pid_kill_grace.js +28 -0
- package/src/ping_socket.js +32 -0
- package/src/ping_socket.md +3 -0
- package/src/plural.js +8 -2
- package/src/plural.test.js +5 -3
- package/src/sanitize_var_name.js +2 -2
- package/src/{camel_to_snake.js → str_camel_to_snake.js} +2 -2
- package/src/{camel_to_snake.test.js → str_camel_to_snake.test.js} +3 -3
- package/src/str_parse_kv.js +10 -0
- package/src/stream_chunk.md +1 -0
- package/src/stream_data_ln.md +1 -0
- package/src/stream_each.md +1 -0
- package/src/stream_filter.md +1 -0
- package/src/stream_lines.md +1 -0
- package/src/stream_map.md +1 -0
- package/src/stream_map_flatten.md +1 -0
- package/src/stream_map_parallel.md +1 -0
- package/src/stream_parse_csv.md +3 -0
- package/src/stream_progress.js +1 -1
- package/src/stream_progress.md +8 -0
- package/src/stream_skip.md +1 -0
- package/src/stream_strpbrk.md +1 -0
- package/src/stream_tap.md +1 -0
- package/src/stream_xml_analyze.md +1 -0
- package/src/stream_xml_parse.md +1 -0
- package/src/throttle.md +3 -0
- package/src/urlclean.js +1 -1
- package/src/urlclean.md +2 -0
- package/src/urlmod.md +7 -0
- package/src/user_friendly_status.md +4 -0
- package/src/wait_while.md +5 -0
- package/src/waitcb.md +12 -0
- package/src/fcmp_strings.js +0 -6
- /package/src/{stream-parse-csv.js → stream_parse_csv.js} +0 -0
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.
|
|
5
|
+
"version": "3.68.0",
|
|
6
6
|
"description": "A set of helpers for JavaScript/Node.js",
|
|
7
7
|
"unpkg": "dist/browser.js",
|
|
8
8
|
"jsdelivr": "dist/browser.js",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"ssh2": "^1.16.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@vbarbarosh/tree": "^0.5.0",
|
|
45
46
|
"copy-webpack-plugin": "^12.0.2",
|
|
46
47
|
"express": "^4.21.2",
|
|
47
48
|
"mocha": "^11.0.1",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"redis-v3": "npm:redis@3",
|
|
51
52
|
"redis-v4": "npm:redis@4",
|
|
52
53
|
"throttle": "^1.0.3",
|
|
54
|
+
"tree-kill": "^1.2.2",
|
|
53
55
|
"webpack": "^5.98.0",
|
|
54
56
|
"webpack-cli": "^6.0.1"
|
|
55
57
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
const Promise = require('bluebird');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const fs_path_join = require('./fs_path_join');
|
|
5
|
+
const fs_rmf = require('./fs_rmf');
|
|
6
|
+
const net = require('net');
|
|
7
|
+
const now_fs = require('./now_fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
class HeartbeatServer extends EventEmitter
|
|
11
|
+
{
|
|
12
|
+
#socket_path;
|
|
13
|
+
#last_ping = Date.now();
|
|
14
|
+
#interval_ms;
|
|
15
|
+
#promise;
|
|
16
|
+
#reject;
|
|
17
|
+
#timer;
|
|
18
|
+
#server;
|
|
19
|
+
|
|
20
|
+
constructor(interval_ms = 5000) {
|
|
21
|
+
super();
|
|
22
|
+
this.#interval_ms = interval_ms;
|
|
23
|
+
this.#init();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#init() {
|
|
27
|
+
const _this = this;
|
|
28
|
+
|
|
29
|
+
this.#timer = setInterval(() => this.#tick(), this.#interval_ms);
|
|
30
|
+
|
|
31
|
+
// Put socket on tmpfs to avoid slow disks.
|
|
32
|
+
this.#socket_path = fs.existsSync('/dev/shm')
|
|
33
|
+
? fs_path_join('/dev/shm', `watchdog-${process.pid}-${now_fs()}.sock`)
|
|
34
|
+
: fs_path_join(os.tmpdir(), `watchdog-${process.pid}-${now_fs()}.sock`);
|
|
35
|
+
|
|
36
|
+
this.#promise = new Promise((resolve, reject) => {
|
|
37
|
+
this.#reject = reject;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.#server = net.createServer(function (socket) {
|
|
41
|
+
socket.setEncoding('utf8');
|
|
42
|
+
socket.on('error', function (error) {
|
|
43
|
+
_this.#reject(new Error(`Client Socket Failed: ${error.message}`));
|
|
44
|
+
});
|
|
45
|
+
socket.on('end', function () {
|
|
46
|
+
_this.#last_ping = Date.now();
|
|
47
|
+
_this.emit('heartbeat');
|
|
48
|
+
});
|
|
49
|
+
socket.on('data', function () {
|
|
50
|
+
// Not really interested in any data
|
|
51
|
+
});
|
|
52
|
+
socket.end('OK');
|
|
53
|
+
});
|
|
54
|
+
this.#server.on('error', function (error) {
|
|
55
|
+
_this.#reject(new Error(`Server Failed: ${error.message}`));
|
|
56
|
+
});
|
|
57
|
+
this.#server.on('close', function () {
|
|
58
|
+
_this.#reject(new Error(`Server Closed`));
|
|
59
|
+
});
|
|
60
|
+
this.#server.listen(this.#socket_path, function () {
|
|
61
|
+
// started = true;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#tick() {
|
|
66
|
+
if (this.#last_ping + this.#interval_ms < Date.now()) {
|
|
67
|
+
clearInterval(this.#timer);
|
|
68
|
+
this.#timer = null;
|
|
69
|
+
this.#reject(new Error(`No heartbeat for the last ${this.#interval_ms}ms`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get socket_path() {
|
|
74
|
+
return this.#socket_path;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
promise() {
|
|
78
|
+
return this.#promise;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async dispose() {
|
|
82
|
+
if (this.#timer) {
|
|
83
|
+
clearInterval(this.#timer);
|
|
84
|
+
this.#timer = null;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const _this = this;
|
|
88
|
+
await new Promise(function (resolve, reject) {
|
|
89
|
+
_this.#server.removeAllListeners();
|
|
90
|
+
_this.#server.close(error => error ? reject(error) : resolve());
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await fs_rmf(this.#socket_path);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = HeartbeatServer;
|
package/src/_finally.js
ADDED
package/src/_finally.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
`_finally` lets you register cleanup code that always runs when the scope ends.
|
|
2
|
+
|
|
3
|
+
```js
|
|
4
|
+
this.foo = 12345;
|
|
5
|
+
using _ = _finally(() => this.foo = null);
|
|
6
|
+
// ...
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
This is essentially the same as:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
this.foo = 12345;
|
|
13
|
+
try {
|
|
14
|
+
// ..
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
this.foo = null;
|
|
18
|
+
}
|
|
19
|
+
```
|
package/src/_perf.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function _perf(name, print = s => console.log(s))
|
|
2
|
+
{
|
|
3
|
+
const time0 = performance.now();
|
|
4
|
+
return {
|
|
5
|
+
times: 1,
|
|
6
|
+
[Symbol.dispose]: function () {
|
|
7
|
+
const duration = performance.now() - time0;
|
|
8
|
+
if (this.times > 1) {
|
|
9
|
+
print(`[${name}] ${duration.toFixed(4)}ms x ${this.times}`);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
print(`[${name}] ${duration.toFixed(4)}ms`);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = _perf;
|
package/src/_perf.md
ADDED
package/src/_restore.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// 1) Capture original value
|
|
2
|
+
// 2) Assign a temporary one
|
|
3
|
+
// 3) Automatically restore on scope exit
|
|
4
|
+
//
|
|
5
|
+
// Basically: set variable, and automatically restore the original value at the end of scope.
|
|
6
|
+
function _restore(value, callback)
|
|
7
|
+
{
|
|
8
|
+
const [original] = callback(value);
|
|
9
|
+
return {
|
|
10
|
+
[Symbol.dispose]: function () {
|
|
11
|
+
callback(original);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = _restore;
|
package/src/_restore.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The pattern "set a new value now, restore the old one when scope ends".
|
|
2
|
+
|
|
3
|
+
```js
|
|
4
|
+
using _ = _restore(false, v => [this.controls.play, this.controls.play = v]);
|
|
5
|
+
// ...
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
This is essentially the same as:
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
{
|
|
12
|
+
const _old = this.controls.play;
|
|
13
|
+
this.controls.play = false;
|
|
14
|
+
try {
|
|
15
|
+
// ...
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
this.controls.play = _old;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
package/src/_try.js
ADDED
package/src/_try.md
ADDED
package/src/array_group.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
const array_group_map = require('./array_group_map');
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Group items by common key and return an array of groups.
|
|
2
|
+
* Group items by a common key and return an array of groups.
|
|
5
3
|
*
|
|
6
|
-
* @
|
|
7
|
-
* @param fn
|
|
8
|
-
* @returns {unknown[]}
|
|
4
|
+
* @alternative Map.groupBy(items, fn)
|
|
9
5
|
*/
|
|
10
6
|
function array_group(array, fn)
|
|
11
7
|
{
|
|
12
|
-
|
|
8
|
+
const map = new Map();
|
|
9
|
+
array.forEach(function (item) {
|
|
10
|
+
const key = fn(item);
|
|
11
|
+
if (!map.has(key)) {
|
|
12
|
+
map.set(key, {key, items: []});
|
|
13
|
+
}
|
|
14
|
+
map.get(key).items.push(item);
|
|
15
|
+
});
|
|
16
|
+
return map.values().toArray();
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
module.exports = array_group;
|
package/src/array_index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const identity = require('./identity');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Usage:
|
|
3
5
|
* array_index(items, v => v.name)
|
|
@@ -5,7 +7,7 @@
|
|
|
5
7
|
* Seems, there is a native way to do it:
|
|
6
8
|
* Object.fromEntries(items.map(v => [v.name, v]));
|
|
7
9
|
*/
|
|
8
|
-
function array_index(array, fn)
|
|
10
|
+
function array_index(array, fn = identity)
|
|
9
11
|
{
|
|
10
12
|
const out = {};
|
|
11
13
|
array.forEach(v => out[fn(v)] = v);
|
package/src/array_max.js
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
|
+
const identity = require('./identity');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @param array
|
|
5
|
-
* @param fn
|
|
4
|
+
* Returns the first element in the array with the maximal weight
|
|
6
5
|
*/
|
|
7
6
|
function array_max(array, fn = identity)
|
|
8
7
|
{
|
|
9
8
|
let out = null;
|
|
10
|
-
let
|
|
11
|
-
array.forEach(function (item) {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
9
|
+
let max = null;
|
|
10
|
+
array.forEach(function (item, i) {
|
|
11
|
+
const weight = fn(item);
|
|
12
|
+
if (i === 0 || max < weight) {
|
|
13
|
+
max = weight;
|
|
14
14
|
out = item;
|
|
15
|
-
out_value = item_value;
|
|
16
15
|
}
|
|
17
16
|
});
|
|
18
17
|
return out;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
function identity(value)
|
|
22
|
-
{
|
|
23
|
-
return value;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
20
|
module.exports = array_max;
|
package/src/array_max.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Returns the first element in the array with the maximal weight.
|
package/src/array_max.test.js
CHANGED
|
@@ -9,4 +9,12 @@ describe('array_max', function () {
|
|
|
9
9
|
assert.deepStrictEqual(array_max([1, 1, 2, 2, 3, 3, 1]), 3);
|
|
10
10
|
assert.deepStrictEqual(array_max([{x:1},{x:2}], v => v.x), {x: 2});
|
|
11
11
|
});
|
|
12
|
+
it('should handle strings', function () {
|
|
13
|
+
const items = [
|
|
14
|
+
{id: 1, time: '2025-08-08T05:03:13+00:00'},
|
|
15
|
+
{id: 2, time: '2025-08-07T05:03:13+00:00'},
|
|
16
|
+
{id: 3, time: '2025-08-06T05:03:13+00:00'},
|
|
17
|
+
];
|
|
18
|
+
assert.deepStrictEqual(array_max(items, v => v.time), items[0]);
|
|
19
|
+
});
|
|
12
20
|
});
|
package/src/array_min.js
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
|
+
const identity = require('./identity');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @param array
|
|
5
|
-
* @param fn
|
|
4
|
+
* Returns the first element in the array with the minimal weight
|
|
6
5
|
*/
|
|
7
6
|
function array_min(array, fn = identity)
|
|
8
7
|
{
|
|
9
8
|
let out = null;
|
|
10
|
-
let
|
|
11
|
-
array.forEach(function (item) {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
9
|
+
let min = null;
|
|
10
|
+
array.forEach(function (item, i) {
|
|
11
|
+
const weight = fn(item);
|
|
12
|
+
if (i === 0 || min > weight) {
|
|
13
|
+
min = weight;
|
|
14
14
|
out = item;
|
|
15
|
-
out_value = item_value;
|
|
16
15
|
}
|
|
17
16
|
});
|
|
18
17
|
return out;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
function identity(value)
|
|
22
|
-
{
|
|
23
|
-
return value;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
20
|
module.exports = array_min;
|
package/src/array_min.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Returns the first element in the array with the minimal weight.
|
package/src/array_min.test.js
CHANGED
|
@@ -9,4 +9,12 @@ describe('array_min', function () {
|
|
|
9
9
|
assert.deepStrictEqual(array_min([1, 1, 2, 2, 3, 3, 1]), 1);
|
|
10
10
|
assert.deepStrictEqual(array_min([{x:2},{x:1}], v => v.x), {x: 1});
|
|
11
11
|
});
|
|
12
|
+
it('should handle strings', function () {
|
|
13
|
+
const items = [
|
|
14
|
+
{id: 1, time: '2025-08-08T05:03:13+00:00'},
|
|
15
|
+
{id: 2, time: '2025-08-07T05:03:13+00:00'},
|
|
16
|
+
{id: 3, time: '2025-08-06T05:03:13+00:00'},
|
|
17
|
+
];
|
|
18
|
+
assert.deepStrictEqual(array_min(items, v => v.time), items[2]);
|
|
19
|
+
});
|
|
12
20
|
});
|
package/src/array_sort.js
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
const
|
|
1
|
+
const fcmp_default = require('./fcmp_default');
|
|
2
|
+
const fcmp_tuples = require('./fcmp_tuples');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Sorts an array in place by the result of applying `fn` to each item,
|
|
6
|
+
* using `fcmp` to compare the results.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param fcmp
|
|
9
|
-
* @returns {*}
|
|
8
|
+
* array_sort(items, v => [v.name])
|
|
9
|
+
* array_sort(items, v => [v.age, v.name])
|
|
10
10
|
*/
|
|
11
|
-
function array_sort(array,
|
|
11
|
+
function array_sort(array, mapper, fcmp = fcmp_default)
|
|
12
12
|
{
|
|
13
|
+
const keys = new Map();
|
|
13
14
|
return array.sort(function (a, b) {
|
|
14
|
-
|
|
15
|
+
if (!keys.has(a)) {
|
|
16
|
+
keys.set(a, mapper(a));
|
|
17
|
+
}
|
|
18
|
+
if (!keys.has(b)) {
|
|
19
|
+
keys.set(b, mapper(b));
|
|
20
|
+
}
|
|
21
|
+
return fcmp_tuples(keys.get(a), keys.get(b), fcmp);
|
|
15
22
|
});
|
|
16
23
|
}
|
|
17
24
|
|
|
18
|
-
function identity
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
// function array_sort(array, fn = identity, fcmp = fcmp_default)
|
|
26
|
+
// {
|
|
27
|
+
// return array.sort(function (a, b) {
|
|
28
|
+
// return fcmp(fn(a), fn(b));
|
|
29
|
+
// });
|
|
30
|
+
// }
|
|
22
31
|
|
|
23
32
|
module.exports = array_sort;
|
package/src/array_sort.test.js
CHANGED
|
@@ -1,10 +1,85 @@
|
|
|
1
1
|
const array_sort = require('./array_sort');
|
|
2
2
|
const assert = require('assert');
|
|
3
|
+
const fs_path_extname = require('./fs_path_extname');
|
|
3
4
|
|
|
4
5
|
describe('array_sort', function () {
|
|
5
6
|
it('should handle basic input', function () {
|
|
6
|
-
assert.deepStrictEqual(array_sort([5,4,3,2,1]), [1,2,3,4,5]);
|
|
7
|
-
assert.deepStrictEqual(array_sort([5,4,3,2,1], v => v), [1,2,3,4,5]);
|
|
8
|
-
assert.deepStrictEqual(array_sort([{id:5},{id:4},{id:3},{id:2},{id:1}], v => v.id), [{id:1},{id:2},{id:3},{id:4},{id:5}]);
|
|
7
|
+
assert.deepStrictEqual(array_sort([5,4,3,2,1], v => [v]), [1,2,3,4,5]);
|
|
8
|
+
assert.deepStrictEqual(array_sort([5,4,3,2,1], v => [v]), [1,2,3,4,5]);
|
|
9
|
+
assert.deepStrictEqual(array_sort([{id:5},{id:4},{id:3},{id:2},{id:1}], v => [v.id]), [{id:1},{id:2},{id:3},{id:4},{id:5}]);
|
|
10
|
+
});
|
|
11
|
+
it('should handle sort by priorities', function () {
|
|
12
|
+
assert.deepStrictEqual(array_sort([1,2,3,4,5], v => [[5].indexOf(v) >>> 0]), [5,1,2,3,4]);
|
|
13
|
+
assert.deepStrictEqual(array_sort([{id:1},{id:2},{id:3},{id:4},{id:5}], v => [[5].indexOf(v.id) >>> 0]), [{id:5},{id:1},{id:2},{id:3},{id:4}]);
|
|
14
|
+
});
|
|
15
|
+
it('should handle sort by priorities #2', function () {
|
|
16
|
+
const files = [
|
|
17
|
+
{name: 'README.md', size: 1024, type: 'file'},
|
|
18
|
+
{name: 'package.json', size: 860, type: 'file'},
|
|
19
|
+
{name: 'src', size: 0, type: 'dir'},
|
|
20
|
+
{name: 'src/index.js', size: 2048, type: 'file'},
|
|
21
|
+
{name: 'src/utils', size: 0, type: 'dir'},
|
|
22
|
+
{name: 'src/utils/helpers.js', size: 1536, type: 'file'},
|
|
23
|
+
{name: 'node_modules', size: 0, type: 'dir'},
|
|
24
|
+
{name: 'LICENSE', size: 1078, type: 'file'},
|
|
25
|
+
];
|
|
26
|
+
const expected = [
|
|
27
|
+
{name: 'node_modules', size: 0, type: 'dir'},
|
|
28
|
+
{name: 'src', size: 0, type: 'dir'},
|
|
29
|
+
{name: 'src/utils', size: 0, type: 'dir'},
|
|
30
|
+
{name: 'LICENSE', size: 1078, type: 'file'},
|
|
31
|
+
{name: 'README.md', size: 1024, type: 'file'},
|
|
32
|
+
{name: 'package.json', size: 860, type: 'file'},
|
|
33
|
+
{name: 'src/index.js', size: 2048, type: 'file'},
|
|
34
|
+
{name: 'src/utils/helpers.js', size: 1536, type: 'file'},
|
|
35
|
+
];
|
|
36
|
+
const actual = array_sort(files, v => [pri_start(v.type, ['dir']), v.name]);
|
|
37
|
+
assert.deepStrictEqual(actual, expected);
|
|
38
|
+
});
|
|
39
|
+
it('sort by name, directory first; .js, then .txt, then all the reset', function () {
|
|
40
|
+
const files = [
|
|
41
|
+
{name: 'docs', type: 'dir', size: 0},
|
|
42
|
+
{name: 'zeta.txt', type: 'file', size: 1200},
|
|
43
|
+
{name: 'alpha.js', type: 'file', size: 1500},
|
|
44
|
+
{name: 'src', type: 'dir', size: 0},
|
|
45
|
+
{name: 'readme.txt', type: 'file', size: 800},
|
|
46
|
+
{name: 'beta.js', type: 'file', size: 1600},
|
|
47
|
+
{name: 'build', type: 'dir', size: 0},
|
|
48
|
+
{name: 'build.foo.js', type: 'dir', size: 0},
|
|
49
|
+
{name: 'build.foo.md', type: 'dir', size: 0},
|
|
50
|
+
{name: 'build.foo.txt', type: 'dir', size: 0},
|
|
51
|
+
{name: 'notes.md', type: 'file', size: 400},
|
|
52
|
+
{name: 'zeta.js', type: 'file', size: 1000},
|
|
53
|
+
{name: 'config', type: 'dir', size: 0},
|
|
54
|
+
];
|
|
55
|
+
const expected = [
|
|
56
|
+
{name: 'build.foo.js', type: 'dir', size: 0},
|
|
57
|
+
{name: 'build.foo.txt', type: 'dir', size: 0},
|
|
58
|
+
{name: 'build', type: 'dir', size: 0},
|
|
59
|
+
{name: 'build.foo.md', type: 'dir', size: 0},
|
|
60
|
+
{name: 'config', type: 'dir', size: 0},
|
|
61
|
+
{name: 'docs', type: 'dir', size: 0},
|
|
62
|
+
{name: 'src', type: 'dir', size: 0},
|
|
63
|
+
{name: 'alpha.js', type: 'file', size: 1500},
|
|
64
|
+
{name: 'beta.js', type: 'file', size: 1600},
|
|
65
|
+
{name: 'zeta.js', type: 'file', size: 1000},
|
|
66
|
+
{name: 'readme.txt', type: 'file', size: 800},
|
|
67
|
+
{name: 'zeta.txt', type: 'file', size: 1200},
|
|
68
|
+
{name: 'notes.md', type: 'file', size: 400},
|
|
69
|
+
];
|
|
70
|
+
array_sort(files, v => [pri_start(v.type, ['dir']), pri_start(fs_path_extname(v.name), ['.js', '.txt']), v.name])
|
|
71
|
+
assert.deepStrictEqual(files, expected);
|
|
9
72
|
});
|
|
10
73
|
});
|
|
74
|
+
|
|
75
|
+
// priorities.indexOf(v) >>> 0
|
|
76
|
+
function pri_start(value, array)
|
|
77
|
+
{
|
|
78
|
+
const i = array.indexOf(value);
|
|
79
|
+
return i === -1 ? array.length : i;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pri_end(value, array)
|
|
83
|
+
{
|
|
84
|
+
return array.indexOf(value);
|
|
85
|
+
}
|
package/src/array_sort_other.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const fcmp_default = require('./fcmp_default');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Sort items in an `array` at the same order as in `other`. Values which
|
|
@@ -10,7 +10,7 @@ const fcmp_strings = require('./fcmp_strings');
|
|
|
10
10
|
* @param fcmp
|
|
11
11
|
* @returns {*}
|
|
12
12
|
*/
|
|
13
|
-
function array_sort_other(array, fn, other, fcmp =
|
|
13
|
+
function array_sort_other(array, fn, other, fcmp = fcmp_default)
|
|
14
14
|
{
|
|
15
15
|
const other_map = {};
|
|
16
16
|
other.forEach((v,i) => other_map[v] = i + 1);
|
package/src/array_unique.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
const identity = require('./identity');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return unique values; if a value occurs multiple times, keep the first one.
|
|
5
|
+
*/
|
|
6
|
+
function array_unique(values, fn = identity)
|
|
2
7
|
{
|
|
3
8
|
const set = new Set();
|
|
4
9
|
return values.filter(function (item) {
|
|
5
|
-
|
|
10
|
+
const key = fn(item);
|
|
11
|
+
if (set.has(key)) {
|
|
6
12
|
return false;
|
|
7
13
|
}
|
|
8
|
-
set.add(
|
|
14
|
+
set.add(key);
|
|
9
15
|
return true;
|
|
10
16
|
});
|
|
11
17
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Return unique values; if a value occurs multiple times, keep the first one.
|
package/src/array_unique_last.js
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
|
+
const identity = require('./identity');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Return
|
|
3
|
-
*
|
|
4
|
-
* @param array
|
|
5
|
-
* @param fn
|
|
6
|
-
* @returns {*[]}
|
|
4
|
+
* Return unique values; if a value occurs multiple times, keep the last one.
|
|
7
5
|
*/
|
|
8
6
|
function array_unique_last(array, fn = identity)
|
|
9
7
|
{
|
|
10
8
|
const out = [];
|
|
11
|
-
const
|
|
9
|
+
const set = new Set();
|
|
12
10
|
for (let i = array.length; --i >= 0; ) {
|
|
13
11
|
const item = array[i];
|
|
14
|
-
const
|
|
15
|
-
if (!
|
|
16
|
-
|
|
12
|
+
const key = fn(item);
|
|
13
|
+
if (!set.has(key)) {
|
|
14
|
+
set.add(key);
|
|
17
15
|
out.push(item);
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
18
|
return out.reverse();
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
function identity(value)
|
|
24
|
-
{
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
module.exports = array_unique_last;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Return unique values; if a value occurs multiple times, keep the last one.
|
package/src/browser/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
1
|
+
const ns = new URL(document.currentScript.src).searchParams.get('var') ?? 'h';
|
|
2
|
+
if (typeof window[ns] !== 'undefined') {
|
|
3
|
+
console.log(`❌ @vbarbarosh/node-helpers@${__VERSION__} was not injected — window.${ns} is already in use`);
|
|
4
|
+
}
|
|
5
|
+
else {
|
|
6
|
+
console.log(`🎉 @vbarbarosh/node-helpers@${__VERSION__} successfully exposed as window.${ns}`);
|
|
7
|
+
window[ns] = {};
|
|
8
|
+
// https://github.com/webpack/webpack/issues/625
|
|
9
|
+
// https://webpack.js.org/guides/dependency-management/#require-context
|
|
10
|
+
const require_tmp = require.context('..', true, /\b(array|date|http_delete|http_get_blob|http_get_buffer|http_get_json|http_get_utf8|http_head|http_patch_json|http_post_json|http_post_multipart|http_put_buffer|http_put_json|http_put_utf8|identity|ignore|fcmp|filter|format|plural|random_int|urlmod|waitcb)[^/]*(?<!\.test)\.js$/);
|
|
11
|
+
require_tmp.keys().forEach(function (key) {
|
|
12
|
+
const [, basename] = key.match(/([^/]+)\.js$/);
|
|
13
|
+
window[ns][basename] = require_tmp(key);
|
|
14
|
+
});
|
|
15
|
+
}
|
package/src/countdown.js
CHANGED
|
@@ -13,7 +13,7 @@ function countdown(ctx)
|
|
|
13
13
|
let timer = null;
|
|
14
14
|
return new Promise(function (resolve, reject) {
|
|
15
15
|
ctx.value = ctx.fn ? Promise.method(ctx.fn).call() : Promise.resolve(ctx.value);
|
|
16
|
-
ctx.timeout = ctx.timeout || 0;
|
|
16
|
+
ctx.timeout = ctx.timeout || 0; // ⚠️ Rename to timeout_ms
|
|
17
17
|
ctx.time_now = new Date();
|
|
18
18
|
ctx.time_begin = new Date();
|
|
19
19
|
ctx.time_end = Date.now() + ctx.timeout;
|