@vbarbarosh/node-helpers 3.75.0 → 3.76.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/README.md +29 -3
  2. package/dist/browser.js +1 -2504
  3. package/dist/index.html +1 -1
  4. package/package.json +33 -12
  5. package/src/HeartbeatServer.js +24 -3
  6. package/src/HeartbeatServer.md +33 -0
  7. package/src/array_chunk.js +1 -1
  8. package/src/array_chunk.md +8 -0
  9. package/src/array_chunk_balanced.md +14 -0
  10. package/src/array_fps.md +12 -0
  11. package/src/array_gcd.js +4 -2
  12. package/src/array_gcd.md +2 -1
  13. package/src/array_group.js +3 -3
  14. package/src/array_group.md +14 -0
  15. package/src/array_group_map.js +3 -3
  16. package/src/array_group_map.md +13 -0
  17. package/src/array_index.js +2 -2
  18. package/src/array_index.md +9 -0
  19. package/src/array_lcm.js +4 -2
  20. package/src/array_lcm.md +2 -1
  21. package/src/array_max.js +11 -4
  22. package/src/array_max.md +1 -1
  23. package/src/array_min.js +11 -4
  24. package/src/array_min.md +1 -1
  25. package/src/array_permutations.js +4 -0
  26. package/src/array_permutations.md +13 -0
  27. package/src/array_sort.js +7 -2
  28. package/src/array_sort.md +13 -0
  29. package/src/array_sort_other.js +8 -5
  30. package/src/array_sort_other.md +11 -0
  31. package/src/array_sum.js +2 -2
  32. package/src/array_sum.md +8 -0
  33. package/src/array_unique.js +2 -2
  34. package/src/array_unique_last.js +2 -2
  35. package/src/assert_valid_expires_at.md +10 -0
  36. package/src/clamp.md +9 -0
  37. package/src/cli-apps/watchdog.js +5 -0
  38. package/src/cli.js +3 -1
  39. package/src/cli.md +15 -0
  40. package/src/const_stream.md +7 -0
  41. package/src/const_type.md +5 -0
  42. package/src/countdown.js +22 -5
  43. package/src/countdown.md +21 -0
  44. package/src/crypto_decrypt_aes256.md +13 -0
  45. package/src/crypto_encrypt_aes256.md +13 -0
  46. package/src/crypto_hash_md5.md +10 -0
  47. package/src/crypto_hash_sha256.md +10 -0
  48. package/src/crypto_hmac_sha256.md +11 -0
  49. package/src/date_add_hours.md +8 -0
  50. package/src/date_add_milliseconds.md +4 -0
  51. package/src/date_add_minutes.md +4 -0
  52. package/src/date_add_months.md +13 -0
  53. package/src/date_add_seconds.md +4 -0
  54. package/src/date_diff_seconds.md +4 -0
  55. package/src/date_is_leap_year.md +9 -0
  56. package/src/escape_content_disposition.js +6 -3
  57. package/src/escape_content_disposition.md +13 -0
  58. package/src/escape_regexp.md +7 -0
  59. package/src/eta.js +7 -1
  60. package/src/eta.md +18 -0
  61. package/src/factorize_ms.js +3 -0
  62. package/src/factorize_ms.md +10 -0
  63. package/src/fastdl.js +14 -3
  64. package/src/fcmp_dates.md +12 -0
  65. package/src/fcmp_default.js +7 -0
  66. package/src/fcmp_default.md +29 -0
  67. package/src/fcmp_default_desc.js +4 -7
  68. package/src/fcmp_default_desc.md +10 -0
  69. package/src/fcmp_from_spec.md +13 -0
  70. package/src/fcmp_numbers.md +11 -0
  71. package/src/fcmp_tuples.md +17 -0
  72. package/src/fcmp_utf8_bin.md +14 -0
  73. package/src/fcmp_utf8_ci.md +14 -0
  74. package/src/fcmp_utf8_cs.md +15 -0
  75. package/src/fcmp_utf8_natural_bin.js +38 -0
  76. package/src/fcmp_utf8_natural_bin.md +8 -0
  77. package/src/fcmp_utf8_natural_ci.md +13 -0
  78. package/src/fcmp_utf8_natural_cs.md +13 -0
  79. package/src/fcmpx.md +26 -9
  80. package/src/filter1_from_spec.js +3 -3
  81. package/src/format_bytes.md +15 -0
  82. package/src/format_date.md +7 -0
  83. package/src/format_date_fs.md +7 -0
  84. package/src/format_date_human.md +6 -0
  85. package/src/format_date_ymd.md +6 -0
  86. package/src/format_error_report.js +3 -3
  87. package/src/format_error_report.md +12 -0
  88. package/src/format_kilo.md +13 -0
  89. package/src/format_ms.js +3 -2
  90. package/src/format_ms.md +12 -0
  91. package/src/format_ms2.js +6 -3
  92. package/src/format_ms2.md +12 -0
  93. package/src/format_ms3.js +3 -2
  94. package/src/format_ms3.md +12 -0
  95. package/src/format_percents.md +10 -0
  96. package/src/format_progress_bytes.md +13 -0
  97. package/src/format_progress_kilo.md +10 -0
  98. package/src/format_seconds.md +11 -0
  99. package/src/format_thousands.md +9 -0
  100. package/src/fs_append.md +7 -0
  101. package/src/fs_append_json.md +8 -0
  102. package/src/fs_append_utf8.md +6 -0
  103. package/src/fs_assert_file_exists.md +3 -0
  104. package/src/fs_assert_file_readable.js +1 -1
  105. package/src/fs_assert_file_readable.md +3 -0
  106. package/src/fs_assert_file_socket.md +2 -0
  107. package/src/fs_copy.md +7 -0
  108. package/src/fs_copy_excl.md +2 -0
  109. package/src/fs_exists.md +6 -0
  110. package/src/fs_fclose.md +5 -0
  111. package/src/fs_fi.js +13 -1
  112. package/src/fs_fi.md +11 -0
  113. package/src/fs_file_md5.md +8 -0
  114. package/src/fs_find.md +7 -0
  115. package/src/fs_fopen.md +6 -0
  116. package/src/fs_fread.js +2 -2
  117. package/src/fs_fread.md +11 -0
  118. package/src/fs_fsize.md +2 -0
  119. package/src/fs_fstat.md +2 -0
  120. package/src/fs_fwrite.js +2 -2
  121. package/src/fs_fwrite.md +9 -0
  122. package/src/fs_list.md +5 -0
  123. package/src/fs_list_deep.md +5 -0
  124. package/src/fs_ls.md +2 -0
  125. package/src/fs_lsr.md +4 -0
  126. package/src/fs_lstat.md +3 -0
  127. package/src/fs_mkdir.md +3 -0
  128. package/src/fs_mkdirp.js +3 -30
  129. package/src/fs_mkdirp.md +7 -0
  130. package/src/fs_path_basename.md +2 -0
  131. package/src/fs_path_dirname.md +2 -0
  132. package/src/fs_path_extname.md +9 -0
  133. package/src/fs_path_join.md +2 -0
  134. package/src/fs_path_relative.md +2 -0
  135. package/src/fs_path_resolve.md +2 -0
  136. package/src/fs_path_tempdir.md +2 -0
  137. package/src/fs_path_untildify.md +8 -0
  138. package/src/fs_read.md +3 -0
  139. package/src/fs_read_buffer.md +5 -0
  140. package/src/fs_read_json.md +6 -0
  141. package/src/fs_read_lines.md +7 -0
  142. package/src/fs_read_stream.md +2 -0
  143. package/src/fs_read_utf8.md +5 -0
  144. package/src/fs_readdir.md +3 -0
  145. package/src/fs_readlink.md +6 -0
  146. package/src/fs_rename.md +6 -0
  147. package/src/fs_rm.md +4 -0
  148. package/src/fs_rmdir.md +3 -0
  149. package/src/fs_rmf.md +2 -0
  150. package/src/fs_rmrf.md +8 -0
  151. package/src/fs_sha256.js +16 -0
  152. package/src/fs_sha256.md +9 -0
  153. package/src/fs_size.js +2 -2
  154. package/src/fs_size.md +6 -0
  155. package/src/fs_stat.md +2 -0
  156. package/src/fs_tempdir.js +20 -5
  157. package/src/fs_touch.md +6 -0
  158. package/src/fs_walk.js +2 -2
  159. package/src/fs_walk.md +14 -0
  160. package/src/fs_write.md +3 -0
  161. package/src/fs_write_json.md +7 -0
  162. package/src/fs_write_stream.md +2 -0
  163. package/src/gettype.md +13 -0
  164. package/src/http_delete.md +11 -0
  165. package/src/http_get_blob.md +8 -0
  166. package/src/http_get_buffer.md +10 -0
  167. package/src/http_get_file.js +8 -8
  168. package/src/http_get_file.md +14 -0
  169. package/src/http_get_json.md +10 -0
  170. package/src/http_get_stream.md +14 -0
  171. package/src/http_get_stream_range.js +27 -9
  172. package/src/http_get_stream_range.md +22 -0
  173. package/src/http_get_utf8.md +9 -0
  174. package/src/http_head.md +9 -0
  175. package/src/http_patch_json.md +10 -0
  176. package/src/http_post_json.md +11 -0
  177. package/src/http_post_multipart.md +15 -0
  178. package/src/http_post_urlencoded.md +11 -0
  179. package/src/http_put_buffer.md +9 -0
  180. package/src/http_put_file.js +9 -6
  181. package/src/http_put_file.md +16 -0
  182. package/src/http_put_json.md +11 -0
  183. package/src/http_put_utf8.md +9 -0
  184. package/src/http_range_parse.js +6 -3
  185. package/src/http_range_parse.md +18 -0
  186. package/src/http_stream_range.js +35 -22
  187. package/src/http_stream_range.md +29 -0
  188. package/src/identity.md +2 -0
  189. package/src/ignore.md +2 -0
  190. package/src/json_stringify_safe.md +10 -0
  191. package/src/json_stringify_stable.md +9 -0
  192. package/src/logger.js +2 -3
  193. package/src/logger.md +15 -0
  194. package/src/make_progress.js +8 -5
  195. package/src/make_progress.md +20 -0
  196. package/src/math_gcd.js +2 -1
  197. package/src/mongo_stream_upsert.js +10 -3
  198. package/src/mongo_stream_upsert.md +17 -0
  199. package/src/mongo_stream_write.js +10 -3
  200. package/src/mongo_stream_write.md +16 -0
  201. package/src/msval.md +10 -0
  202. package/src/now_fs.md +7 -0
  203. package/src/now_human.md +6 -0
  204. package/src/object_defaults.md +11 -0
  205. package/src/object_schema.js +2 -1
  206. package/src/object_schema.md +12 -0
  207. package/src/object_walk_preorder.md +16 -0
  208. package/src/parallel.js +23 -10
  209. package/src/parse_bytes.js +4 -2
  210. package/src/parse_bytes.md +15 -0
  211. package/src/parse_hms.js +1 -1
  212. package/src/parse_http_content_range.js +9 -5
  213. package/src/parse_http_content_range.md +16 -0
  214. package/src/perf_end_human.md +10 -0
  215. package/src/perf_end_ms.md +8 -0
  216. package/src/perf_measure.md +10 -0
  217. package/src/perf_measure_human.md +8 -0
  218. package/src/perf_start.md +9 -0
  219. package/src/pgid_exists.md +6 -0
  220. package/src/pgid_kill_grace.js +1 -1
  221. package/src/pgid_kill_grace.md +15 -0
  222. package/src/pid_exists.md +5 -0
  223. package/src/pid_kill_grace.js +1 -1
  224. package/src/pid_kill_grace.md +14 -0
  225. package/src/plural.md +13 -0
  226. package/src/random_hex.md +9 -0
  227. package/src/random_int.md +8 -0
  228. package/src/range.js +4 -2
  229. package/src/range.md +2 -0
  230. package/src/redis_connect.js +1 -1
  231. package/src/redis_connect.md +10 -0
  232. package/src/redis_lshift.md +14 -0
  233. package/src/redis_poll.js +1 -0
  234. package/src/redis_poll.md +17 -0
  235. package/src/redis_poll_zshift_callback_rpush.js +1 -2
  236. package/src/redis_poll_zshift_callback_rpush.md +17 -0
  237. package/src/redis_poll_zshift_spawn_rpush.js +2 -1
  238. package/src/redis_poll_zshift_spawn_rpush.md +20 -0
  239. package/src/redis_zshift.md +14 -0
  240. package/src/round.js +7 -8
  241. package/src/round.md +15 -0
  242. package/src/sanitize_dash_name.md +13 -0
  243. package/src/sanitize_filename.md +9 -0
  244. package/src/sanitize_var_name.md +14 -0
  245. package/src/sftp_get_file_info.js +2 -2
  246. package/src/sftp_get_file_info.md +14 -0
  247. package/src/sftp_get_stream_range.js +32 -22
  248. package/src/sftp_get_stream_range.md +20 -0
  249. package/src/shell_curl_progress.js +2 -1
  250. package/src/shell_curl_progress.md +17 -0
  251. package/src/shell_lines.js +2 -2
  252. package/src/shell_lines.md +1 -1
  253. package/src/shell_spawn.js +7 -0
  254. package/src/shell_ytdlp_progress.js +2 -1
  255. package/src/shell_ytdlp_progress.md +17 -0
  256. package/src/str_camel_to_snake.md +11 -0
  257. package/src/str_parse_kv.md +11 -0
  258. package/src/stream_curl_progress.md +14 -0
  259. package/src/stream_data_ln.js +7 -1
  260. package/src/stream_discard.md +8 -0
  261. package/src/stream_group.md +3 -0
  262. package/src/stream_gunzip.md +7 -0
  263. package/src/stream_hash.md +10 -0
  264. package/src/stream_map.js +8 -1
  265. package/src/stream_map_parallel.js +9 -1
  266. package/src/stream_md5.md +9 -0
  267. package/src/stream_multiplex.js +11 -0
  268. package/src/stream_multiplex.md +16 -0
  269. package/src/stream_through.md +3 -0
  270. package/src/stream_transform.md +2 -0
  271. package/src/stream_xml_analyze.js +9 -1
  272. package/src/stream_xml_parse.js +9 -1
  273. package/src/stream_ytdlp_progress.js +1 -1
  274. package/src/stream_ytdlp_progress.md +18 -0
  275. package/src/strtr.js +15 -5
  276. package/src/strtr.md +11 -0
  277. package/src/urlclean.js +9 -2
  278. package/src/urlmod.js +22 -11
  279. package/src/urlmod.md +15 -3
  280. package/src/urlparts.md +17 -0
  281. package/src/wait_while.js +1 -1
  282. package/src/wait_while.md +1 -1
  283. package/src/HeartbeatServer.test.js +0 -31
  284. package/src/array_chunk.test.js +0 -24
  285. package/src/array_chunk_balanced.test.js +0 -37
  286. package/src/array_fps.test.js +0 -72
  287. package/src/array_gcd.test.js +0 -10
  288. package/src/array_index.test.js +0 -14
  289. package/src/array_lcm.test.js +0 -12
  290. package/src/array_max.test.js +0 -20
  291. package/src/array_min.test.js +0 -20
  292. package/src/array_permutations.test.js +0 -20
  293. package/src/array_sort.test.js +0 -85
  294. package/src/array_sort_other.test.js +0 -9
  295. package/src/array_sum.test.js +0 -11
  296. package/src/cli-apps/watchdog.d/connect_close.js +0 -17
  297. package/src/cli-apps/watchdog.d/connect_close2.js +0 -20
  298. package/src/cli-apps/watchdog.d/connect_close3.js +0 -25
  299. package/src/cli-apps/watchdog.d/connect_no_ping.js +0 -13
  300. package/src/cli-apps/watchdog.d/early_exit.js +0 -7
  301. package/src/cli-apps/watchdog.d/flood.js +0 -13
  302. package/src/cli-apps/watchdog.d/flood2.js +0 -15
  303. package/src/cli-apps/watchdog.d/hang.js +0 -8
  304. package/src/cli-apps/watchdog.d/huge_delay.js +0 -18
  305. package/src/cli-apps/watchdog.d/ignore_sigterm.js +0 -29
  306. package/src/cli-apps/watchdog.d/late_heartbeat.js +0 -21
  307. package/src/cli-apps/watchdog.d/my-exit-after-2-pings.js +0 -16
  308. package/src/cli-apps/watchdog.d/my-happy-path.js +0 -16
  309. package/src/cli-apps/watchdog.d/my-ignore-sigterm.js +0 -22
  310. package/src/cli-apps/watchdog.d/my-never-ping.js +0 -15
  311. package/src/cli-apps/watchdog.d/no_heartbeat.js +0 -5
  312. package/src/cli-apps/watchdog.d/ok.js +0 -17
  313. package/src/cli-apps/watchdog.d/quick-exit-after-2-pings.js +0 -15
  314. package/src/cli-apps/watchdog.d/quick-hang-after-1-ping.js +0 -15
  315. package/src/cli-apps/watchdog.d/spawn_children.js +0 -14
  316. package/src/cli-apps/watchdog.test.js +0 -48
  317. package/src/crypto_encrypt_aes256.test.js +0 -10
  318. package/src/crypto_hash_sha256.test.js +0 -10
  319. package/src/date_add_months.test.js +0 -40
  320. package/src/date_is_leap_year.test.js +0 -14
  321. package/src/escape_content_disposition.test.js +0 -22
  322. package/src/fcmp_from_spec.test.js +0 -91
  323. package/src/fcmpx.test.js +0 -173
  324. package/src/filter1_from_spec.test.js +0 -31
  325. package/src/format_bytes.test.js +0 -22
  326. package/src/format_error_report.d/src/1.js +0 -26
  327. package/src/format_error_report.d/src/2.js +0 -29
  328. package/src/format_kilo.test.js +0 -25
  329. package/src/format_ms.test.js +0 -33
  330. package/src/format_ms2.test.js +0 -33
  331. package/src/format_ms3.test.js +0 -33
  332. package/src/format_percents.test.js +0 -22
  333. package/src/format_progress_bytes.test.js +0 -19
  334. package/src/format_progress_kilo.test.js +0 -19
  335. package/src/format_seconds.test.js +0 -42
  336. package/src/format_thousands.test.js +0 -28
  337. package/src/fs_file_md5.test.js +0 -11
  338. package/src/fs_mkdirp.test.js +0 -24
  339. package/src/fs_path_extname.test.js +0 -22
  340. package/src/gettype.test.js +0 -27
  341. package/src/object_schema.test.js +0 -15
  342. package/src/object_walk_preorder.test.js +0 -17
  343. package/src/parallel.test.js +0 -11
  344. package/src/parse_bytes.test.js +0 -21
  345. package/src/parse_hms.test.js +0 -32
  346. package/src/parse_http_content_range.test.js +0 -14
  347. package/src/pid_kill_grace.d/ignore-sigterm-for-10ms.js +0 -33
  348. package/src/pid_kill_grace.d/ignore-sigterm.js +0 -22
  349. package/src/pid_kill_grace.test.js +0 -63
  350. package/src/ping_socket.test.js +0 -58
  351. package/src/plural.test.js +0 -20
  352. package/src/range.test.js +0 -41
  353. package/src/redis_lshift.test.js +0 -57
  354. package/src/redis_zshift.test.js +0 -58
  355. package/src/round.test.js +0 -56
  356. package/src/sanitize_dash_name.test.js +0 -59
  357. package/src/sanitize_var_name.test.js +0 -39
  358. package/src/shell_spawn.test.js +0 -30
  359. package/src/str_camel_to_snake.test.js +0 -17
  360. package/src/stream_curl_progress.d/basic +0 -3
  361. package/src/stream_curl_progress.d/basic.json +0 -42
  362. package/src/stream_curl_progress.test.js +0 -17
  363. package/src/stream_data_ln.test.js +0 -24
  364. package/src/stream_map_parallel.test.js +0 -26
  365. package/src/stream_multiplex.test.js +0 -67
  366. package/src/stream_strpbrk.test.js +0 -29
  367. package/src/urlclean.test.js +0 -18
  368. package/src/urlmod.test.js +0 -40
  369. package/src/urlparts.test.js +0 -294
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);
package/src/cli.md ADDED
@@ -0,0 +1,15 @@
1
+ Entry point for Node CLI apps. Runs `main` (sync or async), keeps the event
2
+ loop alive until it settles, and turns a failure into a proper exit code.
3
+
4
+ - Resolves → the process exits naturally with code `0`.
5
+ - Rejects or throws → the error is passed to `report` (default
6
+ `console.error`), then `process.exit(1)`.
7
+ - An `ExitCodeError` exits with its `exit_code` instead of `1`.
8
+ - Synchronous throws from `main` are handled the same way as rejections —
9
+ they do not bypass `report` or the exit code handling.
10
+
11
+ ```js
12
+ cli(async function main() {
13
+ // ...
14
+ });
15
+ ```
@@ -0,0 +1,7 @@
1
+ Constants for passing values through Node.js streams that the stream
2
+ protocol itself cannot carry.
3
+
4
+ **Members:**
5
+ - `const_stream.null` — a `Symbol('stream[null]')` used to represent `null`
6
+ inside an object-mode stream, since pushing a real `null` is the
7
+ end-of-stream mark.
@@ -0,0 +1,5 @@
1
+ String constants for the type names returned by `gettype`. Covers the
2
+ standard `typeof` results (`'null'`, `'undefined'`, `'boolean'`, `'number'`,
3
+ `'bigint'`, `'string'`, `'symbol'`, `'function'`, `'object'`) plus the extra
4
+ distinctions `gettype` makes: `array` → `'array'`, `nan` → `'nan'`,
5
+ `neg_inf` → `'-inf'`, `pos_inf` → `'+inf'`.
package/src/countdown.js CHANGED
@@ -2,7 +2,7 @@ const Promise = require('bluebird');
2
2
 
3
3
  /**
4
4
  * Wait for a promise/value to be settled. Meanwhile, call `tick` function every `tick_ms` milliseconds.
5
- * After `timeout` seconds, reject with 'Timeout' error.
5
+ * After `timeout` milliseconds, reject with 'Timeout' error.
6
6
  *
7
7
  * @see user_friendly_status
8
8
  * @see progress
@@ -11,38 +11,55 @@ function countdown(ctx)
11
11
  {
12
12
  let done = false;
13
13
  let timer = null;
14
+ let timeout_timer = null;
14
15
  return new Promise(function (resolve, reject) {
15
16
  ctx.value = ctx.fn ? Promise.method(ctx.fn).call() : Promise.resolve(ctx.value);
16
17
  ctx.timeout = ctx.timeout || 0; // ⚠️ Rename to timeout_ms
17
18
  ctx.time_now = new Date();
18
19
  ctx.time_begin = new Date();
19
- ctx.time_end = Date.now() + ctx.timeout;
20
+ // No timeout means no deadline
21
+ ctx.time_end = ctx.timeout ? Date.now() + ctx.timeout : Infinity;
20
22
  ctx.tick_ms = ctx.tick_ms || 1000;
21
23
  ctx.tick = ctx.tick || function () {};
22
24
  ctx.resolve = function (value) {
23
25
  if (done) {
24
- throw new Error('Already settled');
26
+ return;
25
27
  }
26
28
  done = true;
27
29
  clearInterval(timer);
30
+ clearTimeout(timeout_timer);
28
31
  resolve(value);
29
32
  };
30
33
  ctx.reject = function (error) {
31
34
  if (done) {
32
- throw new Error('Already settled');
35
+ return;
33
36
  }
34
37
  done = true;
35
38
  clearInterval(timer);
39
+ clearTimeout(timeout_timer);
36
40
  reject(error);
37
41
  };
38
42
  timer = setInterval(tick, ctx.tick_ms);
43
+ if (ctx.timeout) {
44
+ // The interval alone enforces the deadline only at tick_ms granularity
45
+ timeout_timer = setTimeout(tick, ctx.timeout);
46
+ }
39
47
  ctx.value.then(ctx.resolve, ctx.reject);
40
48
  tick();
41
49
  function tick() {
42
50
  ctx.time_now = Date.now();
43
51
  ctx.time_elapsed = ctx.time_now - ctx.time_begin;
44
52
  ctx.time_remained = Math.max(0, ctx.time_end - ctx.time_now);
45
- ctx.tick(ctx);
53
+ try {
54
+ ctx.tick(ctx);
55
+ }
56
+ catch (error) {
57
+ // A throwing tick must reject and clear the timers: a bare
58
+ // throw inside setInterval is an uncaughtException, and the
59
+ // interval would keep firing (and throwing) forever.
60
+ ctx.reject(error);
61
+ return;
62
+ }
46
63
  if (!ctx.time_remained) {
47
64
  ctx.reject(new Error('Timeout'));
48
65
  }
@@ -0,0 +1,21 @@
1
+ Wait for a promise/value to settle while calling `ctx.tick(ctx)` every
2
+ `ctx.tick_ms` milliseconds (default 1000, first call is immediate). After
3
+ `ctx.timeout` milliseconds, reject with a `Timeout` error; without `timeout`
4
+ there is no deadline.
5
+
6
+ **Input:** a single `ctx` object with either `value` (promise or plain value)
7
+ or `fn` (called once; sync throws become rejections). `countdown` mutates
8
+ `ctx`, exposing `time_begin`, `time_now`, `time_elapsed`, `time_remained`,
9
+ and `resolve`/`reject` functions — so `tick` can render progress or settle
10
+ the countdown early. A throwing `tick` rejects and stops the timers.
11
+
12
+ ```js
13
+ await countdown({
14
+ fn: () => upload(file),
15
+ timeout: 60000,
16
+ tick_ms: 1000,
17
+ tick: ctx => console.log(`${ctx.time_elapsed}ms elapsed`),
18
+ });
19
+ ```
20
+
21
+ @see user_friendly_status, eta
@@ -0,0 +1,13 @@
1
+ Decrypts a `Buffer` produced by `crypto_encrypt_aes256` (`aes-256-ctr`, key =
2
+ `sha256(password)`, layout `[iv_length, iv, ciphertext]`). Returns the
3
+ plaintext as a `Buffer` — call `.toString('utf8')` for text.
4
+
5
+ ```js
6
+ const ivenc = crypto_encrypt_aes256('pass123', 'hello');
7
+ crypto_decrypt_aes256('pass123', ivenc).toString('utf8'); // 'hello'
8
+ crypto_decrypt_aes256('wrong', ivenc).toString('utf8'); // garbage, no error
9
+ ```
10
+
11
+ **No integrity check:** CTR mode does not authenticate — a wrong password or
12
+ tampered ciphertext yields garbage bytes, never an error, so verify the result
13
+ yourself if you need to detect bad input.
@@ -0,0 +1,13 @@
1
+ Encrypts data with `aes-256-ctr`. The key is `sha256(password)`, the IV is a
2
+ fresh random 16 bytes on every call, so encrypting the same input twice gives
3
+ different output. Returns a `Buffer` laid out as `[iv_length, iv, ciphertext]`
4
+ (1 + 16 + data-length bytes); pass it as-is to `crypto_decrypt_aes256`.
5
+
6
+ ```js
7
+ const ivenc = crypto_encrypt_aes256('pass123', 'hello');
8
+ // <Buffer 10 ...> — 22 bytes: 0x10 (iv length), 16-byte iv, 5-byte ciphertext
9
+ crypto_decrypt_aes256('pass123', ivenc).toString('utf8'); // 'hello'
10
+ ```
11
+
12
+ **No integrity check:** CTR mode does not authenticate — a wrong password or
13
+ tampered data decrypts to garbage, not an error.
@@ -0,0 +1,10 @@
1
+ Computes the MD5 hash of a string or `Buffer`. Returns a 16-byte `Buffer` —
2
+ call `.toString('hex')` for the usual hex digest.
3
+
4
+ ```js
5
+ crypto_hash_md5('hello').toString('hex')
6
+ // '5d41402abc4b2a76b9719d911017c592'
7
+ ```
8
+
9
+ MD5 is broken for security purposes; use it for checksums and cache keys, and
10
+ `crypto_hash_sha256` where collision resistance matters.
@@ -0,0 +1,10 @@
1
+ Computes the SHA-256 hash of a string or `Buffer`. Returns a 32-byte `Buffer`
2
+ — call `.toString('hex')` for the usual hex digest.
3
+
4
+ ```js
5
+ crypto_hash_sha256('hello').toString('hex')
6
+ // '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
7
+ ```
8
+
9
+ For a keyed hash (verifying that data came from someone who knows a secret)
10
+ use `crypto_hmac_sha256` instead of hashing a concatenation.
@@ -0,0 +1,11 @@
1
+ Computes the HMAC-SHA256 of `data` keyed with `password` (both strings or
2
+ `Buffer`s). Returns a 32-byte `Buffer` — call `.toString('hex')` for the usual
3
+ hex form.
4
+
5
+ ```js
6
+ crypto_hmac_sha256('key', 'The quick brown fox jumps over the lazy dog').toString('hex')
7
+ // 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8'
8
+ ```
9
+
10
+ Unlike `crypto_hash_sha256`, the output depends on the key, so it can
11
+ authenticate data: only holders of the key can produce or verify the mac.
@@ -0,0 +1,8 @@
1
+ Adds `hours` hours to a `Date` via `setHours`. **Mutates the passed `Date` in
2
+ place and returns the same instance** — clone first if the original must be
3
+ preserved. Negative values subtract; overflow rolls over into the next
4
+ day/month/year.
5
+
6
+ ```js
7
+ date_add_hours(new Date('2026/07/06 23:30:00'), 2) // 2026/07/07 01:30:00 (same Date object)
8
+ ```
@@ -0,0 +1,4 @@
1
+ Adds `milliseconds` milliseconds to a `Date` via `setMilliseconds`. **Mutates
2
+ the passed `Date` in place and returns the same instance** — clone first if the
3
+ original must be preserved. Negative values subtract; overflow rolls over into
4
+ seconds and beyond.
@@ -0,0 +1,4 @@
1
+ Adds `minutes` minutes to a `Date` via `setMinutes`. **Mutates the passed
2
+ `Date` in place and returns the same instance** — clone first if the original
3
+ must be preserved. Negative values subtract; overflow rolls over into hours and
4
+ beyond.
@@ -0,0 +1,13 @@
1
+ Adds `months` calendar months to a `Date`. **Mutates the passed `Date` in
2
+ place and returns the same instance** (a falsy `months`, e.g. `0`, is a no-op).
3
+ Negative values subtract.
4
+
5
+ Unlike a bare `setMonth`, the day of month never overflows into the next
6
+ month: when the target month is shorter, the date is clamped to its last day
7
+ (Carbon-style behavior).
8
+
9
+ ```js
10
+ date_add_months(new Date('2023/01/31'), 1) // 2023/02/28, not 2023/03/03
11
+ date_add_months(new Date('2019/03/31'), -1) // 2019/02/28
12
+ date_add_months(new Date('2024/02/29'), 12) // 2025/02/28
13
+ ```
@@ -0,0 +1,4 @@
1
+ Adds `seconds` seconds to a `Date` via `setSeconds`. **Mutates the passed
2
+ `Date` in place and returns the same instance** — clone first if the original
3
+ must be preserved. Negative values subtract; overflow rolls over into minutes
4
+ and beyond.
@@ -0,0 +1,4 @@
1
+ Returns the difference `a - b` between two `Date`s in whole seconds:
2
+ `Math.floor((a.getTime() - b.getTime()) / 1000)`. Positive when `a` is later
3
+ than `b`. Because of the flooring, negative sub-second differences round away
4
+ from zero: if `b` is 500 ms later than `a`, the result is `-1`, not `0`.
@@ -0,0 +1,9 @@
1
+ Returns `true` if the `Date`'s year (local time, `getFullYear`) is a Gregorian
2
+ leap year: divisible by 4, except century years not divisible by 400. Verified
3
+ by brute force against `Date` itself for years 100–9998.
4
+
5
+ ```js
6
+ date_is_leap_year(new Date('2024/01/01')) // true
7
+ date_is_leap_year(new Date('1900/01/01')) // false (century, not /400)
8
+ date_is_leap_year(new Date('2000/01/01')) // true
9
+ ```
@@ -9,10 +9,13 @@ function escape_content_disposition(s)
9
9
  .replaceAll('%', '\\%')
10
10
  .replaceAll('\n', '%x0A')
11
11
  .replaceAll('\0', '%x00');
12
- if (out.match(/\s|;/)) {
13
- return `"${out}"`;
12
+ // A bare value must be a pure RFC 7230 token: any other character
13
+ // (space, ';', ',', '"', '\', ...) requires the whole value to be a
14
+ // quoted-string — an unquoted 'a\"b' or 'a,b' breaks header parsing.
15
+ if (out === s && /^[!#$%&'*+.^_`|~0-9a-zA-Z-]+$/.test(s)) {
16
+ return s;
14
17
  }
15
- return out;
18
+ return `"${out}"`;
16
19
  }
17
20
 
18
21
  module.exports = escape_content_disposition;
@@ -0,0 +1,13 @@
1
+ Escapes a value (typically a filename) for use in a `Content-Disposition`
2
+ header. A pure RFC 7230 token (letters, digits, `!#$%&'*+.^_`|~-`) is returned
3
+ as-is; anything else — spaces, `;`, `,`, quotes, etc. — becomes a quoted-string
4
+ with `\` `"` `%` backslash-escaped and `\n`/`\0` encoded as `%x0A`/`%x00`.
5
+ An unquoted `a"b` or `a,b` would break header parsing.
6
+
7
+ ```js
8
+ escape_content_disposition('report.pdf') // 'report.pdf'
9
+ escape_content_disposition('foo bar.txt') // '"foo bar.txt"'
10
+ escape_content_disposition('foo"bar') // '"foo\\"bar"'
11
+ escape_content_disposition('foo\nbar') // '"foo%x0Abar"'
12
+ escape_content_disposition('') // '""'
13
+ ```
@@ -0,0 +1,7 @@
1
+ Escapes regular-expression special characters (`. * + ? ^ $ { } ( ) | [ ] \`)
2
+ in a string with a backslash, so the string can be embedded literally in
3
+ `new RegExp(...)`.
4
+
5
+ ```js
6
+ escape_regexp('a.b*c?') // 'a\\.b\\*c\\?'
7
+ ```
package/src/eta.js CHANGED
@@ -5,7 +5,13 @@ function eta(time0, total, done, resumed = 0)
5
5
  const seconds = (Date.now() - time0)/1000;
6
6
  const remained = total - done;
7
7
  const bps = (done - resumed)/seconds;
8
- return format_seconds(remained/bps);
8
+ const seconds_remained = remained/bps;
9
+ // No progress yet (e.g. the first tick of a progress loop): remained/bps
10
+ // is Infinity or NaN, which format_seconds renders as 0Infinity:0NaN:0NaN
11
+ if (!Number.isFinite(seconds_remained)) {
12
+ return '--:--:--';
13
+ }
14
+ return format_seconds(seconds_remained);
9
15
  }
10
16
 
11
17
  module.exports = eta;
package/src/eta.md ADDED
@@ -0,0 +1,18 @@
1
+ Estimates the remaining time of a progress loop and formats it as `HH:MM:SS`
2
+ (via `format_seconds`). The rate is derived from what was processed since
3
+ `time0`; pass `resumed` to exclude units that were already done at start (e.g.
4
+ a resumed download), so they don't inflate the rate.
5
+
6
+ ## Signature
7
+
8
+ ```
9
+ eta(time0, total, done, resumed = 0) → string
10
+ ```
11
+
12
+ `time0` is a `Date.now()`-style start timestamp in milliseconds. When no
13
+ progress was made yet (rate is zero), returns the placeholder `'--:--:--'`.
14
+
15
+ ```js
16
+ eta(Date.now() - 10000, 100, 50) // '00:00:10'
17
+ eta(Date.now(), 100, 0) // '--:--:--'
18
+ ```
@@ -1,5 +1,8 @@
1
1
  function factorize_ms(v)
2
2
  {
3
+ // Factorize the magnitude: the sign is the caller's concern, and
4
+ // sub-millisecond fractions are truncated.
5
+ v = Math.trunc(Math.abs(v));
3
6
  const h = Math.floor(v / 3600000);
4
7
  const m = Math.floor(v % 3600000 / 60000);
5
8
  const s = Math.floor(v % 60000 / 1000);
@@ -0,0 +1,10 @@
1
+ Splits a duration in milliseconds into `[hours, minutes, seconds, ms]`.
2
+ Hours are unbounded (no days component). The magnitude is factorized: negative
3
+ input gives the same result as its absolute value — the sign is the caller's
4
+ concern — and sub-millisecond fractions are truncated.
5
+
6
+ ```js
7
+ factorize_ms(3723456) // [1, 2, 3, 456]
8
+ factorize_ms(-1500.7) // [0, 0, 1, 500]
9
+ factorize_ms(999) // [0, 0, 0, 999]
10
+ ```
package/src/fastdl.js CHANGED
@@ -36,6 +36,7 @@ async function fastdl({file, read_stream_with_range, concurrency = 60, user_frie
36
36
  let connections = 0;
37
37
  let next_first = 0;
38
38
  let total_written = 0;
39
+ let rs0_used = false;
39
40
 
40
41
  const timer = setInterval(tick, 1000);
41
42
  function tick() {
@@ -70,15 +71,17 @@ async function fastdl({file, read_stream_with_range, concurrency = 60, user_frie
70
71
  if (first > last) {
71
72
  return;
72
73
  }
73
- const rs = (first === 0) ? rs0 : await read_stream_with_range(first, last);
74
+ const rs = (first === 0 && !rs0_used) ? rs0 : await read_stream_with_range(first, last);
75
+ rs0_used = rs0_used || (rs === rs0);
74
76
  if (rs.content_range.total !== total) {
75
77
  rs.once('error', ignore);
76
78
  rs.destroy();
77
79
  throw new UserFriendlyError('Size of a file changed during download');
78
80
  }
81
+ let counted = 0;
79
82
  const acc = new stream.PassThrough({
80
83
  transform(buf, encoding, next) {
81
- first += buf.length;
84
+ counted += buf.length;
82
85
  total_written += buf.length;
83
86
  progress.add(buf.length);
84
87
  next(null, buf);
@@ -88,7 +91,15 @@ async function fastdl({file, read_stream_with_range, concurrency = 60, user_frie
88
91
  flags: fs.constants.O_WRONLY, // |fs.constants.O_CREAT,
89
92
  start: first,
90
93
  });
91
- await stream.promises.pipeline(rs, acc, ws);
94
+ try {
95
+ await stream.promises.pipeline(rs, acc, ws);
96
+ }
97
+ catch (error) {
98
+ // Counted bytes may not have reached the disk; retry rewrites the chunk from its start
99
+ total_written -= counted;
100
+ progress.add(-counted);
101
+ throw error;
102
+ }
92
103
  }
93
104
  }
94
105
  }
@@ -0,0 +1,12 @@
1
+ Compares two `Date` objects for sorting, ascending, by returning the difference
2
+ of their timestamps in milliseconds (`a.getTime() - b.getTime()`). Distinct
3
+ `Date` objects with the same timestamp compare equal. Throws if either argument
4
+ is not a `Date`.
5
+
6
+ ```js
7
+ const input = ['2022-05-01', '1999-12-31', '2000-01-01'].map(v => new Date(v));
8
+ input.sort(fcmp_dates); // 1999-12-31, 2000-01-01, 2022-05-01
9
+ ```
10
+
11
+ `fcmp_default` also orders `Date` objects (via `<`/`>`), so this is only needed
12
+ when a dedicated date comparator is clearer.
@@ -1,5 +1,12 @@
1
+ const fcmp_utf8_natural_bin = require('./fcmp_utf8_natural_bin');
2
+
3
+ // See fcmp_default.md
1
4
  function fcmp_default(a, b)
2
5
  {
6
+ if (typeof a === 'string' && typeof b === 'string') {
7
+ // Natural order: '2' < '10', 'file2' < 'file10'
8
+ return fcmp_utf8_natural_bin(a, b);
9
+ }
3
10
  if (a < b) {
4
11
  return -1;
5
12
  }
@@ -0,0 +1,29 @@
1
+ Compares two values for sorting. Numbers, dates, and bigints compare via
2
+ `<`/`>`. Strings compare in natural order: digit chunks — ASCII `[0-9]` only,
3
+ Unicode digits are treated as plain text — compare numerically, all other
4
+ characters compare in UTF-16 code-unit order, exactly like the plain `<`/`>`
5
+ operators. No `Intl`/ICU is involved — the order is identical on every machine.
6
+
7
+ ```js
8
+ [999, 22, 15, 10, 3, 2, 1].sort(fcmp_default) // 1 2 3 10 15 22 999
9
+ ['10', '2', '1'].sort(fcmp_default) // '1' '2' '10'
10
+ ['ccc', 'aaa', 'CCC', 'AAA'].sort(fcmp_default) // 'AAA' 'CCC' 'aaa' 'ccc'
11
+ ['readme.md', 'README.md'].sort(fcmp_default) // 'README.md' 'readme.md'
12
+ ['a10', 'a2', 'A20', 'A3'].sort(fcmp_default) // 'A3' 'A20' 'a2' 'a10'
13
+ ```
14
+
15
+ This is the default comparator behind `array_sort`, `array_sort_other`,
16
+ `fcmp_tuples`, and `fcmpx`; `fcmp_default_desc` is its descending mirror. For
17
+ strict UTF-16 code-unit ordering of digit-bearing strings use `fcmp_utf8_bin`;
18
+ for locale-aware ordering use the `fcmp_utf8_*` family.
19
+
20
+ Caveats:
21
+
22
+ - Equal digit values with different padding order by padding: `'2' < '02'`
23
+ (fewer leading zeros first). They never compare equal, so the order is
24
+ total and stable.
25
+ - Mixed types (number vs string) are not a total order; sorting a
26
+ heterogeneous array gives unspecified order between types.
27
+
28
+ Changed in v3.76: digit chunks used to compare by code units, which put
29
+ `'10'` before `'2'`.
@@ -1,12 +1,9 @@
1
+ const fcmp_default = require('./fcmp_default');
2
+
3
+ // See fcmp_default.md
1
4
  function fcmp_default_desc(b, a)
2
5
  {
3
- if (a < b) {
4
- return -1;
5
- }
6
- if (a > b) {
7
- return 1;
8
- }
9
- return 0;
6
+ return fcmp_default(a, b);
10
7
  }
11
8
 
12
9
  module.exports = fcmp_default_desc;
@@ -0,0 +1,10 @@
1
+ Descending mirror of `fcmp_default`: it calls `fcmp_default` with the arguments
2
+ swapped, so everything in `fcmp_default.md` (natural string order, deterministic
3
+ no-`Intl` comparison, caveats) applies here in reverse.
4
+
5
+ ```js
6
+ [1, 10, 2, 9].sort(fcmp_default_desc) // 10 9 2 1
7
+ ['file1', 'file10', 'file2'].sort(fcmp_default_desc) // 'file10' 'file2' 'file1'
8
+ ```
9
+
10
+ For descending order by an object property use `fcmpx('-prop')` instead.
@@ -0,0 +1,13 @@
1
+ Creates a comparator from an array of property names; prefix a name with `-`
2
+ for descending order. Each property compares with `localeCompare` when both
3
+ values are strings (locale-dependent, no natural order), and with subtraction
4
+ otherwise.
5
+
6
+ ```js
7
+ items.sort(fcmp_from_spec(['-price', 'name', 'id']));
8
+ // price desc, then name asc, then id asc
9
+ ```
10
+
11
+ **Deprecated:** use `fcmpx` instead — it supports paths (`'user.email'`),
12
+ functions, natural string order via `fcmp_default`, and defined-before-missing
13
+ ordering.
@@ -0,0 +1,11 @@
1
+ Compares two numbers for sorting, ascending, via plain subtraction (`a - b`).
2
+ Useful because the default `[].sort()` compares numbers as strings.
3
+
4
+ ```js
5
+ [10, 9, 1, 100, 2].sort() // 1 10 100 2 9 (default sort is lexicographic)
6
+ [10, 9, 1, 100, 2].sort(fcmp_numbers) // 1 2 9 10 100
7
+ ```
8
+
9
+ Incomparable pairs (`NaN` vs anything, `Infinity` vs `Infinity`) return `NaN`,
10
+ not `0`. For mixed types use `fcmp_default`, which also sorts numbers
11
+ ascending and handles `NaN` by returning `0`.
@@ -0,0 +1,17 @@
1
+ Compares two arrays (tuples) lexicographically: items are compared pairwise
2
+ with `fcmp` (default `fcmp_default`) and the first non-zero result wins.
3
+
4
+ ## Signature
5
+
6
+ ```js
7
+ fcmp_tuples(a, b, fcmp = fcmp_default)
8
+ ```
9
+
10
+ ```js
11
+ [[2, 1], [1, 2], [1, 1]].sort(fcmp_tuples) // [1,1] [1,2] [2,1]
12
+ fcmp_tuples([1], [1, 2]) // 0 — length is not a tiebreaker
13
+ ```
14
+
15
+ Only the first `Math.min(a.length, b.length)` items are compared: a shorter
16
+ tuple that is a prefix of a longer one compares as equal (unlike string
17
+ comparison, where `'a' < 'ab'`).
@@ -0,0 +1,14 @@
1
+ Strict binary string comparison via the plain `<`/`>` operators — UTF-16
2
+ code-unit order, like MySQL's `utf8_bin` collation. Case-sensitive, all
3
+ uppercase ASCII sorts before lowercase, no numeric awareness
4
+ (`'file10' < 'file2'`). No `Intl`/ICU is involved — the order is identical
5
+ on every machine, unlike the other `fcmp_utf8_*` comparators.
6
+
7
+ ```js
8
+ ['b', 'A', 'a', 'B'].sort(fcmp_utf8_bin) // 'A' 'B' 'a' 'b'
9
+ ['file10', 'file2'].sort(fcmp_utf8_bin) // 'file10' 'file2'
10
+ ```
11
+
12
+ For deterministic natural order (`'file2' < 'file10'`) use
13
+ `fcmp_utf8_natural_bin` (the string order behind `fcmp_default`/`fcmpx`);
14
+ for locale-aware ordering use `fcmp_utf8_cs`/`_ci`.
@@ -0,0 +1,14 @@
1
+ Locale-aware, case-insensitive string comparison, like MySQL's `utf8_ci` /
2
+ `utf8mb4_ci` collations. Uses `localeCompare` with `{sensitivity: 'base'}`,
3
+ so both case and accents are ignored: `'a'` = `'A'`, `'é'` = `'e'`. No numeric
4
+ awareness (`'file10' < 'file2'`).
5
+
6
+ ```js
7
+ ['b', 'A', 'B', 'a'].sort(fcmp_utf8_ci) // 'A' 'a' 'b' 'B' ([].sort is stable)
8
+ fcmp_utf8_ci('HELLO', 'hello') // 0
9
+ ```
10
+
11
+ Results depend on the runtime locale/ICU, unlike `fcmp_utf8_bin` and
12
+ `fcmp_utf8_natural_bin`, which are deterministic on every machine. For a
13
+ case-sensitive locale-aware order use `fcmp_utf8_cs`; for locale-aware natural
14
+ order use `fcmp_utf8_natural_ci`.
@@ -0,0 +1,15 @@
1
+ Locale-aware, case-sensitive string comparison, like MySQL's `utf8_cs` /
2
+ `utf8mb4_cs` collations but using the runtime locale. Uses `localeCompare`
3
+ with `{sensitivity: 'variant'}`: base letters dominate, so `'apple' < 'Banana'`
4
+ (unlike code-unit order), while case and accents still distinguish otherwise
5
+ equal strings. No numeric awareness.
6
+
7
+ ```js
8
+ ['cherry', 'Apple', 'banana'].sort(fcmp_utf8_cs) // 'Apple' 'banana' 'cherry'
9
+ ```
10
+
11
+ Results depend on the runtime locale/ICU — the exact order of case/accent
12
+ variants varies between machines; use `fcmp_utf8_bin` or
13
+ `fcmp_utf8_natural_bin` (the string order behind `fcmp_default`) for a
14
+ deterministic order. For ignoring case use `fcmp_utf8_ci`; for natural order
15
+ use `fcmp_utf8_natural_cs`.
@@ -0,0 +1,38 @@
1
+ // Natural sort (numeric), code-unit order for everything else.
2
+ // Deterministic on every machine — no Intl/ICU involved. See fcmp_default.md
3
+ function fcmp_utf8_natural_bin(a, b)
4
+ {
5
+ const ax = a.match(/\d+|\D+/g) || [];
6
+ const bx = b.match(/\d+|\D+/g) || [];
7
+ const end = Math.min(ax.length, bx.length);
8
+ for (let i = 0; i < end; ++i) {
9
+ const is_digits = (ax[i][0] <= '9' && bx[i][0] <= '9' && ax[i][0] >= '0' && bx[i][0] >= '0');
10
+ const tmp = is_digits ? fcmp_digits(ax[i], bx[i]) : fcmp_units(ax[i], bx[i]);
11
+ if (tmp) {
12
+ return tmp;
13
+ }
14
+ }
15
+ return ax.length - bx.length;
16
+ }
17
+
18
+ // Compare digit runs of any length by numeric value (no Number() overflow);
19
+ // equal values differ by padding: fewer leading zeros first ('2' < '02')
20
+ function fcmp_digits(a, b)
21
+ {
22
+ const as = a.replace(/^0+/, '');
23
+ const bs = b.replace(/^0+/, '');
24
+ return (as.length - bs.length) || fcmp_units(as, bs) || (a.length - b.length);
25
+ }
26
+
27
+ function fcmp_units(a, b)
28
+ {
29
+ if (a < b) {
30
+ return -1;
31
+ }
32
+ if (a > b) {
33
+ return 1;
34
+ }
35
+ return 0;
36
+ }
37
+
38
+ module.exports = fcmp_utf8_natural_bin;
@@ -0,0 +1,8 @@
1
+ Natural string comparison without locale: digit chunks — ASCII `[0-9]` only,
2
+ Unicode digits are treated as plain text — compare numerically (`'2' < '10'`,
3
+ `'file2' < 'file10'`), everything else compares in UTF-16 code-unit order
4
+ (uppercase before lowercase). Deterministic on every machine — no `Intl`/ICU.
5
+ Accepts strings only, like the rest of the `fcmp_utf8_*` family; `fcmp_default`
6
+ handles other types before delegating string pairs here.
7
+ This is the string order behind `fcmp_default`; see `fcmp_default.md` for
8
+ caveats. For locale-aware natural order use `fcmp_utf8_natural_cs`/`_ci`.