@vbarbarosh/node-helpers 3.71.0 → 3.72.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 +1 -1
- package/dist/browser.js +289 -13
- package/package.json +8 -2
- package/src/HeartbeatServer.js +38 -10
- package/src/HeartbeatServer.test.js +31 -0
- package/src/array_gcd.js +3 -10
- package/src/array_gcd.md +2 -0
- package/src/array_lcm.js +8 -0
- package/src/array_lcm.md +2 -0
- package/src/array_lcm.test.js +12 -0
- package/src/array_max.md +2 -1
- package/src/array_min.md +2 -1
- package/src/cli-apps/watchdog.d/connect_close.js +17 -0
- package/src/cli-apps/watchdog.d/connect_close2.js +20 -0
- package/src/cli-apps/watchdog.d/connect_close3.js +25 -0
- package/src/cli-apps/watchdog.d/connect_no_ping.js +13 -0
- package/src/cli-apps/watchdog.d/early_exit.js +7 -0
- package/src/cli-apps/watchdog.d/flood.js +13 -0
- package/src/cli-apps/watchdog.d/flood2.js +15 -0
- package/src/cli-apps/watchdog.d/hang.js +8 -0
- package/src/cli-apps/watchdog.d/huge_delay.js +18 -0
- package/src/cli-apps/watchdog.d/ignore_sigterm.js +29 -0
- package/src/cli-apps/watchdog.d/late_heartbeat.js +21 -0
- package/src/cli-apps/watchdog.d/my-exit-after-2-pings.js +16 -0
- package/src/cli-apps/watchdog.d/my-happy-path.js +16 -0
- package/src/cli-apps/watchdog.d/my-ignore-sigterm.js +22 -0
- package/src/cli-apps/watchdog.d/my-never-ping.js +15 -0
- package/src/cli-apps/watchdog.d/no_heartbeat.js +5 -0
- package/src/cli-apps/watchdog.d/ok.js +17 -0
- package/src/cli-apps/watchdog.d/spawn_children.js +14 -0
- package/src/cli-apps/watchdog.js +146 -0
- package/src/cli.js +11 -3
- package/src/errors/ExitCodeError.js +9 -0
- package/src/fs_assert_file_socket.js +11 -0
- package/src/math_gcd.js +10 -0
- package/src/math_gcd.md +2 -0
- package/src/math_lcm.js +8 -0
- package/src/math_lcm.md +2 -0
- package/src/parse_hms.js +43 -0
- package/src/parse_hms.md +9 -0
- package/src/parse_hms.test.js +32 -0
- package/src/pgid_exists.js +14 -0
- package/src/pgid_kill_grace.js +55 -0
- package/src/pid_exists.js +1 -0
- package/src/pid_kill_grace.d/ignore-sigterm-for-10ms.js +33 -0
- package/src/pid_kill_grace.d/ignore-sigterm.js +22 -0
- package/src/pid_kill_grace.js +35 -9
- package/src/pid_kill_grace.test.js +63 -0
- package/src/ping_socket.js +8 -8
- package/src/ping_socket.test.js +58 -0
- package/src/range.js +44 -0
- package/src/range.md +15 -0
- package/src/range.test.js +41 -0
- package/src/shell_spawn.js +26 -1
- package/src/strtr.js +15 -0
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.
|
|
23
|
+
<script src="https://unpkg.com/@vbarbarosh/node-helpers@3.72.0/dist/browser.js"></script>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
⚠️ All browser functions are exposed globally.
|
package/dist/browser.js
CHANGED
|
@@ -84,6 +84,7 @@ var map = {
|
|
|
84
84
|
"./array_group.js": "./src/array_group.js",
|
|
85
85
|
"./array_group_map.js": "./src/array_group_map.js",
|
|
86
86
|
"./array_index.js": "./src/array_index.js",
|
|
87
|
+
"./array_lcm.js": "./src/array_lcm.js",
|
|
87
88
|
"./array_max.js": "./src/array_max.js",
|
|
88
89
|
"./array_min.js": "./src/array_min.js",
|
|
89
90
|
"./array_permutations.js": "./src/array_permutations.js",
|
|
@@ -93,6 +94,8 @@ var map = {
|
|
|
93
94
|
"./array_sum.js": "./src/array_sum.js",
|
|
94
95
|
"./array_unique.js": "./src/array_unique.js",
|
|
95
96
|
"./array_unique_last.js": "./src/array_unique_last.js",
|
|
97
|
+
"./cli-apps/watchdog.d/ignore_sigterm.js": "./src/cli-apps/watchdog.d/ignore_sigterm.js",
|
|
98
|
+
"./cli-apps/watchdog.d/my-ignore-sigterm.js": "./src/cli-apps/watchdog.d/my-ignore-sigterm.js",
|
|
96
99
|
"./date_add_hours.js": "./src/date_add_hours.js",
|
|
97
100
|
"./date_add_milliseconds.js": "./src/date_add_milliseconds.js",
|
|
98
101
|
"./date_add_minutes.js": "./src/date_add_minutes.js",
|
|
@@ -143,6 +146,8 @@ var map = {
|
|
|
143
146
|
"./http_put_utf8.js": "./src/http_put_utf8.js",
|
|
144
147
|
"./identity.js": "./src/identity.js",
|
|
145
148
|
"./ignore.js": "./src/ignore.js",
|
|
149
|
+
"./pid_kill_grace.d/ignore-sigterm-for-10ms.js": "./src/pid_kill_grace.d/ignore-sigterm-for-10ms.js",
|
|
150
|
+
"./pid_kill_grace.d/ignore-sigterm.js": "./src/pid_kill_grace.d/ignore-sigterm.js",
|
|
146
151
|
"./plural.js": "./src/plural.js",
|
|
147
152
|
"./random_int.js": "./src/random_int.js",
|
|
148
153
|
"./urlmod.js": "./src/urlmod.js",
|
|
@@ -206,21 +211,14 @@ module.exports = array_chunk;
|
|
|
206
211
|
/*!**************************!*\
|
|
207
212
|
!*** ./src/array_gcd.js ***!
|
|
208
213
|
\**************************/
|
|
209
|
-
/***/ ((module) => {
|
|
214
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
210
215
|
|
|
211
|
-
|
|
212
|
-
function array_gcd(array, fn = Number)
|
|
213
|
-
{
|
|
214
|
-
return array.reduce((a,b) => gcd(fn(a), fn(b)));
|
|
215
|
-
}
|
|
216
|
+
const math_gcd = __webpack_require__(/*! ./math_gcd */ "./src/math_gcd.js");
|
|
216
217
|
|
|
217
218
|
// https://stackoverflow.com/a/39764792/1478566
|
|
218
|
-
function
|
|
219
|
+
function array_gcd(array, fn = Number)
|
|
219
220
|
{
|
|
220
|
-
|
|
221
|
-
return gcd(b, a % b);
|
|
222
|
-
}
|
|
223
|
-
return a;
|
|
221
|
+
return array.reduce((a,b) => math_gcd(fn(a), fn(b)));
|
|
224
222
|
}
|
|
225
223
|
|
|
226
224
|
module.exports = array_gcd;
|
|
@@ -311,6 +309,24 @@ function array_index(array, fn = identity)
|
|
|
311
309
|
module.exports = array_index;
|
|
312
310
|
|
|
313
311
|
|
|
312
|
+
/***/ }),
|
|
313
|
+
|
|
314
|
+
/***/ "./src/array_lcm.js":
|
|
315
|
+
/*!**************************!*\
|
|
316
|
+
!*** ./src/array_lcm.js ***!
|
|
317
|
+
\**************************/
|
|
318
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
319
|
+
|
|
320
|
+
const math_lcm = __webpack_require__(/*! ./math_lcm */ "./src/math_lcm.js");
|
|
321
|
+
|
|
322
|
+
function array_lcm(array, fn = Number)
|
|
323
|
+
{
|
|
324
|
+
return array.reduce((a,b) => math_lcm(fn(a), fn(b)));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = array_lcm;
|
|
328
|
+
|
|
329
|
+
|
|
314
330
|
/***/ }),
|
|
315
331
|
|
|
316
332
|
/***/ "./src/array_max.js":
|
|
@@ -632,6 +648,116 @@ function array_unique_last(array, fn = identity)
|
|
|
632
648
|
module.exports = array_unique_last;
|
|
633
649
|
|
|
634
650
|
|
|
651
|
+
/***/ }),
|
|
652
|
+
|
|
653
|
+
/***/ "./src/cli-apps/watchdog.d/ignore_sigterm.js":
|
|
654
|
+
/*!***************************************************!*\
|
|
655
|
+
!*** ./src/cli-apps/watchdog.d/ignore_sigterm.js ***!
|
|
656
|
+
\***************************************************/
|
|
657
|
+
/***/ (() => {
|
|
658
|
+
|
|
659
|
+
//#!/usr/bin/env node
|
|
660
|
+
|
|
661
|
+
// 1️⃣ Client that ignores SIGTERM
|
|
662
|
+
// Goal: process stays alive after SIGTERM and only dies on SIGKILL (exactly what pid_kill_grace should handle).
|
|
663
|
+
//
|
|
664
|
+
// What this tests for watchdog:
|
|
665
|
+
// - watchdog sends SIGTERM → process prints “ignored” and keeps running.
|
|
666
|
+
// - pid_kill_grace waits grace_timeout_ms, sees PID still alive, sends SIGKILL.
|
|
667
|
+
// - Process must die after SIGKILL.
|
|
668
|
+
// - Watchdog should log the whole sequence correctly.
|
|
669
|
+
// This is a realistic scenario.
|
|
670
|
+
|
|
671
|
+
console.log('PID:', process.pid);
|
|
672
|
+
|
|
673
|
+
// Ignore SIGTERM
|
|
674
|
+
process.on('SIGTERM', function () {
|
|
675
|
+
console.log('SIGTERM received, but intentionally ignored');
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Still react to SIGINT (Ctrl+C) so you can stop it manually if needed
|
|
679
|
+
process.on('SIGINT', function () {
|
|
680
|
+
console.log('SIGINT received, exiting');
|
|
681
|
+
process.exit(130);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Keep process alive
|
|
685
|
+
setInterval(() => {
|
|
686
|
+
// simulate work
|
|
687
|
+
}, 1000);
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
/***/ }),
|
|
691
|
+
|
|
692
|
+
/***/ "./src/cli-apps/watchdog.d/my-ignore-sigterm.js":
|
|
693
|
+
/*!******************************************************!*\
|
|
694
|
+
!*** ./src/cli-apps/watchdog.d/my-ignore-sigterm.js ***!
|
|
695
|
+
\******************************************************/
|
|
696
|
+
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
|
697
|
+
|
|
698
|
+
//#!/usr/bin/env node
|
|
699
|
+
|
|
700
|
+
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
701
|
+
const cli = __webpack_require__(/*! @vbarbarosh/node-helpers/src/cli */ "./src/cli.js");
|
|
702
|
+
const now_human = __webpack_require__(/*! @vbarbarosh/node-helpers/src/now_human */ "./src/now_human.js");
|
|
703
|
+
|
|
704
|
+
cli(main);
|
|
705
|
+
|
|
706
|
+
async function main()
|
|
707
|
+
{
|
|
708
|
+
process.on('SIGTERM', function () {
|
|
709
|
+
console.log(`[${now_human()}][ignore-sigterm] SIGTERM, ignoring...`);
|
|
710
|
+
});
|
|
711
|
+
process.on('SIGINT', function () {
|
|
712
|
+
console.log(`[${now_human()}][ignore-sigterm] SIGINT, ignoring...`);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
for (let iter = 1, last = 100; iter <= last; ++iter) {
|
|
716
|
+
console.log(`[${now_human()}][ignore-sigterm] ${iter} of ${last}`);
|
|
717
|
+
await Promise.delay(100);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
/***/ }),
|
|
723
|
+
|
|
724
|
+
/***/ "./src/cli.js":
|
|
725
|
+
/*!********************!*\
|
|
726
|
+
!*** ./src/cli.js ***!
|
|
727
|
+
\********************/
|
|
728
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
729
|
+
|
|
730
|
+
const ExitCodeError = __webpack_require__(/*! ./errors/ExitCodeError */ "./src/errors/ExitCodeError.js");
|
|
731
|
+
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Entry point for Node CLI apps
|
|
735
|
+
*/
|
|
736
|
+
function cli(main, report = error => console.error(error))
|
|
737
|
+
{
|
|
738
|
+
// https://stackoverflow.com/a/46916601/1478566
|
|
739
|
+
const timer = setInterval(v => v, 1E9);
|
|
740
|
+
|
|
741
|
+
Promise.resolve(main()).then(resolve, reject);
|
|
742
|
+
|
|
743
|
+
function resolve() {
|
|
744
|
+
clearInterval(timer);
|
|
745
|
+
}
|
|
746
|
+
function reject(error) {
|
|
747
|
+
clearInterval(timer);
|
|
748
|
+
report(error);
|
|
749
|
+
if (error instanceof ExitCodeError) {
|
|
750
|
+
process.exit(error.exit_code);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
module.exports = cli;
|
|
759
|
+
|
|
760
|
+
|
|
635
761
|
/***/ }),
|
|
636
762
|
|
|
637
763
|
/***/ "./src/date_add_hours.js":
|
|
@@ -775,6 +901,25 @@ function date_is_leap_year(d)
|
|
|
775
901
|
module.exports = date_is_leap_year;
|
|
776
902
|
|
|
777
903
|
|
|
904
|
+
/***/ }),
|
|
905
|
+
|
|
906
|
+
/***/ "./src/errors/ExitCodeError.js":
|
|
907
|
+
/*!*************************************!*\
|
|
908
|
+
!*** ./src/errors/ExitCodeError.js ***!
|
|
909
|
+
\*************************************/
|
|
910
|
+
/***/ ((module) => {
|
|
911
|
+
|
|
912
|
+
class ExitCodeError extends Error
|
|
913
|
+
{
|
|
914
|
+
constructor(exit_code, message = '') {
|
|
915
|
+
super(message);
|
|
916
|
+
this.exit_code = exit_code;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
module.exports = ExitCodeError;
|
|
921
|
+
|
|
922
|
+
|
|
778
923
|
/***/ }),
|
|
779
924
|
|
|
780
925
|
/***/ "./src/factorize_ms.js":
|
|
@@ -2036,6 +2181,137 @@ const json_stringify_safe = __webpack_require__(/*! json-stringify-safe */ "./no
|
|
|
2036
2181
|
module.exports = json_stringify_safe;
|
|
2037
2182
|
|
|
2038
2183
|
|
|
2184
|
+
/***/ }),
|
|
2185
|
+
|
|
2186
|
+
/***/ "./src/math_gcd.js":
|
|
2187
|
+
/*!*************************!*\
|
|
2188
|
+
!*** ./src/math_gcd.js ***!
|
|
2189
|
+
\*************************/
|
|
2190
|
+
/***/ ((module) => {
|
|
2191
|
+
|
|
2192
|
+
// https://stackoverflow.com/a/39764792/1478566
|
|
2193
|
+
function math_gcd(a, b)
|
|
2194
|
+
{
|
|
2195
|
+
if (b) {
|
|
2196
|
+
return math_gcd(b, a % b);
|
|
2197
|
+
}
|
|
2198
|
+
return a;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
module.exports = math_gcd;
|
|
2202
|
+
|
|
2203
|
+
|
|
2204
|
+
/***/ }),
|
|
2205
|
+
|
|
2206
|
+
/***/ "./src/math_lcm.js":
|
|
2207
|
+
/*!*************************!*\
|
|
2208
|
+
!*** ./src/math_lcm.js ***!
|
|
2209
|
+
\*************************/
|
|
2210
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2211
|
+
|
|
2212
|
+
const math_gcd = __webpack_require__(/*! ./math_gcd */ "./src/math_gcd.js");
|
|
2213
|
+
|
|
2214
|
+
function math_lcm(a, b)
|
|
2215
|
+
{
|
|
2216
|
+
return Math.abs(a*b) / math_gcd(a, b);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
module.exports = math_lcm;
|
|
2220
|
+
|
|
2221
|
+
|
|
2222
|
+
/***/ }),
|
|
2223
|
+
|
|
2224
|
+
/***/ "./src/now_human.js":
|
|
2225
|
+
/*!**************************!*\
|
|
2226
|
+
!*** ./src/now_human.js ***!
|
|
2227
|
+
\**************************/
|
|
2228
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2229
|
+
|
|
2230
|
+
const format_date_human = __webpack_require__(/*! ./format_date_human */ "./src/format_date_human.js");
|
|
2231
|
+
|
|
2232
|
+
function now_human()
|
|
2233
|
+
{
|
|
2234
|
+
return format_date_human(new Date());
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
module.exports = now_human;
|
|
2238
|
+
|
|
2239
|
+
|
|
2240
|
+
/***/ }),
|
|
2241
|
+
|
|
2242
|
+
/***/ "./src/pid_kill_grace.d/ignore-sigterm-for-10ms.js":
|
|
2243
|
+
/*!*********************************************************!*\
|
|
2244
|
+
!*** ./src/pid_kill_grace.d/ignore-sigterm-for-10ms.js ***!
|
|
2245
|
+
\*********************************************************/
|
|
2246
|
+
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
|
2247
|
+
|
|
2248
|
+
//#!/usr/bin/env node
|
|
2249
|
+
|
|
2250
|
+
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
2251
|
+
const cli = __webpack_require__(/*! ../cli */ "./src/cli.js");
|
|
2252
|
+
const now_human = __webpack_require__(/*! ../now_human */ "./src/now_human.js");
|
|
2253
|
+
|
|
2254
|
+
// Child ignores SIGTERM for 10 ms, but dies naturally before the grace loop ends
|
|
2255
|
+
|
|
2256
|
+
cli(main);
|
|
2257
|
+
|
|
2258
|
+
async function main()
|
|
2259
|
+
{
|
|
2260
|
+
let timer;
|
|
2261
|
+
|
|
2262
|
+
process.on('SIGTERM', function () {
|
|
2263
|
+
console.log(`[${now_human()}][ignore-sigterm-for-10ms] SIGTERM_ignoring...`);
|
|
2264
|
+
timer ??= setTimeout(terminate, 10);
|
|
2265
|
+
});
|
|
2266
|
+
process.on('SIGINT', function () {
|
|
2267
|
+
console.log(`[${now_human()}][ignore-sigterm-for-10ms] SIGINT_ignoring...`);
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
for (let iter = 1, last = 100; iter <= last; ++iter) {
|
|
2271
|
+
console.log(`[${now_human()}][ignore-sigterm-for-10ms] ${iter} of ${last}`);
|
|
2272
|
+
await Promise.delay(10);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
function terminate()
|
|
2277
|
+
{
|
|
2278
|
+
console.log(`[${now_human()}][ignore-sigterm-for-10ms] TERMINATE_AFTER_10MS`);
|
|
2279
|
+
process.exit(0);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
|
|
2283
|
+
/***/ }),
|
|
2284
|
+
|
|
2285
|
+
/***/ "./src/pid_kill_grace.d/ignore-sigterm.js":
|
|
2286
|
+
/*!************************************************!*\
|
|
2287
|
+
!*** ./src/pid_kill_grace.d/ignore-sigterm.js ***!
|
|
2288
|
+
\************************************************/
|
|
2289
|
+
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
|
2290
|
+
|
|
2291
|
+
//#!/usr/bin/env node
|
|
2292
|
+
|
|
2293
|
+
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
2294
|
+
const cli = __webpack_require__(/*! ../cli */ "./src/cli.js");
|
|
2295
|
+
const now_human = __webpack_require__(/*! ../now_human */ "./src/now_human.js");
|
|
2296
|
+
|
|
2297
|
+
cli(main);
|
|
2298
|
+
|
|
2299
|
+
async function main()
|
|
2300
|
+
{
|
|
2301
|
+
process.on('SIGTERM', function () {
|
|
2302
|
+
console.log(`[${now_human()}][ignore-sigterm] SIGTERM_ignoring...`);
|
|
2303
|
+
});
|
|
2304
|
+
process.on('SIGINT', function () {
|
|
2305
|
+
console.log(`[${now_human()}][ignore-sigterm] SIGINT_ignoring...`);
|
|
2306
|
+
});
|
|
2307
|
+
|
|
2308
|
+
for (let iter = 1, last = 100; iter <= last; ++iter) {
|
|
2309
|
+
console.log(`[${now_human()}][ignore-sigterm] ${iter} of ${last}`);
|
|
2310
|
+
await Promise.delay(10);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
|
|
2039
2315
|
/***/ }),
|
|
2040
2316
|
|
|
2041
2317
|
/***/ "./src/plural.js":
|
|
@@ -2223,10 +2499,10 @@ var __webpack_exports__ = {};
|
|
|
2223
2499
|
\******************************/
|
|
2224
2500
|
const ns = new URL(document.currentScript.src).searchParams.get('var') ?? 'h';
|
|
2225
2501
|
if (typeof window[ns] !== 'undefined') {
|
|
2226
|
-
console.log(`❌ @vbarbarosh/node-helpers@${"3.
|
|
2502
|
+
console.log(`❌ @vbarbarosh/node-helpers@${"3.72.0"} was not injected — window.${ns} is already in use`);
|
|
2227
2503
|
}
|
|
2228
2504
|
else {
|
|
2229
|
-
console.log(`🎉 @vbarbarosh/node-helpers@${"3.
|
|
2505
|
+
console.log(`🎉 @vbarbarosh/node-helpers@${"3.72.0"} successfully exposed as window.${ns}`);
|
|
2230
2506
|
window[ns] = {};
|
|
2231
2507
|
// https://github.com/webpack/webpack/issues/625
|
|
2232
2508
|
// https://webpack.js.org/guides/dependency-management/#require-context
|
package/package.json
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
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.72.0",
|
|
6
6
|
"description": "A set of helpers for JavaScript/Node.js",
|
|
7
7
|
"unpkg": "dist/browser.js",
|
|
8
8
|
"jsdelivr": "dist/browser.js",
|
|
9
9
|
"homepage": "https://vbarbarosh.github.io/node-helpers/",
|
|
10
|
+
"bin": {
|
|
11
|
+
"watchdog": "src/cli-apps/watchdog.js"
|
|
12
|
+
},
|
|
10
13
|
"files": [
|
|
11
14
|
"dist",
|
|
12
15
|
"src"
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
"url": "https://github.com/vbarbarosh/node-helpers/issues"
|
|
29
32
|
},
|
|
30
33
|
"peerDependencies": {
|
|
34
|
+
"@paralleldrive/cuid2": "^3.0.4",
|
|
31
35
|
"@vbarbarosh/type-helpers": "^0.2.0",
|
|
32
36
|
"axios": "^1.7.9",
|
|
33
37
|
"bluebird": "^3.7.2",
|
|
@@ -38,7 +42,9 @@
|
|
|
38
42
|
"json-stringify-safe": "^5.0.1",
|
|
39
43
|
"mime-types": "^2.1.35",
|
|
40
44
|
"sanitize-filename": "^1.6.3",
|
|
41
|
-
"split": "^1.0.1"
|
|
45
|
+
"split": "^1.0.1"
|
|
46
|
+
},
|
|
47
|
+
"optionalDependencies": {
|
|
42
48
|
"ssh2": "^1.16.0"
|
|
43
49
|
},
|
|
44
50
|
"devDependencies": {
|
package/src/HeartbeatServer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
|
+
const ExitCodeError = require('./errors/ExitCodeError');
|
|
2
3
|
const Promise = require('bluebird');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const fs_path_join = require('./fs_path_join');
|
|
@@ -7,6 +8,7 @@ const net = require('net');
|
|
|
7
8
|
const now_fs = require('./now_fs');
|
|
8
9
|
const os = require('os');
|
|
9
10
|
|
|
11
|
+
// 💎 Only the lack of a valid PING within WATCHDOG_INTERVAL is fatal
|
|
10
12
|
class HeartbeatServer extends EventEmitter
|
|
11
13
|
{
|
|
12
14
|
#socket_path;
|
|
@@ -37,36 +39,52 @@ class HeartbeatServer extends EventEmitter
|
|
|
37
39
|
this.#reject = reject;
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
this.#server = net.createServer(function (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
this.#server = net.createServer(function (client) {
|
|
43
|
+
let bytes_received = 0;
|
|
44
|
+
client.setEncoding('utf8');
|
|
45
|
+
client.on('error', function (error) {
|
|
46
|
+
if (!_this.#server) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
_this.emit('warning', error);
|
|
44
50
|
});
|
|
45
|
-
|
|
51
|
+
client.on('end', function () {
|
|
52
|
+
if (!_this.#server) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
46
55
|
_this.#last_ping = Date.now();
|
|
47
56
|
_this.emit('heartbeat');
|
|
48
57
|
});
|
|
49
|
-
|
|
58
|
+
client.on('data', function (buf) {
|
|
59
|
+
if (!_this.#server) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
50
62
|
// Not really interested in any data
|
|
63
|
+
bytes_received += buf.length;
|
|
64
|
+
if (bytes_received > 1024*1024) {
|
|
65
|
+
_this.emit('warning', new Error(`Client floods. Bytes recieved: ${bytes_received}`));
|
|
66
|
+
client.destroy();
|
|
67
|
+
}
|
|
51
68
|
});
|
|
52
|
-
|
|
69
|
+
client.end('OK');
|
|
53
70
|
});
|
|
54
71
|
this.#server.on('error', function (error) {
|
|
55
72
|
_this.#reject(new Error(`Server Failed: ${error.message}`));
|
|
56
73
|
});
|
|
57
74
|
this.#server.on('close', function () {
|
|
58
|
-
_this.#reject(new Error(
|
|
75
|
+
_this.#reject(new Error('Server Closed'));
|
|
59
76
|
});
|
|
60
77
|
this.#server.listen(this.#socket_path, function () {
|
|
61
78
|
// started = true;
|
|
62
79
|
});
|
|
63
80
|
}
|
|
64
81
|
|
|
65
|
-
#tick() {
|
|
82
|
+
async #tick() {
|
|
66
83
|
if (this.#last_ping + this.#interval_ms < Date.now()) {
|
|
67
84
|
clearInterval(this.#timer);
|
|
68
85
|
this.#timer = null;
|
|
69
|
-
this.#reject(new
|
|
86
|
+
this.#reject(new ExitCodeError(124, `No heartbeat for the last ${this.#interval_ms}ms`));
|
|
87
|
+
await this.dispose();
|
|
70
88
|
}
|
|
71
89
|
}
|
|
72
90
|
|
|
@@ -78,20 +96,30 @@ class HeartbeatServer extends EventEmitter
|
|
|
78
96
|
return this.#promise;
|
|
79
97
|
}
|
|
80
98
|
|
|
99
|
+
// - Stops the watchdog
|
|
100
|
+
// - Closes socket
|
|
101
|
+
// - Removes socket file
|
|
102
|
+
// - Rejects the internal promise with a "Server Closed" error
|
|
103
|
+
// - Safe to call multiple times (idempotent)
|
|
81
104
|
async dispose() {
|
|
82
105
|
if (this.#timer) {
|
|
83
106
|
clearInterval(this.#timer);
|
|
84
107
|
this.#timer = null;
|
|
85
108
|
}
|
|
109
|
+
if (!this.#server) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
86
112
|
try {
|
|
87
113
|
const _this = this;
|
|
88
114
|
await new Promise(function (resolve, reject) {
|
|
89
115
|
_this.#server.removeAllListeners();
|
|
90
116
|
_this.#server.close(error => error ? reject(error) : resolve());
|
|
117
|
+
_this.#server = null;
|
|
91
118
|
});
|
|
92
119
|
}
|
|
93
120
|
finally {
|
|
94
121
|
await fs_rmf(this.#socket_path);
|
|
122
|
+
this.#socket_path = null;
|
|
95
123
|
}
|
|
96
124
|
}
|
|
97
125
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const HeartbeatServer = require('./HeartbeatServer');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const fs_assert_file_socket = require('./fs_assert_file_socket');
|
|
4
|
+
const ping_socket = require('./ping_socket');
|
|
5
|
+
|
|
6
|
+
// 💎 Only the lack of a valid PING within WATCHDOG_INTERVAL is fatal
|
|
7
|
+
|
|
8
|
+
// 📕 Notes by ChatGPT
|
|
9
|
+
// If listen() fails (EADDRINUSE, perms, etc.), constructor will throw,
|
|
10
|
+
// and heartbeat_server will be undefined in finally { await heartbeat_server.dispose() }.
|
|
11
|
+
|
|
12
|
+
describe('HeartbeatServer', function () {
|
|
13
|
+
it('Happy Path', async function () {
|
|
14
|
+
const server = new HeartbeatServer();
|
|
15
|
+
try {
|
|
16
|
+
let counter = 0;
|
|
17
|
+
server.on('heartbeat', () => counter++);
|
|
18
|
+
await fs_assert_file_socket(server.socket_path);
|
|
19
|
+
await ping_socket(server.socket_path);
|
|
20
|
+
await ping_socket(server.socket_path);
|
|
21
|
+
assert.strictEqual(counter, 2);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await server.dispose();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// should emit a "warning" event on any client errors
|
|
29
|
+
// should not send any heartbeat signals after rejection (decision to kill a child process)
|
|
30
|
+
// should reject when no heartbeat received in an expected interval
|
|
31
|
+
});
|
package/src/array_gcd.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
function array_gcd(array, fn = Number)
|
|
3
|
-
{
|
|
4
|
-
return array.reduce((a,b) => gcd(fn(a), fn(b)));
|
|
5
|
-
}
|
|
1
|
+
const math_gcd = require('./math_gcd');
|
|
6
2
|
|
|
7
3
|
// https://stackoverflow.com/a/39764792/1478566
|
|
8
|
-
function
|
|
4
|
+
function array_gcd(array, fn = Number)
|
|
9
5
|
{
|
|
10
|
-
|
|
11
|
-
return gcd(b, a % b);
|
|
12
|
-
}
|
|
13
|
-
return a;
|
|
6
|
+
return array.reduce((a,b) => math_gcd(fn(a), fn(b)));
|
|
14
7
|
}
|
|
15
8
|
|
|
16
9
|
module.exports = array_gcd;
|
package/src/array_gcd.md
ADDED
package/src/array_lcm.js
ADDED
package/src/array_lcm.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const array_lcm = require('./array_lcm');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('array_lcm', function () {
|
|
5
|
+
it('should handle basic input', function () {
|
|
6
|
+
assert.deepStrictEqual(array_lcm([1,2,3,4,5]), 60);
|
|
7
|
+
assert.deepStrictEqual(array_lcm([15,25]), 75);
|
|
8
|
+
assert.deepStrictEqual(array_lcm([4,6,8]), 24);
|
|
9
|
+
assert.deepStrictEqual(array_lcm([4,8,16]), 16);
|
|
10
|
+
assert.deepStrictEqual(array_lcm([300,100,200,300]), 600);
|
|
11
|
+
});
|
|
12
|
+
});
|
package/src/array_max.md
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
Returns the first element
|
|
1
|
+
Returns the first element of the array for which `fn(item)` is maximized.
|
|
2
|
+
If multiple elements have the same maximal weight, the earliest one is returned.
|
package/src/array_min.md
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
Returns the first element
|
|
1
|
+
Returns the first element of the array for which `fn(item)` is minimized.
|
|
2
|
+
If multiple elements have the same minimal weight, the earliest one is returned.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// 9. Client that connects but sends absolutely NO DATA.
|
|
4
|
+
// Watchdog must NOT treat this as a PING.
|
|
5
|
+
|
|
6
|
+
const net = require('net');
|
|
7
|
+
|
|
8
|
+
const SOCKET = process.env.WATCHDOG_SOCKET;
|
|
9
|
+
if (!SOCKET) throw new Error('WATCHDOG_SOCKET missing');
|
|
10
|
+
|
|
11
|
+
// Connect → immediately destroy the socket → NO write events
|
|
12
|
+
const socket = net.connect(SOCKET, () => {
|
|
13
|
+
console.log('connected (no ping), closing immediately');
|
|
14
|
+
socket.destroy(); // IMPORTANT: destroy() does not flush anything
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
setInterval(() => {}, 1000);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// 9. Client that connects but sends absolutely NO DATA.
|
|
4
|
+
// Watchdog must NOT treat this as a PING.
|
|
5
|
+
|
|
6
|
+
const net = require('net');
|
|
7
|
+
|
|
8
|
+
const SOCKET = process.env.WATCHDOG_SOCKET;
|
|
9
|
+
if (!SOCKET) throw new Error('WATCHDOG_SOCKET missing');
|
|
10
|
+
|
|
11
|
+
setInterval(ping, 1000);
|
|
12
|
+
|
|
13
|
+
function ping()
|
|
14
|
+
{
|
|
15
|
+
// Connect → immediately destroy the socket → NO write events
|
|
16
|
+
const socket = net.connect(SOCKET, () => {
|
|
17
|
+
console.log('connected (no ping), closing immediately');
|
|
18
|
+
socket.destroy(); // IMPORTANT: destroy() does not flush anything
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ❌ 11. Client sends heartbeats sporadically (sometimes missing)
|
|
4
|
+
// Simulates flaky client.
|
|
5
|
+
|
|
6
|
+
const net = require('net');
|
|
7
|
+
|
|
8
|
+
const SOCKET = process.env.WATCHDOG_SOCKET;
|
|
9
|
+
if (!SOCKET) throw new Error('WATCHDOG_SOCKET missing');
|
|
10
|
+
|
|
11
|
+
setInterval(ping, 1000);
|
|
12
|
+
|
|
13
|
+
function ping()
|
|
14
|
+
{
|
|
15
|
+
// Connect → immediately destroy the socket → NO write events
|
|
16
|
+
const socket = net.connect(SOCKET, () => {
|
|
17
|
+
if (Math.random() > 0.5) {
|
|
18
|
+
socket.end('PING');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log('connected (no ping), closing immediately');
|
|
22
|
+
socket.destroy(); // IMPORTANT: destroy() does not flush anything
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|