@vbarbarosh/node-helpers 3.75.0 → 3.76.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.
Files changed (367) hide show
  1. package/README.md +29 -3
  2. package/dist/browser.js +1 -2504
  3. package/package.json +31 -11
  4. package/src/HeartbeatServer.js +24 -3
  5. package/src/HeartbeatServer.md +33 -0
  6. package/src/array_chunk.js +1 -1
  7. package/src/array_chunk.md +8 -0
  8. package/src/array_chunk_balanced.md +14 -0
  9. package/src/array_fps.md +12 -0
  10. package/src/array_gcd.js +4 -2
  11. package/src/array_gcd.md +2 -1
  12. package/src/array_group.js +3 -3
  13. package/src/array_group.md +14 -0
  14. package/src/array_group_map.js +3 -3
  15. package/src/array_group_map.md +13 -0
  16. package/src/array_index.js +2 -2
  17. package/src/array_index.md +9 -0
  18. package/src/array_lcm.js +4 -2
  19. package/src/array_lcm.md +2 -1
  20. package/src/array_max.js +11 -4
  21. package/src/array_max.md +1 -1
  22. package/src/array_min.js +11 -4
  23. package/src/array_min.md +1 -1
  24. package/src/array_permutations.js +4 -0
  25. package/src/array_permutations.md +13 -0
  26. package/src/array_sort.js +7 -2
  27. package/src/array_sort.md +13 -0
  28. package/src/array_sort_other.js +8 -5
  29. package/src/array_sort_other.md +11 -0
  30. package/src/array_sum.js +2 -2
  31. package/src/array_sum.md +8 -0
  32. package/src/array_unique.js +2 -2
  33. package/src/array_unique_last.js +2 -2
  34. package/src/assert_valid_expires_at.md +10 -0
  35. package/src/clamp.md +9 -0
  36. package/src/cli-apps/watchdog.js +5 -0
  37. package/src/cli.js +3 -1
  38. package/src/cli.md +15 -0
  39. package/src/const_stream.md +7 -0
  40. package/src/const_type.md +5 -0
  41. package/src/countdown.js +22 -5
  42. package/src/countdown.md +21 -0
  43. package/src/crypto_decrypt_aes256.md +13 -0
  44. package/src/crypto_encrypt_aes256.md +13 -0
  45. package/src/crypto_hash_md5.md +10 -0
  46. package/src/crypto_hash_sha256.md +10 -0
  47. package/src/crypto_hmac_sha256.md +11 -0
  48. package/src/date_add_hours.md +8 -0
  49. package/src/date_add_milliseconds.md +4 -0
  50. package/src/date_add_minutes.md +4 -0
  51. package/src/date_add_months.md +13 -0
  52. package/src/date_add_seconds.md +4 -0
  53. package/src/date_diff_seconds.md +4 -0
  54. package/src/date_is_leap_year.md +9 -0
  55. package/src/escape_content_disposition.js +6 -3
  56. package/src/escape_content_disposition.md +13 -0
  57. package/src/escape_regexp.md +7 -0
  58. package/src/eta.js +7 -1
  59. package/src/eta.md +18 -0
  60. package/src/factorize_ms.js +3 -0
  61. package/src/factorize_ms.md +10 -0
  62. package/src/fastdl.js +14 -3
  63. package/src/fcmp_dates.md +12 -0
  64. package/src/fcmp_default.js +7 -0
  65. package/src/fcmp_default.md +29 -0
  66. package/src/fcmp_default_desc.js +4 -7
  67. package/src/fcmp_default_desc.md +10 -0
  68. package/src/fcmp_from_spec.md +13 -0
  69. package/src/fcmp_numbers.md +11 -0
  70. package/src/fcmp_tuples.md +17 -0
  71. package/src/fcmp_utf8_bin.md +14 -0
  72. package/src/fcmp_utf8_ci.md +14 -0
  73. package/src/fcmp_utf8_cs.md +15 -0
  74. package/src/fcmp_utf8_natural_bin.js +38 -0
  75. package/src/fcmp_utf8_natural_bin.md +8 -0
  76. package/src/fcmp_utf8_natural_ci.md +13 -0
  77. package/src/fcmp_utf8_natural_cs.md +13 -0
  78. package/src/fcmpx.md +26 -9
  79. package/src/filter1_from_spec.js +3 -3
  80. package/src/format_bytes.md +15 -0
  81. package/src/format_date.md +7 -0
  82. package/src/format_date_fs.md +7 -0
  83. package/src/format_date_human.md +6 -0
  84. package/src/format_date_ymd.md +6 -0
  85. package/src/format_error_report.js +3 -3
  86. package/src/format_error_report.md +12 -0
  87. package/src/format_kilo.md +13 -0
  88. package/src/format_ms.js +3 -2
  89. package/src/format_ms.md +12 -0
  90. package/src/format_ms2.js +6 -3
  91. package/src/format_ms2.md +12 -0
  92. package/src/format_ms3.js +3 -2
  93. package/src/format_ms3.md +12 -0
  94. package/src/format_percents.md +10 -0
  95. package/src/format_progress_bytes.md +13 -0
  96. package/src/format_progress_kilo.md +10 -0
  97. package/src/format_seconds.md +11 -0
  98. package/src/format_thousands.md +9 -0
  99. package/src/fs_append.md +7 -0
  100. package/src/fs_append_json.md +8 -0
  101. package/src/fs_append_utf8.md +6 -0
  102. package/src/fs_assert_file_exists.md +3 -0
  103. package/src/fs_assert_file_readable.js +1 -1
  104. package/src/fs_assert_file_readable.md +3 -0
  105. package/src/fs_assert_file_socket.md +2 -0
  106. package/src/fs_copy.md +7 -0
  107. package/src/fs_copy_excl.md +2 -0
  108. package/src/fs_exists.md +6 -0
  109. package/src/fs_fclose.md +5 -0
  110. package/src/fs_fi.js +13 -1
  111. package/src/fs_fi.md +11 -0
  112. package/src/fs_file_md5.md +8 -0
  113. package/src/fs_find.md +7 -0
  114. package/src/fs_fopen.md +6 -0
  115. package/src/fs_fread.js +2 -2
  116. package/src/fs_fread.md +11 -0
  117. package/src/fs_fsize.md +2 -0
  118. package/src/fs_fstat.md +2 -0
  119. package/src/fs_fwrite.js +2 -2
  120. package/src/fs_fwrite.md +9 -0
  121. package/src/fs_list.md +5 -0
  122. package/src/fs_list_deep.md +5 -0
  123. package/src/fs_ls.md +2 -0
  124. package/src/fs_lsr.md +4 -0
  125. package/src/fs_lstat.md +3 -0
  126. package/src/fs_mkdir.md +3 -0
  127. package/src/fs_mkdirp.js +3 -30
  128. package/src/fs_mkdirp.md +7 -0
  129. package/src/fs_path_basename.md +2 -0
  130. package/src/fs_path_dirname.md +2 -0
  131. package/src/fs_path_extname.md +9 -0
  132. package/src/fs_path_join.md +2 -0
  133. package/src/fs_path_relative.md +2 -0
  134. package/src/fs_path_resolve.md +2 -0
  135. package/src/fs_path_tempdir.md +2 -0
  136. package/src/fs_path_untildify.md +8 -0
  137. package/src/fs_read.md +3 -0
  138. package/src/fs_read_buffer.md +5 -0
  139. package/src/fs_read_json.md +6 -0
  140. package/src/fs_read_lines.md +7 -0
  141. package/src/fs_read_stream.md +2 -0
  142. package/src/fs_read_utf8.md +5 -0
  143. package/src/fs_readdir.md +3 -0
  144. package/src/fs_readlink.md +6 -0
  145. package/src/fs_rename.md +6 -0
  146. package/src/fs_rm.md +4 -0
  147. package/src/fs_rmdir.md +3 -0
  148. package/src/fs_rmf.md +2 -0
  149. package/src/fs_rmrf.md +8 -0
  150. package/src/fs_sha256.js +16 -0
  151. package/src/fs_sha256.md +9 -0
  152. package/src/fs_size.js +2 -2
  153. package/src/fs_size.md +6 -0
  154. package/src/fs_stat.md +2 -0
  155. package/src/fs_tempdir.js +20 -5
  156. package/src/fs_touch.md +6 -0
  157. package/src/fs_walk.js +2 -2
  158. package/src/fs_walk.md +14 -0
  159. package/src/fs_write.md +3 -0
  160. package/src/fs_write_json.md +7 -0
  161. package/src/fs_write_stream.md +2 -0
  162. package/src/gettype.md +13 -0
  163. package/src/http_delete.md +11 -0
  164. package/src/http_get_blob.md +8 -0
  165. package/src/http_get_buffer.md +10 -0
  166. package/src/http_get_file.js +8 -8
  167. package/src/http_get_file.md +14 -0
  168. package/src/http_get_json.md +10 -0
  169. package/src/http_get_stream.md +14 -0
  170. package/src/http_get_stream_range.js +27 -9
  171. package/src/http_get_stream_range.md +22 -0
  172. package/src/http_get_utf8.md +9 -0
  173. package/src/http_head.md +9 -0
  174. package/src/http_patch_json.md +10 -0
  175. package/src/http_post_json.md +11 -0
  176. package/src/http_post_multipart.md +15 -0
  177. package/src/http_post_urlencoded.md +11 -0
  178. package/src/http_put_buffer.md +9 -0
  179. package/src/http_put_file.js +9 -6
  180. package/src/http_put_file.md +16 -0
  181. package/src/http_put_json.md +11 -0
  182. package/src/http_put_utf8.md +9 -0
  183. package/src/http_range_parse.js +6 -3
  184. package/src/http_range_parse.md +18 -0
  185. package/src/http_stream_range.js +35 -22
  186. package/src/http_stream_range.md +29 -0
  187. package/src/identity.md +2 -0
  188. package/src/ignore.md +2 -0
  189. package/src/json_stringify_safe.md +10 -0
  190. package/src/json_stringify_stable.md +9 -0
  191. package/src/logger.js +2 -3
  192. package/src/logger.md +15 -0
  193. package/src/make_progress.js +8 -5
  194. package/src/make_progress.md +20 -0
  195. package/src/math_gcd.js +2 -1
  196. package/src/mongo_stream_upsert.js +10 -3
  197. package/src/mongo_stream_upsert.md +17 -0
  198. package/src/mongo_stream_write.js +10 -3
  199. package/src/mongo_stream_write.md +16 -0
  200. package/src/msval.md +10 -0
  201. package/src/now_fs.md +7 -0
  202. package/src/now_human.md +6 -0
  203. package/src/object_defaults.md +11 -0
  204. package/src/object_schema.js +2 -1
  205. package/src/object_schema.md +12 -0
  206. package/src/object_walk_preorder.md +16 -0
  207. package/src/parallel.js +23 -10
  208. package/src/parse_bytes.js +4 -2
  209. package/src/parse_bytes.md +15 -0
  210. package/src/parse_hms.js +1 -1
  211. package/src/parse_http_content_range.js +9 -5
  212. package/src/parse_http_content_range.md +16 -0
  213. package/src/perf_end_human.md +10 -0
  214. package/src/perf_end_ms.md +8 -0
  215. package/src/perf_measure.md +10 -0
  216. package/src/perf_measure_human.md +8 -0
  217. package/src/perf_start.md +9 -0
  218. package/src/pgid_exists.md +6 -0
  219. package/src/pgid_kill_grace.js +1 -1
  220. package/src/pgid_kill_grace.md +15 -0
  221. package/src/pid_exists.md +5 -0
  222. package/src/pid_kill_grace.js +1 -1
  223. package/src/pid_kill_grace.md +14 -0
  224. package/src/plural.md +13 -0
  225. package/src/random_hex.md +9 -0
  226. package/src/random_int.md +8 -0
  227. package/src/range.js +4 -2
  228. package/src/range.md +2 -0
  229. package/src/redis_connect.js +1 -1
  230. package/src/redis_connect.md +10 -0
  231. package/src/redis_lshift.md +14 -0
  232. package/src/redis_poll.js +1 -0
  233. package/src/redis_poll.md +17 -0
  234. package/src/redis_poll_zshift_callback_rpush.js +1 -2
  235. package/src/redis_poll_zshift_callback_rpush.md +17 -0
  236. package/src/redis_poll_zshift_spawn_rpush.js +2 -1
  237. package/src/redis_poll_zshift_spawn_rpush.md +20 -0
  238. package/src/redis_zshift.md +14 -0
  239. package/src/round.js +7 -8
  240. package/src/round.md +15 -0
  241. package/src/sanitize_dash_name.md +13 -0
  242. package/src/sanitize_filename.md +9 -0
  243. package/src/sanitize_var_name.md +14 -0
  244. package/src/sftp_get_file_info.js +2 -2
  245. package/src/sftp_get_file_info.md +14 -0
  246. package/src/sftp_get_stream_range.js +32 -22
  247. package/src/sftp_get_stream_range.md +20 -0
  248. package/src/shell_curl_progress.js +2 -1
  249. package/src/shell_curl_progress.md +17 -0
  250. package/src/shell_lines.js +2 -2
  251. package/src/shell_lines.md +1 -1
  252. package/src/shell_spawn.js +7 -0
  253. package/src/shell_ytdlp_progress.js +2 -1
  254. package/src/shell_ytdlp_progress.md +17 -0
  255. package/src/str_camel_to_snake.md +11 -0
  256. package/src/str_parse_kv.md +11 -0
  257. package/src/stream_curl_progress.md +14 -0
  258. package/src/stream_data_ln.js +7 -1
  259. package/src/stream_discard.md +8 -0
  260. package/src/stream_group.md +3 -0
  261. package/src/stream_gunzip.md +7 -0
  262. package/src/stream_hash.md +10 -0
  263. package/src/stream_map.js +8 -1
  264. package/src/stream_map_parallel.js +9 -1
  265. package/src/stream_md5.md +9 -0
  266. package/src/stream_multiplex.js +11 -0
  267. package/src/stream_multiplex.md +16 -0
  268. package/src/stream_through.md +3 -0
  269. package/src/stream_transform.md +2 -0
  270. package/src/stream_xml_analyze.js +9 -1
  271. package/src/stream_xml_parse.js +9 -1
  272. package/src/stream_ytdlp_progress.js +1 -1
  273. package/src/stream_ytdlp_progress.md +18 -0
  274. package/src/strtr.js +15 -5
  275. package/src/strtr.md +11 -0
  276. package/src/urlclean.js +9 -2
  277. package/src/urlmod.js +5 -0
  278. package/src/urlparts.md +17 -0
  279. package/src/wait_while.js +1 -1
  280. package/src/wait_while.md +1 -1
  281. package/src/HeartbeatServer.test.js +0 -31
  282. package/src/array_chunk.test.js +0 -24
  283. package/src/array_chunk_balanced.test.js +0 -37
  284. package/src/array_fps.test.js +0 -72
  285. package/src/array_gcd.test.js +0 -10
  286. package/src/array_index.test.js +0 -14
  287. package/src/array_lcm.test.js +0 -12
  288. package/src/array_max.test.js +0 -20
  289. package/src/array_min.test.js +0 -20
  290. package/src/array_permutations.test.js +0 -20
  291. package/src/array_sort.test.js +0 -85
  292. package/src/array_sort_other.test.js +0 -9
  293. package/src/array_sum.test.js +0 -11
  294. package/src/cli-apps/watchdog.d/connect_close.js +0 -17
  295. package/src/cli-apps/watchdog.d/connect_close2.js +0 -20
  296. package/src/cli-apps/watchdog.d/connect_close3.js +0 -25
  297. package/src/cli-apps/watchdog.d/connect_no_ping.js +0 -13
  298. package/src/cli-apps/watchdog.d/early_exit.js +0 -7
  299. package/src/cli-apps/watchdog.d/flood.js +0 -13
  300. package/src/cli-apps/watchdog.d/flood2.js +0 -15
  301. package/src/cli-apps/watchdog.d/hang.js +0 -8
  302. package/src/cli-apps/watchdog.d/huge_delay.js +0 -18
  303. package/src/cli-apps/watchdog.d/ignore_sigterm.js +0 -29
  304. package/src/cli-apps/watchdog.d/late_heartbeat.js +0 -21
  305. package/src/cli-apps/watchdog.d/my-exit-after-2-pings.js +0 -16
  306. package/src/cli-apps/watchdog.d/my-happy-path.js +0 -16
  307. package/src/cli-apps/watchdog.d/my-ignore-sigterm.js +0 -22
  308. package/src/cli-apps/watchdog.d/my-never-ping.js +0 -15
  309. package/src/cli-apps/watchdog.d/no_heartbeat.js +0 -5
  310. package/src/cli-apps/watchdog.d/ok.js +0 -17
  311. package/src/cli-apps/watchdog.d/quick-exit-after-2-pings.js +0 -15
  312. package/src/cli-apps/watchdog.d/quick-hang-after-1-ping.js +0 -15
  313. package/src/cli-apps/watchdog.d/spawn_children.js +0 -14
  314. package/src/cli-apps/watchdog.test.js +0 -48
  315. package/src/crypto_encrypt_aes256.test.js +0 -10
  316. package/src/crypto_hash_sha256.test.js +0 -10
  317. package/src/date_add_months.test.js +0 -40
  318. package/src/date_is_leap_year.test.js +0 -14
  319. package/src/escape_content_disposition.test.js +0 -22
  320. package/src/fcmp_from_spec.test.js +0 -91
  321. package/src/fcmpx.test.js +0 -173
  322. package/src/filter1_from_spec.test.js +0 -31
  323. package/src/format_bytes.test.js +0 -22
  324. package/src/format_error_report.d/src/1.js +0 -26
  325. package/src/format_error_report.d/src/2.js +0 -29
  326. package/src/format_kilo.test.js +0 -25
  327. package/src/format_ms.test.js +0 -33
  328. package/src/format_ms2.test.js +0 -33
  329. package/src/format_ms3.test.js +0 -33
  330. package/src/format_percents.test.js +0 -22
  331. package/src/format_progress_bytes.test.js +0 -19
  332. package/src/format_progress_kilo.test.js +0 -19
  333. package/src/format_seconds.test.js +0 -42
  334. package/src/format_thousands.test.js +0 -28
  335. package/src/fs_file_md5.test.js +0 -11
  336. package/src/fs_mkdirp.test.js +0 -24
  337. package/src/fs_path_extname.test.js +0 -22
  338. package/src/gettype.test.js +0 -27
  339. package/src/object_schema.test.js +0 -15
  340. package/src/object_walk_preorder.test.js +0 -17
  341. package/src/parallel.test.js +0 -11
  342. package/src/parse_bytes.test.js +0 -21
  343. package/src/parse_hms.test.js +0 -32
  344. package/src/parse_http_content_range.test.js +0 -14
  345. package/src/pid_kill_grace.d/ignore-sigterm-for-10ms.js +0 -33
  346. package/src/pid_kill_grace.d/ignore-sigterm.js +0 -22
  347. package/src/pid_kill_grace.test.js +0 -63
  348. package/src/ping_socket.test.js +0 -58
  349. package/src/plural.test.js +0 -20
  350. package/src/range.test.js +0 -41
  351. package/src/redis_lshift.test.js +0 -57
  352. package/src/redis_zshift.test.js +0 -58
  353. package/src/round.test.js +0 -56
  354. package/src/sanitize_dash_name.test.js +0 -59
  355. package/src/sanitize_var_name.test.js +0 -39
  356. package/src/shell_spawn.test.js +0 -30
  357. package/src/str_camel_to_snake.test.js +0 -17
  358. package/src/stream_curl_progress.d/basic +0 -3
  359. package/src/stream_curl_progress.d/basic.json +0 -42
  360. package/src/stream_curl_progress.test.js +0 -17
  361. package/src/stream_data_ln.test.js +0 -24
  362. package/src/stream_map_parallel.test.js +0 -26
  363. package/src/stream_multiplex.test.js +0 -67
  364. package/src/stream_strpbrk.test.js +0 -29
  365. package/src/urlclean.test.js +0 -18
  366. package/src/urlmod.test.js +0 -40
  367. package/src/urlparts.test.js +0 -294
package/package.json CHANGED
@@ -2,20 +2,30 @@
2
2
  "author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
3
3
  "license": "MIT",
4
4
  "name": "@vbarbarosh/node-helpers",
5
- "version": "3.75.0",
5
+ "version": "3.76.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
+ "exports": {
10
+ "./src/*.js": "./src/*.js",
11
+ "./src/*": "./src/*.js",
12
+ "./dist/*": "./dist/*",
13
+ "./package.json": "./package.json"
14
+ },
9
15
  "homepage": "https://vbarbarosh.github.io/node-helpers/",
10
16
  "bin": {
11
17
  "watchdog": "src/cli-apps/watchdog.js"
12
18
  },
13
19
  "files": [
14
20
  "dist",
15
- "src"
21
+ "src",
22
+ "!src/**/*.test.js",
23
+ "!src/**/*.d"
16
24
  ],
17
25
  "scripts": {
18
- "build": "rm -rf dist; webpack",
26
+ "build": "rm -rf dist && webpack",
27
+ "coverage": "c8 mocha 'src/**/*.test.js'",
28
+ "lint": "eslint src webpack.config.js eslint.config.js",
19
29
  "test": "mocha 'src/**/*.test.js'"
20
30
  },
21
31
  "keywords": [
@@ -23,6 +33,9 @@
23
33
  "node",
24
34
  "helpers"
25
35
  ],
36
+ "engines": {
37
+ "node": ">=22"
38
+ },
26
39
  "repository": {
27
40
  "type": "git",
28
41
  "url": "git+https://github.com/vbarbarosh/node-helpers.git"
@@ -30,35 +43,42 @@
30
43
  "bugs": {
31
44
  "url": "https://github.com/vbarbarosh/node-helpers/issues"
32
45
  },
33
- "peerDependencies": {
34
- "@paralleldrive/cuid2": "^3.0.4",
46
+ "dependencies": {
47
+ "@paralleldrive/cuid2": "^3.3.0",
35
48
  "@vbarbarosh/type-helpers": "^0.2.0",
36
49
  "axios": "^1.7.9",
37
50
  "bluebird": "^3.7.2",
38
- "csv-parse": "^5.6.0",
51
+ "csv-parse": "^7.0.1",
39
52
  "form-data": "^4.0.1",
40
- "htmlparser2": "^10.0.0",
53
+ "htmlparser2": "^12.0.0",
41
54
  "json-stable-stringify": "^1.2.1",
42
55
  "json-stringify-safe": "^5.0.1",
43
- "mime-types": "^2.1.35",
56
+ "mime-types": "^3.0.2",
44
57
  "sanitize-filename": "^1.6.3",
45
58
  "split": "^1.0.1"
46
59
  },
47
60
  "optionalDependencies": {
61
+ "redis": "^4.6.0",
48
62
  "ssh2": "^1.16.0"
49
63
  },
50
64
  "devDependencies": {
65
+ "@eslint/js": "^10.0.1",
51
66
  "@vbarbarosh/tree": "^0.5.0",
52
- "copy-webpack-plugin": "^12.0.2",
53
- "express": "^4.21.2",
67
+ "c8": "^11.0.0",
68
+ "copy-webpack-plugin": "^14.0.0",
69
+ "eslint": "^10.7.0",
70
+ "express": "^5.2.1",
71
+ "globals": "^17.7.0",
54
72
  "mocha": "^11.0.1",
55
73
  "redis-v1": "npm:redis@1",
56
74
  "redis-v2": "npm:redis@2",
57
75
  "redis-v3": "npm:redis@3",
58
76
  "redis-v4": "npm:redis@4",
77
+ "redis-v5": "npm:redis@^5.12.1",
78
+ "redis-v6": "npm:redis@^6.1.0",
59
79
  "throttle": "^1.0.3",
60
80
  "tree-kill": "^1.2.2",
61
81
  "webpack": "^5.98.0",
62
- "webpack-cli": "^6.0.1"
82
+ "webpack-cli": "^7.2.1"
63
83
  }
64
84
  }
@@ -4,10 +4,15 @@ const Promise = require('bluebird');
4
4
  const fs = require('fs');
5
5
  const fs_path_join = require('./fs_path_join');
6
6
  const fs_rmf = require('./fs_rmf');
7
+ const ignore = require('./ignore');
7
8
  const net = require('net');
8
9
  const now_fs = require('./now_fs');
9
10
  const os = require('os');
10
11
 
12
+ // now_fs has 1 second resolution: without a counter two instances created
13
+ // within the same second would collide on the socket path (EADDRINUSE)
14
+ let socket_counter = 0;
15
+
11
16
  // 💎 Only the lack of a valid PING within WATCHDOG_INTERVAL is fatal
12
17
  class HeartbeatServer extends EventEmitter
13
18
  {
@@ -31,13 +36,17 @@ class HeartbeatServer extends EventEmitter
31
36
  this.#timer = setInterval(() => this.#tick(), this.#interval_ms);
32
37
 
33
38
  // Put socket on tmpfs to avoid slow disks.
39
+ const socket_name = `watchdog-${process.pid}-${now_fs()}-${++socket_counter}.sock`;
34
40
  this.#socket_path = fs.existsSync('/dev/shm')
35
- ? fs_path_join('/dev/shm', `watchdog-${process.pid}-${now_fs()}.sock`)
36
- : fs_path_join(os.tmpdir(), `watchdog-${process.pid}-${now_fs()}.sock`);
41
+ ? fs_path_join('/dev/shm', socket_name)
42
+ : fs_path_join(os.tmpdir(), socket_name);
37
43
 
38
44
  this.#promise = new Promise((resolve, reject) => {
39
45
  this.#reject = reject;
40
46
  });
47
+ // A clean dispose rejects the internal promise: without a handler of
48
+ // our own that raises an unhandled rejection when nobody awaits promise()
49
+ this.#promise.catch(ignore);
41
50
 
42
51
  this.#server = net.createServer(function (client) {
43
52
  let bytes_received = 0;
@@ -109,11 +118,23 @@ class HeartbeatServer extends EventEmitter
109
118
  if (!this.#server) {
110
119
  return;
111
120
  }
121
+ // removeAllListeners below also removes the 'close' handler which
122
+ // would have rejected: reject explicitly, as documented
123
+ this.#reject(new Error('Server Closed'));
112
124
  try {
113
125
  const _this = this;
114
126
  await new Promise(function (resolve, reject) {
115
127
  _this.#server.removeAllListeners();
116
- _this.#server.close(error => error ? reject(error) : resolve());
128
+ _this.#server.close(function (error) {
129
+ // close() reports ERR_SERVER_NOT_RUNNING when listen
130
+ // never completed (failed or still pending): dispose is
131
+ // documented idempotent, "nothing to close" is not an error
132
+ if (error && error.code !== 'ERR_SERVER_NOT_RUNNING') {
133
+ reject(error);
134
+ return;
135
+ }
136
+ resolve();
137
+ });
117
138
  _this.#server = null;
118
139
  });
119
140
  }
@@ -0,0 +1,33 @@
1
+ Watchdog for worker processes. Listens on a unix domain socket and expects
2
+ regular pings (see `ping_socket`); the internal promise rejects when no valid
3
+ ping arrives within `interval_ms` (default 5000). Only the lack of a valid
4
+ PING within the interval is fatal — everything else (client errors, floods)
5
+ merely emits a `warning` event.
6
+
7
+ **Socket:** placed on `/dev/shm` when available (tmpfs, avoids slow disks),
8
+ otherwise `os.tmpdir()`. The path is unique per instance, even for instances
9
+ created within the same second. A ping is any connection that ends cleanly;
10
+ the server replies `OK` and ignores the payload (a client sending over 1 MiB
11
+ is destroyed with a `warning`).
12
+
13
+ **promise():** rejects with `ExitCodeError` (`exit_code = 124`,
14
+ `No heartbeat...`) when the watchdog fires — the instance then disposes
15
+ itself, so no `heartbeat` events follow. Rejects with `Server Failed: ...`
16
+ if the socket cannot listen. Never resolves.
17
+
18
+ **dispose():** stops the timer, closes the server, removes the socket file,
19
+ and rejects the internal promise with a `Server Closed` error. Idempotent —
20
+ safe to call before listening completed, after a failed listen, and twice.
21
+
22
+ **Events:** `heartbeat` (a ping was received), `warning` (non-fatal error).
23
+
24
+ ```js
25
+ const server = new HeartbeatServer(5000);
26
+ try {
27
+ worker.send({watchdog_socket: server.socket_path});
28
+ await server.promise(); // rejects on missing heartbeat or dispose()
29
+ }
30
+ finally {
31
+ await server.dispose();
32
+ }
33
+ ```
@@ -8,7 +8,7 @@
8
8
  function array_chunk(array = [], limit = 1)
9
9
  {
10
10
  if (limit < 1) {
11
- throw new Error('Limit value should be greater than 1');
11
+ throw new Error('Limit value should be at least 1');
12
12
  }
13
13
 
14
14
  const out = [];
@@ -0,0 +1,8 @@
1
+ Splits an array into consecutive chunks of at most `limit` items (default `1`).
2
+ The last chunk may be shorter. Throws if `limit` is less than 1.
3
+
4
+ ```js
5
+ array_chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
6
+ array_chunk([1, 2, 3], 5) // [[1, 2, 3]]
7
+ array_chunk([]) // []
8
+ ```
@@ -0,0 +1,14 @@
1
+ Splits an array into up to `max_chunks` balanced chunks, keeping at least
2
+ `min_items_per_chunk` items per chunk when possible. Chunk sizes differ by at
3
+ most 1 item; earlier chunks receive the extras. Originally designed for
4
+ distributing jobs across threads: `array_chunk_balanced(jobs, max_threads, min_jobs_per_thread)`.
5
+
6
+ **Edge cases:**
7
+ - Empty input returns `[]`.
8
+ - If there are too few items to satisfy `min_items_per_chunk` even for a
9
+ single chunk, all items go into one chunk.
10
+
11
+ ```js
12
+ array_chunk_balanced([1, 2, 3, 4, 5, 6, 7], 3, 2) // [[1, 2, 3], [4, 5], [6, 7]]
13
+ array_chunk_balanced([1, 2, 3], 5, 5) // [[1, 2, 3]]
14
+ ```
@@ -0,0 +1,12 @@
1
+ Returns frame timestamps (in seconds) for a clip of `duration_sec` sampled at
2
+ `fps` frames per second: `Math.floor(duration_sec * fps)` evenly spaced values
3
+ from `0` up to just under `duration_sec`. Each timestamp is pulled back by an
4
+ epsilon of `0.00001` and rounded to it, so the last frame never lands exactly
5
+ on the duration.
6
+
7
+ If the clip fits one frame or less (`duration_sec * fps <= 1`), returns `[0]`.
8
+
9
+ ```js
10
+ array_fps(1, 4) // [0, 0.33333, 0.66666, 0.99999]
11
+ array_fps(1, 1) // [0]
12
+ ```
package/src/array_gcd.js CHANGED
@@ -1,9 +1,11 @@
1
1
  const math_gcd = require('./math_gcd');
2
2
 
3
3
  // https://stackoverflow.com/a/39764792/1478566
4
- function array_gcd(array, fn = Number)
4
+ function array_gcd(array, read = Number)
5
5
  {
6
- return array.reduce((a,b) => math_gcd(fn(a), fn(b)));
6
+ // 0 is the identity of gcd (gcd(0, x) = x); it also makes an empty
7
+ // array return 0 instead of throwing
8
+ return array.map(v => read(v)).reduce((a,b) => math_gcd(a, b), 0);
7
9
  }
8
10
 
9
11
  module.exports = array_gcd;
package/src/array_gcd.md CHANGED
@@ -1,2 +1,3 @@
1
1
  Returns the greatest common divisor (GCD) of all elements in the array.
2
- `fn(item)` is applied to each element before computing.
2
+ `read(item)` is applied to each element before computing.
3
+ The result is always non-negative; an empty array returns `0`.
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Group items by a common key and return an array of groups.
3
3
  *
4
- * @alternative Map.groupBy(items, fn)
4
+ * @alternative Map.groupBy(items, read)
5
5
  */
6
- function array_group(array, fn)
6
+ function array_group(array, read)
7
7
  {
8
8
  const map = new Map();
9
9
  array.forEach(function (item) {
10
- const key = fn(item);
10
+ const key = read(item);
11
11
  if (!map.has(key)) {
12
12
  map.set(key, {key, items: []});
13
13
  }
@@ -0,0 +1,14 @@
1
+ Groups items by the key returned from `read(item)` and returns an array of
2
+ `{key, items}` groups, in the order each key was first seen. Keys are compared
3
+ with `Map` semantics, so any value type works as a key.
4
+
5
+ ```js
6
+ array_group([{t: 'a', v: 1}, {t: 'b', v: 2}, {t: 'a', v: 3}], x => x.t)
7
+ // [
8
+ // {key: 'a', items: [{t: 'a', v: 1}, {t: 'a', v: 3}]},
9
+ // {key: 'b', items: [{t: 'b', v: 2}]},
10
+ // ]
11
+ ```
12
+
13
+ Native alternative: `Map.groupBy(items, read)`. See `array_group_map` for the
14
+ same grouping returned as an object keyed by `key`.
@@ -2,14 +2,14 @@
2
2
  * Group items by common key and return an object of items grouped by key.
3
3
  *
4
4
  * @param array
5
- * @param fn
5
+ * @param read
6
6
  * @returns {{}}
7
7
  */
8
- function array_group_map(array, fn)
8
+ function array_group_map(array, read)
9
9
  {
10
10
  const out = {};
11
11
  array.forEach(function (item) {
12
- const key = fn(item);
12
+ const key = read(item);
13
13
  out[key] = out[key] || {key, items: []};
14
14
  out[key].items.push(item);
15
15
  });
@@ -0,0 +1,13 @@
1
+ Groups items by the key returned from `read(item)` and returns a plain object
2
+ mapping each key to a `{key, items}` group. Keys are used as object properties,
3
+ so they are coerced to strings.
4
+
5
+ ```js
6
+ array_group_map([{t: 'a', v: 1}, {t: 'b', v: 2}, {t: 'a', v: 3}], x => x.t)
7
+ // {
8
+ // a: {key: 'a', items: [{t: 'a', v: 1}, {t: 'a', v: 3}]},
9
+ // b: {key: 'b', items: [{t: 'b', v: 2}]},
10
+ // }
11
+ ```
12
+
13
+ See `array_group` for the same grouping returned as an array of groups.
@@ -7,10 +7,10 @@ const identity = require('./identity');
7
7
  * Seems, there is a native way to do it:
8
8
  * Object.fromEntries(items.map(v => [v.name, v]));
9
9
  */
10
- function array_index(array, fn = identity)
10
+ function array_index(array, read = identity)
11
11
  {
12
12
  const out = {};
13
- array.forEach(v => out[fn(v)] = v);
13
+ array.forEach(v => out[read(v)] = v);
14
14
  return out;
15
15
  }
16
16
 
@@ -0,0 +1,9 @@
1
+ Builds an object mapping `read(item)` to the item itself. `read` defaults to
2
+ `identity`. When several items share a key, the last one wins.
3
+
4
+ ```js
5
+ array_index([{uid: 'a'}, {uid: 'b'}], v => v.uid)
6
+ // {a: {uid: 'a'}, b: {uid: 'b'}}
7
+ ```
8
+
9
+ Native alternative: `Object.fromEntries(items.map(v => [v.uid, v]))`.
package/src/array_lcm.js CHANGED
@@ -1,8 +1,10 @@
1
1
  const math_lcm = require('./math_lcm');
2
2
 
3
- function array_lcm(array, fn = Number)
3
+ function array_lcm(array, read = Number)
4
4
  {
5
- return array.reduce((a,b) => math_lcm(fn(a), fn(b)));
5
+ // 1 is the identity of lcm (lcm(1, x) = x); it also makes an empty
6
+ // array return 1 instead of throwing
7
+ return array.map(v => read(v)).reduce((a,b) => math_lcm(a, b), 1);
6
8
  }
7
9
 
8
10
  module.exports = array_lcm;
package/src/array_lcm.md CHANGED
@@ -1,2 +1,3 @@
1
1
  Returns the least common multiple (LCM) of all elements in the array.
2
- `fn(item)` is applied to each element before computing.
2
+ `read(item)` is applied to each element before computing.
3
+ The result is always non-negative; an empty array returns `1`.
package/src/array_max.js CHANGED
@@ -3,13 +3,20 @@ const identity = require('./identity');
3
3
  /**
4
4
  * Returns the first element in the array with the maximal weight
5
5
  */
6
- function array_max(array, fn = identity)
6
+ function array_max(array, read = identity)
7
7
  {
8
8
  let out = null;
9
9
  let max = null;
10
- array.forEach(function (item, i) {
11
- const weight = fn(item);
12
- if (i === 0 || max < weight) {
10
+ let seeded = false;
11
+ array.forEach(function (item) {
12
+ const weight = read(item);
13
+ // NaN compares false with everything: seeding max with it would
14
+ // make every subsequent comparison fail
15
+ if (Number.isNaN(weight)) {
16
+ return;
17
+ }
18
+ if (!seeded || max < weight) {
19
+ seeded = true;
13
20
  max = weight;
14
21
  out = item;
15
22
  }
package/src/array_max.md CHANGED
@@ -1,2 +1,2 @@
1
- Returns the first element of the array for which `fn(item)` is maximized.
1
+ Returns the first element of the array for which `read(item)` is maximized.
2
2
  If multiple elements have the same maximal weight, the earliest one is returned.
package/src/array_min.js CHANGED
@@ -3,13 +3,20 @@ const identity = require('./identity');
3
3
  /**
4
4
  * Returns the first element in the array with the minimal weight
5
5
  */
6
- function array_min(array, fn = identity)
6
+ function array_min(array, read = identity)
7
7
  {
8
8
  let out = null;
9
9
  let min = null;
10
- array.forEach(function (item, i) {
11
- const weight = fn(item);
12
- if (i === 0 || min > weight) {
10
+ let seeded = false;
11
+ array.forEach(function (item) {
12
+ const weight = read(item);
13
+ // NaN compares false with everything: seeding min with it would
14
+ // make every subsequent comparison fail
15
+ if (Number.isNaN(weight)) {
16
+ return;
17
+ }
18
+ if (!seeded || min > weight) {
19
+ seeded = true;
13
20
  min = weight;
14
21
  out = item;
15
22
  }
package/src/array_min.md CHANGED
@@ -1,2 +1,2 @@
1
- Returns the first element of the array for which `fn(item)` is minimized.
1
+ Returns the first element of the array for which `read(item)` is minimized.
2
2
  If multiple elements have the same minimal weight, the earliest one is returned.
@@ -5,6 +5,10 @@ function array_permutations(array, k = array.length)
5
5
  return array_permutations_k(array, k);
6
6
  }
7
7
 
8
+ // Heap's algorithm permutes in place: work on a copy, the caller's
9
+ // array must not be left in an arbitrary permuted state.
10
+ array = array.slice();
11
+
8
12
  let length = array.length;
9
13
  let out = [array.slice()];
10
14
  let c = new Array(length).fill(0);
@@ -0,0 +1,13 @@
1
+ Returns all permutations of the array. With the optional `k`, returns all
2
+ ordered arrangements of `k` elements instead. The input array is never mutated.
3
+
4
+ **Order:** full permutations come out in Heap's-algorithm order, not
5
+ lexicographic; `k`-arrangements come out grouped by the first element in input
6
+ order.
7
+
8
+ **Edge cases:** `k = 0` gives `[[]]`; `k` larger than the array length gives `[]`.
9
+
10
+ ```js
11
+ array_permutations([1, 2, 3]) // [[1,2,3], [2,1,3], [3,1,2], [1,3,2], [2,3,1], [3,2,1]]
12
+ array_permutations([1, 2, 3], 2) // [[1,2], [1,3], [2,1], [2,3], [3,1], [3,2]]
13
+ ```
package/src/array_sort.js CHANGED
@@ -5,6 +5,7 @@ const fcmp_tuples = require('./fcmp_tuples');
5
5
  * Sorts an array in place by the result of applying `fn` to each item,
6
6
  * using `fcmp` to compare the results.
7
7
  *
8
+ * array_sort(items, v => v.name)
8
9
  * array_sort(items, v => [v.name])
9
10
  * array_sort(items, v => [v.age, v.name])
10
11
  */
@@ -12,11 +13,15 @@ function array_sort(array, mapper, fcmp = fcmp_default)
12
13
  {
13
14
  const keys = new Map();
14
15
  return array.sort(function (a, b) {
16
+ // A scalar key is a 1-tuple: fcmp_tuples on scalars would compare
17
+ // nothing and silently leave the array unsorted
15
18
  if (!keys.has(a)) {
16
- keys.set(a, mapper(a));
19
+ const key = mapper(a);
20
+ keys.set(a, Array.isArray(key) ? key : [key]);
17
21
  }
18
22
  if (!keys.has(b)) {
19
- keys.set(b, mapper(b));
23
+ const key = mapper(b);
24
+ keys.set(b, Array.isArray(key) ? key : [key]);
20
25
  }
21
26
  return fcmp_tuples(keys.get(a), keys.get(b), fcmp);
22
27
  });
@@ -0,0 +1,13 @@
1
+ Sorts an array in place by the key(s) that `mapper` returns for each item,
2
+ comparing with `fcmp` (default `fcmp_default`, natural order). The mapper may
3
+ return a scalar or a tuple; tuples compare element by element via
4
+ `fcmp_tuples`, so later elements break ties. Keys are cached, so the mapper
5
+ runs at most once per item. Returns the sorted array.
6
+
7
+ ```js
8
+ array_sort([{id: 3}, {id: 1}, {id: 2}], v => v.id) // sorted by id
9
+ array_sort(files, v => [v.type, v.name]) // by type, then by name
10
+ ```
11
+
12
+ A common pattern for "these values first" is a priority index in the tuple:
13
+ `array_sort(items, v => [priorities.indexOf(v.type) >>> 0, v.name])`.
@@ -5,18 +5,21 @@ const fcmp_default = require('./fcmp_default');
5
5
  * are not in the `other` are added to the end of the result defined by `fcmp`.
6
6
  *
7
7
  * @param array
8
- * @param fn
8
+ * @param read
9
9
  * @param other
10
10
  * @param fcmp
11
11
  * @returns {*}
12
12
  */
13
- function array_sort_other(array, fn, other, fcmp = fcmp_default)
13
+ function array_sort_other(array, read, other, fcmp = fcmp_default)
14
14
  {
15
- const other_map = {};
15
+ // No-prototype object: with a plain {} the keys 'constructor',
16
+ // 'toString', '__proto__', ... would collide with Object.prototype
17
+ // members and corrupt the sort order.
18
+ const other_map = Object.create(null);
16
19
  other.forEach((v,i) => other_map[v] = i + 1);
17
20
  return array.sort(function (a, b) {
18
- const ax = other_map[fn(a)];
19
- const bx = other_map[fn(b)];
21
+ const ax = other_map[read(a)];
22
+ const bx = other_map[read(b)];
20
23
  if (ax && bx) {
21
24
  return ax - bx;
22
25
  }
@@ -0,0 +1,11 @@
1
+ Sorts `array` in place so that items whose `read(item)` value appears in
2
+ `other` come first, in the same order as in `other`. Items not found in
3
+ `other` go to the end, ordered by `fcmp(a, b)` — note the fallback compares
4
+ the items themselves, not their `read` keys (default `fcmp_default`).
5
+
6
+ Lookup keys are coerced to object property strings; a null-prototype map is
7
+ used internally, so values like `'constructor'` or `'__proto__'` are safe.
8
+
9
+ ```js
10
+ array_sort_other([1, 2, 3, 4, 5], v => v, [5]) // [5, 1, 2, 3, 4]
11
+ ```
package/src/array_sum.js CHANGED
@@ -1,8 +1,8 @@
1
- function array_sum(array, fn = Number)
1
+ function array_sum(array, read = Number)
2
2
  {
3
3
  let out = 0;
4
4
  array.forEach(function (item) {
5
- out += fn(item);
5
+ out += read(item);
6
6
  });
7
7
  return out;
8
8
  }
@@ -0,0 +1,8 @@
1
+ Sums `read(item)` over the array. `read` defaults to `Number`, so string
2
+ items are cast. An empty array sums to `0`.
3
+
4
+ ```js
5
+ array_sum([1, 2, 3, 4, 5]) // 15
6
+ array_sum(['1', '2', '3']) // 6
7
+ array_sum([{n: 1}, {n: 2}], v => v.n) // 3
8
+ ```
@@ -3,11 +3,11 @@ const identity = require('./identity');
3
3
  /**
4
4
  * Return unique values; if a value occurs multiple times, keep the first one.
5
5
  */
6
- function array_unique(values, fn = identity)
6
+ function array_unique(values, read = identity)
7
7
  {
8
8
  const set = new Set();
9
9
  return values.filter(function (item) {
10
- const key = fn(item);
10
+ const key = read(item);
11
11
  if (set.has(key)) {
12
12
  return false;
13
13
  }
@@ -3,13 +3,13 @@ const identity = require('./identity');
3
3
  /**
4
4
  * Return unique values; if a value occurs multiple times, keep the last one.
5
5
  */
6
- function array_unique_last(array, fn = identity)
6
+ function array_unique_last(array, read = identity)
7
7
  {
8
8
  const out = [];
9
9
  const set = new Set();
10
10
  for (let i = array.length; --i >= 0; ) {
11
11
  const item = array[i];
12
- const key = fn(item);
12
+ const key = read(item);
13
13
  if (!set.has(key)) {
14
14
  set.add(key);
15
15
  out.push(item);
@@ -0,0 +1,10 @@
1
+ Asserts that `expires_at` is a `Date` object pointing to the future
2
+ (`getTime() > Date.now()`). Returns nothing on success; throws an `Error`
3
+ for anything else — past dates, the current instant, timestamps, date
4
+ strings, or non-`Date` values.
5
+
6
+ ```js
7
+ assert_valid_expires_at(new Date(Date.now() + 60000)) // ok, returns undefined
8
+ assert_valid_expires_at(new Date(Date.now() - 1)) // throws Error
9
+ assert_valid_expires_at(Date.now() + 60000) // throws Error (number, not Date)
10
+ ```
package/src/clamp.md ADDED
@@ -0,0 +1,9 @@
1
+ Clamps `value` into the `[min, max]` range. Note the argument order:
2
+ `clamp(min, max, value)`, the value comes last.
3
+
4
+ ```js
5
+ clamp(0, 10, 15) // 10
6
+ clamp(0, 10, -1) // 0
7
+ clamp(0, 10, 5) // 5
8
+ clamp(5, 5, 7) // 5 (degenerate range)
9
+ ```
@@ -47,6 +47,11 @@ async function main()
47
47
  }
48
48
 
49
49
  switch (ARGS[0]) {
50
+ case '-h':
51
+ case '--help':
52
+ usage();
53
+ process.exit(0);
54
+ break;
50
55
  case '-v':
51
56
  case '--version':
52
57
  version();
package/src/cli.js CHANGED
@@ -9,7 +9,9 @@ function cli(main, report = error => console.error(error))
9
9
  // https://stackoverflow.com/a/46916601/1478566
10
10
  const timer = setInterval(v => v, 1E9);
11
11
 
12
- Promise.resolve(main()).then(resolve, reject);
12
+ // Promise.resolve(main()) would evaluate main() before wrapping:
13
+ // a synchronous throw would bypass report() and the exit code handling
14
+ Promise.try(main).then(resolve, reject);
13
15
 
14
16
  function resolve() {
15
17
  clearInterval(timer);