@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.
- package/README.md +29 -3
- package/dist/browser.js +1 -2504
- package/package.json +31 -11
- package/src/HeartbeatServer.js +24 -3
- package/src/HeartbeatServer.md +33 -0
- package/src/array_chunk.js +1 -1
- package/src/array_chunk.md +8 -0
- package/src/array_chunk_balanced.md +14 -0
- package/src/array_fps.md +12 -0
- package/src/array_gcd.js +4 -2
- package/src/array_gcd.md +2 -1
- package/src/array_group.js +3 -3
- package/src/array_group.md +14 -0
- package/src/array_group_map.js +3 -3
- package/src/array_group_map.md +13 -0
- package/src/array_index.js +2 -2
- package/src/array_index.md +9 -0
- package/src/array_lcm.js +4 -2
- package/src/array_lcm.md +2 -1
- package/src/array_max.js +11 -4
- package/src/array_max.md +1 -1
- package/src/array_min.js +11 -4
- package/src/array_min.md +1 -1
- package/src/array_permutations.js +4 -0
- package/src/array_permutations.md +13 -0
- package/src/array_sort.js +7 -2
- package/src/array_sort.md +13 -0
- package/src/array_sort_other.js +8 -5
- package/src/array_sort_other.md +11 -0
- package/src/array_sum.js +2 -2
- package/src/array_sum.md +8 -0
- package/src/array_unique.js +2 -2
- package/src/array_unique_last.js +2 -2
- package/src/assert_valid_expires_at.md +10 -0
- package/src/clamp.md +9 -0
- package/src/cli-apps/watchdog.js +5 -0
- package/src/cli.js +3 -1
- package/src/cli.md +15 -0
- package/src/const_stream.md +7 -0
- package/src/const_type.md +5 -0
- package/src/countdown.js +22 -5
- package/src/countdown.md +21 -0
- package/src/crypto_decrypt_aes256.md +13 -0
- package/src/crypto_encrypt_aes256.md +13 -0
- package/src/crypto_hash_md5.md +10 -0
- package/src/crypto_hash_sha256.md +10 -0
- package/src/crypto_hmac_sha256.md +11 -0
- package/src/date_add_hours.md +8 -0
- package/src/date_add_milliseconds.md +4 -0
- package/src/date_add_minutes.md +4 -0
- package/src/date_add_months.md +13 -0
- package/src/date_add_seconds.md +4 -0
- package/src/date_diff_seconds.md +4 -0
- package/src/date_is_leap_year.md +9 -0
- package/src/escape_content_disposition.js +6 -3
- package/src/escape_content_disposition.md +13 -0
- package/src/escape_regexp.md +7 -0
- package/src/eta.js +7 -1
- package/src/eta.md +18 -0
- package/src/factorize_ms.js +3 -0
- package/src/factorize_ms.md +10 -0
- package/src/fastdl.js +14 -3
- package/src/fcmp_dates.md +12 -0
- package/src/fcmp_default.js +7 -0
- package/src/fcmp_default.md +29 -0
- package/src/fcmp_default_desc.js +4 -7
- package/src/fcmp_default_desc.md +10 -0
- package/src/fcmp_from_spec.md +13 -0
- package/src/fcmp_numbers.md +11 -0
- package/src/fcmp_tuples.md +17 -0
- package/src/fcmp_utf8_bin.md +14 -0
- package/src/fcmp_utf8_ci.md +14 -0
- package/src/fcmp_utf8_cs.md +15 -0
- package/src/fcmp_utf8_natural_bin.js +38 -0
- package/src/fcmp_utf8_natural_bin.md +8 -0
- package/src/fcmp_utf8_natural_ci.md +13 -0
- package/src/fcmp_utf8_natural_cs.md +13 -0
- package/src/fcmpx.md +26 -9
- package/src/filter1_from_spec.js +3 -3
- package/src/format_bytes.md +15 -0
- package/src/format_date.md +7 -0
- package/src/format_date_fs.md +7 -0
- package/src/format_date_human.md +6 -0
- package/src/format_date_ymd.md +6 -0
- package/src/format_error_report.js +3 -3
- package/src/format_error_report.md +12 -0
- package/src/format_kilo.md +13 -0
- package/src/format_ms.js +3 -2
- package/src/format_ms.md +12 -0
- package/src/format_ms2.js +6 -3
- package/src/format_ms2.md +12 -0
- package/src/format_ms3.js +3 -2
- package/src/format_ms3.md +12 -0
- package/src/format_percents.md +10 -0
- package/src/format_progress_bytes.md +13 -0
- package/src/format_progress_kilo.md +10 -0
- package/src/format_seconds.md +11 -0
- package/src/format_thousands.md +9 -0
- package/src/fs_append.md +7 -0
- package/src/fs_append_json.md +8 -0
- package/src/fs_append_utf8.md +6 -0
- package/src/fs_assert_file_exists.md +3 -0
- package/src/fs_assert_file_readable.js +1 -1
- package/src/fs_assert_file_readable.md +3 -0
- package/src/fs_assert_file_socket.md +2 -0
- package/src/fs_copy.md +7 -0
- package/src/fs_copy_excl.md +2 -0
- package/src/fs_exists.md +6 -0
- package/src/fs_fclose.md +5 -0
- package/src/fs_fi.js +13 -1
- package/src/fs_fi.md +11 -0
- package/src/fs_file_md5.md +8 -0
- package/src/fs_find.md +7 -0
- package/src/fs_fopen.md +6 -0
- package/src/fs_fread.js +2 -2
- package/src/fs_fread.md +11 -0
- package/src/fs_fsize.md +2 -0
- package/src/fs_fstat.md +2 -0
- package/src/fs_fwrite.js +2 -2
- package/src/fs_fwrite.md +9 -0
- package/src/fs_list.md +5 -0
- package/src/fs_list_deep.md +5 -0
- package/src/fs_ls.md +2 -0
- package/src/fs_lsr.md +4 -0
- package/src/fs_lstat.md +3 -0
- package/src/fs_mkdir.md +3 -0
- package/src/fs_mkdirp.js +3 -30
- package/src/fs_mkdirp.md +7 -0
- package/src/fs_path_basename.md +2 -0
- package/src/fs_path_dirname.md +2 -0
- package/src/fs_path_extname.md +9 -0
- package/src/fs_path_join.md +2 -0
- package/src/fs_path_relative.md +2 -0
- package/src/fs_path_resolve.md +2 -0
- package/src/fs_path_tempdir.md +2 -0
- package/src/fs_path_untildify.md +8 -0
- package/src/fs_read.md +3 -0
- package/src/fs_read_buffer.md +5 -0
- package/src/fs_read_json.md +6 -0
- package/src/fs_read_lines.md +7 -0
- package/src/fs_read_stream.md +2 -0
- package/src/fs_read_utf8.md +5 -0
- package/src/fs_readdir.md +3 -0
- package/src/fs_readlink.md +6 -0
- package/src/fs_rename.md +6 -0
- package/src/fs_rm.md +4 -0
- package/src/fs_rmdir.md +3 -0
- package/src/fs_rmf.md +2 -0
- package/src/fs_rmrf.md +8 -0
- package/src/fs_sha256.js +16 -0
- package/src/fs_sha256.md +9 -0
- package/src/fs_size.js +2 -2
- package/src/fs_size.md +6 -0
- package/src/fs_stat.md +2 -0
- package/src/fs_tempdir.js +20 -5
- package/src/fs_touch.md +6 -0
- package/src/fs_walk.js +2 -2
- package/src/fs_walk.md +14 -0
- package/src/fs_write.md +3 -0
- package/src/fs_write_json.md +7 -0
- package/src/fs_write_stream.md +2 -0
- package/src/gettype.md +13 -0
- package/src/http_delete.md +11 -0
- package/src/http_get_blob.md +8 -0
- package/src/http_get_buffer.md +10 -0
- package/src/http_get_file.js +8 -8
- package/src/http_get_file.md +14 -0
- package/src/http_get_json.md +10 -0
- package/src/http_get_stream.md +14 -0
- package/src/http_get_stream_range.js +27 -9
- package/src/http_get_stream_range.md +22 -0
- package/src/http_get_utf8.md +9 -0
- package/src/http_head.md +9 -0
- package/src/http_patch_json.md +10 -0
- package/src/http_post_json.md +11 -0
- package/src/http_post_multipart.md +15 -0
- package/src/http_post_urlencoded.md +11 -0
- package/src/http_put_buffer.md +9 -0
- package/src/http_put_file.js +9 -6
- package/src/http_put_file.md +16 -0
- package/src/http_put_json.md +11 -0
- package/src/http_put_utf8.md +9 -0
- package/src/http_range_parse.js +6 -3
- package/src/http_range_parse.md +18 -0
- package/src/http_stream_range.js +35 -22
- package/src/http_stream_range.md +29 -0
- package/src/identity.md +2 -0
- package/src/ignore.md +2 -0
- package/src/json_stringify_safe.md +10 -0
- package/src/json_stringify_stable.md +9 -0
- package/src/logger.js +2 -3
- package/src/logger.md +15 -0
- package/src/make_progress.js +8 -5
- package/src/make_progress.md +20 -0
- package/src/math_gcd.js +2 -1
- package/src/mongo_stream_upsert.js +10 -3
- package/src/mongo_stream_upsert.md +17 -0
- package/src/mongo_stream_write.js +10 -3
- package/src/mongo_stream_write.md +16 -0
- package/src/msval.md +10 -0
- package/src/now_fs.md +7 -0
- package/src/now_human.md +6 -0
- package/src/object_defaults.md +11 -0
- package/src/object_schema.js +2 -1
- package/src/object_schema.md +12 -0
- package/src/object_walk_preorder.md +16 -0
- package/src/parallel.js +23 -10
- package/src/parse_bytes.js +4 -2
- package/src/parse_bytes.md +15 -0
- package/src/parse_hms.js +1 -1
- package/src/parse_http_content_range.js +9 -5
- package/src/parse_http_content_range.md +16 -0
- package/src/perf_end_human.md +10 -0
- package/src/perf_end_ms.md +8 -0
- package/src/perf_measure.md +10 -0
- package/src/perf_measure_human.md +8 -0
- package/src/perf_start.md +9 -0
- package/src/pgid_exists.md +6 -0
- package/src/pgid_kill_grace.js +1 -1
- package/src/pgid_kill_grace.md +15 -0
- package/src/pid_exists.md +5 -0
- package/src/pid_kill_grace.js +1 -1
- package/src/pid_kill_grace.md +14 -0
- package/src/plural.md +13 -0
- package/src/random_hex.md +9 -0
- package/src/random_int.md +8 -0
- package/src/range.js +4 -2
- package/src/range.md +2 -0
- package/src/redis_connect.js +1 -1
- package/src/redis_connect.md +10 -0
- package/src/redis_lshift.md +14 -0
- package/src/redis_poll.js +1 -0
- package/src/redis_poll.md +17 -0
- package/src/redis_poll_zshift_callback_rpush.js +1 -2
- package/src/redis_poll_zshift_callback_rpush.md +17 -0
- package/src/redis_poll_zshift_spawn_rpush.js +2 -1
- package/src/redis_poll_zshift_spawn_rpush.md +20 -0
- package/src/redis_zshift.md +14 -0
- package/src/round.js +7 -8
- package/src/round.md +15 -0
- package/src/sanitize_dash_name.md +13 -0
- package/src/sanitize_filename.md +9 -0
- package/src/sanitize_var_name.md +14 -0
- package/src/sftp_get_file_info.js +2 -2
- package/src/sftp_get_file_info.md +14 -0
- package/src/sftp_get_stream_range.js +32 -22
- package/src/sftp_get_stream_range.md +20 -0
- package/src/shell_curl_progress.js +2 -1
- package/src/shell_curl_progress.md +17 -0
- package/src/shell_lines.js +2 -2
- package/src/shell_lines.md +1 -1
- package/src/shell_spawn.js +7 -0
- package/src/shell_ytdlp_progress.js +2 -1
- package/src/shell_ytdlp_progress.md +17 -0
- package/src/str_camel_to_snake.md +11 -0
- package/src/str_parse_kv.md +11 -0
- package/src/stream_curl_progress.md +14 -0
- package/src/stream_data_ln.js +7 -1
- package/src/stream_discard.md +8 -0
- package/src/stream_group.md +3 -0
- package/src/stream_gunzip.md +7 -0
- package/src/stream_hash.md +10 -0
- package/src/stream_map.js +8 -1
- package/src/stream_map_parallel.js +9 -1
- package/src/stream_md5.md +9 -0
- package/src/stream_multiplex.js +11 -0
- package/src/stream_multiplex.md +16 -0
- package/src/stream_through.md +3 -0
- package/src/stream_transform.md +2 -0
- package/src/stream_xml_analyze.js +9 -1
- package/src/stream_xml_parse.js +9 -1
- package/src/stream_ytdlp_progress.js +1 -1
- package/src/stream_ytdlp_progress.md +18 -0
- package/src/strtr.js +15 -5
- package/src/strtr.md +11 -0
- package/src/urlclean.js +9 -2
- package/src/urlmod.js +5 -0
- package/src/urlparts.md +17 -0
- package/src/wait_while.js +1 -1
- package/src/wait_while.md +1 -1
- package/src/HeartbeatServer.test.js +0 -31
- package/src/array_chunk.test.js +0 -24
- package/src/array_chunk_balanced.test.js +0 -37
- package/src/array_fps.test.js +0 -72
- package/src/array_gcd.test.js +0 -10
- package/src/array_index.test.js +0 -14
- package/src/array_lcm.test.js +0 -12
- package/src/array_max.test.js +0 -20
- package/src/array_min.test.js +0 -20
- package/src/array_permutations.test.js +0 -20
- package/src/array_sort.test.js +0 -85
- package/src/array_sort_other.test.js +0 -9
- package/src/array_sum.test.js +0 -11
- package/src/cli-apps/watchdog.d/connect_close.js +0 -17
- package/src/cli-apps/watchdog.d/connect_close2.js +0 -20
- package/src/cli-apps/watchdog.d/connect_close3.js +0 -25
- package/src/cli-apps/watchdog.d/connect_no_ping.js +0 -13
- package/src/cli-apps/watchdog.d/early_exit.js +0 -7
- package/src/cli-apps/watchdog.d/flood.js +0 -13
- package/src/cli-apps/watchdog.d/flood2.js +0 -15
- package/src/cli-apps/watchdog.d/hang.js +0 -8
- package/src/cli-apps/watchdog.d/huge_delay.js +0 -18
- package/src/cli-apps/watchdog.d/ignore_sigterm.js +0 -29
- package/src/cli-apps/watchdog.d/late_heartbeat.js +0 -21
- package/src/cli-apps/watchdog.d/my-exit-after-2-pings.js +0 -16
- package/src/cli-apps/watchdog.d/my-happy-path.js +0 -16
- package/src/cli-apps/watchdog.d/my-ignore-sigterm.js +0 -22
- package/src/cli-apps/watchdog.d/my-never-ping.js +0 -15
- package/src/cli-apps/watchdog.d/no_heartbeat.js +0 -5
- package/src/cli-apps/watchdog.d/ok.js +0 -17
- package/src/cli-apps/watchdog.d/quick-exit-after-2-pings.js +0 -15
- package/src/cli-apps/watchdog.d/quick-hang-after-1-ping.js +0 -15
- package/src/cli-apps/watchdog.d/spawn_children.js +0 -14
- package/src/cli-apps/watchdog.test.js +0 -48
- package/src/crypto_encrypt_aes256.test.js +0 -10
- package/src/crypto_hash_sha256.test.js +0 -10
- package/src/date_add_months.test.js +0 -40
- package/src/date_is_leap_year.test.js +0 -14
- package/src/escape_content_disposition.test.js +0 -22
- package/src/fcmp_from_spec.test.js +0 -91
- package/src/fcmpx.test.js +0 -173
- package/src/filter1_from_spec.test.js +0 -31
- package/src/format_bytes.test.js +0 -22
- package/src/format_error_report.d/src/1.js +0 -26
- package/src/format_error_report.d/src/2.js +0 -29
- package/src/format_kilo.test.js +0 -25
- package/src/format_ms.test.js +0 -33
- package/src/format_ms2.test.js +0 -33
- package/src/format_ms3.test.js +0 -33
- package/src/format_percents.test.js +0 -22
- package/src/format_progress_bytes.test.js +0 -19
- package/src/format_progress_kilo.test.js +0 -19
- package/src/format_seconds.test.js +0 -42
- package/src/format_thousands.test.js +0 -28
- package/src/fs_file_md5.test.js +0 -11
- package/src/fs_mkdirp.test.js +0 -24
- package/src/fs_path_extname.test.js +0 -22
- package/src/gettype.test.js +0 -27
- package/src/object_schema.test.js +0 -15
- package/src/object_walk_preorder.test.js +0 -17
- package/src/parallel.test.js +0 -11
- package/src/parse_bytes.test.js +0 -21
- package/src/parse_hms.test.js +0 -32
- package/src/parse_http_content_range.test.js +0 -14
- package/src/pid_kill_grace.d/ignore-sigterm-for-10ms.js +0 -33
- package/src/pid_kill_grace.d/ignore-sigterm.js +0 -22
- package/src/pid_kill_grace.test.js +0 -63
- package/src/ping_socket.test.js +0 -58
- package/src/plural.test.js +0 -20
- package/src/range.test.js +0 -41
- package/src/redis_lshift.test.js +0 -57
- package/src/redis_zshift.test.js +0 -58
- package/src/round.test.js +0 -56
- package/src/sanitize_dash_name.test.js +0 -59
- package/src/sanitize_var_name.test.js +0 -39
- package/src/shell_spawn.test.js +0 -30
- package/src/str_camel_to_snake.test.js +0 -17
- package/src/stream_curl_progress.d/basic +0 -3
- package/src/stream_curl_progress.d/basic.json +0 -42
- package/src/stream_curl_progress.test.js +0 -17
- package/src/stream_data_ln.test.js +0 -24
- package/src/stream_map_parallel.test.js +0 -26
- package/src/stream_multiplex.test.js +0 -67
- package/src/stream_strpbrk.test.js +0 -29
- package/src/urlclean.test.js +0 -18
- package/src/urlmod.test.js +0 -40
- package/src/urlparts.test.js +0 -294
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Sends a `PUT` request via axios with a string body sent as-is and resolves
|
|
2
|
+
with `response.data`. Extra axios options — e.g. a `Content-Type` header —
|
|
3
|
+
pass through.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
await http_put_utf8(upload_url, csv, {headers: {'Content-Type': 'text/csv'}});
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Sibling of `http_put_buffer` (binary body) and `http_put_json` (JSON body).
|
package/src/http_range_parse.js
CHANGED
|
@@ -12,15 +12,18 @@ function http_range_parse(range, total)
|
|
|
12
12
|
let first = +m[1];
|
|
13
13
|
let last = +m[2];
|
|
14
14
|
|
|
15
|
-
if (!m[1]) { // -LAST
|
|
16
|
-
first = total - last;
|
|
15
|
+
if (!m[1]) { // -LAST: a suffix longer than the resource means the whole resource (RFC 7233)
|
|
16
|
+
first = Math.max(0, total - last);
|
|
17
17
|
last = total - 1;
|
|
18
18
|
}
|
|
19
19
|
else if (!m[2]) { // FIRST-
|
|
20
20
|
last = total - 1;
|
|
21
21
|
}
|
|
22
|
+
else if (last >= total) { // FIRST-LAST reaching past the end reads to the end (RFC 7233)
|
|
23
|
+
last = total - 1;
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
if (first < 0 || first > last || first >= total
|
|
26
|
+
if (first < 0 || first > last || first >= total) {
|
|
24
27
|
throw new Error(`Invalid range: first=${first}, last=${last}, total=${total}, expr="${range}"`);
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Parses an HTTP `Range` request header against a known resource size. Supports
|
|
2
|
+
`bytes=FIRST-LAST`, `bytes=FIRST-`, and `bytes=-LAST` (single range, `bytes`
|
|
3
|
+
unit only; spaces around `=` are allowed). Returns `{first, last}` with both
|
|
4
|
+
bounds inclusive.
|
|
5
|
+
|
|
6
|
+
Clamping follows RFC 7233:
|
|
7
|
+
|
|
8
|
+
- a suffix longer than the resource (`bytes=-5000` of 100) → the whole resource
|
|
9
|
+
- `last` past the end (`bytes=0-999999` of 100) → reads to the end
|
|
10
|
+
|
|
11
|
+
Throws for unparsable expressions and for unsatisfiable ranges: `first` past
|
|
12
|
+
the end (`bytes=100-` of 100), `first > last`, or an empty suffix (`bytes=-0`).
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
http_range_parse('bytes=500-', 1000) // {first: 500, last: 999}
|
|
16
|
+
http_range_parse('bytes=-100', 1000) // {first: 900, last: 999}
|
|
17
|
+
http_range_parse('bytes=0-999999', 100) // {first: 0, last: 99}
|
|
18
|
+
```
|
package/src/http_stream_range.js
CHANGED
|
@@ -27,6 +27,7 @@ async function http_stream_range(req, res, file)
|
|
|
27
27
|
const mime = mime_types.lookup(file);
|
|
28
28
|
|
|
29
29
|
if (req.method === 'HEAD') {
|
|
30
|
+
res.header('Accept-Ranges', 'bytes');
|
|
30
31
|
res.header('Content-Type', mime);
|
|
31
32
|
res.header('Content-Length', total);
|
|
32
33
|
res.header('Content-Disposition', `inline; filename=${escape_content_disposition(fs_path_basename(file))};`);
|
|
@@ -40,6 +41,34 @@ async function http_stream_range(req, res, file)
|
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
res.header('Accept-Ranges', 'bytes');
|
|
45
|
+
|
|
46
|
+
let first = 0;
|
|
47
|
+
let last = total - 1;
|
|
48
|
+
let status = 200;
|
|
49
|
+
|
|
50
|
+
// bytes=0-
|
|
51
|
+
// bytes=-100
|
|
52
|
+
if (req.headers.range) {
|
|
53
|
+
try {
|
|
54
|
+
const range = http_range_parse(req.headers.range, total);
|
|
55
|
+
first = range.first;
|
|
56
|
+
last = range.last;
|
|
57
|
+
status = 206;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// A Range expression we cannot parse is ignored (RFC 7233 says to
|
|
61
|
+
// serve the whole resource), but a syntactically valid range which
|
|
62
|
+
// cannot be satisfied gets 416
|
|
63
|
+
if (error.message.startsWith('Invalid range')) {
|
|
64
|
+
res.status(416);
|
|
65
|
+
res.header('Content-Range', `bytes */${total}`);
|
|
66
|
+
res.end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
43
72
|
const fp = await fs_fopen(file);
|
|
44
73
|
const buf = Buffer.alloc(2*1024*1024);
|
|
45
74
|
|
|
@@ -47,33 +76,17 @@ async function http_stream_range(req, res, file)
|
|
|
47
76
|
let req_close = false;
|
|
48
77
|
req.once('close', () => req_close = true);
|
|
49
78
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
const range = http_range_parse(req.headers.range, total);
|
|
56
|
-
first = range.first;
|
|
57
|
-
last = range.last;
|
|
58
|
-
// req.log(`[http_stream_range_range] ${JSON.stringify({range: req.headers.range, first, last, total, orig: req.headers.range})}`);
|
|
59
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
|
|
60
|
-
res.status(206);
|
|
61
|
-
res.header('Content-Type', mime);
|
|
79
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
|
|
80
|
+
res.status(status);
|
|
81
|
+
res.header('Content-Type', mime);
|
|
82
|
+
res.header('Content-Length', last - first + 1);
|
|
83
|
+
if (status === 206) {
|
|
62
84
|
res.header('Content-Range', `bytes ${first}-${last}/${total}`);
|
|
63
|
-
res.header('Content-Length', last - first + 1);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
first = 0;
|
|
67
|
-
last = total - 1;
|
|
68
|
-
res.header('Content-Type', mime);
|
|
69
|
-
res.header('Content-Length', last - first + 1);
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
let total_read = 0;
|
|
73
87
|
for (let offset = first; offset <= last && !req_close; ) {
|
|
74
88
|
const chunk = await fs_fread(fp, buf, offset, Math.min(buf.length, last - offset + 1));
|
|
75
89
|
offset += chunk.length;
|
|
76
|
-
total_read += chunk.length;
|
|
77
90
|
if (res.write(chunk)) {
|
|
78
91
|
// req.log(`[http_stream_range_write] ${chunk.length}`);
|
|
79
92
|
continue;
|
|
@@ -96,7 +109,7 @@ async function http_stream_range(req, res, file)
|
|
|
96
109
|
});
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
// req.log(`[http_stream_range_end_ok] ${JSON.stringify({req_close
|
|
112
|
+
// req.log(`[http_stream_range_end_ok] ${JSON.stringify({req_close})}`);
|
|
100
113
|
res.end();
|
|
101
114
|
}
|
|
102
115
|
finally {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Express/Node request handler that streams a file from disk, honoring a single
|
|
2
|
+
HTTP `Range` header (parsed with `http_range_parse`; see its doc for supported
|
|
3
|
+
forms and clamping). `Content-Type` comes from `mime-types` lookup on the file
|
|
4
|
+
name.
|
|
5
|
+
|
|
6
|
+
## Signature
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
http_stream_range(req, res, file) → Promise
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- `HEAD` → 200 with `Content-Type`, `Content-Length`, and an
|
|
13
|
+
`inline; filename=...` `Content-Disposition` (escaped via
|
|
14
|
+
`escape_content_disposition`); no body.
|
|
15
|
+
- `GET` with `Range` → 206 with `Content-Range: bytes first-last/total` and a
|
|
16
|
+
matching `Content-Length`.
|
|
17
|
+
- `GET` without `Range` → 200 with the whole file.
|
|
18
|
+
- A `Range` expression it cannot parse is ignored → 200 with the whole file
|
|
19
|
+
(RFC 7233); an unsatisfiable range → 416 with `Content-Range: bytes */total`.
|
|
20
|
+
- Any other method → 405.
|
|
21
|
+
|
|
22
|
+
`GET` and `HEAD` responses advertise `Accept-Ranges: bytes`.
|
|
23
|
+
|
|
24
|
+
Reads in 2 MiB chunks with an `fs_fread` loop, respecting backpressure
|
|
25
|
+
(`res.write` + `drain`) and stopping early when the client disconnects.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
app.get('/file', (req, res) => http_stream_range(req, res, '/data/movie.mp4'));
|
|
29
|
+
```
|
package/src/identity.md
ADDED
package/src/ignore.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Re-export of the [json-stringify-safe](https://www.npmjs.com/package/json-stringify-safe)
|
|
2
|
+
package: `JSON.stringify` that never throws on circular references — cycles
|
|
3
|
+
are replaced with `'[Circular ~]'`-style markers. Same signature as
|
|
4
|
+
`JSON.stringify(obj, replacer, spaces)` plus an optional 4th `cycleReplacer`.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
const a = {name: 'x'};
|
|
8
|
+
a.self = a;
|
|
9
|
+
json_stringify_safe(a) // '{"name":"x","self":"[Circular ~]"}'
|
|
10
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Re-export of the [json-stable-stringify](https://www.npmjs.com/package/json-stable-stringify)
|
|
2
|
+
package: deterministic `JSON.stringify` — object keys are sorted at every
|
|
3
|
+
level, so equal objects always produce byte-identical output. Useful for
|
|
4
|
+
hashing, caching keys, and diff-friendly snapshots.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
json_stringify_stable({b: 2, a: 1, c: {z: 1, y: 2}})
|
|
8
|
+
// '{"a":1,"b":2,"c":{"y":2,"z":1}}'
|
|
9
|
+
```
|
package/src/logger.js
CHANGED
|
@@ -5,15 +5,14 @@ const re = /^((?:\[\s*[^\]]*\])*)\s*(.*)$/m;
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* log = logger();
|
|
8
|
-
* log
|
|
9
|
-
* log
|
|
8
|
+
* log('hey');
|
|
9
|
+
* log('[tag] heyhey');
|
|
10
10
|
*/
|
|
11
11
|
function logger(options = {})
|
|
12
12
|
{
|
|
13
13
|
function out(message) {
|
|
14
14
|
const v = typeof message === 'string' ? JSON.stringify(message.trim()).slice(1, -1) : JSON.stringify(message);
|
|
15
15
|
const [, a, m] = v.match(re);
|
|
16
|
-
const time = new Date().toJSON();
|
|
17
16
|
console.log(`${a} ${m}`.trim());
|
|
18
17
|
};
|
|
19
18
|
out.spawn = function () {
|
package/src/logger.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Creates a minimal console logger. The returned function prints one line per
|
|
2
|
+
call via `console.log`: string messages are trimmed and JSON-escaped (without
|
|
3
|
+
the surrounding quotes), non-strings are printed as JSON. Leading `[tag]`
|
|
4
|
+
prefixes are split off and the whitespace between the tags and the message is
|
|
5
|
+
normalized to a single space.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const log = logger();
|
|
9
|
+
log('hey'); // hey
|
|
10
|
+
log('[tag] heyhey'); // [tag] heyhey
|
|
11
|
+
log({a: 1}); // {"a":1}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`log.spawn()` is a stub that throws `NotImplemented` — the `redis_poll_*`
|
|
15
|
+
helpers expect a logger whose `spawn()` returns a child logger.
|
package/src/make_progress.js
CHANGED
|
@@ -22,7 +22,7 @@ function make_progress(total)
|
|
|
22
22
|
percents: null,
|
|
23
23
|
add: function (delta = 0) {
|
|
24
24
|
const now_minus_10sec = Date.now() - 10000;
|
|
25
|
-
history.splice(0, history.findLastIndex(v => v.time < now_minus_10sec));
|
|
25
|
+
history.splice(0, history.findLastIndex(v => v.time < now_minus_10sec) + 1);
|
|
26
26
|
history.push({time: Date.now(), delta});
|
|
27
27
|
out.done += delta;
|
|
28
28
|
out.refresh();
|
|
@@ -32,16 +32,19 @@ function make_progress(total)
|
|
|
32
32
|
},
|
|
33
33
|
refresh: function () {
|
|
34
34
|
out.duration = (Date.now() - time0)/1000;
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// `rate` and `eta` are null while unknown (same as before the
|
|
36
|
+
// first add): a zero time window would give rate = Infinity,
|
|
37
|
+
// and a zero rate would give eta = Infinity.
|
|
37
38
|
if (history.length) {
|
|
38
39
|
const delta = history.reduce((a,v) => a + v.delta, 0);
|
|
39
40
|
const time_sec = (Date.now() - history[0].time)/1000;
|
|
40
|
-
out.rate = delta/time_sec;
|
|
41
|
+
out.rate = time_sec ? delta/time_sec : null;
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
|
-
out.rate = out.done/out.duration;
|
|
44
|
+
out.rate = out.duration ? out.done/out.duration : null;
|
|
44
45
|
}
|
|
46
|
+
out.percents = !out.total ? null : out.done/out.total;
|
|
47
|
+
out.eta = (!out.total || !out.rate) ? null : (out.total - out.done)/out.rate;
|
|
45
48
|
},
|
|
46
49
|
};
|
|
47
50
|
return out;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Creates a progress tracker for any countable work (bytes copied, jobs done,
|
|
2
|
+
rows created). Pass the snapshot to `format_progress_bytes` /
|
|
3
|
+
`format_progress_kilo` for display.
|
|
4
|
+
|
|
5
|
+
**Fields:** `done`, `total`, `duration` (seconds since creation), `rate`
|
|
6
|
+
(units/second over a sliding 10-second history window), `eta` (seconds),
|
|
7
|
+
`percents` (a fraction, `done/total`). `rate` and `eta` are `null` while
|
|
8
|
+
unknown — before the first `add`, or when they would be `Infinity` (zero
|
|
9
|
+
time window, zero rate); `percents` is `null` when `total` is falsy.
|
|
10
|
+
|
|
11
|
+
**Methods:**
|
|
12
|
+
- `add(delta)` — add `delta` to `done` and recompute
|
|
13
|
+
- `update(done)` — set the absolute `done` value
|
|
14
|
+
- `refresh()` — recompute `duration`/`rate`/`eta`/`percents` without adding
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const p = make_progress(100);
|
|
18
|
+
p.add(10); // p.done → 10, p.percents → 0.1
|
|
19
|
+
p.rate; p.eta; // numbers once enough history exists, null while unknown
|
|
20
|
+
```
|
package/src/math_gcd.js
CHANGED
|
@@ -15,8 +15,9 @@ function mongo_stream_upsert({collection, concurrency = 1})
|
|
|
15
15
|
next();
|
|
16
16
|
},
|
|
17
17
|
destroy: async function (error, next) {
|
|
18
|
+
// Wait for in-flight bulkWrite operations before tearing down.
|
|
18
19
|
await wait_while(() => this.running > 0);
|
|
19
|
-
next();
|
|
20
|
+
next(error);
|
|
20
21
|
},
|
|
21
22
|
write: async function (items, enc, next) {
|
|
22
23
|
if (errors.length) {
|
|
@@ -39,7 +40,10 @@ function mongo_stream_upsert({collection, concurrency = 1})
|
|
|
39
40
|
};
|
|
40
41
|
});
|
|
41
42
|
this.running++;
|
|
42
|
-
Promise.
|
|
43
|
+
// Promise.try turns a synchronous throw from bulkWrite into a
|
|
44
|
+
// rejection, so `running` is always decremented; otherwise
|
|
45
|
+
// destroy/final would wait for it forever.
|
|
46
|
+
Promise.try(() => collection.bulkWrite(operations)).catch(e => errors.push(e)).finally(() => this.running--);
|
|
43
47
|
next();
|
|
44
48
|
}
|
|
45
49
|
catch (error) {
|
|
@@ -47,11 +51,14 @@ function mongo_stream_upsert({collection, concurrency = 1})
|
|
|
47
51
|
}
|
|
48
52
|
},
|
|
49
53
|
final: async function (next) {
|
|
54
|
+
// Wait for in-flight bulkWrite operations before checking `errors`,
|
|
55
|
+
// otherwise an error from the last batch(es) would be silently
|
|
56
|
+
// dropped and the stream would finish successfully.
|
|
57
|
+
await wait_while(() => this.running > 0);
|
|
50
58
|
if (errors.length) {
|
|
51
59
|
next(errors[0]);
|
|
52
60
|
return;
|
|
53
61
|
}
|
|
54
|
-
await wait_while(() => this.running > 0);
|
|
55
62
|
next();
|
|
56
63
|
},
|
|
57
64
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Object-mode writable that inserts or replaces documents in a mongo collection.
|
|
2
|
+
Every chunk written to it must be an array of documents; each document becomes
|
|
3
|
+
a `replaceOne` upsert keyed by its `_id`, and the batch goes to
|
|
4
|
+
`collection.bulkWrite`.
|
|
5
|
+
|
|
6
|
+
- Up to `concurrency` (default `1`) `bulkWrite` calls run in flight; further
|
|
7
|
+
writes wait until a slot frees up.
|
|
8
|
+
- A `bulkWrite` error is reported on the next write or when the stream
|
|
9
|
+
finishes. `final` and `destroy` wait for all in-flight batches first, so an
|
|
10
|
+
error from the last batch is never silently dropped.
|
|
11
|
+
- A non-array chunk errors the stream (`An array of objects is expected.`).
|
|
12
|
+
|
|
13
|
+
## Signature
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
mongo_stream_upsert({collection, concurrency = 1}) → stream.Writable
|
|
17
|
+
```
|
|
@@ -15,8 +15,9 @@ function mongo_stream_write({collection, concurrency = 1})
|
|
|
15
15
|
next();
|
|
16
16
|
},
|
|
17
17
|
destroy: async function (error, next) {
|
|
18
|
+
// Wait for in-flight bulkWrite operations before tearing down.
|
|
18
19
|
await wait_while(() => this.running > 0);
|
|
19
|
-
next();
|
|
20
|
+
next(error);
|
|
20
21
|
},
|
|
21
22
|
write: async function (operations, enc, next) {
|
|
22
23
|
if (errors.length) {
|
|
@@ -30,7 +31,10 @@ function mongo_stream_write({collection, concurrency = 1})
|
|
|
30
31
|
}
|
|
31
32
|
await wait_while(() => this.running >= concurrency);
|
|
32
33
|
this.running++;
|
|
33
|
-
Promise.
|
|
34
|
+
// Promise.try turns a synchronous throw from bulkWrite into a
|
|
35
|
+
// rejection, so `running` is always decremented; otherwise
|
|
36
|
+
// destroy/final would wait for it forever.
|
|
37
|
+
Promise.try(() => collection.bulkWrite(operations)).catch(e => errors.push(e)).finally(() => this.running--);
|
|
34
38
|
next();
|
|
35
39
|
}
|
|
36
40
|
catch (error) {
|
|
@@ -38,11 +42,14 @@ function mongo_stream_write({collection, concurrency = 1})
|
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
44
|
final: async function (next) {
|
|
45
|
+
// Wait for in-flight bulkWrite operations before checking `errors`,
|
|
46
|
+
// otherwise an error from the last batch(es) would be silently
|
|
47
|
+
// dropped and the stream would finish successfully.
|
|
48
|
+
await wait_while(() => this.running > 0);
|
|
41
49
|
if (errors.length) {
|
|
42
50
|
next(errors[0]);
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
45
|
-
await wait_while(() => this.running > 0);
|
|
46
53
|
next();
|
|
47
54
|
},
|
|
48
55
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Object-mode writable that feeds batches of `bulkWrite` operations to a mongo
|
|
2
|
+
collection. Every chunk written to it must be an array of operations; it is
|
|
3
|
+
passed to `collection.bulkWrite(operations)` as is.
|
|
4
|
+
|
|
5
|
+
- Up to `concurrency` (default `1`) `bulkWrite` calls run in flight; further
|
|
6
|
+
writes wait until a slot frees up.
|
|
7
|
+
- A `bulkWrite` error is reported on the next write or when the stream
|
|
8
|
+
finishes. `final` and `destroy` wait for all in-flight batches first, so an
|
|
9
|
+
error from the last batch is never silently dropped.
|
|
10
|
+
- A non-array chunk errors the stream (`An array of objects is expected.`).
|
|
11
|
+
|
|
12
|
+
## Signature
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
mongo_stream_write({collection, concurrency = 1}) → stream.Writable
|
|
16
|
+
```
|
package/src/msval.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Converts hours, minutes, seconds, and milliseconds to a single millisecond
|
|
2
|
+
value: `msval(h, m, s, ms)` → `h*3600000 + m*60000 + s*1000 + ms`. All four
|
|
3
|
+
arguments are required. Components are not normalized — `msval(0, 90, 0, 0)`
|
|
4
|
+
is fine — and negative components subtract.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
msval(1, 30, 15, 500) // 5415500
|
|
8
|
+
msval(0, 90, 0, 0) // 5400000
|
|
9
|
+
msval(1, 0, 0, -1) // 3599999
|
|
10
|
+
```
|
package/src/now_fs.md
ADDED
package/src/now_human.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Fills missing keys of `obj` from `defaults`, mutating and returning `obj`.
|
|
2
|
+
Only keys whose current value is `undefined` (missing or explicitly set) are
|
|
3
|
+
filled; falsy values like `0`, `''`, `false`, and `null` are kept. The
|
|
4
|
+
`defaults` object is never modified.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
object_defaults({a: 1, b: undefined}, {a: 10, b: 20, c: 30})
|
|
8
|
+
// {a: 1, b: 20, c: 30}
|
|
9
|
+
object_defaults({a: 0, b: null}, {a: 5, b: 5})
|
|
10
|
+
// {a: 0, b: null}
|
|
11
|
+
```
|
package/src/object_schema.js
CHANGED
|
@@ -8,12 +8,13 @@ function object_schema(obj)
|
|
|
8
8
|
switch (type) {
|
|
9
9
|
case const_type.array:
|
|
10
10
|
return obj.map(object_schema).map(v => json_stringify_stable(v)).sort().filter((v,i,a) => a[i-1] !== v).map(v => JSON.parse(v));
|
|
11
|
-
case const_type.object:
|
|
11
|
+
case const_type.object: {
|
|
12
12
|
const out = {};
|
|
13
13
|
Object.keys(obj).forEach(function (key) {
|
|
14
14
|
out[key] = object_schema(obj[key]);
|
|
15
15
|
});
|
|
16
16
|
return out;
|
|
17
|
+
}
|
|
17
18
|
default:
|
|
18
19
|
return type;
|
|
19
20
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Returns the shape of a value with every leaf replaced by its `gettype` name.
|
|
2
|
+
Objects keep their keys; an array becomes the deduplicated, sorted list of
|
|
3
|
+
its distinct member schemas (compared via `json_stringify_stable`), so
|
|
4
|
+
`[1, 2, 'x']` collapses to `['number', 'string']`. Useful for eyeballing the
|
|
5
|
+
structure of unknown JSON.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
object_schema({name: 'Bob', tags: ['a', 1], meta: {ok: true}})
|
|
9
|
+
// {name: 'string', tags: ['number', 'string'], meta: {ok: 'boolean'}}
|
|
10
|
+
object_schema([{a: 1}, {a: 2}, {a: 'x'}])
|
|
11
|
+
// [{a: 'number'}, {a: 'string'}]
|
|
12
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Recursively visits every nested value of an object/array in preorder
|
|
2
|
+
(parents before children), calling `fn(value, path)`. The root itself is not
|
|
3
|
+
visited (`path` would be empty). `path` is an array of keys from the root;
|
|
4
|
+
array elements contribute `'*'` instead of their index. `null` values are
|
|
5
|
+
visited but not descended into. Returns nothing.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
object_walk_preorder({aaa: {foo: 1}, bbb: [10, 20]}, function (value, path) {
|
|
9
|
+
console.log(path.join('/'), value);
|
|
10
|
+
});
|
|
11
|
+
// aaa {foo: 1}
|
|
12
|
+
// aaa/foo 1
|
|
13
|
+
// bbb [10, 20]
|
|
14
|
+
// bbb/* 10
|
|
15
|
+
// bbb/* 20
|
|
16
|
+
```
|
package/src/parallel.js
CHANGED
|
@@ -28,11 +28,22 @@ async function parallel({concurrency, spawn, progress})
|
|
|
28
28
|
if (failed) {
|
|
29
29
|
break;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
let w;
|
|
32
|
+
try {
|
|
33
|
+
w = spawn();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// A synchronous throw from spawn must go through fail():
|
|
37
|
+
// when schedule() runs inside a resolved() handler it
|
|
38
|
+
// would otherwise become an unhandled rejection and this
|
|
39
|
+
// promise would never settle (and the timer would leak).
|
|
40
|
+
fail(error);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
32
43
|
if (!w) {
|
|
33
44
|
break;
|
|
34
45
|
}
|
|
35
|
-
const item = Promise.resolve(w).then(resolved,
|
|
46
|
+
const item = Promise.resolve(w).then(resolved, fail);
|
|
36
47
|
running.push(item);
|
|
37
48
|
function resolved() {
|
|
38
49
|
if (failed) {
|
|
@@ -45,20 +56,22 @@ async function parallel({concurrency, spawn, progress})
|
|
|
45
56
|
running.splice(i, 1);
|
|
46
57
|
schedule();
|
|
47
58
|
}
|
|
48
|
-
function rejected(error) {
|
|
49
|
-
if (failed) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
running.splice(0, running.length);
|
|
53
|
-
reject(error);
|
|
54
|
-
}
|
|
55
59
|
}
|
|
56
|
-
if (running.length === 0) {
|
|
60
|
+
if (running.length === 0 && !failed) {
|
|
57
61
|
tick();
|
|
58
62
|
clearInterval(timer);
|
|
59
63
|
resolve();
|
|
60
64
|
}
|
|
61
65
|
}
|
|
66
|
+
function fail(error) {
|
|
67
|
+
if (failed) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
failed = true;
|
|
71
|
+
running.splice(0, running.length);
|
|
72
|
+
clearInterval(timer);
|
|
73
|
+
reject(error);
|
|
74
|
+
}
|
|
62
75
|
function tick() {
|
|
63
76
|
if (progress) {
|
|
64
77
|
progress(running);
|
package/src/parse_bytes.js
CHANGED
|
@@ -4,11 +4,13 @@ function parse_bytes(bytes)
|
|
|
4
4
|
if (typeof bytes !== 'string') {
|
|
5
5
|
return Number.NaN;
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
// Anchored: trailing garbage ('12KB34') is NaN rather than 12KB. A bare
|
|
8
|
+
// number means bytes; whitespace around and between number and unit is ok
|
|
9
|
+
const m = bytes.toUpperCase().match(/^\s*(\d+(?:[.]\d+)?)\s*((?:[KMGTP]I?)?B)?\s*$/);
|
|
8
10
|
if (!m) {
|
|
9
11
|
return Number.NaN;
|
|
10
12
|
}
|
|
11
|
-
return Math.floor(m[1]*Math.pow(1024, sizes.indexOf(m[
|
|
13
|
+
return Math.floor(m[1]*Math.pow(1024, sizes.indexOf((m[2] || 'B').replace('I', ''))));
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
module.exports = parse_bytes;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Parses a human-readable size string into a number of bytes. Units are
|
|
2
|
+
1024-based: `B`, `KB`, `MB`, `GB`, `TB`, `PB`; the `KiB`/`MiB` spellings are
|
|
3
|
+
accepted and mean the same thing. Matching is case-insensitive and anchored:
|
|
4
|
+
whitespace around and between number and unit is fine, but any other
|
|
5
|
+
trailing/leading garbage gives `NaN`. A bare number means bytes. The result
|
|
6
|
+
is `Math.floor`ed. Non-strings (including numbers) return `NaN`.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
parse_bytes('10.5MB') // 11010048
|
|
10
|
+
parse_bytes('1KIB') // 1024
|
|
11
|
+
parse_bytes('5 mb') // 5242880
|
|
12
|
+
parse_bytes('123') // 123 (bare number means bytes)
|
|
13
|
+
parse_bytes('12KB34') // NaN (trailing garbage, not 12KB)
|
|
14
|
+
parse_bytes(123) // NaN (not a string)
|
|
15
|
+
```
|
package/src/parse_hms.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
function parse_http_content_range(value)
|
|
2
2
|
{
|
|
3
3
|
// Content-Range: bytes 262144000-272629759/5037662208
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Content-Range: bytes */5037662208 (unsatisfied range → first/last are null)
|
|
5
|
+
// Content-Range: bytes 0-499/* (unknown total → total is null)
|
|
6
|
+
const m = value.match(/^(\w+)\s+(?:(\d+)-(\d+)|[*])[/](?:(\d+)|[*])$/);
|
|
7
|
+
// "*/*" is not in the RFC 7233 grammar: an unsatisfied range must state
|
|
8
|
+
// the complete length
|
|
9
|
+
if (!m || (m[2] === undefined && m[4] === undefined)) {
|
|
6
10
|
throw new Error(`Invalid Content-Range: [${value}]`);
|
|
7
11
|
}
|
|
8
12
|
const type = m[1];
|
|
9
|
-
const first = +m[2];
|
|
10
|
-
const last = +m[3];
|
|
11
|
-
const total = +m[4];
|
|
13
|
+
const first = m[2] === undefined ? null : +m[2];
|
|
14
|
+
const last = m[3] === undefined ? null : +m[3];
|
|
15
|
+
const total = m[4] === undefined ? null : +m[4];
|
|
12
16
|
return {type, first, last, total};
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Parses an HTTP `Content-Range` response header into
|
|
2
|
+
`{type, first, last, total}` (numbers, bounds inclusive).
|
|
3
|
+
|
|
4
|
+
- `bytes 0-499/1000` — a regular range.
|
|
5
|
+
- `bytes */5000` — unsatisfied range: `first` and `last` are `null`.
|
|
6
|
+
- `bytes 0-499/*` — unknown total: `total` is `null`.
|
|
7
|
+
- `bytes */*` throws — it is not in the RFC 7233 grammar: an unsatisfied
|
|
8
|
+
range must state the complete length.
|
|
9
|
+
|
|
10
|
+
Throws for anything else it cannot parse.
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
parse_http_content_range('bytes 0-499/1000') // {type: 'bytes', first: 0, last: 499, total: 1000}
|
|
14
|
+
parse_http_content_range('bytes */5000') // {type: 'bytes', first: null, last: null, total: 5000}
|
|
15
|
+
parse_http_content_range('bytes 0-499/*') // {type: 'bytes', first: 0, last: 499, total: null}
|
|
16
|
+
```
|