@vbarbarosh/node-helpers 1.0.1 → 1.1.3

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 (69) hide show
  1. package/README.md +1 -1
  2. package/package.json +7 -3
  3. package/src/array_gcd.js +16 -0
  4. package/src/array_gcd.test.js +10 -0
  5. package/src/array_index.js +8 -0
  6. package/src/array_index.test.js +14 -0
  7. package/src/array_sort.js +23 -0
  8. package/src/array_sort.test.js +10 -0
  9. package/src/array_sort_other.js +5 -5
  10. package/src/array_sort_other.test.js +2 -1
  11. package/src/array_sum.js +10 -0
  12. package/src/array_sum.test.js +11 -0
  13. package/src/cli.js +38 -0
  14. package/src/crypto_decrypt_aes256.js +16 -0
  15. package/src/crypto_encrypt_aes256.js +18 -0
  16. package/src/crypto_encrypt_aes256.test.js +10 -0
  17. package/src/crypto_hash_md5.js +14 -0
  18. package/src/crypto_hash_sha256.js +14 -0
  19. package/src/crypto_hash_sha256.test.js +10 -0
  20. package/src/crypto_hmac_sha256.js +15 -0
  21. package/src/format_thousands.js +7 -0
  22. package/src/fs_append.js +5 -0
  23. package/src/fs_append_json.js +9 -0
  24. package/src/fs_assert_file_exists.js +11 -0
  25. package/src/fs_assert_file_readable.js +14 -0
  26. package/src/fs_copy.js +5 -0
  27. package/src/fs_exists.js +12 -0
  28. package/src/fs_fclose.js +5 -0
  29. package/src/fs_fi.js +55 -0
  30. package/src/fs_find.js +21 -0
  31. package/src/fs_fopen.js +5 -0
  32. package/src/fs_fopen_update.js +34 -0
  33. package/src/fs_fread.js +15 -0
  34. package/src/fs_fsize.js +8 -0
  35. package/src/fs_fstat.js +5 -0
  36. package/src/fs_fwrite.js +15 -0
  37. package/src/fs_ls.js +16 -0
  38. package/src/fs_lsr.js +26 -0
  39. package/src/fs_lstat.js +5 -0
  40. package/src/fs_mkdir.js +5 -0
  41. package/src/fs_mkdirp.js +26 -0
  42. package/src/fs_path_basename.js +8 -0
  43. package/src/fs_path_dirname.js +8 -0
  44. package/src/fs_path_join.js +8 -0
  45. package/src/fs_path_resolve.js +8 -0
  46. package/src/fs_path_sanitize.js +8 -0
  47. package/src/fs_path_tempdir.js +8 -0
  48. package/src/fs_read.js +5 -0
  49. package/src/fs_read_json.js +10 -0
  50. package/src/fs_read_stream.js +1 -0
  51. package/src/fs_read_utf8.js +8 -0
  52. package/src/fs_readdir.js +5 -0
  53. package/src/fs_readlink.js +5 -0
  54. package/src/fs_rename.js +5 -0
  55. package/src/fs_rm.js +5 -0
  56. package/src/fs_rmdir.js +5 -0
  57. package/src/fs_rmrf.js +25 -0
  58. package/src/fs_size.js +8 -0
  59. package/src/fs_tempdir.js +42 -0
  60. package/src/fs_write.js +5 -0
  61. package/src/fs_write_stream.js +1 -0
  62. package/src/http_put_file.js +60 -0
  63. package/src/identity.js +6 -0
  64. package/src/ignore.js +5 -0
  65. package/src/json_stringify_stable.js +3 -0
  66. package/src/stream_data_ln.js +42 -0
  67. package/src/stream_data_ln.test.js +15 -0
  68. package/src/stream_promise.js +62 -0
  69. package/src/stream_promise.test.js +14 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@ A set of helpers for JavaScript/Node.js
2
2
 
3
3
  ## Installation
4
4
 
5
- npm i @vbarbarosh/node-helpers
5
+ npm install vbarbarosh/node-helpers
6
6
 
7
7
  ## Test
8
8
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
3
3
  "license": "MIT",
4
4
  "name": "@vbarbarosh/node-helpers",
5
- "version": "1.0.1",
5
+ "version": "1.1.3",
6
6
  "description": "A set of helpers for JavaScript/Node.js",
7
7
  "files": [
8
8
  "src"
@@ -24,12 +24,16 @@
24
24
  },
25
25
  "peerDependencies": {
26
26
  "axios": "^0.21.1",
27
- "form-data": "^4.0.0"
27
+ "bluebird": "^3.7.2",
28
+ "form-data": "^4.0.0",
29
+ "json-stable-stringify": "^1.0.1",
30
+ "sanitize-filename": "^1.6.3"
28
31
  },
29
32
  "devDependencies": {
30
33
  "@babel/preset-env": "^7.14.8",
31
34
  "@babel/register": "^7.14.5",
32
35
  "babel": "^6.23.0",
33
- "mocha": "^9.0.2"
36
+ "mocha": "^9.0.2",
37
+ "throttle": "^1.0.3"
34
38
  }
35
39
  }
@@ -0,0 +1,16 @@
1
+ // https://stackoverflow.com/a/39764792/1478566
2
+ function array_gcd(array, fn = Number)
3
+ {
4
+ return array.reduce((a,b) => gcd(fn(a), fn(b)));
5
+ }
6
+
7
+ // https://stackoverflow.com/a/39764792/1478566
8
+ function gcd(a, b)
9
+ {
10
+ if (b) {
11
+ return gcd(b, a % b);
12
+ }
13
+ return a;
14
+ }
15
+
16
+ export default array_gcd;
@@ -0,0 +1,10 @@
1
+ import array_gcd from './array_gcd';
2
+ import assert from 'assert';
3
+
4
+ describe('array_gcd', function () {
5
+ it('should handle basic input', function () {
6
+ assert.deepStrictEqual(array_gcd([1,2,3,4,5]), 1);
7
+ assert.deepStrictEqual(array_gcd([4,8,16]), 4);
8
+ assert.deepStrictEqual(array_gcd([300,100,200,300]), 100);
9
+ });
10
+ });
@@ -0,0 +1,8 @@
1
+ function array_index(array, fn)
2
+ {
3
+ const out = {};
4
+ array.forEach(v => out[fn(v)] = v);
5
+ return out;
6
+ }
7
+
8
+ export default array_index;
@@ -0,0 +1,14 @@
1
+ import array_index from './array_index';
2
+ import assert from 'assert';
3
+
4
+ describe('array_index', function () {
5
+ it('should accept empty array', function () {
6
+ assert.deepStrictEqual(array_index([]), {});
7
+ });
8
+ it('should handle basic input', function () {
9
+ const a = {uid: 'a'};
10
+ const b = {uid: 'b'};
11
+ const c = {uid: 'c'};
12
+ assert.deepStrictEqual(array_index([a, b, c], v => v.uid), {a, b, c});
13
+ });
14
+ });
@@ -0,0 +1,23 @@
1
+ import fcmp_strings from './fcmp_strings';
2
+
3
+ /**
4
+ * Sort items in an `array`.
5
+ *
6
+ * @param array
7
+ * @param fn
8
+ * @param fcmp
9
+ * @returns {*}
10
+ */
11
+ function array_sort(array, fn = identity, fcmp = fcmp_strings)
12
+ {
13
+ return array.sort(function (a, b) {
14
+ return fcmp(fn(a), fn(b));
15
+ });
16
+ }
17
+
18
+ function identity(v)
19
+ {
20
+ return v;
21
+ }
22
+
23
+ export default array_sort;
@@ -0,0 +1,10 @@
1
+ import array_sort from './array_sort';
2
+ import assert from 'assert';
3
+
4
+ describe('array_sort', function () {
5
+ it('should handle basic input', function () {
6
+ assert.deepStrictEqual(array_sort([5,4,3,2,1]), [1,2,3,4,5]);
7
+ assert.deepStrictEqual(array_sort([5,4,3,2,1], v => v), [1,2,3,4,5]);
8
+ assert.deepStrictEqual(array_sort([{id:5},{id:4},{id:3},{id:2},{id:1}], v => v.id), [{id:1},{id:2},{id:3},{id:4},{id:5}]);
9
+ });
10
+ });
@@ -2,21 +2,21 @@ import fcmp_strings from './fcmp_strings';
2
2
 
3
3
  /**
4
4
  * Sort items in an `array` at the same order as in `other`. Values which
5
- * are not in `other` are added to the end in the order defined by `fcmp`.
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
9
  * @param other
9
10
  * @param fcmp
10
11
  * @returns {*}
11
12
  */
12
-
13
- function array_sort_other(array, other, fcmp = fcmp_strings)
13
+ function array_sort_other(array, fn, other, fcmp = fcmp_strings)
14
14
  {
15
15
  const other_map = {};
16
16
  other.forEach((v,i) => other_map[v] = i + 1);
17
17
  return array.sort(function (a, b) {
18
- const ax = other_map[a];
19
- const bx = other_map[b];
18
+ const ax = other_map[fn(a)];
19
+ const bx = other_map[fn(b)];
20
20
  if (ax && bx) {
21
21
  return ax - bx;
22
22
  }
@@ -3,6 +3,7 @@ import assert from 'assert';
3
3
 
4
4
  describe('array_sort_other', function () {
5
5
  it('should handle basic input', function () {
6
- assert.deepStrictEqual(array_sort_other([1,2,3,4,5], [5]), [5,1,2,3,4]);
6
+ assert.deepStrictEqual(array_sort_other([1,2,3,4,5], v => v, [5]), [5,1,2,3,4]);
7
+ assert.deepStrictEqual(array_sort_other([{id:1},{id:2},{id:3},{id:4},{id:5}], v => v.id, [5]), [{id:5},{id:1},{id:2},{id:3},{id:4}]);
7
8
  });
8
9
  });
@@ -0,0 +1,10 @@
1
+ function array_sum(array, fn = Number)
2
+ {
3
+ let out = 0;
4
+ array.forEach(function (item) {
5
+ out += fn(item);
6
+ });
7
+ return out;
8
+ }
9
+
10
+ export default array_sum;
@@ -0,0 +1,11 @@
1
+ import array_sum from './array_sum';
2
+ import assert from 'assert';
3
+
4
+ describe('array_sum', function () {
5
+ it('should handle basic input', function () {
6
+ assert.deepStrictEqual(array_sum([1,2,3,4,5]), 15);
7
+ });
8
+ it('should cast strings to numbers', function () {
9
+ assert.deepStrictEqual(array_sum(['1','2','3','4','5']), 15);
10
+ });
11
+ });
package/src/cli.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Entry point for Node CLI apps
3
+ *
4
+ * @link https://github.com/vbarbarosh/node-cli
5
+ */
6
+ async function cli(main)
7
+ {
8
+ // https://stackoverflow.com/a/46916601/1478566
9
+ const timer = setInterval(v => v, 1E9);
10
+
11
+ try {
12
+ return await main();
13
+ }
14
+ catch (error) {
15
+ console.error(error);
16
+ process.exit(1);
17
+ }
18
+ finally {
19
+ clearInterval(timer);
20
+ }
21
+ }
22
+
23
+ // import Promise from 'bluebird';
24
+ //
25
+ // // https://stackoverflow.com/a/57241059/1478566
26
+ // function cli(main)
27
+ // {
28
+ // // https://stackoverflow.com/a/46916601/1478566
29
+ // return Promise.method(main).call().catch(panic).finally(clearInterval.bind(null, setInterval(v=>v, 1E9)));
30
+ // }
31
+ //
32
+ // function panic(error)
33
+ // {
34
+ // console.error(error);
35
+ // process.exit(1);
36
+ // }
37
+
38
+ export default cli;
@@ -0,0 +1,16 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * @link https://nodejs.org/api/crypto.html#crypto_class_cipher
5
+ * @link https://attacomsian.com/blog/nodejs-encrypt-decrypt-data
6
+ */
7
+ function crypto_decrypt_aes256(password, ivenc)
8
+ {
9
+ const key = crypto.createHash('sha256').update(password).digest();
10
+ const iv = ivenc.slice(1, ivenc[0] + 1);
11
+ const enc = ivenc.slice(ivenc[0] + 1);
12
+ const decipher = crypto.createDecipheriv('aes-256-ctr', key, iv);
13
+ return Buffer.concat([decipher.update(enc), decipher.final()]);
14
+ }
15
+
16
+ export default crypto_decrypt_aes256;
@@ -0,0 +1,18 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * @link https://nodejs.org/api/crypto.html#crypto_class_cipher
5
+ * @link https://attacomsian.com/blog/nodejs-encrypt-decrypt-data
6
+ */
7
+ function crypto_encrypt_aes256(password, data)
8
+ {
9
+ const iv = crypto.randomBytes(16);
10
+ const key = crypto.createHash('sha256').update(password).digest();
11
+ const cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
12
+ if (iv.length > 255) {
13
+ throw new Error('iv size is too big');
14
+ }
15
+ return Buffer.concat([Buffer.from([iv.length]), iv, cipher.update(data), cipher.final()]);
16
+ }
17
+
18
+ export default crypto_encrypt_aes256;
@@ -0,0 +1,10 @@
1
+ import assert from 'assert';
2
+ import crypto_decrypt_aes256 from './crypto_decrypt_aes256';
3
+ import crypto_encrypt_aes256 from './crypto_encrypt_aes256';
4
+
5
+ describe('crypto_encrypt_aes256', function () {
6
+ it('should handle basic input', function () {
7
+ const password = 'pass123';
8
+ assert.deepStrictEqual(crypto_decrypt_aes256(password, crypto_encrypt_aes256(password, 'hello')).toString('utf8'), 'hello');
9
+ });
10
+ });
@@ -0,0 +1,14 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ *
5
+ * @param data
6
+ * @returns {Buffer}
7
+ * @link https://nodejs.org/en/knowledge/cryptography/how-to-use-crypto-module/#how-to-calculate-hashes-with-crypto
8
+ */
9
+ function crypto_hash_md5(data)
10
+ {
11
+ return crypto.createHash('md5').update(data).digest();
12
+ }
13
+
14
+ export default crypto_hash_md5;
@@ -0,0 +1,14 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ *
5
+ * @param data
6
+ * @returns {Buffer}
7
+ * @link https://nodejs.org/en/knowledge/cryptography/how-to-use-crypto-module/#how-to-calculate-hashes-with-crypto
8
+ */
9
+ function crypto_hash_sha256(data)
10
+ {
11
+ return crypto.createHash('sha256').update(data).digest();
12
+ }
13
+
14
+ export default crypto_hash_sha256;
@@ -0,0 +1,10 @@
1
+ import crypto_hash_sha256 from './crypto_hash_sha256';
2
+ import assert from 'assert';
3
+
4
+ describe('crypto_hash_sha256', function () {
5
+ it('should handle basic input', function () {
6
+ assert.deepStrictEqual(crypto_hash_sha256('').toString('hex'), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
7
+ assert.deepStrictEqual(crypto_hash_sha256('1').toString('hex'), '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b');
8
+ assert.deepStrictEqual(crypto_hash_sha256('hello').toString('hex'), '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824');
9
+ });
10
+ });
@@ -0,0 +1,15 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ *
5
+ * @param password
6
+ * @param data
7
+ * @returns {Buffer}
8
+ * @link https://nodejs.org/en/knowledge/cryptography/how-to-use-crypto-module/#hmac
9
+ */
10
+ function crypto_hmac_sha256(password, data)
11
+ {
12
+ return crypto.createHmac('sha256', password).update(data).digest();
13
+ }
14
+
15
+ export default crypto_hmac_sha256;
@@ -0,0 +1,7 @@
1
+ // https://stackoverflow.com/a/2901298
2
+ function format_thousands(x)
3
+ {
4
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
5
+ }
6
+
7
+ export default format_thousands;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.appendFile);
@@ -0,0 +1,9 @@
1
+ import fs_append from './fs_append';
2
+ import json_stringify_stable from './json_stringify_stable';
3
+
4
+ function fs_append_json(file, value)
5
+ {
6
+ return fs_append(file, json_stringify_stable(value) + '\n');
7
+ }
8
+
9
+ export default fs_append_json;
@@ -0,0 +1,11 @@
1
+ import fs_fi from './fs_fi';
2
+
3
+ async function fs_assert_file_exists(file)
4
+ {
5
+ const fi = await fs_fi(file);
6
+ if (!fi.isFile()) {
7
+ throw new Error(`Not a file: ${file}`);
8
+ }
9
+ }
10
+
11
+ export default fs_assert_file_exists;
@@ -0,0 +1,14 @@
1
+ import fs_fi from './fs_fi';
2
+
3
+ async function fs_assert_file_readable(file)
4
+ {
5
+ const fi = await fs_fi(file);
6
+ if (!fi.isFile()) {
7
+ throw new Error(`Not a file: ${file}`);
8
+ }
9
+ if (fi.mode & 0o111) {
10
+ throw new Error(`File is not readable by all: ${file}`);
11
+ }
12
+ }
13
+
14
+ export default fs_assert_file_readable;
package/src/fs_copy.js ADDED
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.copyFile);
@@ -0,0 +1,12 @@
1
+ import fs from 'fs';
2
+
3
+ async function fs_exists(path)
4
+ {
5
+ return new Promise(function (resolve) {
6
+ fs.access(path, function (error) {
7
+ error ? resolve(false) : resolve(true);
8
+ });
9
+ });
10
+ }
11
+
12
+ export default fs_exists;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.close);
package/src/fs_fi.js ADDED
@@ -0,0 +1,55 @@
1
+ import fs_lstat from './fs_lstat';
2
+ import fs_path_basename from './fs_path_basename';
3
+ import fs_path_dirname from './fs_path_dirname';
4
+ import fs_path_resolve from './fs_path_resolve';
5
+ import fs_readlink from './fs_readlink';
6
+
7
+ async function fs_fi(pathname)
8
+ {
9
+ const basename = fs_path_basename(pathname);
10
+ const fi = await fs_lstat(pathname);
11
+ fi.pathname = pathname;
12
+ fi.basename = basename;
13
+ flags(fi);
14
+ if (fi.isSymbolicLink()) {
15
+ const target = await fs_readlink(fi.pathname);
16
+ const target_pathname = fs_path_resolve(fs_path_dirname(pathname), target);
17
+ fi.target = target;
18
+ fi.target_fi = await fs_lstat(target_pathname);
19
+ fi.target_fi.pathname = target_pathname;
20
+ fi.target_fi.basename = fs_path_basename(target_pathname);
21
+ flags(fi.target_fi);
22
+ }
23
+ return fi;
24
+ }
25
+
26
+ function flags(fi)
27
+ {
28
+ const tmp = [];
29
+ if (fi.isFile()) {
30
+ tmp.push('is_file');
31
+ }
32
+ if (fi.isDirectory()) {
33
+ tmp.push('is_directory');
34
+ }
35
+ if (fi.isBlockDevice()) {
36
+ tmp.push('is_block_device');
37
+ }
38
+ if (fi.isCharacterDevice()) {
39
+ tmp.push('is_character_device');
40
+ }
41
+ if (fi.isSymbolicLink()) {
42
+ tmp.push('is_symbolic_link');
43
+ }
44
+ if (fi.isFIFO()) {
45
+ tmp.push('is_fifo');
46
+ }
47
+ if (fi.isSocket()) {
48
+ tmp.push('is_socket');
49
+ }
50
+ fi.flags = tmp;
51
+ fi.flags_map = {};
52
+ tmp.forEach(key => fi.flags_map[key] = true);
53
+ }
54
+
55
+ export default fs_fi;
package/src/fs_find.js ADDED
@@ -0,0 +1,21 @@
1
+ import Promise from 'bluebird';
2
+ import fs_fi from './fs_fi';
3
+ import fs_path_resolve from './fs_path_resolve';
4
+ import fs_readdir from './fs_readdir';
5
+
6
+ async function fs_find(pathname = '.')
7
+ {
8
+ const fi = await fs_fi(fs_path_resolve(pathname));
9
+ if (fi.isDirectory()) {
10
+ const names = await fs_readdir(pathname);
11
+ const rows = await Promise.all(names.map(v => fs_find(fs_path_resolve(pathname, v))));
12
+ const out = [fi];
13
+ for (let i = 0, end = rows.length; i < end; ++i) {
14
+ out.push(...rows[i]);
15
+ }
16
+ return out;
17
+ }
18
+ return [fi];
19
+ }
20
+
21
+ export default fs_find;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.open);
@@ -0,0 +1,34 @@
1
+ import fs_fopen from './fs_fopen';
2
+
3
+ // Main use case is uploading files in chunks. Each time
4
+ // a chunk is uploaded do update a target file. At the end
5
+ // you will have a ready to go file.
6
+
7
+ async function fs_fopen_update(pathname)
8
+ {
9
+ try {
10
+ // Open a file for updating...
11
+ return await fs_fopen(pathname, 'r+');
12
+ }
13
+ catch (error) {
14
+ if (error.code != 'ENOENT') {
15
+ throw error;
16
+ }
17
+ }
18
+
19
+ try {
20
+ // There is no such a file, try to create it...
21
+ return await fs_fopen(pathname, 'wx');
22
+ }
23
+ catch (error) {
24
+ if (error.code != 'EEXIST') {
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ // Another process might have already created it, try to
30
+ // open for updating...
31
+ return fs_fopen(pathname, 'r+');
32
+ }
33
+
34
+ export default fs_fopen_update;
@@ -0,0 +1,15 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ const read = util.promisify(fs.read);
6
+
7
+ // Main use case is for reading chunks of data from file.
8
+
9
+ async function fs_fread(fp, buffer, offset = null)
10
+ {
11
+ const {bytesRead} = await read(fp, buffer, 0, buffer.length, offset);
12
+ return (bytesRead == buffer.length) ? buffer : buffer.slice(0, bytesRead);
13
+ }
14
+
15
+ export default fs_fread;
@@ -0,0 +1,8 @@
1
+ import fs_fstat from './fs_fstat';
2
+
3
+ function fs_fsize(fp)
4
+ {
5
+ return fs_fstat(fp).then(v => v.size);
6
+ }
7
+
8
+ export default fs_fsize;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.fstat);
@@ -0,0 +1,15 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ const write = util.promisify(fs.write);
6
+
7
+ // Main use case is for writing chunks of data to file.
8
+
9
+ async function fs_fwrite(fp, buffer, offset = null)
10
+ {
11
+ const {bytesWritten} = await write(fp, buffer, 0, buffer.length, offset);
12
+ return bytesWritten;
13
+ }
14
+
15
+ export default fs_fwrite;
package/src/fs_ls.js ADDED
@@ -0,0 +1,16 @@
1
+ import Promise from 'bluebird';
2
+ import fs_fi from './fs_fi';
3
+ import fs_path_join from './fs_path_join';
4
+ import fs_readdir from './fs_readdir';
5
+
6
+ async function fs_ls(pathname = '.')
7
+ {
8
+ const fi = await fs_fi(pathname);
9
+ if (fi.isDirectory()) {
10
+ const names = await fs_readdir(pathname);
11
+ return Promise.all(names.map(v => fs_fi(fs_path_join(pathname, v))));
12
+ }
13
+ return [fi];
14
+ }
15
+
16
+ export default fs_ls;
package/src/fs_lsr.js ADDED
@@ -0,0 +1,26 @@
1
+ import Promise from 'bluebird';
2
+ import fs_fi from './fs_fi';
3
+ import fs_path_join from './fs_path_join';
4
+ import fs_readdir from './fs_readdir';
5
+
6
+ async function fs_lsr(pathname = '.')
7
+ {
8
+ const fi = await fs_fi(pathname);
9
+ const ret = [fi];
10
+ if (fi.isDirectory()) {
11
+ fi.content_size = 0;
12
+ const names = await fs_readdir(pathname);
13
+ const all = await Promise.all(names.map(v => fs_lsr(fs_path_join(pathname, v))));
14
+ for (let i = 0, end = all.length; i < end; ++i) {
15
+ const rows = all[i];
16
+ ret.push(...rows);
17
+ fi.content_size += rows[0].content_size;
18
+ }
19
+ }
20
+ else {
21
+ fi.content_size = fi.isFile() ? fi.size : 0;
22
+ }
23
+ return ret;
24
+ }
25
+
26
+ export default fs_lsr;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.lstat);
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.mkdir);
@@ -0,0 +1,26 @@
1
+ import fs_lstat from './fs_lstat';
2
+ import fs_mkdir from './fs_mkdir';
3
+ import fs_path_dirname from './fs_path_dirname';
4
+
5
+ async function fs_mkdirp(pathname)
6
+ {
7
+ const parents = [];
8
+
9
+ for (let p = pathname; p && p != '/'; p = fs_path_dirname(p)) {
10
+ try {
11
+ await fs_lstat(p);
12
+ break;
13
+ }
14
+ catch (error) {
15
+ parents.unshift(p);
16
+ }
17
+ }
18
+
19
+ for (let i = 0, end = parents.length; i < end; ++i) {
20
+ await fs_mkdir(parents[i]);
21
+ }
22
+
23
+ return pathname;
24
+ }
25
+
26
+ export default fs_mkdirp;
@@ -0,0 +1,8 @@
1
+ import path from 'path';
2
+
3
+ function fs_path_basename(p)
4
+ {
5
+ return path.basename(p);
6
+ }
7
+
8
+ export default fs_path_basename;
@@ -0,0 +1,8 @@
1
+ import path from 'path';
2
+
3
+ function fs_path_dirname(p)
4
+ {
5
+ return path.dirname(p);
6
+ }
7
+
8
+ export default fs_path_dirname;
@@ -0,0 +1,8 @@
1
+ import path from 'path';
2
+
3
+ function fs_path_join(...parts)
4
+ {
5
+ return path.join(...parts);
6
+ }
7
+
8
+ export default fs_path_join;
@@ -0,0 +1,8 @@
1
+ import path from 'path';
2
+
3
+ function fs_path_resolve(...parts)
4
+ {
5
+ return path.resolve(...parts);
6
+ }
7
+
8
+ export default fs_path_resolve;
@@ -0,0 +1,8 @@
1
+ import sanitize_filename from 'sanitize-filename';
2
+
3
+ function fs_path_sanitize(filename)
4
+ {
5
+ return sanitize_filename(filename);
6
+ }
7
+
8
+ export default fs_path_sanitize;
@@ -0,0 +1,8 @@
1
+ import os from 'os';
2
+
3
+ function fs_path_tempdir()
4
+ {
5
+ return os.tmpdir();
6
+ }
7
+
8
+ export default fs_path_tempdir;
package/src/fs_read.js ADDED
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.readFile);
@@ -0,0 +1,10 @@
1
+ import fs_read from './fs_read';
2
+
3
+ // XXX Should support catchReturn, e.g.
4
+ // fs_read_json(file).catchReturn(default_value)
5
+ function fs_read_json(file)
6
+ {
7
+ return fs_read(file, {encoding: 'utf8'}).then(JSON.parse);
8
+ }
9
+
10
+ export default fs_read_json;
@@ -0,0 +1 @@
1
+ export {createReadStream as default} from 'fs';
@@ -0,0 +1,8 @@
1
+ import fs_read from './fs_read';
2
+
3
+ async function fs_read_utf8(file)
4
+ {
5
+ return await fs_read(file, {encoding: 'utf8'});
6
+ }
7
+
8
+ export default fs_read_utf8;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.readdir);
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.readlink);
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.rename);
package/src/fs_rm.js ADDED
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.unlink);
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.rmdir);
package/src/fs_rmrf.js ADDED
@@ -0,0 +1,25 @@
1
+ import Promise from 'bluebird';
2
+ import fs_fi from './fs_fi';
3
+ import fs_rm from './fs_rm';
4
+ import fs_rmdir from './fs_rmdir';
5
+ import fs_path_resolve from './fs_path_resolve';
6
+ import fs_readdir from './fs_readdir';
7
+
8
+ async function fs_rmrf(pathname)
9
+ {
10
+ const fi = await fs_fi(pathname).catch(() => null);
11
+ if (fi === null) {
12
+ return;
13
+ }
14
+
15
+ if (fi.isDirectory()) {
16
+ const names = await fs_readdir(pathname);
17
+ await Promise.all(names.map(v => fs_rmrf(fs_path_resolve(pathname, v))));
18
+ await fs_rmdir(fi.pathname);
19
+ }
20
+ else {
21
+ await fs_rm(fi.pathname);
22
+ }
23
+ }
24
+
25
+ export default fs_rmrf;
package/src/fs_size.js ADDED
@@ -0,0 +1,8 @@
1
+ import fs_fi from './fs_fi';
2
+
3
+ async function fs_size(file)
4
+ {
5
+ return fs_fi(file).then(v => v.size);
6
+ }
7
+
8
+ export default fs_size;
@@ -0,0 +1,42 @@
1
+ import fs from 'fs';
2
+ import fs_path_resolve from './fs_path_resolve';
3
+ import fs_path_tempdir from './fs_path_tempdir';
4
+ import fs_rmrf from './fs_rmrf';
5
+ import util from 'util';
6
+
7
+ // https://nodejs.org/api/util.html#util_util_promisify_original
8
+ const mkdtemp = util.promisify(fs.mkdtemp);
9
+
10
+ // * https://www.cons.org/cracauer/sigint.html from https://stackoverflow.com/q/14031763/1478566
11
+ // * http://www.tldp.org/LDP/abs/html/exitcodes.html from https://stackoverflow.com/q/14031763/1478566
12
+ // * https://www.exratione.com/2013/05/die-child-process-die/
13
+
14
+ async function fs_tempdir(fn)
15
+ {
16
+ process.on('SIGINT', sigint);
17
+
18
+ let d = null, out;
19
+ try {
20
+ d = await mkdtemp(fs_path_resolve(fs_path_tempdir(), 'vbtemp'));
21
+ out = await fn(d);
22
+ }
23
+ finally {
24
+ await clean();
25
+ }
26
+ return out;
27
+
28
+ async function clean() {
29
+ process.removeListener('SIGINT', sigint);
30
+ if (d) {
31
+ await fs_rmrf(d);
32
+ }
33
+ }
34
+
35
+ async function sigint() {
36
+ await clean().catch(function () {/* ignore */});
37
+ // https://stackoverflow.com/questions/14031763/doing-a-cleanup-action-just-before-node-js-exits#comment68567869_21947851
38
+ process.kill(process.pid, 'SIGINT');
39
+ }
40
+ }
41
+
42
+ export default fs_tempdir;
@@ -0,0 +1,5 @@
1
+ import fs from 'fs';
2
+ import util from 'util';
3
+
4
+ // https://nodejs.org/api/util.html#util_util_promisify_original
5
+ export default util.promisify(fs.writeFile);
@@ -0,0 +1 @@
1
+ export {createWriteStream as default} from 'fs';
@@ -0,0 +1,60 @@
1
+ import fs from 'fs';
2
+ import fs_size from './fs_size';
3
+ import http from 'http';
4
+ import https from 'https';
5
+
6
+ async function http_put_file(url, file, options)
7
+ {
8
+ // <?xml version="1.0" encoding="UTF-8"?>
9
+ // <Error>
10
+ // <Code>NotImplemented</Code>
11
+ // <Message>A header you provided implies functionality that is not implemented</Message>
12
+ // <Header>Transfer-Encoding</Header>
13
+ // <RequestId>XXXXXXXXXX</RequestId>
14
+ // <HostId>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</HostId>
15
+ // </Error>
16
+
17
+ const total = await fs_size(file);
18
+ return new Promise(function (resolve, reject) {
19
+ const chunks = [];
20
+ const request = (url.startsWith('http:') ? http : https).request(url, {method: 'PUT', headers: {'Content-Length': total}}, function (response) {
21
+ response.on('error', reject);
22
+ if (options.progress_download) {
23
+ const total_download = response.headers['content-length'] ? parseInt(response.headers['content-length']) : null;
24
+ let ready = 0;
25
+ response.on('data', function (chunk) {
26
+ const delta = chunk.length;
27
+ ready += delta;
28
+ options.progress_download(delta, ready, total_download);
29
+ });
30
+ }
31
+ response.on('data', function (chunk) {
32
+ chunks.push(chunk);
33
+ });
34
+ response.on('end', function () {
35
+ switch (response.statusCode) {
36
+ case 200:
37
+ resolve({request, response, data: Buffer.concat(chunks)});
38
+ break;
39
+ default:
40
+ reject(new Error(`${response.statusCode} ${response.statusMessage}\n\n${Buffer.concat(chunks).toString('utf8')}`));
41
+ break;
42
+ }
43
+ });
44
+ });
45
+ request.on('error', reject);
46
+ // https://stackoverflow.com/a/39492211/1478566
47
+ if (options.progress_upload) {
48
+ let prev = 0;
49
+ request.on('drain', function () {
50
+ const ready = request.socket.bytesWritten;
51
+ const delta = ready - prev;
52
+ options.progress_upload(delta, ready, total);
53
+ prev = ready;
54
+ });
55
+ }
56
+ fs.createReadStream(file).pipe(request);
57
+ });
58
+ }
59
+
60
+ export default http_put_file;
@@ -0,0 +1,6 @@
1
+ function identity(value)
2
+ {
3
+ return value;
4
+ }
5
+
6
+ export default identity;
package/src/ignore.js ADDED
@@ -0,0 +1,5 @@
1
+ function ignore()
2
+ {
3
+ }
4
+
5
+ export default ignore;
@@ -0,0 +1,3 @@
1
+ import json_stringify_stable from 'json-stable-stringify';
2
+
3
+ export default json_stringify_stable;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Split stream on each \n and call `fn` for each line.
3
+ *
4
+ * @param stream
5
+ * @param fn
6
+ * @return function
7
+ */
8
+ function stream_data_ln(stream, fn)
9
+ {
10
+ let utf8 = '';
11
+ stream.on('data', data);
12
+ stream.on('end', end);
13
+ return off;
14
+
15
+ function off() {
16
+ stream.off('data', data);
17
+ stream.off('end', end);
18
+ }
19
+ function end() {
20
+ off();
21
+ if (utf8) {
22
+ fn(utf8, true);
23
+ }
24
+ }
25
+ function data(buffer) {
26
+ utf8 += buffer.toString('utf8');
27
+ for (let iteration = 1; true; ++iteration) {
28
+ if (iteration == 1000000) {
29
+ throw new Error('Too many iterations');
30
+ }
31
+ const i = utf8.indexOf('\n');
32
+ if (i == -1) {
33
+ break;
34
+ }
35
+ const line = utf8.substr(0, i);
36
+ utf8 = utf8.substr(i + 1);
37
+ fn(line, false);
38
+ }
39
+ }
40
+ }
41
+
42
+ export default stream_data_ln;
@@ -0,0 +1,15 @@
1
+ import assert from 'assert';
2
+ import fs from 'fs';
3
+ import stream_data_ln from './stream_data_ln';
4
+ import stream_promise from './stream_promise';
5
+
6
+ describe('stream_data_ln', function () {
7
+ it('should handle basic input', async function () {
8
+ let tmp = '';
9
+ const s = fs.createReadStream(__filename);
10
+ const off = stream_data_ln(s, v => tmp += v + '\n');
11
+ await stream_promise(s);
12
+ off();
13
+ assert.deepStrictEqual(tmp, fs.readFileSync(__filename, {encoding: 'utf8'}));
14
+ });
15
+ });
@@ -0,0 +1,62 @@
1
+ import {finished} from 'stream/promises';
2
+
3
+ // /**
4
+ // * Wait for a stream to finish with `await stream_promise(stream)`.
5
+ // *
6
+ // * @param stream
7
+ // * @returns {Promise}
8
+ // */
9
+ // function stream_promise(stream)
10
+ // {
11
+ // /**
12
+ // * https://stackoverflow.com/a/34310963/1478566
13
+ // * > end and finish are the same event BUT on different types of
14
+ // * > Streams.
15
+ // * > * stream.Readable fires ONLY end and NEVER finish
16
+ // * > * stream.Writable fires ONLY finish and NEVER end
17
+ // */
18
+ // return new Promise(function (resolve, reject) {
19
+ // stream.once('end', resolve); // Stream.Readable
20
+ // stream.once('finish', resolve); // Stream.Writable
21
+ // stream.once('error', reject);
22
+ // function end() { off(); resolve(); }
23
+ // function finish() { off(); resolve(); }
24
+ // function error() { off(); reject(); }
25
+ // function off() {
26
+ // stream.off('end', end);
27
+ // stream.off('finish', finish);
28
+ // stream.off('error', error);
29
+ // }
30
+ // });
31
+ // }
32
+
33
+ /**
34
+ * Wait for a stream to finish with `await stream_promise(stream)`.
35
+ *
36
+ * https://nodejs.org/api/stream.html#stream_stream_finished_stream_options_callback
37
+ * > Especially useful in error handling scenarios where a stream
38
+ * > is destroyed prematurely (like an aborted HTTP request), and
39
+ * > will not emit 'end' or 'finish'.
40
+ *
41
+ * @param stream
42
+ * @returns {Promise<void>}
43
+ */
44
+ function stream_promise(stream)
45
+ {
46
+ // https://nodejs.org/api/stream.html#stream_stream_finished_stream_options_callback
47
+ // > stream.finished() leaves dangling event listeners (in particular
48
+ // > 'error', 'end', 'finish' and 'close') after callback has been
49
+ // > invoked. The reason for this is so that unexpected 'error'
50
+ // > events (due to incorrect stream implementations) do not cause
51
+ // > unexpected crashes. If this is unwanted behavior then the
52
+ // > returned cleanup function needs to be invoked in the callback:
53
+ // >
54
+ // > const cleanup = finished(rs, (err) => {
55
+ // > cleanup();
56
+ // > // ...
57
+ // > });
58
+ // >
59
+ return finished(stream);
60
+ }
61
+
62
+ export default stream_promise;
@@ -0,0 +1,14 @@
1
+ import fs from 'fs';
2
+ import ignore from './ignore';
3
+ import stream_promise from './stream_promise';
4
+
5
+ describe('stream_promise', function () {
6
+ it('should handle ReadableStream', async function () {
7
+ this.timeout(100);
8
+ await stream_promise(fs.createReadStream(__filename).on('data', ignore));
9
+ });
10
+ it('should handle WritableStream', async function () {
11
+ this.timeout(100);
12
+ await stream_promise(fs.createWriteStream('/dev/null').end('hello\n'));
13
+ });
14
+ });